using System;
using System.Runtime.CompilerServices;
using System.Threading;
namespace Ryujinx.Memory
{
///
/// Represents a block of contiguous physical guest memory.
///
public sealed class MemoryBlock : IDisposable
{
private IntPtr _pointer;
///
/// Pointer to the memory block data.
///
public IntPtr Pointer => _pointer;
///
/// Size of the memory block.
///
public ulong Size { get; }
///
/// Initializes a new instance of the memory block class.
///
/// Size of the memory block
/// 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.Reserve))
{
_pointer = MemoryManagement.Reserve(size);
}
else
{
_pointer = MemoryManagement.Allocate(size);
}
Size = size;
GC.AddMemoryPressure((long)Size);
}
///
/// 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);
}
///
/// Reprotects a region of memory.
///
/// Starting offset of the range to be reprotected
/// Size of the range to be reprotected
/// New memory permissions
/// 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)
{
MemoryManagement.Reprotect(GetPointerInternal(offset, size), size, permission);
}
///
/// 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 << 30;
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 zeros.
///
/// Offset of the region to fill with zeros
/// Size in bytes of the region to fill
/// Throw when the memory block has already been disposed
/// Throw when either or are out of range
public void ZeroFill(ulong offset, ulong size)
{
const int MaxChunkSize = 1 << 30;
for (ulong subOffset = 0; subOffset < size; subOffset += MaxChunkSize)
{
int copySize = (int)Math.Min(MaxChunkSize, size - subOffset);
GetSpan(offset + subOffset, copySize).Fill(0);
}
}
///
/// 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 IntPtr GetPointer(ulong offset, int size) => GetPointerInternal(offset, (ulong)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*)GetPointer(offset, 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*)GetPointer(offset, size), size).Memory;
}
///
/// 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)
{
MemoryManagement.Free(ptr);
GC.RemoveMemoryPressure((long)Size);
}
}
private void ThrowObjectDisposed() => throw new ObjectDisposedException(nameof(MemoryBlock));
private void ThrowInvalidMemoryRegionException() => throw new InvalidMemoryRegionException();
}
}