Support memory aliasing (#2954)

* Back to the origins: Make memory manager take guest PA rather than host address once again

* Direct mapping with alias support on Windows

* Fixes and remove more of the emulated shared memory

* Linux support

* Make shared and transfer memory not depend on SharedMemoryStorage

* More efficient view mapping on Windows (no more restricted to 4KB pages at a time)

* Handle potential access violations caused by partial unmap

* Implement host mapping using shared memory on Linux

* Add new GetPhysicalAddressChecked method, used to ensure the virtual address is mapped before address translation

Also align GetRef behaviour with software memory manager

* We don't need a mirrorable memory block for software memory manager mode

* Disable memory aliasing tests while we don't have shared memory support on Mac

* Shared memory & SIGBUS handler for macOS

* Fix typo + nits + re-enable memory tests

* Set MAP_JIT_DARWIN on x86 Mac too

* Add back the address space mirror

* Only set MAP_JIT_DARWIN if we are mapping as executable

* Disable aliasing tests again (still fails on Mac)

* Fix UnmapView4KB (by not casting size to int)

* Use ref counting on memory blocks to delay closing the shared memory handle until all blocks using it are disposed

* Address PR feedback

* Make RO hold a reference to the guest process memory manager to avoid early disposal

Co-authored-by: nastys <nastys@users.noreply.github.com>
This commit is contained in:
gdkchan 2022-05-02 20:30:02 -03:00 committed by GitHub
parent 4a892fbdc9
commit 95017b8c66
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 2373 additions and 2155 deletions

View file

@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Threading;
@ -11,8 +12,12 @@ namespace Ryujinx.Memory
{
private readonly bool _usesSharedMemory;
private readonly bool _isMirror;
private readonly bool _viewCompatible;
private readonly bool _forceWindows4KBView;
private IntPtr _sharedMemory;
private IntPtr _pointer;
private ConcurrentDictionary<MemoryBlock, byte> _viewStorages;
private int _viewCount;
/// <summary>
/// Pointer to the memory block data.
@ -36,12 +41,14 @@ namespace Ryujinx.Memory
if (flags.HasFlag(MemoryAllocationFlags.Mirrorable))
{
_sharedMemory = MemoryManagement.CreateSharedMemory(size, flags.HasFlag(MemoryAllocationFlags.Reserve));
_pointer = MemoryManagement.MapSharedMemory(_sharedMemory);
_pointer = MemoryManagement.MapSharedMemory(_sharedMemory, size);
_usesSharedMemory = true;
}
else if (flags.HasFlag(MemoryAllocationFlags.Reserve))
{
_pointer = MemoryManagement.Reserve(size);
_viewCompatible = flags.HasFlag(MemoryAllocationFlags.ViewCompatible);
_forceWindows4KBView = flags.HasFlag(MemoryAllocationFlags.ForceWindows4KBViewMapping);
_pointer = MemoryManagement.Reserve(size, _viewCompatible);
}
else
{
@ -49,6 +56,10 @@ namespace Ryujinx.Memory
}
Size = size;
_viewStorages = new ConcurrentDictionary<MemoryBlock, byte>();
_viewStorages.TryAdd(this, 0);
_viewCount = 1;
}
/// <summary>
@ -60,7 +71,7 @@ namespace Ryujinx.Memory
/// <exception cref="PlatformNotSupportedException">Throw when the current platform is not supported</exception>
private MemoryBlock(ulong size, IntPtr sharedMemory)
{
_pointer = MemoryManagement.MapSharedMemory(sharedMemory);
_pointer = MemoryManagement.MapSharedMemory(sharedMemory, size);
Size = size;
_usesSharedMemory = true;
_isMirror = true;
@ -112,6 +123,42 @@ namespace Ryujinx.Memory
return MemoryManagement.Decommit(GetPointerInternal(offset, size), size);
}
/// <summary>
/// Maps a view of memory from another memory block.
/// </summary>
/// <param name="srcBlock">Memory block from where the backing memory will be taken</param>
/// <param name="srcOffset">Offset on <paramref name="srcBlock"/> of the region that should be mapped</param>
/// <param name="dstOffset">Offset to map the view into on this block</param>
/// <param name="size">Size of the range to be mapped</param>
/// <exception cref="NotSupportedException">Throw when the source memory block does not support mirroring</exception>
/// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception>
/// <exception cref="InvalidMemoryRegionException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception>
public void MapView(MemoryBlock srcBlock, ulong srcOffset, ulong dstOffset, ulong size)
{
if (srcBlock._sharedMemory == IntPtr.Zero)
{
throw new ArgumentException("The source memory block is not mirrorable, and thus cannot be mapped on the current block.");
}
if (_viewStorages.TryAdd(srcBlock, 0))
{
srcBlock.IncrementViewCount();
}
MemoryManagement.MapView(srcBlock._sharedMemory, srcOffset, GetPointerInternal(dstOffset, size), size, _forceWindows4KBView);
}
/// <summary>
/// Unmaps a view of memory from another memory block.
/// </summary>
/// <param name="srcBlock">Memory block from where the backing memory was taken during map</param>
/// <param name="offset">Offset of the view previously mapped with <see cref="MapView"/></param>
/// <param name="size">Size of the range to be unmapped</param>
public void UnmapView(MemoryBlock srcBlock, ulong offset, ulong size)
{
MemoryManagement.UnmapView(srcBlock._sharedMemory, GetPointerInternal(offset, size), size, _forceWindows4KBView);
}
/// <summary>
/// Reprotects a region of memory.
/// </summary>
@ -124,21 +171,7 @@ namespace Ryujinx.Memory
/// <exception cref="MemoryProtectionException">Throw when <paramref name="permission"/> is invalid</exception>
public void Reprotect(ulong offset, ulong size, MemoryPermission permission, bool throwOnFail = true)
{
MemoryManagement.Reprotect(GetPointerInternal(offset, size), size, permission, throwOnFail);
}
/// <summary>
/// Remaps a region of memory into this memory block.
/// </summary>
/// <param name="offset">Starting offset of the range to be remapped into</param>
/// <param name="sourceAddress">Starting offset of the range to be remapped from</param>
/// <param name="size">Size of the range to be remapped</param>
/// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception>
/// <exception cref="InvalidMemoryRegionException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception>
/// <exception cref="MemoryProtectionException">Throw when <paramref name="permission"/> is invalid</exception>
public void Remap(ulong offset, IntPtr sourceAddress, ulong size)
{
MemoryManagement.Remap(GetPointerInternal(offset, size), sourceAddress, size);
MemoryManagement.Reprotect(GetPointerInternal(offset, size), size, permission, _viewCompatible, _forceWindows4KBView, throwOnFail);
}
/// <summary>
@ -274,7 +307,7 @@ namespace Ryujinx.Memory
/// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception>
/// <exception cref="InvalidMemoryRegionException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public nuint GetPointer(ulong offset, ulong size) => (nuint)(ulong)GetPointerInternal(offset, size);
public IntPtr GetPointer(ulong offset, ulong size) => GetPointerInternal(offset, size);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private IntPtr GetPointerInternal(ulong offset, ulong size)
@ -367,22 +400,63 @@ namespace Ryujinx.Memory
{
if (_usesSharedMemory)
{
MemoryManagement.UnmapSharedMemory(ptr);
if (_sharedMemory != IntPtr.Zero && !_isMirror)
{
MemoryManagement.DestroySharedMemory(_sharedMemory);
_sharedMemory = IntPtr.Zero;
}
MemoryManagement.UnmapSharedMemory(ptr, Size);
}
else
{
MemoryManagement.Free(ptr);
}
foreach (MemoryBlock viewStorage in _viewStorages.Keys)
{
viewStorage.DecrementViewCount();
}
_viewStorages.Clear();
}
}
private void ThrowObjectDisposed() => throw new ObjectDisposedException(nameof(MemoryBlock));
private void ThrowInvalidMemoryRegionException() => throw new InvalidMemoryRegionException();
/// <summary>
/// Increments the number of views that uses this memory block as storage.
/// </summary>
private void IncrementViewCount()
{
Interlocked.Increment(ref _viewCount);
}
/// <summary>
/// Decrements the number of views that uses this memory block as storage.
/// </summary>
private void DecrementViewCount()
{
if (Interlocked.Decrement(ref _viewCount) == 0 && _sharedMemory != IntPtr.Zero && !_isMirror)
{
MemoryManagement.DestroySharedMemory(_sharedMemory);
_sharedMemory = IntPtr.Zero;
}
}
/// <summary>
/// Checks if the specified memory allocation flags are supported on the current platform.
/// </summary>
/// <param name="flags">Flags to be checked</param>
/// <returns>True if the platform supports all the flags, false otherwise</returns>
public static bool SupportsFlags(MemoryAllocationFlags flags)
{
if (flags.HasFlag(MemoryAllocationFlags.ViewCompatible))
{
if (OperatingSystem.IsWindows())
{
return OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134);
}
return OperatingSystem.IsLinux() || OperatingSystem.IsMacOS();
}
return true;
}
private static void ThrowObjectDisposed() => throw new ObjectDisposedException(nameof(MemoryBlock));
private static void ThrowInvalidMemoryRegionException() => throw new InvalidMemoryRegionException();
}
}