| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703 |
- using Ryujinx.Memory.Range;
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Runtime.InteropServices;
- namespace Ryujinx.Memory.WindowsShared
- {
- class EmulatedSharedMemoryWindows : IDisposable
- {
- private static readonly IntPtr InvalidHandleValue = new IntPtr(-1);
- private static readonly IntPtr CurrentProcessHandle = new IntPtr(-1);
- public const int MappingBits = 16; // Windows 64kb granularity.
- public const ulong MappingGranularity = 1 << MappingBits;
- public const ulong MappingMask = MappingGranularity - 1;
- public const ulong BackingSize32GB = 32UL * 1024UL * 1024UL * 1024UL; // Reasonable max size of 32GB.
- private class SharedMemoryMapping : INonOverlappingRange
- {
- public ulong Address { get; }
- public ulong Size { get; private set; }
- public ulong EndAddress { get; private set; }
- public List<int> Blocks;
- public SharedMemoryMapping(ulong address, ulong size, List<int> blocks = null)
- {
- Address = address;
- Size = size;
- EndAddress = address + size;
- Blocks = blocks ?? new List<int>();
- }
- public bool OverlapsWith(ulong address, ulong size)
- {
- return Address < address + size && address < EndAddress;
- }
- public void ExtendTo(ulong endAddress, RangeList<SharedMemoryMapping> list)
- {
- EndAddress = endAddress;
- Size = endAddress - Address;
- list.UpdateEndAddress(this);
- }
- public void AddBlocks(IEnumerable<int> blocks)
- {
- if (Blocks.Count > 0 && blocks.Count() > 0 && Blocks.Last() == blocks.First())
- {
- Blocks.AddRange(blocks.Skip(1));
- }
- else
- {
- Blocks.AddRange(blocks);
- }
- }
- public INonOverlappingRange Split(ulong splitAddress)
- {
- SharedMemoryMapping newRegion = new SharedMemoryMapping(splitAddress, EndAddress - splitAddress);
- int end = (int)((EndAddress + MappingMask) >> MappingBits);
- int start = (int)(Address >> MappingBits);
- Size = splitAddress - Address;
- EndAddress = splitAddress;
- int splitEndBlock = (int)((splitAddress + MappingMask) >> MappingBits);
- int splitStartBlock = (int)(splitAddress >> MappingBits);
- newRegion.AddBlocks(Blocks.Skip(splitStartBlock - start));
- Blocks.RemoveRange(splitEndBlock - start, end - splitEndBlock);
- return newRegion;
- }
- }
- [DllImport("kernel32.dll", SetLastError = true)]
- private static extern IntPtr CreateFileMapping(
- IntPtr hFile,
- IntPtr lpFileMappingAttributes,
- FileMapProtection flProtect,
- uint dwMaximumSizeHigh,
- uint dwMaximumSizeLow,
- [MarshalAs(UnmanagedType.LPWStr)] string lpName);
- [DllImport("kernel32.dll", SetLastError = true)]
- private static extern bool CloseHandle(IntPtr hObject);
- [DllImport("KernelBase.dll", SetLastError = true)]
- private static extern IntPtr VirtualAlloc2(
- IntPtr process,
- IntPtr lpAddress,
- IntPtr dwSize,
- AllocationType flAllocationType,
- MemoryProtection flProtect,
- IntPtr extendedParameters,
- ulong parameterCount);
- [DllImport("kernel32.dll", SetLastError = true)]
- private static extern bool VirtualFree(IntPtr lpAddress, IntPtr dwSize, AllocationType dwFreeType);
- [DllImport("KernelBase.dll", SetLastError = true)]
- private static extern IntPtr MapViewOfFile3(
- IntPtr hFileMappingObject,
- IntPtr process,
- IntPtr baseAddress,
- ulong offset,
- IntPtr dwNumberOfBytesToMap,
- ulong allocationType,
- MemoryProtection dwDesiredAccess,
- IntPtr extendedParameters,
- ulong parameterCount);
- [DllImport("KernelBase.dll", SetLastError = true)]
- private static extern bool UnmapViewOfFile2(IntPtr process, IntPtr lpBaseAddress, ulong unmapFlags);
- private ulong _size;
- private object _lock = new object();
- private ulong _backingSize;
- private IntPtr _backingMemHandle;
- private int _backingEnd;
- private int _backingAllocated;
- private Queue<int> _backingFreeList;
- private List<ulong> _mappedBases;
- private RangeList<SharedMemoryMapping> _mappings;
- private SharedMemoryMapping[] _foundMappings = new SharedMemoryMapping[32];
- private PlaceholderList _placeholders;
- public EmulatedSharedMemoryWindows(ulong size)
- {
- ulong backingSize = BackingSize32GB;
- _size = size;
- _backingSize = backingSize;
- _backingMemHandle = CreateFileMapping(
- InvalidHandleValue,
- IntPtr.Zero,
- FileMapProtection.PageReadWrite | FileMapProtection.SectionReserve,
- (uint)(backingSize >> 32),
- (uint)backingSize,
- null);
- if (_backingMemHandle == IntPtr.Zero)
- {
- throw new OutOfMemoryException();
- }
- _backingFreeList = new Queue<int>();
- _mappings = new RangeList<SharedMemoryMapping>();
- _mappedBases = new List<ulong>();
- _placeholders = new PlaceholderList(size >> MappingBits);
- }
- private (ulong granularStart, ulong granularEnd) GetAlignedRange(ulong address, ulong size)
- {
- return (address & (~MappingMask), (address + size + MappingMask) & (~MappingMask));
- }
- private void Commit(ulong address, ulong size)
- {
- (ulong granularStart, ulong granularEnd) = GetAlignedRange(address, size);
- ulong endAddress = address + size;
- lock (_lock)
- {
- // Search a bit before and after the new mapping.
- // When adding our new mapping, we may need to join an existing mapping into our new mapping (or in some cases, to the other side!)
- ulong searchStart = granularStart == 0 ? 0 : (granularStart - 1);
- int mappingCount = _mappings.FindOverlapsNonOverlapping(searchStart, (granularEnd - searchStart) + 1, ref _foundMappings);
- int first = -1;
- int last = -1;
- SharedMemoryMapping startOverlap = null;
- SharedMemoryMapping endOverlap = null;
- int lastIndex = (int)(address >> MappingBits);
- int endIndex = (int)((endAddress + MappingMask) >> MappingBits);
- int firstBlock = -1;
- int endBlock = -1;
- for (int i = 0; i < mappingCount; i++)
- {
- SharedMemoryMapping mapping = _foundMappings[i];
- if (mapping.Address < address)
- {
- if (mapping.EndAddress >= address)
- {
- startOverlap = mapping;
- }
- if ((int)((mapping.EndAddress - 1) >> MappingBits) == lastIndex)
- {
- lastIndex = (int)((mapping.EndAddress + MappingMask) >> MappingBits);
- firstBlock = mapping.Blocks.Last();
- }
- }
- if (mapping.EndAddress > endAddress)
- {
- if (mapping.Address <= endAddress)
- {
- endOverlap = mapping;
- }
- if ((int)((mapping.Address) >> MappingBits) + 1 == endIndex)
- {
- endIndex = (int)((mapping.Address) >> MappingBits);
- endBlock = mapping.Blocks.First();
- }
- }
- if (mapping.OverlapsWith(address, size))
- {
- if (first == -1)
- {
- first = i;
- }
- last = i;
- }
- }
- if (startOverlap == endOverlap && startOverlap != null)
- {
- // Already fully committed.
- return;
- }
- var blocks = new List<int>();
- int lastBlock = -1;
- if (firstBlock != -1)
- {
- blocks.Add(firstBlock);
- lastBlock = firstBlock;
- }
- bool hasMapped = false;
- Action map = () =>
- {
- if (!hasMapped)
- {
- _placeholders.EnsurePlaceholders(address >> MappingBits, (granularEnd - granularStart) >> MappingBits, SplitPlaceholder);
- hasMapped = true;
- }
- // There's a gap between this index and the last. Allocate blocks to fill it.
- blocks.Add(MapBackingBlock(MappingGranularity * (ulong)lastIndex++));
- };
- if (first != -1)
- {
- for (int i = first; i <= last; i++)
- {
- SharedMemoryMapping mapping = _foundMappings[i];
- int mapIndex = (int)(mapping.Address >> MappingBits);
- while (lastIndex < mapIndex)
- {
- map();
- }
- if (lastBlock == mapping.Blocks[0])
- {
- blocks.AddRange(mapping.Blocks.Skip(1));
- }
- else
- {
- blocks.AddRange(mapping.Blocks);
- }
- lastIndex = (int)((mapping.EndAddress - 1) >> MappingBits) + 1;
- }
- }
- while (lastIndex < endIndex)
- {
- map();
- }
- if (endBlock != -1 && endBlock != lastBlock)
- {
- blocks.Add(endBlock);
- }
- if (startOverlap != null && endOverlap != null)
- {
- // Both sides should be coalesced. Extend the start overlap to contain the end overlap, and add together their blocks.
- _mappings.Remove(endOverlap);
- startOverlap.ExtendTo(endOverlap.EndAddress, _mappings);
- startOverlap.AddBlocks(blocks);
- startOverlap.AddBlocks(endOverlap.Blocks);
- }
- else if (startOverlap != null)
- {
- startOverlap.ExtendTo(endAddress, _mappings);
- startOverlap.AddBlocks(blocks);
- }
- else
- {
- var mapping = new SharedMemoryMapping(address, size, blocks);
- if (endOverlap != null)
- {
- mapping.ExtendTo(endOverlap.EndAddress, _mappings);
- mapping.AddBlocks(endOverlap.Blocks);
- _mappings.Remove(endOverlap);
- }
- _mappings.Add(mapping);
- }
- }
- }
- private void Decommit(ulong address, ulong size)
- {
- (ulong granularStart, ulong granularEnd) = GetAlignedRange(address, size);
- ulong endAddress = address + size;
- lock (_lock)
- {
- int mappingCount = _mappings.FindOverlapsNonOverlapping(granularStart, granularEnd - granularStart, ref _foundMappings);
- int first = -1;
- int last = -1;
- for (int i = 0; i < mappingCount; i++)
- {
- SharedMemoryMapping mapping = _foundMappings[i];
- if (mapping.OverlapsWith(address, size))
- {
- if (first == -1)
- {
- first = i;
- }
- last = i;
- }
- }
- if (first == -1)
- {
- return; // Could not find any regions to decommit.
- }
- int lastReleasedBlock = -1;
- bool releasedFirst = false;
- bool releasedLast = false;
- for (int i = last; i >= first; i--)
- {
- SharedMemoryMapping mapping = _foundMappings[i];
- bool releaseEnd = true;
- bool releaseStart = true;
- if (i == last)
- {
- // If this is the last region, do not release the block if there is a page ahead of us, or the block continues after us. (it is keeping the block alive)
- releaseEnd = last == mappingCount - 1;
- // If the end region starts after the decommit end address, split and readd it after modifying its base address.
- if (mapping.EndAddress > endAddress)
- {
- var newMapping = (SharedMemoryMapping)mapping.Split(endAddress);
- _mappings.UpdateEndAddress(mapping);
- _mappings.Add(newMapping);
- if ((endAddress & MappingMask) != 0)
- {
- releaseEnd = false;
- }
- }
- releasedLast = releaseEnd;
- }
- if (i == first)
- {
- // If this is the first region, do not release the block if there is a region behind us. (it is keeping the block alive)
- releaseStart = first == 0;
- // If the first region starts before the decommit address, split it by modifying its end address.
- if (mapping.Address < address)
- {
- var oldMapping = mapping;
- mapping = (SharedMemoryMapping)mapping.Split(address);
- _mappings.UpdateEndAddress(oldMapping);
- if ((address & MappingMask) != 0)
- {
- releaseStart = false;
- }
- }
- releasedFirst = releaseStart;
- }
- _mappings.Remove(mapping);
- ulong releasePointer = (mapping.EndAddress + MappingMask) & (~MappingMask);
- for (int j = mapping.Blocks.Count - 1; j >= 0; j--)
- {
- int blockId = mapping.Blocks[j];
- releasePointer -= MappingGranularity;
- if (lastReleasedBlock == blockId)
- {
- // When committed regions are fragmented, multiple will have the same block id for their start/end granular block.
- // Avoid releasing these blocks twice.
- continue;
- }
- if ((j != 0 || releaseStart) && (j != mapping.Blocks.Count - 1 || releaseEnd))
- {
- ReleaseBackingBlock(releasePointer, blockId);
- }
- lastReleasedBlock = blockId;
- }
- }
- ulong placeholderStart = (granularStart >> MappingBits) + (releasedFirst ? 0UL : 1UL);
- ulong placeholderEnd = (granularEnd >> MappingBits) - (releasedLast ? 0UL : 1UL);
- if (placeholderEnd > placeholderStart)
- {
- _placeholders.RemovePlaceholders(placeholderStart, placeholderEnd - placeholderStart, CoalescePlaceholder);
- }
- }
- }
- public bool CommitMap(IntPtr address, IntPtr size)
- {
- lock (_lock)
- {
- foreach (ulong mapping in _mappedBases)
- {
- ulong offset = (ulong)address - mapping;
- if (offset < _size)
- {
- Commit(offset, (ulong)size);
- return true;
- }
- }
- }
- return false;
- }
- public bool DecommitMap(IntPtr address, IntPtr size)
- {
- lock (_lock)
- {
- foreach (ulong mapping in _mappedBases)
- {
- ulong offset = (ulong)address - mapping;
- if (offset < _size)
- {
- Decommit(offset, (ulong)size);
- return true;
- }
- }
- }
- return false;
- }
- private int MapBackingBlock(ulong offset)
- {
- bool allocate = false;
- int backing;
- if (_backingFreeList.Count > 0)
- {
- backing = _backingFreeList.Dequeue();
- }
- else
- {
- if (_backingAllocated == _backingEnd)
- {
- // Allocate the backing.
- _backingAllocated++;
- allocate = true;
- }
- backing = _backingEnd++;
- }
- ulong backingOffset = MappingGranularity * (ulong)backing;
- foreach (ulong baseAddress in _mappedBases)
- {
- CommitToMap(baseAddress, offset, MappingGranularity, backingOffset, allocate);
- allocate = false;
- }
- return backing;
- }
- private void ReleaseBackingBlock(ulong offset, int id)
- {
- foreach (ulong baseAddress in _mappedBases)
- {
- DecommitFromMap(baseAddress, offset);
- }
- if (_backingEnd - 1 == id)
- {
- _backingEnd = id;
- }
- else
- {
- _backingFreeList.Enqueue(id);
- }
- }
- public IntPtr Map()
- {
- IntPtr newMapping = VirtualAlloc2(
- CurrentProcessHandle,
- IntPtr.Zero,
- (IntPtr)_size,
- AllocationType.Reserve | AllocationType.ReservePlaceholder,
- MemoryProtection.NoAccess,
- IntPtr.Zero,
- 0);
- if (newMapping == IntPtr.Zero)
- {
- throw new OutOfMemoryException();
- }
- // Apply all existing mappings to the new mapping
- lock (_lock)
- {
- int lastBlock = -1;
- foreach (SharedMemoryMapping mapping in _mappings)
- {
- ulong blockAddress = mapping.Address & (~MappingMask);
- foreach (int block in mapping.Blocks)
- {
- if (block != lastBlock)
- {
- ulong backingOffset = MappingGranularity * (ulong)block;
- CommitToMap((ulong)newMapping, blockAddress, MappingGranularity, backingOffset, false);
- lastBlock = block;
- }
- blockAddress += MappingGranularity;
- }
- }
- _mappedBases.Add((ulong)newMapping);
- }
- return newMapping;
- }
- private void SplitPlaceholder(ulong address, ulong size)
- {
- ulong byteAddress = address << MappingBits;
- IntPtr byteSize = (IntPtr)(size << MappingBits);
- foreach (ulong mapAddress in _mappedBases)
- {
- bool result = VirtualFree((IntPtr)(mapAddress + byteAddress), byteSize, AllocationType.PreservePlaceholder | AllocationType.Release);
- if (!result)
- {
- throw new InvalidOperationException("Placeholder could not be split.");
- }
- }
- }
- private void CoalescePlaceholder(ulong address, ulong size)
- {
- ulong byteAddress = address << MappingBits;
- IntPtr byteSize = (IntPtr)(size << MappingBits);
- foreach (ulong mapAddress in _mappedBases)
- {
- bool result = VirtualFree((IntPtr)(mapAddress + byteAddress), byteSize, AllocationType.CoalescePlaceholders | AllocationType.Release);
- if (!result)
- {
- throw new InvalidOperationException("Placeholder could not be coalesced.");
- }
- }
- }
- private void CommitToMap(ulong mapAddress, ulong address, ulong size, ulong backingOffset, bool allocate)
- {
- IntPtr targetAddress = (IntPtr)(mapAddress + address);
- // Assume the placeholder worked (or already exists)
- // Map the backing memory into the mapped location.
- IntPtr mapped = MapViewOfFile3(
- _backingMemHandle,
- CurrentProcessHandle,
- targetAddress,
- backingOffset,
- (IntPtr)MappingGranularity,
- 0x4000, // REPLACE_PLACEHOLDER
- MemoryProtection.ReadWrite,
- IntPtr.Zero,
- 0);
- if (mapped == IntPtr.Zero)
- {
- throw new InvalidOperationException($"Could not map view of backing memory. (va=0x{address:X16} size=0x{size:X16}, error code {Marshal.GetLastWin32Error()})");
- }
- if (allocate)
- {
- // Commit this part of the shared memory.
- VirtualAlloc2(CurrentProcessHandle, targetAddress, (IntPtr)MappingGranularity, AllocationType.Commit, MemoryProtection.ReadWrite, IntPtr.Zero, 0);
- }
- }
- private void DecommitFromMap(ulong baseAddress, ulong address)
- {
- UnmapViewOfFile2(CurrentProcessHandle, (IntPtr)(baseAddress + address), 2);
- }
- public bool Unmap(ulong baseAddress)
- {
- lock (_lock)
- {
- if (_mappedBases.Remove(baseAddress))
- {
- int lastBlock = -1;
- foreach (SharedMemoryMapping mapping in _mappings)
- {
- ulong blockAddress = mapping.Address & (~MappingMask);
- foreach (int block in mapping.Blocks)
- {
- if (block != lastBlock)
- {
- DecommitFromMap(baseAddress, blockAddress);
- lastBlock = block;
- }
- blockAddress += MappingGranularity;
- }
- }
- if (!VirtualFree((IntPtr)baseAddress, (IntPtr)0, AllocationType.Release))
- {
- throw new InvalidOperationException("Couldn't free mapping placeholder.");
- }
- return true;
- }
- return false;
- }
- }
- public void Dispose()
- {
- // Remove all file mappings
- lock (_lock)
- {
- foreach (ulong baseAddress in _mappedBases.ToArray())
- {
- Unmap(baseAddress);
- }
- }
- // Finally, delete the file mapping.
- CloseHandle(_backingMemHandle);
- }
- }
- }
|