using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using static Ryujinx.Memory.MemoryManagerUnixHelper;
namespace Ryujinx.Memory
{
[SupportedOSPlatform("linux")]
[SupportedOSPlatform("macos")]
static class MemoryManagementUnix
private struct UnixSharedMemory
public IntPtr Pointer;
public ulong Size;
public IntPtr SourcePointer;
}
private static readonly List<UnixSharedMemory> _sharedMemory = new List<UnixSharedMemory>();
private static readonly ConcurrentDictionary<IntPtr, ulong> _sharedMemorySource = new ConcurrentDictionary<IntPtr, ulong>();
private static readonly ConcurrentDictionary<IntPtr, ulong> _allocations = new ConcurrentDictionary<IntPtr, ulong>();
public static IntPtr Allocate(ulong size)
return AllocateInternal(size, MmapProts.PROT_READ | MmapProts.PROT_WRITE);
public static IntPtr Reserve(ulong size)
return AllocateInternal(size, MmapProts.PROT_NONE);
private static IntPtr AllocateInternal(ulong size, MmapProts prot, bool shared = false)
MmapFlags flags = MmapFlags.MAP_ANONYMOUS;
if (shared)
flags |= MmapFlags.MAP_SHARED | MmapFlags.MAP_UNLOCKED;
else
flags |= MmapFlags.MAP_PRIVATE;
if (prot == MmapProts.PROT_NONE)
flags |= MmapFlags.MAP_NORESERVE;
IntPtr ptr = mmap(IntPtr.Zero, size, prot, flags, -1, 0);
if (ptr == new IntPtr(-1L))
throw new OutOfMemoryException();
if (!_allocations.TryAdd(ptr, size))
// This should be impossible, kernel shouldn't return an already mapped address.
throw new InvalidOperationException();
return ptr;
public static bool Commit(IntPtr address, ulong size)
bool success = mprotect(address, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE) == 0;
if (success)
foreach (var shared in _sharedMemory)
if ((ulong)address + size > (ulong)shared.SourcePointer && (ulong)address < (ulong)shared.SourcePointer + shared.Size)
ulong sharedAddress = ((ulong)address - (ulong)shared.SourcePointer) + (ulong)shared.Pointer;
if (mprotect((IntPtr)sharedAddress, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE) != 0)
return false;
return success;
public static bool Decommit(IntPtr address, ulong size)
bool isShared;
lock (_sharedMemory)
isShared = _sharedMemory.Exists(x => (ulong)address >= (ulong)x.Pointer && (ulong)address + size <= (ulong)x.Pointer + x.Size);
// Must be writable for madvise to work properly.
mprotect(address, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE);
madvise(address, size, isShared ? MADV_REMOVE : MADV_DONTNEED);
return mprotect(address, size, MmapProts.PROT_NONE) == 0;
public static bool Reprotect(IntPtr address, ulong size, MemoryPermission permission)
return mprotect(address, size, GetProtection(permission)) == 0;
private static MmapProts GetProtection(MemoryPermission permission)
return permission switch
MemoryPermission.None => MmapProts.PROT_NONE,
MemoryPermission.Read => MmapProts.PROT_READ,
MemoryPermission.ReadAndWrite => MmapProts.PROT_READ | MmapProts.PROT_WRITE,
MemoryPermission.ReadAndExecute => MmapProts.PROT_READ | MmapProts.PROT_EXEC,
MemoryPermission.ReadWriteExecute => MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC,
MemoryPermission.Execute => MmapProts.PROT_EXEC,
_ => throw new MemoryProtectionException(permission)
};
public static bool Free(IntPtr address)
if (_allocations.TryRemove(address, out ulong size))
return munmap(address, size) == 0;
public static IntPtr Remap(IntPtr target, IntPtr source, ulong size)
int flags = 1;
if (target != IntPtr.Zero)
flags |= 2;
IntPtr result = mremap(source, 0, size, flags, target);
if (result == IntPtr.Zero)
return result;
public static IntPtr CreateSharedMemory(ulong size, bool reserve)
IntPtr result = AllocateInternal(
size,
reserve ? MmapProts.PROT_NONE : MmapProts.PROT_READ | MmapProts.PROT_WRITE,
true);
_sharedMemorySource[result] = (ulong)size;
public static void DestroySharedMemory(IntPtr handle)
foreach (var memory in _sharedMemory)
if (memory.SourcePointer == handle)
throw new InvalidOperationException("Shared memory cannot be destroyed unless fully unmapped.");
_sharedMemorySource.Remove(handle, out ulong _);
public static IntPtr MapSharedMemory(IntPtr handle)
// Try find the handle for this shared memory. If it is mapped, then we want to map
// it a second time in another location.
// If it is not mapped, then its handle is the mapping.
ulong size = _sharedMemorySource[handle];
if (size == 0)
throw new InvalidOperationException("Shared memory cannot be mapped after its source is unmapped.");
if (memory.Pointer == handle)
memory.Size,
MmapProts.PROT_NONE
);
Remap(result, handle, memory.Size);
_sharedMemory.Add(new UnixSharedMemory
Pointer = result,
Size = memory.Size,
SourcePointer = handle
});
Pointer = handle,
Size = size,
return handle;
public static void UnmapSharedMemory(IntPtr address)
int removed = _sharedMemory.RemoveAll(memory =>
if (memory.Pointer == address)
if (memory.Pointer == memory.SourcePointer)
// After removing the original mapping, it cannot be mapped again.
_sharedMemorySource[memory.SourcePointer] = 0;
Free(address);
return true;
if (removed == 0)
throw new InvalidOperationException("Shared memory mapping could not be found.");