MemoryBlock.cs
1 using System; 2 using System.Runtime.CompilerServices; 3 using System.Threading; 4 5 namespace Ryujinx.Memory 6 { 7 /// <summary> 8 /// Represents a block of contiguous physical guest memory. 9 /// </summary> 10 public sealed class MemoryBlock : IWritableBlock, IDisposable 11 { 12 private readonly bool _usesSharedMemory; 13 private readonly bool _isMirror; 14 private readonly bool _viewCompatible; 15 private readonly bool _forJit; 16 private IntPtr _sharedMemory; 17 private IntPtr _pointer; 18 19 /// <summary> 20 /// Pointer to the memory block data. 21 /// </summary> 22 public IntPtr Pointer => _pointer; 23 24 /// <summary> 25 /// Size of the memory block. 26 /// </summary> 27 public ulong Size { get; } 28 29 /// <summary> 30 /// Creates a new instance of the memory block class. 31 /// </summary> 32 /// <param name="size">Size of the memory block in bytes</param> 33 /// <param name="flags">Flags that controls memory block memory allocation</param> 34 /// <exception cref="SystemException">Throw when there's an error while allocating the requested size</exception> 35 /// <exception cref="PlatformNotSupportedException">Throw when the current platform is not supported</exception> 36 public MemoryBlock(ulong size, MemoryAllocationFlags flags = MemoryAllocationFlags.None) 37 { 38 if (flags.HasFlag(MemoryAllocationFlags.Mirrorable)) 39 { 40 _sharedMemory = MemoryManagement.CreateSharedMemory(size, flags.HasFlag(MemoryAllocationFlags.Reserve)); 41 42 if (!flags.HasFlag(MemoryAllocationFlags.NoMap)) 43 { 44 _pointer = MemoryManagement.MapSharedMemory(_sharedMemory, size); 45 } 46 47 _usesSharedMemory = true; 48 } 49 else if (flags.HasFlag(MemoryAllocationFlags.Reserve)) 50 { 51 _viewCompatible = flags.HasFlag(MemoryAllocationFlags.ViewCompatible); 52 _forJit = flags.HasFlag(MemoryAllocationFlags.Jit); 53 _pointer = MemoryManagement.Reserve(size, _forJit, _viewCompatible); 54 } 55 else 56 { 57 _forJit = flags.HasFlag(MemoryAllocationFlags.Jit); 58 _pointer = MemoryManagement.Allocate(size, _forJit); 59 } 60 61 Size = size; 62 } 63 64 /// <summary> 65 /// Creates a new instance of the memory block class, with a existing backing storage. 66 /// </summary> 67 /// <param name="size">Size of the memory block in bytes</param> 68 /// <param name="sharedMemory">Shared memory to use as backing storage for this block</param> 69 /// <exception cref="SystemException">Throw when there's an error while mapping the shared memory</exception> 70 /// <exception cref="PlatformNotSupportedException">Throw when the current platform is not supported</exception> 71 private MemoryBlock(ulong size, IntPtr sharedMemory) 72 { 73 _pointer = MemoryManagement.MapSharedMemory(sharedMemory, size); 74 Size = size; 75 _usesSharedMemory = true; 76 _isMirror = true; 77 } 78 79 /// <summary> 80 /// Creates a memory block that shares the backing storage with this block. 81 /// The memory and page commitments will be shared, however memory protections are separate. 82 /// </summary> 83 /// <returns>A new memory block that shares storage with this one</returns> 84 /// <exception cref="NotSupportedException">Throw when the current memory block does not support mirroring</exception> 85 /// <exception cref="SystemException">Throw when there's an error while mapping the shared memory</exception> 86 /// <exception cref="PlatformNotSupportedException">Throw when the current platform is not supported</exception> 87 public MemoryBlock CreateMirror() 88 { 89 if (_sharedMemory == IntPtr.Zero) 90 { 91 throw new NotSupportedException("Mirroring is not supported on the memory block because the Mirrorable flag was not set."); 92 } 93 94 return new MemoryBlock(Size, _sharedMemory); 95 } 96 97 /// <summary> 98 /// Commits a region of memory that has previously been reserved. 99 /// This can be used to allocate memory on demand. 100 /// </summary> 101 /// <param name="offset">Starting offset of the range to be committed</param> 102 /// <param name="size">Size of the range to be committed</param> 103 /// <exception cref="SystemException">Throw when the operation was not successful</exception> 104 /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception> 105 /// <exception cref="InvalidMemoryRegionException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception> 106 public void Commit(ulong offset, ulong size) 107 { 108 MemoryManagement.Commit(GetPointerInternal(offset, size), size, _forJit); 109 } 110 111 /// <summary> 112 /// Decommits a region of memory that has previously been reserved and optionally comitted. 113 /// This can be used to free previously allocated memory on demand. 114 /// </summary> 115 /// <param name="offset">Starting offset of the range to be decommitted</param> 116 /// <param name="size">Size of the range to be decommitted</param> 117 /// <exception cref="SystemException">Throw when the operation was not successful</exception> 118 /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception> 119 /// <exception cref="InvalidMemoryRegionException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception> 120 public void Decommit(ulong offset, ulong size) 121 { 122 MemoryManagement.Decommit(GetPointerInternal(offset, size), size); 123 } 124 125 /// <summary> 126 /// Maps a view of memory from another memory block. 127 /// </summary> 128 /// <param name="srcBlock">Memory block from where the backing memory will be taken</param> 129 /// <param name="srcOffset">Offset on <paramref name="srcBlock"/> of the region that should be mapped</param> 130 /// <param name="dstOffset">Offset to map the view into on this block</param> 131 /// <param name="size">Size of the range to be mapped</param> 132 /// <exception cref="NotSupportedException">Throw when the source memory block does not support mirroring</exception> 133 /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception> 134 /// <exception cref="InvalidMemoryRegionException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception> 135 public void MapView(MemoryBlock srcBlock, ulong srcOffset, ulong dstOffset, ulong size) 136 { 137 if (srcBlock._sharedMemory == IntPtr.Zero) 138 { 139 throw new ArgumentException("The source memory block is not mirrorable, and thus cannot be mapped on the current block."); 140 } 141 142 MemoryManagement.MapView(srcBlock._sharedMemory, srcOffset, GetPointerInternal(dstOffset, size), size, this); 143 } 144 145 /// <summary> 146 /// Unmaps a view of memory from another memory block. 147 /// </summary> 148 /// <param name="srcBlock">Memory block from where the backing memory was taken during map</param> 149 /// <param name="offset">Offset of the view previously mapped with <see cref="MapView"/></param> 150 /// <param name="size">Size of the range to be unmapped</param> 151 public void UnmapView(MemoryBlock srcBlock, ulong offset, ulong size) 152 { 153 MemoryManagement.UnmapView(srcBlock._sharedMemory, GetPointerInternal(offset, size), size, this); 154 } 155 156 /// <summary> 157 /// Reprotects a region of memory. 158 /// </summary> 159 /// <param name="offset">Starting offset of the range to be reprotected</param> 160 /// <param name="size">Size of the range to be reprotected</param> 161 /// <param name="permission">New memory permissions</param> 162 /// <param name="throwOnFail">True if a failed reprotect should throw</param> 163 /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception> 164 /// <exception cref="InvalidMemoryRegionException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception> 165 /// <exception cref="MemoryProtectionException">Throw when <paramref name="permission"/> is invalid</exception> 166 public void Reprotect(ulong offset, ulong size, MemoryPermission permission, bool throwOnFail = true) 167 { 168 MemoryManagement.Reprotect(GetPointerInternal(offset, size), size, permission, _viewCompatible, throwOnFail); 169 } 170 171 /// <summary> 172 /// Reads bytes from the memory block. 173 /// </summary> 174 /// <param name="offset">Starting offset of the range being read</param> 175 /// <param name="data">Span where the bytes being read will be copied to</param> 176 /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception> 177 /// <exception cref="InvalidMemoryRegionException">Throw when the memory region specified for the data is out of range</exception> 178 [MethodImpl(MethodImplOptions.AggressiveInlining)] 179 public void Read(ulong offset, Span<byte> data) 180 { 181 GetSpan(offset, data.Length).CopyTo(data); 182 } 183 184 /// <summary> 185 /// Reads data from the memory block. 186 /// </summary> 187 /// <typeparam name="T">Type of the data</typeparam> 188 /// <param name="offset">Offset where the data is located</param> 189 /// <returns>Data at the specified address</returns> 190 /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception> 191 /// <exception cref="InvalidMemoryRegionException">Throw when the memory region specified for the data is out of range</exception> 192 [MethodImpl(MethodImplOptions.AggressiveInlining)] 193 public T Read<T>(ulong offset) where T : unmanaged 194 { 195 return GetRef<T>(offset); 196 } 197 198 /// <summary> 199 /// Writes bytes to the memory block. 200 /// </summary> 201 /// <param name="offset">Starting offset of the range being written</param> 202 /// <param name="data">Span where the bytes being written will be copied from</param> 203 /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception> 204 /// <exception cref="InvalidMemoryRegionException">Throw when the memory region specified for the data is out of range</exception> 205 [MethodImpl(MethodImplOptions.AggressiveInlining)] 206 public void Write(ulong offset, ReadOnlySpan<byte> data) 207 { 208 data.CopyTo(GetSpan(offset, data.Length)); 209 } 210 211 /// <summary> 212 /// Writes data to the memory block. 213 /// </summary> 214 /// <typeparam name="T">Type of the data being written</typeparam> 215 /// <param name="offset">Offset to write the data into</param> 216 /// <param name="data">Data to be written</param> 217 /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception> 218 /// <exception cref="InvalidMemoryRegionException">Throw when the memory region specified for the data is out of range</exception> 219 [MethodImpl(MethodImplOptions.AggressiveInlining)] 220 public void Write<T>(ulong offset, T data) where T : unmanaged 221 { 222 GetRef<T>(offset) = data; 223 } 224 225 /// <summary> 226 /// Copies data from one memory location to another. 227 /// </summary> 228 /// <param name="dstOffset">Destination offset to write the data into</param> 229 /// <param name="srcOffset">Source offset to read the data from</param> 230 /// <param name="size">Size of the copy in bytes</param> 231 /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception> 232 /// <exception cref="InvalidMemoryRegionException">Throw when <paramref name="srcOffset"/>, <paramref name="dstOffset"/> or <paramref name="size"/> is out of range</exception> 233 public void Copy(ulong dstOffset, ulong srcOffset, ulong size) 234 { 235 const int MaxChunkSize = 1 << 24; 236 237 for (ulong offset = 0; offset < size; offset += MaxChunkSize) 238 { 239 int copySize = (int)Math.Min(MaxChunkSize, size - offset); 240 241 Write(dstOffset + offset, GetSpan(srcOffset + offset, copySize)); 242 } 243 } 244 245 /// <summary> 246 /// Fills a region of memory with <paramref name="value"/>. 247 /// </summary> 248 /// <param name="offset">Offset of the region to fill with <paramref name="value"/></param> 249 /// <param name="size">Size in bytes of the region to fill</param> 250 /// <param name="value">Value to use for the fill</param> 251 /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception> 252 /// <exception cref="InvalidMemoryRegionException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception> 253 public void Fill(ulong offset, ulong size, byte value) 254 { 255 const int MaxChunkSize = 1 << 24; 256 257 for (ulong subOffset = 0; subOffset < size; subOffset += MaxChunkSize) 258 { 259 int copySize = (int)Math.Min(MaxChunkSize, size - subOffset); 260 261 GetSpan(offset + subOffset, copySize).Fill(value); 262 } 263 } 264 265 /// <summary> 266 /// Gets a reference of the data at a given memory block region. 267 /// </summary> 268 /// <typeparam name="T">Data type</typeparam> 269 /// <param name="offset">Offset of the memory region</param> 270 /// <returns>A reference to the given memory region data</returns> 271 /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception> 272 /// <exception cref="InvalidMemoryRegionException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception> 273 [MethodImpl(MethodImplOptions.AggressiveInlining)] 274 public unsafe ref T GetRef<T>(ulong offset) where T : unmanaged 275 { 276 IntPtr ptr = _pointer; 277 278 ObjectDisposedException.ThrowIf(ptr == IntPtr.Zero, this); 279 280 int size = Unsafe.SizeOf<T>(); 281 282 ulong endOffset = offset + (ulong)size; 283 284 if (endOffset > Size || endOffset < offset) 285 { 286 ThrowInvalidMemoryRegionException(); 287 } 288 289 return ref Unsafe.AsRef<T>((void*)PtrAddr(ptr, offset)); 290 } 291 292 /// <summary> 293 /// Gets the pointer of a given memory block region. 294 /// </summary> 295 /// <param name="offset">Start offset of the memory region</param> 296 /// <param name="size">Size in bytes of the region</param> 297 /// <returns>The pointer to the memory region</returns> 298 /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception> 299 /// <exception cref="InvalidMemoryRegionException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception> 300 [MethodImpl(MethodImplOptions.AggressiveInlining)] 301 public IntPtr GetPointer(ulong offset, ulong size) => GetPointerInternal(offset, size); 302 303 [MethodImpl(MethodImplOptions.AggressiveInlining)] 304 private IntPtr GetPointerInternal(ulong offset, ulong size) 305 { 306 IntPtr ptr = _pointer; 307 308 ObjectDisposedException.ThrowIf(ptr == IntPtr.Zero, this); 309 310 ulong endOffset = offset + size; 311 312 if (endOffset > Size || endOffset < offset) 313 { 314 ThrowInvalidMemoryRegionException(); 315 } 316 317 return PtrAddr(ptr, offset); 318 } 319 320 /// <summary> 321 /// Gets the <see cref="Span{T}"/> of a given memory block region. 322 /// </summary> 323 /// <param name="offset">Start offset of the memory region</param> 324 /// <param name="size">Size in bytes of the region</param> 325 /// <returns>Span of the memory region</returns> 326 /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception> 327 /// <exception cref="InvalidMemoryRegionException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception> 328 [MethodImpl(MethodImplOptions.AggressiveInlining)] 329 public unsafe Span<byte> GetSpan(ulong offset, int size) 330 { 331 return new Span<byte>((void*)GetPointerInternal(offset, (ulong)size), size); 332 } 333 334 /// <summary> 335 /// Gets the <see cref="Memory{T}"/> of a given memory block region. 336 /// </summary> 337 /// <param name="offset">Start offset of the memory region</param> 338 /// <param name="size">Size in bytes of the region</param> 339 /// <returns>Memory of the memory region</returns> 340 /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception> 341 /// <exception cref="InvalidMemoryRegionException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception> 342 [MethodImpl(MethodImplOptions.AggressiveInlining)] 343 public unsafe Memory<byte> GetMemory(ulong offset, int size) 344 { 345 return new NativeMemoryManager<byte>((byte*)GetPointerInternal(offset, (ulong)size), size).Memory; 346 } 347 348 /// <summary> 349 /// Gets a writable region of a given memory block region. 350 /// </summary> 351 /// <param name="offset">Start offset of the memory region</param> 352 /// <param name="size">Size in bytes of the region</param> 353 /// <returns>Writable region of the memory region</returns> 354 /// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception> 355 /// <exception cref="InvalidMemoryRegionException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception> 356 public WritableRegion GetWritableRegion(ulong offset, int size) 357 { 358 return new WritableRegion(null, offset, GetMemory(offset, size)); 359 } 360 361 /// <summary> 362 /// Adds a 64-bits offset to a native pointer. 363 /// </summary> 364 /// <param name="pointer">Native pointer</param> 365 /// <param name="offset">Offset to add</param> 366 /// <returns>Native pointer with the added offset</returns> 367 private static IntPtr PtrAddr(IntPtr pointer, ulong offset) 368 { 369 return new IntPtr(pointer.ToInt64() + (long)offset); 370 } 371 372 /// <summary> 373 /// Frees the memory allocated for this memory block. 374 /// </summary> 375 /// <remarks> 376 /// It's an error to use the memory block after disposal. 377 /// </remarks> 378 public void Dispose() 379 { 380 FreeMemory(); 381 382 GC.SuppressFinalize(this); 383 } 384 385 ~MemoryBlock() => FreeMemory(); 386 387 private void FreeMemory() 388 { 389 IntPtr ptr = Interlocked.Exchange(ref _pointer, IntPtr.Zero); 390 391 // If pointer is null, the memory was already freed or never allocated. 392 if (ptr != IntPtr.Zero) 393 { 394 if (_usesSharedMemory) 395 { 396 MemoryManagement.UnmapSharedMemory(ptr, Size); 397 } 398 else 399 { 400 MemoryManagement.Free(ptr, Size); 401 } 402 } 403 404 if (!_isMirror) 405 { 406 IntPtr sharedMemory = Interlocked.Exchange(ref _sharedMemory, IntPtr.Zero); 407 408 if (sharedMemory != IntPtr.Zero) 409 { 410 MemoryManagement.DestroySharedMemory(sharedMemory); 411 } 412 } 413 } 414 415 /// <summary> 416 /// Checks if the specified memory allocation flags are supported on the current platform. 417 /// </summary> 418 /// <param name="flags">Flags to be checked</param> 419 /// <returns>True if the platform supports all the flags, false otherwise</returns> 420 public static bool SupportsFlags(MemoryAllocationFlags flags) 421 { 422 if (flags.HasFlag(MemoryAllocationFlags.ViewCompatible)) 423 { 424 if (OperatingSystem.IsWindows()) 425 { 426 return OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134); 427 } 428 429 return OperatingSystem.IsLinux() || OperatingSystem.IsMacOS(); 430 } 431 432 return true; 433 } 434 435 public static ulong GetPageSize() 436 { 437 return (ulong)Environment.SystemPageSize; 438 } 439 440 private static void ThrowInvalidMemoryRegionException() => throw new InvalidMemoryRegionException(); 441 } 442 }