diff --git a/BizHawk.Emulation.Cores/Waterbox/MapHeap.cs b/BizHawk.Emulation.Cores/Waterbox/MapHeap.cs
index b8d786693b..8e4dcae4ae 100644
--- a/BizHawk.Emulation.Cores/Waterbox/MapHeap.cs
+++ b/BizHawk.Emulation.Cores/Waterbox/MapHeap.cs
@@ -5,6 +5,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
+using System.Runtime.InteropServices;
namespace BizHawk.Emulation.Cores.Waterbox
{
@@ -40,155 +41,137 @@ namespace BizHawk.Emulation.Cores.Waterbox
return ((ulong)page << WaterboxUtils.PageShift) + Memory.Start;
}
- private class Bin
- {
- public const MemoryBlock.Protection _FreeProt = (MemoryBlock.Protection)255;
+ private const MemoryBlock.Protection FREE = (MemoryBlock.Protection)255;
- ///
- /// first page# in this bin, inclusive
- ///
- public int StartPage;
- ///
- /// numbe of pages in this bin
- ///
- public int PageCount;
- ///
- /// first page# not in this bin
- ///
- public int EndPage => StartPage + PageCount;
- public MemoryBlock.Protection Protection;
- ///
- /// true if not mapped (we distinguish between PROT_NONE and not mapped)
- ///
- public bool Free
- {
- get
- {
- return Protection == _FreeProt;
- }
- set
- {
- Protection = value ? _FreeProt : MemoryBlock.Protection.None;
- }
- }
-
- public Bin Next;
-
- ///
- /// split this bin, keeping only numPages pages
- ///
- 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;
- }
- }
-
- ///
- /// activate the protection specified by this block
- ///
- 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);
- }
- }
-
- private Bin _root;
+ private readonly MemoryBlock.Protection[] _pages;
+ private readonly byte[] _pagesAsBytes;
public MapHeap(ulong start, ulong size, string name)
{
size = WaterboxUtils.AlignUp(size);
Memory = new MemoryBlock(start, size);
Name = name;
+ _pagesAsBytes = new byte[size >> WaterboxUtils.PageShift];
+ _pages = (MemoryBlock.Protection[])(object)_pagesAsBytes;
+ for (var i = 0; i < _pages.Length; i++)
+ _pages[i] = FREE;
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
- };
}
- ///
- /// gets the bin that contains a page
- ///
- private Bin GetBinForStartPage(int page)
+ // find consecutive unused pages to map
+ private int FindConsecutiveFreePages(int count)
{
- Bin curr = _root;
- while (curr.StartPage + curr.PageCount <= page)
- curr = curr.Next;
- return curr;
+ return FindConsecutiveFreePagesAssumingFreed(count, -1, -1);
}
- ///
- /// gets the bin that contains the page before the passed page, returning null if
- /// any bin along the way is Free
- ///
- private Bin GetBinForEndPageEnsureAllocated(int page, Bin start)
+ // find consecutive unused pages to map, pretending that [startPage..startPage + numPages) is free
+ // used in realloc
+ private int FindConsecutiveFreePagesAssumingFreed(int count, int startPage, int numPages)
{
- Bin curr = start;
- while (curr != null)
+ var starts = new List();
+ var sizes = new List();
+
+ var currStart = 0;
+ for (var i = 0; i <= _pages.Length; i++)
{
- if (curr.Free)
- return null;
- if (curr.EndPage >= page)
- return curr;
- curr = curr.Next;
+ if (i == _pages.Length || _pages[i] != FREE && (i < startPage || i >= startPage + numPages))
+ {
+ if (currStart < i)
+ {
+ starts.Add(currStart);
+ var size = i - currStart;
+ if (size == count)
+ return currStart;
+ sizes.Add(i - currStart);
+ }
+ currStart = i + 1;
+ }
}
- return curr; // ran off the end
+ int bestIdx = -1;
+ int bestSize = int.MaxValue;
+ for (int i = 0; i < sizes.Count; i++)
+ {
+ if (sizes[i] < bestSize && sizes[i] >= count)
+ {
+ bestSize = sizes[i];
+ bestIdx = i;
+ }
+ }
+ if (bestIdx != -1)
+ return starts[bestIdx];
+ else
+ return -1;
+ }
+
+ private void ProtectInternal(int startPage, int numPages, MemoryBlock.Protection prot, bool wasUsed)
+ {
+ for (var i = startPage; i < startPage + numPages; i++)
+ _pages[i] = prot;
+
+ ulong start = GetStartAddr(startPage);
+ ulong length = ((ulong)numPages) << WaterboxUtils.PageShift;
+ if (prot == FREE)
+ {
+ Memory.Protect(start, length, MemoryBlock.Protection.RW);
+ WaterboxUtils.ZeroMemory(Z.US(start), (long)length);
+ Memory.Protect(start, length, MemoryBlock.Protection.None);
+ Used -= length;
+ Console.WriteLine($"Freed {length} bytes on {Name}, utilization {Used}/{Memory.Size} ({100.0 * Used / Memory.Size:0.#}%)");
+ }
+ else
+ {
+ Memory.Protect(start, length, prot);
+ if (wasUsed)
+ {
+ Console.WriteLine($"Set protection for {length} bytes on {Name} to {prot}");
+ }
+ else
+ {
+ Used += length;
+ Console.WriteLine($"Allocated {length} bytes on {Name}, utilization {Used}/{Memory.Size} ({100.0 * Used / Memory.Size:0.#}%)");
+ }
+ }
+ }
+
+ private void RefreshProtections(int startPage, int pageCount)
+ {
+ int ps = 0;
+ for (int i = startPage; i < pageCount; i++)
+ {
+ if (i == pageCount - 1 || _pages[i] != _pages[i + 1])
+ {
+ var p = _pages[i];
+ ulong zstart = GetStartAddr(ps);
+ ulong zlength = (ulong)(i - ps + 1) << WaterboxUtils.PageShift;
+ Memory.Protect(zstart, zlength, p == FREE ? MemoryBlock.Protection.None : p);
+ ps = i + 1;
+ }
+ }
+ }
+
+ private void RefreshAllProtections()
+ {
+ RefreshProtections(0, _pages.Length);
+ }
+
+ private bool EnsureMapped(int startPage, int pageCount)
+ {
+ for (int i = startPage; i < startPage + pageCount; i++)
+ if (_pages[i] == FREE)
+ return false;
+ return true;
}
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)
+ if (size == 0)
return 0;
-
- if (best.Cleave(numPages))
- best.Next.Free = true;
- best.Protection = prot;
-
- var ret = GetStartAddr(best.StartPage);
- 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.#}%)");
- //EnsureUsedInternal();
+ int numPages = WaterboxUtils.PagesNeeded(size);
+ int startPage = FindConsecutiveFreePages(numPages);
+ if (startPage == -1)
+ return 0;
+ var ret = GetStartAddr(startPage);
+ ProtectInternal(startPage, numPages, prot, false);
return ret;
}
@@ -196,180 +179,83 @@ namespace BizHawk.Emulation.Cores.Waterbox
{
// TODO: what is the expected behavior when everything requested for remap is allocated,
// but with different protections?
-
- if (start < Memory.Start || start + oldSize > Memory.End)
+ if (start < Memory.Start || start + oldSize > Memory.End || oldSize == 0 || newSize == 0)
return 0;
var oldStartPage = GetPage(start);
- var oldStartBin = GetBinForStartPage(oldStartPage);
- if (oldSize == 0 && canMove)
- {
- if (oldStartBin.Free)
- return 0;
- else
- return Map(newSize, oldStartBin.Protection);
- }
-
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)
+ if (!EnsureMapped(oldStartPage, oldNumPages))
return 0;
+ var oldProt = _pages[oldStartPage];
- var newNumPages = WaterboxUtils.PagesNeeded(newSize);
- var newEndPage = oldStartPage + newNumPages;
- if (newEndPage > oldEndPage)
+ int newNumPages = WaterboxUtils.PagesNeeded(newSize);
+
+ if (!canMove)
{
- // increase size
- // the only way this will work in place is if all of the remaining space is free
- Bin nextBin;
- if (oldEndBin.EndPage == oldEndPage // if end bin is too bag, space after that is used by something else
- && (nextBin = oldEndBin.Next) != null // can't go off the edge
- && nextBin.Free
- && nextBin.EndPage >= newEndPage)
+ if (newNumPages <= oldNumPages)
{
- nextBin.Protection = oldStartBin.Protection;
- if (nextBin.Cleave(newEndPage - nextBin.StartPage))
- nextBin.Next.Free = true;
-
- 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.#}%)");
- //EnsureUsedInternal();
+ if (newNumPages < oldNumPages)
+ ProtectInternal(oldStartPage + newNumPages, oldNumPages - newNumPages, FREE, true);
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)
+ else if (newNumPages > oldNumPages)
{
- // 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);
- ProtectPagesInternal(oldStartPage, oldNumPages, oldStartBin, Bin._FreeProt);
- //EnsureUsedInternal();
- return ret;
- }
- else
- {
- return 0;
+ for (var i = oldStartPage + oldNumPages; i < oldStartPage + newNumPages; i++)
+ if (_pages[i] != FREE)
+ return 0;
+ ProtectInternal(oldStartPage + oldNumPages, newNumPages - oldNumPages, oldProt, false);
+ return start;
}
}
- else if (newEndPage < oldEndPage)
- {
- // shrink in place
- var s = GetBinForStartPage(newEndPage);
- ProtectPagesInternal(newEndPage, oldEndPage - newEndPage, s, Bin._FreeProt);
- //EnsureUsedInternal();
- return start;
- }
- else
- {
- // no change
- return start;
- }
+
+ // if moving is allowed, we always move to simplify and defragment when possible
+ int newStartPage = FindConsecutiveFreePagesAssumingFreed(newNumPages, oldStartPage, oldNumPages);
+ if (newStartPage == -1)
+ return 0;
+
+ var copyDataLen = Math.Min(oldSize, newSize);
+ var copyPageLen = Math.Min(oldNumPages, newNumPages);
+
+ var data = new byte[copyDataLen];
+ Memory.Protect(start, copyDataLen, MemoryBlock.Protection.RW);
+ Marshal.Copy(Z.US(start), data, 0, (int)copyDataLen);
+
+ var pages = new MemoryBlock.Protection[copyPageLen];
+ Array.Copy(_pages, oldStartPage, pages, 0, copyPageLen);
+
+ ProtectInternal(oldStartPage, oldNumPages, FREE, true);
+ ProtectInternal(newStartPage, newNumPages, MemoryBlock.Protection.RW, false);
+
+ var ret = GetStartAddr(newStartPage);
+ Marshal.Copy(data, 0, Z.US(ret), (int)copyDataLen);
+
+ Array.Copy(pages, 0, _pages, newStartPage, copyPageLen);
+ RefreshProtections(newStartPage, copyPageLen);
+ if (newNumPages > oldNumPages)
+ ProtectInternal(newStartPage + oldNumPages, newNumPages - oldNumPages, oldProt, true);
+
+ return ret;
}
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;
-
- ProtectPagesInternal(startPage, numPages, startBin, Bin._FreeProt);
- return true;
+ return Protect(start, size, FREE);
}
public bool Protect(ulong start, ulong size, MemoryBlock.Protection prot)
{
- // TODO: lots of copy paste here
- if (start < Memory.Start || start + size > Memory.End)
+ if (start < Memory.Start || start + size > Memory.End || size == 0)
return false;
- if (size == 0)
- return true;
var startPage = GetPage(start);
var numPages = WaterboxUtils.PagesNeeded(size);
- var endPage = startPage + numPages;
- // to change protection, the entire area must currently be mapped
- var startBin = GetBinForStartPage(startPage);
- if (GetBinForEndPageEnsureAllocated(endPage, startBin) == null)
+ if (!EnsureMapped(startPage, numPages))
return false;
- ProtectPagesInternal(startPage, numPages, startBin, prot);
+ ProtectInternal(startPage, numPages, prot, true);
return true;
}
- ///
- /// frees or changes the protection on some pages. assumes they are all allocated
- ///
- private void ProtectPagesInternal(int startPage, int numPages, Bin startBin, MemoryBlock.Protection prot)
- {
- // 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.StartPage != startPage)
- {
- freeBin.Cleave(startPage - freeBin.StartPage);
- freeBin.Next.Protection = freeBin.Protection;
- freeBin = freeBin.Next;
- }
- MemoryBlock.Protection lastEaten = freeBin.Protection;
- while (freeBin.EndPage < endPage)
- {
- freeBin.PageCount += freeBin.Next.PageCount;
- lastEaten = freeBin.Next.Protection;
- freeBin.Next = freeBin.Next.Next;
- }
- if (freeBin.Cleave(endPage - freeBin.StartPage))
- {
- freeBin.Next.Protection = lastEaten;
- }
- freeBin.Protection = prot;
- freeBin.ApplyProtection(Memory);
-
- var totalSize = ((ulong)numPages) << WaterboxUtils.PageShift;
- if (prot == Bin._FreeProt)
- {
- Used -= totalSize;
- Console.WriteLine($"Freed {totalSize} bytes on {Name}, utilization {Used}/{Memory.Size} ({100.0 * Used / Memory.Size:0.#}%)");
- }
- else
- {
- Console.WriteLine($"Set protection for {totalSize} bytes on {Name} to {prot}");
- }
- //EnsureUsedInternal();
- }
-
public void Dispose()
{
if (Memory != null)
@@ -379,24 +265,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
}
}
- private ulong CalcUsedInternal()
- {
- ulong ret = 0;
- var bin = _root;
- while (bin != null)
- {
- if (!bin.Free)
- ret += (ulong)bin.PageCount << WaterboxUtils.PageShift;
- bin = bin.Next;
- }
- return ret;
- }
-
- private void EnsureUsedInternal()
- {
- if (Used != CalcUsedInternal())
- throw new Exception();
- }
+ private const ulong MAGIC = 0x1590abbcdeef5910;
public void SaveStateBinary(BinaryWriter bw)
{
@@ -404,24 +273,20 @@ namespace BizHawk.Emulation.Cores.Waterbox
bw.Write(Memory.Size);
bw.Write(Used);
bw.Write(Memory.XorHash);
- var bin = _root;
- do
+ bw.Write(_pagesAsBytes);
+
+ Memory.Protect(Memory.Start, Memory.Size, MemoryBlock.Protection.R);
+ var srcs = Memory.GetXorStream(Memory.Start, Memory.Size, false);
+ for (int i = 0, addr = 0; i < _pages.Length; i++, addr += WaterboxUtils.PageSize)
{
- bw.Write(bin.PageCount);
- bw.Write((byte)bin.Protection);
- if (!bin.Free)
+ if (_pages[i] != 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);
+ srcs.Seek(addr, SeekOrigin.Begin);
+ WaterboxUtils.CopySome(srcs, bw.BaseStream, WaterboxUtils.PageSize);
}
- bin = bin.Next;
- } while (bin != null);
- bw.Write(-1);
+ }
+ bw.Write(MAGIC);
+ RefreshAllProtections();
}
public void LoadStateBinary(BinaryReader br)
@@ -437,37 +302,22 @@ namespace BizHawk.Emulation.Cores.Waterbox
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;
+ if (br.BaseStream.Read(_pagesAsBytes, 0, _pagesAsBytes.Length) != _pagesAsBytes.Length)
+ throw new InvalidOperationException("Unexpected error reading!");
- int startPage = 0;
- int pageCount;
- Bin scratch = new Bin(), curr = scratch;
- while ((pageCount = br.ReadInt32()) != -1)
+ Memory.Protect(Memory.Start, Memory.Size, MemoryBlock.Protection.RW);
+ var dsts = Memory.GetXorStream(Memory.Start, Memory.Size, true);
+ for (int i = 0, addr = 0; i < _pages.Length; i++, addr += WaterboxUtils.PageSize)
{
- var next = new Bin
+ if (_pages[i] != FREE)
{
- 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;
+ dsts.Seek(addr, SeekOrigin.Begin);
+ WaterboxUtils.CopySome(br.BaseStream, dsts, WaterboxUtils.PageSize);
}
- 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;
+ if (br.ReadUInt64() != MAGIC)
+ throw new InvalidOperationException("Savestate internal error");
+ RefreshAllProtections();
}
public static void StressTest()
@@ -480,7 +330,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
{
ulong siz = (ulong)(rnd.Next(256 * 1024) + 384 * 1024);
siz = siz / 4096 * 4096;
- var ptr = mmo.Map(siz, Waterbox.MemoryBlock.Protection.RW);
+ var ptr = mmo.Map(siz, MemoryBlock.Protection.RW);
allocs.Add(ptr, siz);
}
@@ -496,7 +346,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
{
ulong siz = (ulong)(rnd.Next(256 * 1024) + 384 * 1024);
siz = siz / 4096 * 4096;
- var ptr = mmo.Map(siz, Waterbox.MemoryBlock.Protection.RW);
+ var ptr = mmo.Map(siz, MemoryBlock.Protection.RW);
allocs.Add(ptr, siz);
}
diff --git a/BizHawk.Emulation.Cores/Waterbox/MemoryBlock.cs b/BizHawk.Emulation.Cores/Waterbox/MemoryBlock.cs
index 7c00c0b8cf..df8a8157fa 100644
--- a/BizHawk.Emulation.Cores/Waterbox/MemoryBlock.cs
+++ b/BizHawk.Emulation.Cores/Waterbox/MemoryBlock.cs
@@ -290,18 +290,18 @@ namespace BizHawk.Emulation.Cores.Waterbox
private MemoryBlock _owner;
- private bool _readable;
- private bool _writable;
+ private readonly bool _readable;
+ private readonly bool _writable;
private long _length;
private long _pos;
- private long _ptr;
+ private readonly long _ptr;
- public override bool CanRead { get { return _readable; } }
- public override bool CanSeek { get { return true; } }
- public override bool CanWrite { get { return _writable; } }
+ public override bool CanRead => _readable;
+ public override bool CanSeek => true;
+ public override bool CanWrite => _writable;
public override void Flush() { }
- public override long Length { get { return _length; } }
+ public override long Length => _length;
public override long Position
{