2017-05-29 02:17:48 +00:00
using BizHawk.Emulation.Common ;
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using System.Threading.Tasks ;
using System.IO ;
namespace BizHawk.Emulation.Cores.Waterbox
{
/// <summary>
/// a heap that supports basic alloc, free, and realloc calls
/// </summary>
internal sealed class MapHeap : IBinaryStateable , IDisposable
{
public MemoryBlock Memory { get ; private set ; }
/// <summary>
/// name, used in identifying errors
/// </summary>
public string Name { get ; private set ; }
2017-05-29 15:26:38 +00:00
/// <summary>
/// total number of bytes allocated
/// </summary>
public ulong Used { get ; private set ; }
2017-05-29 02:17:48 +00:00
/// <summary>
/// get a page index within the block
/// </summary>
private int GetPage ( ulong addr )
{
return ( int ) ( ( addr - Memory . Start ) > > WaterboxUtils . PageShift ) ;
}
/// <summary>
/// get a start address for a page index within the block
/// </summary>
private ulong GetStartAddr ( int page )
{
return ( ( ulong ) page < < WaterboxUtils . PageShift ) + Memory . Start ;
}
private class Bin
{
2017-05-29 15:26:38 +00:00
/// <summary>
/// first page# in this bin, inclusive
/// </summary>
2017-05-29 02:17:48 +00:00
public int StartPage ;
2017-05-29 15:26:38 +00:00
/// <summary>
/// numbe of pages in this bin
/// </summary>
2017-05-29 02:17:48 +00:00
public int PageCount ;
2017-05-29 15:26:38 +00:00
/// <summary>
/// first page# not in this bin
/// </summary>
public int EndPage = > StartPage + PageCount ;
2017-05-29 02:17:48 +00:00
public MemoryBlock . Protection Protection ;
2017-05-29 15:26:38 +00:00
/// <summary>
/// true if not mapped (we distinguish between PROT_NONE and not mapped)
/// </summary>
2017-05-29 02:17:48 +00:00
public bool Free
{
get
{
return ( byte ) Protection = = 255 ;
}
set
{
Protection = value ? ( MemoryBlock . Protection ) 255 : MemoryBlock . Protection . None ;
}
}
public Bin Next ;
/// <summary>
/// split this bin, keeping only numPages pages
/// </summary>
public bool Cleave ( int numPages )
{
int nextPages = PageCount - numPages ;
if ( nextPages > 0 )
{
Next = new Bin
{
StartPage = StartPage + numPages ,
PageCount = nextPages ,
Next = Next
} ;
PageCount = numPages ;
return true ;
}
else
{
return false ;
}
}
2017-05-29 15:26:38 +00:00
/// <summary>
/// activate the protection specified by this block
/// </summary>
public void ApplyProtection ( MemoryBlock m )
{
var prot = Free ? MemoryBlock . Protection . None : Protection ;
var start = ( ( ulong ) StartPage < < WaterboxUtils . PageShift ) + m . Start ;
var length = ( ulong ) PageCount < < WaterboxUtils . PageShift ;
m . Protect ( start , length , prot ) ;
}
2017-05-29 02:17:48 +00:00
}
private Bin _root ;
public MapHeap ( ulong start , ulong size , string name )
{
size = WaterboxUtils . AlignUp ( size ) ;
Memory = new MemoryBlock ( start , size ) ;
Name = name ;
Console . WriteLine ( "Created mapheap `{1}` at {0:x16}:{2:x16}" , start , name , start + size ) ;
_root = new Bin
{
StartPage = 0 ,
PageCount = ( int ) ( size > > WaterboxUtils . PageShift ) ,
Free = true
} ;
}
/// <summary>
/// gets the bin that contains a page
/// </summary>
private Bin GetBinForStartPage ( int page )
{
Bin curr = _root ;
while ( curr . StartPage + curr . PageCount < = page )
curr = curr . Next ;
return curr ;
}
/// <summary>
/// gets the bin that contains the page before the passed page, returning null if
/// any bin along the way is Free
/// </summary>
private Bin GetBinForEndPageEnsureAllocated ( int page , Bin start )
{
Bin curr = start ;
while ( curr ! = null & & curr . StartPage + curr . PageCount < page )
{
if ( curr . Free )
return null ;
curr = curr . Next ;
}
return curr ;
}
public ulong Map ( ulong size , MemoryBlock . Protection prot )
{
int numPages = WaterboxUtils . PagesNeeded ( size ) ;
Bin best = null ;
Bin curr = _root ;
// find smallest potential bin
do
{
if ( curr . Free & & curr . PageCount > = numPages )
{
if ( best = = null | | curr . PageCount < best . PageCount )
{
best = curr ;
if ( curr . PageCount = = numPages )
break ;
}
}
curr = curr . Next ;
} while ( curr ! = null ) ;
if ( best = = null )
return 0 ;
if ( best . Cleave ( numPages ) )
best . Next . Free = true ;
best . Protection = prot ;
var ret = GetStartAddr ( best . StartPage ) ;
2017-05-29 15:26:38 +00:00
var totalSize = ( ( ulong ) numPages ) < < WaterboxUtils . PageShift ;
Memory . Protect ( ret , totalSize , prot ) ;
Used + = totalSize ;
Console . WriteLine ( $"Allocated {totalSize} bytes on {Name}, utilization {Used}/{Memory.Size} ({100.0 * Used / Memory.Size:0.#}%)" ) ;
2017-05-29 02:17:48 +00:00
return ret ;
}
public ulong Remap ( ulong start , ulong oldSize , ulong newSize , bool canMove )
{
2017-05-29 15:26:38 +00:00
// TODO: what is the expected behavior when everything requested for remap is allocated,
// but with different protections?
2017-05-29 02:17:48 +00:00
if ( start < Memory . Start | | start + oldSize > Memory . End )
return 0 ;
var oldStartPage = GetPage ( start ) ;
var oldStartBin = GetBinForStartPage ( oldStartPage ) ;
if ( oldSize = = 0 & & canMove )
2017-05-29 15:26:38 +00:00
{
if ( oldStartBin . Free )
return 0 ;
else
return Map ( newSize , oldStartBin . Protection ) ;
}
2017-05-29 02:17:48 +00:00
var oldNumPages = WaterboxUtils . PagesNeeded ( oldSize ) ;
var oldEndPage = oldStartPage + oldNumPages ;
// first, check if the requested area is actually mapped
var oldEndBin = GetBinForEndPageEnsureAllocated ( oldEndPage , oldStartBin ) ;
if ( oldEndBin = = null )
return 0 ;
var newNumPages = WaterboxUtils . PagesNeeded ( newSize ) ;
var newEndPage = oldStartPage + newNumPages ;
if ( newEndPage > oldEndPage )
{
// increase size
// the only way this will work in place is if all of the remaining space is free
Bin nextBin ;
2017-05-29 15:26:38 +00:00
if ( oldEndBin . EndPage = = oldEndPage // if end bin is too bag, space after that is used by something else
2017-05-29 02:17:48 +00:00
& & ( nextBin = oldEndBin . Next ) ! = null // can't go off the edge
& & nextBin . Free
2017-05-29 15:26:38 +00:00
& & nextBin . EndPage > = newEndPage )
2017-05-29 02:17:48 +00:00
{
nextBin . Protection = oldStartBin . Protection ;
if ( nextBin . Cleave ( newEndPage - nextBin . StartPage ) )
nextBin . Next . Free = true ;
2017-05-29 15:26:38 +00:00
nextBin . ApplyProtection ( Memory ) ;
var oldTotalSize = ( ( ulong ) oldNumPages ) < < WaterboxUtils . PageShift ;
var newTotalSize = ( ( ulong ) newNumPages ) < < WaterboxUtils . PageShift ;
Used + = newTotalSize ;
Used - = oldTotalSize ;
Console . WriteLine ( $"Reallocated from {oldTotalSize} bytes to {newTotalSize} bytes on {Name}, utilization {Used}/{Memory.Size} ({100.0 * Used / Memory.Size:0.#}%)" ) ;
2017-05-29 02:17:48 +00:00
return start ;
}
// could not increase in place, so move
if ( ! canMove )
return 0 ;
// if there's some free space right before `start`, and some right after, but not enough
// to extend in place, it's possible that a realloc would succeed reusing the same space,
// but would fail anywhere else due to heavy memory pressure.
// that would be a much more complicated algorithm; we'd need to compute a new allocation
// as if this one had been freed, but still be able to preserve this if that allocation
// still failed. instead, we ignore this case.
var ret = Map ( newSize , oldStartBin . Protection ) ;
if ( ret ! = 0 )
{
// move data
// NB: oldSize > 0
Memory . Protect ( start , oldSize , MemoryBlock . Protection . R ) ;
var ss = Memory . GetStream ( start , oldSize , false ) ;
Memory . Protect ( ret , oldSize , MemoryBlock . Protection . RW ) ;
var ds = Memory . GetStream ( ret , oldSize , true ) ;
ss . CopyTo ( ds ) ;
Memory . Protect ( ret , oldSize , oldStartBin . Protection ) ;
UnmapPagesInternal ( oldStartPage , oldNumPages , oldStartBin ) ;
return ret ;
}
else
{
return 0 ;
}
}
else if ( newEndPage < oldEndPage )
{
// shrink in place
var s = GetBinForStartPage ( newEndPage ) ;
UnmapPagesInternal ( newEndPage , oldEndPage - newEndPage , s ) ;
return start ;
}
else
{
// no change
return start ;
}
}
public bool Unmap ( ulong start , ulong size )
{
if ( start < Memory . Start | | start + size > Memory . End )
return false ;
if ( size = = 0 )
return true ;
var startPage = GetPage ( start ) ;
var numPages = WaterboxUtils . PagesNeeded ( size ) ;
var endPage = startPage + numPages ;
// check to see if the requested area is actually mapped
var startBin = GetBinForStartPage ( startPage ) ;
if ( GetBinForEndPageEnsureAllocated ( endPage , startBin ) = = null )
return false ;
UnmapPagesInternal ( startPage , numPages , startBin ) ;
return true ;
}
/// <summary>
/// frees some pages. assumes they are all allocated
/// </summary>
private void UnmapPagesInternal ( int startPage , int numPages , Bin startBin )
{
// from the various paths we took to get here, we must be unmapping at least one page
var endPage = startPage + numPages ;
Bin freeBin = startBin ;
if ( ! freeBin . Free & & freeBin . StartPage ! = startPage )
{
freeBin . Cleave ( startPage - freeBin . StartPage ) ;
freeBin = freeBin . Next ;
freeBin . Free = true ;
}
MemoryBlock . Protection lastEaten = MemoryBlock . Protection . None ;
2017-05-29 15:26:38 +00:00
while ( freeBin . EndPage < endPage )
2017-05-29 02:17:48 +00:00
{
freeBin . PageCount + = freeBin . Next . PageCount ;
lastEaten = freeBin . Next . Protection ;
freeBin . Next = freeBin . Next . Next ;
}
2017-05-29 15:26:38 +00:00
if ( freeBin . Cleave ( freeBin . EndPage - endPage ) )
2017-05-29 02:17:48 +00:00
{
freeBin . Next . Protection = lastEaten ;
}
2017-05-29 15:26:38 +00:00
freeBin . ApplyProtection ( Memory ) ;
var totalSize = ( ( ulong ) numPages ) < < WaterboxUtils . PageShift ;
Used - = totalSize ;
Console . WriteLine ( $"Freed {totalSize} bytes on {Name}, utilization {Used}/{Memory.Size} ({100.0 * Used / Memory.Size:0.#}%)" ) ;
2017-05-29 02:17:48 +00:00
}
public void Dispose ( )
{
if ( Memory ! = null )
{
Memory . Dispose ( ) ;
Memory = null ;
}
}
2017-05-29 15:26:38 +00:00
public void SaveStateBinary ( BinaryWriter bw )
2017-05-29 02:17:48 +00:00
{
2017-05-29 15:26:38 +00:00
bw . Write ( Name ) ;
bw . Write ( Memory . Size ) ;
bw . Write ( Used ) ;
bw . Write ( Memory . XorHash ) ;
var bin = _root ;
do
{
bw . Write ( bin . PageCount ) ;
bw . Write ( ( byte ) bin . Protection ) ;
if ( ! bin . Free )
{
var start = GetStartAddr ( bin . StartPage ) ;
var length = ( ulong ) bin . PageCount < < WaterboxUtils . PageShift ;
if ( bin . Protection = = MemoryBlock . Protection . None )
Memory . Protect ( start , length , MemoryBlock . Protection . R ) ;
Memory . GetXorStream ( start , length , false ) . CopyTo ( bw . BaseStream ) ;
if ( bin . Protection = = MemoryBlock . Protection . None )
Memory . Protect ( start , length , MemoryBlock . Protection . None ) ;
}
bin = bin . Next ;
} while ( bin ! = null ) ;
bw . Write ( - 1 ) ;
2017-05-29 02:17:48 +00:00
}
2017-05-29 15:26:38 +00:00
public void LoadStateBinary ( BinaryReader br )
2017-05-29 02:17:48 +00:00
{
2017-05-29 15:26:38 +00:00
var name = br . ReadString ( ) ;
if ( name ! = Name )
throw new InvalidOperationException ( string . Format ( "Name did not match for mapheap {0}" , Name ) ) ;
var size = br . ReadUInt64 ( ) ;
if ( size ! = Memory . Size )
throw new InvalidOperationException ( string . Format ( "Size did not match for mapheap {0}" , Name ) ) ;
var used = br . ReadUInt64 ( ) ;
var hash = br . ReadBytes ( Memory . XorHash . Length ) ;
if ( ! hash . SequenceEqual ( Memory . XorHash ) )
throw new InvalidOperationException ( string . Format ( "Hash did not match for mapheap {0}. Is this the same rom?" , Name ) ) ;
Used = 0 ;
int startPage = 0 ;
int pageCount ;
Bin scratch = new Bin ( ) , curr = scratch ;
while ( ( pageCount = br . ReadInt32 ( ) ) ! = - 1 )
{
var next = new Bin
{
StartPage = startPage ,
PageCount = pageCount ,
Protection = ( MemoryBlock . Protection ) br . ReadByte ( )
} ;
startPage + = pageCount ;
if ( ! next . Free )
{
var start = GetStartAddr ( next . StartPage ) ;
var length = ( ulong ) pageCount < < WaterboxUtils . PageShift ;
Memory . Protect ( start , length , MemoryBlock . Protection . RW ) ;
WaterboxUtils . CopySome ( br . BaseStream , Memory . GetXorStream ( start , length , true ) , ( long ) length ) ;
Used + = length ;
}
next . ApplyProtection ( Memory ) ;
curr . Next = next ;
curr = next ;
}
if ( used ! = Used )
throw new InvalidOperationException ( string . Format ( "Inernal error loading mapheap {0}" , Name ) ) ;
_root = scratch . Next ;
2017-05-29 02:17:48 +00:00
}
}
}