From 19883585c62feb1b528b9ae2da4784d84999ab43 Mon Sep 17 00:00:00 2001 From: nattthebear Date: Mon, 12 Jun 2017 17:58:19 -0400 Subject: [PATCH] rewrite MapHeap to be much easier for me to understand --- BizHawk.Emulation.Cores/Waterbox/MapHeap.cs | 516 +++++++----------- .../Waterbox/MemoryBlock.cs | 14 +- 2 files changed, 190 insertions(+), 340 deletions(-) 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 {