using System;
using System.Runtime.CompilerServices;
using System.Threading;
namespace Ryujinx.Memory
{
///
/// Represents a block of contiguous physical guest memory.
///
public sealed class MemoryBlock : IWritableBlock, IDisposable
{
private readonly bool _usesSharedMemory;
private readonly bool _isMirror;
private IntPtr _sharedMemory;
private IntPtr _pointer;
///
/// Pointer to the memory block data.
///
public IntPtr Pointer => _pointer;
///
/// Size of the memory block.
///
public ulong Size { get; }
///
/// Creates a new instance of the memory block class.
///
/// Size of the memory block in bytes
/// Flags that controls memory block memory allocation
/// Throw when there's no enough memory to allocate the requested size
/// Throw when the current platform is not supported
public MemoryBlock(ulong size, MemoryAllocationFlags flags = MemoryAllocationFlags.None)
{
if (flags.HasFlag(MemoryAllocationFlags.Mirrorable))
{
_sharedMemory = MemoryManagement.CreateSharedMemory(size, flags.HasFlag(MemoryAllocationFlags.Reserve));
_pointer = MemoryManagement.MapSharedMemory(_sharedMemory);
_usesSharedMemory = true;
}
else if (flags.HasFlag(MemoryAllocationFlags.Reserve))
{
_pointer = MemoryManagement.Reserve(size);
}
else
{
_pointer = MemoryManagement.Allocate(size);
}
Size = size;
}
///
/// Creates a new instance of the memory block class, with a existing backing storage.
///
/// Size of the memory block in bytes
/// Shared memory to use as backing storage for this block
/// Throw when there's no enough address space left to map the shared memory
/// Throw when the current platform is not supported
private MemoryBlock(ulong size, IntPtr sharedMemory)
{
_pointer = MemoryManagement.MapSharedMemory(sharedMemory);
Size = size;
_usesSharedMemory = true;
_isMirror = true;
}
///
/// Creates a memory block that shares the backing storage with this block.
/// The memory and page commitments will be shared, however memory protections are separate.
///
/// A new memory block that shares storage with this one
/// Throw when the current memory block does not support mirroring
/// Throw when there's no enough address space left to map the shared memory
/// Throw when the current platform is not supported
public MemoryBlock CreateMirror()
{
if (_sharedMemory == IntPtr.Zero)
{
throw new NotSupportedException("Mirroring is not supported on the memory block because the Mirrorable flag was not set.");
}
return new MemoryBlock(Size, _sharedMemory);
}
///
/// Commits a region of memory that has previously been reserved.
/// This can be used to allocate memory on demand.
///
/// Starting offset of the range to be committed
/// Size of the range to be committed
/// True if the operation was successful, false otherwise
/// Throw when the memory block has already been disposed
/// Throw when either or are out of range
public bool Commit(ulong offset, ulong size)
{
return MemoryManagement.Commit(GetPointerInternal(offset, size), size);
}
///
/// Decommits a region of memory that has previously been reserved and optionally comitted.
/// This can be used to free previously allocated memory on demand.
///
/// Starting offset of the range to be decommitted
/// Size of the range to be decommitted
/// True if the operation was successful, false otherwise
/// Throw when the memory block has already been disposed
/// Throw when either or are out of range
public bool Decommit(ulong offset, ulong size)
{
return MemoryManagement.Decommit(GetPointerInternal(offset, size), size);
}
///
/// Reprotects a region of memory.
///
/// Starting offset of the range to be reprotected
/// Size of the range to be reprotected
/// New memory permissions
/// True if a failed reprotect should throw
/// Throw when the memory block has already been disposed
/// Throw when either or are out of range
/// Throw when is invalid
public void Reprotect(ulong offset, ulong size, MemoryPermission permission, bool throwOnFail = true)
{
MemoryManagement.Reprotect(GetPointerInternal(offset, size), size, permission, throwOnFail);
}
///
/// Remaps a region of memory into this memory block.
///
/// Starting offset of the range to be remapped into
/// Starting offset of the range to be remapped from
/// Size of the range to be remapped
/// Throw when the memory block has already been disposed
/// Throw when either or are out of range
/// Throw when is invalid
public void Remap(ulong offset, IntPtr sourceAddress, ulong size)
{
MemoryManagement.Remap(GetPointerInternal(offset, size), sourceAddress, size);
}
///
/// Reads bytes from the memory block.
///
/// Starting offset of the range being read
/// Span where the bytes being read will be copied to
/// Throw when the memory block has already been disposed
/// Throw when the memory region specified for the the data is out of range
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Read(ulong offset, Span data)
{
GetSpan(offset, data.Length).CopyTo(data);
}
///
/// Reads data from the memory block.
///
/// Type of the data
/// Offset where the data is located
/// Data at the specified address
/// Throw when the memory block has already been disposed
/// Throw when the memory region specified for the the data is out of range
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Read(ulong offset) where T : unmanaged
{
return GetRef(offset);
}
///
/// Writes bytes to the memory block.
///
/// Starting offset of the range being written
/// Span where the bytes being written will be copied from
/// Throw when the memory block has already been disposed
/// Throw when the memory region specified for the the data is out of range
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ulong offset, ReadOnlySpan data)
{
data.CopyTo(GetSpan(offset, data.Length));
}
///
/// Writes data to the memory block.
///
/// Type of the data being written
/// Offset to write the data into
/// Data to be written
/// Throw when the memory block has already been disposed
/// Throw when the memory region specified for the the data is out of range
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ulong offset, T data) where T : unmanaged
{
GetRef(offset) = data;
}
///
/// Copies data from one memory location to another.
///
/// Destination offset to write the data into
/// Source offset to read the data from
/// Size of the copy in bytes
/// Throw when the memory block has already been disposed
/// Throw when , or is out of range
public void Copy(ulong dstOffset, ulong srcOffset, ulong size)
{
const int MaxChunkSize = 1 << 24;
for (ulong offset = 0; offset < size; offset += MaxChunkSize)
{
int copySize = (int)Math.Min(MaxChunkSize, size - offset);
Write(dstOffset + offset, GetSpan(srcOffset + offset, copySize));
}
}
///
/// Fills a region of memory with .
///
/// Offset of the region to fill with
/// Size in bytes of the region to fill
/// Value to use for the fill
/// Throw when the memory block has already been disposed
/// Throw when either or are out of range
public void Fill(ulong offset, ulong size, byte value)
{
const int MaxChunkSize = 1 << 24;
for (ulong subOffset = 0; subOffset < size; subOffset += MaxChunkSize)
{
int copySize = (int)Math.Min(MaxChunkSize, size - subOffset);
GetSpan(offset + subOffset, copySize).Fill(value);
}
}
///
/// Gets a reference of the data at a given memory block region.
///
/// Data type
/// Offset of the memory region
/// A reference to the given memory region data
/// Throw when the memory block has already been disposed
/// Throw when either or are out of range
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe ref T GetRef(ulong offset) where T : unmanaged
{
IntPtr ptr = _pointer;
if (ptr == IntPtr.Zero)
{
ThrowObjectDisposed();
}
int size = Unsafe.SizeOf();
ulong endOffset = offset + (ulong)size;
if (endOffset > Size || endOffset < offset)
{
ThrowInvalidMemoryRegionException();
}
return ref Unsafe.AsRef((void*)PtrAddr(ptr, offset));
}
///
/// Gets the pointer of a given memory block region.
///
/// Start offset of the memory region
/// Size in bytes of the region
/// The pointer to the memory region
/// Throw when the memory block has already been disposed
/// Throw when either or are out of range
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public nuint GetPointer(ulong offset, ulong size) => (nuint)(ulong)GetPointerInternal(offset, size);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private IntPtr GetPointerInternal(ulong offset, ulong size)
{
IntPtr ptr = _pointer;
if (ptr == IntPtr.Zero)
{
ThrowObjectDisposed();
}
ulong endOffset = offset + size;
if (endOffset > Size || endOffset < offset)
{
ThrowInvalidMemoryRegionException();
}
return PtrAddr(ptr, offset);
}
///
/// Gets the of a given memory block region.
///
/// Start offset of the memory region
/// Size in bytes of the region
/// Span of the memory region
/// Throw when the memory block has already been disposed
/// Throw when either or are out of range
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe Span GetSpan(ulong offset, int size)
{
return new Span((void*)GetPointerInternal(offset, (ulong)size), size);
}
///
/// Gets the of a given memory block region.
///
/// Start offset of the memory region
/// Size in bytes of the region
/// Memory of the memory region
/// Throw when the memory block has already been disposed
/// Throw when either or are out of range
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe Memory GetMemory(ulong offset, int size)
{
return new NativeMemoryManager((byte*)GetPointerInternal(offset, (ulong)size), size).Memory;
}
///
/// Gets a writable region of a given memory block region.
///
/// Start offset of the memory region
/// Size in bytes of the region
/// Writable region of the memory region
/// Throw when the memory block has already been disposed
/// Throw when either or are out of range
public WritableRegion GetWritableRegion(ulong offset, int size)
{
return new WritableRegion(null, offset, GetMemory(offset, size));
}
///
/// Adds a 64-bits offset to a native pointer.
///
/// Native pointer
/// Offset to add
/// Native pointer with the added offset
private IntPtr PtrAddr(IntPtr pointer, ulong offset)
{
return (IntPtr)(pointer.ToInt64() + (long)offset);
}
///
/// Frees the memory allocated for this memory block.
///
///
/// It's an error to use the memory block after disposal.
///
public void Dispose() => FreeMemory();
~MemoryBlock() => FreeMemory();
private void FreeMemory()
{
IntPtr ptr = Interlocked.Exchange(ref _pointer, IntPtr.Zero);
// If pointer is null, the memory was already freed or never allocated.
if (ptr != IntPtr.Zero)
{
if (_usesSharedMemory)
{
MemoryManagement.UnmapSharedMemory(ptr);
if (_sharedMemory != IntPtr.Zero && !_isMirror)
{
MemoryManagement.DestroySharedMemory(_sharedMemory);
_sharedMemory = IntPtr.Zero;
}
}
else
{
MemoryManagement.Free(ptr);
}
}
}
private void ThrowObjectDisposed() => throw new ObjectDisposedException(nameof(MemoryBlock));
private void ThrowInvalidMemoryRegionException() => throw new InvalidMemoryRegionException();
}
}