/ src / Ryujinx.Memory / VirtualMemoryManagerBase.cs
VirtualMemoryManagerBase.cs
  1  using Ryujinx.Common.Memory;
  2  using System;
  3  using System.Buffers;
  4  using System.Runtime.CompilerServices;
  5  using System.Runtime.InteropServices;
  6  
  7  namespace Ryujinx.Memory
  8  {
  9      public abstract class VirtualMemoryManagerBase : IWritableBlock
 10      {
 11          public const int PageBits = 12;
 12          public const int PageSize = 1 << PageBits;
 13          public const int PageMask = PageSize - 1;
 14  
 15          protected abstract ulong AddressSpaceSize { get; }
 16  
 17          public virtual ReadOnlySequence<byte> GetReadOnlySequence(ulong va, int size, bool tracked = false)
 18          {
 19              if (size == 0)
 20              {
 21                  return ReadOnlySequence<byte>.Empty;
 22              }
 23  
 24              if (tracked)
 25              {
 26                  SignalMemoryTracking(va, (ulong)size, false);
 27              }
 28  
 29              if (IsContiguousAndMapped(va, size))
 30              {
 31                  nuint pa = TranslateVirtualAddressUnchecked(va);
 32  
 33                  return new ReadOnlySequence<byte>(GetPhysicalAddressMemory(pa, size));
 34              }
 35              else
 36              {
 37                  AssertValidAddressAndSize(va, size);
 38  
 39                  int offset = 0, segmentSize;
 40  
 41                  BytesReadOnlySequenceSegment first = null, last = null;
 42  
 43                  if ((va & PageMask) != 0)
 44                  {
 45                      nuint pa = TranslateVirtualAddressChecked(va);
 46  
 47                      segmentSize = Math.Min(size, PageSize - (int)(va & PageMask));
 48  
 49                      Memory<byte> memory = GetPhysicalAddressMemory(pa, segmentSize);
 50  
 51                      first = last = new BytesReadOnlySequenceSegment(memory);
 52  
 53                      offset += segmentSize;
 54                  }
 55  
 56                  for (; offset < size; offset += segmentSize)
 57                  {
 58                      nuint pa = TranslateVirtualAddressChecked(va + (ulong)offset);
 59  
 60                      segmentSize = Math.Min(size - offset, PageSize);
 61  
 62                      Memory<byte> memory = GetPhysicalAddressMemory(pa, segmentSize);
 63  
 64                      if (first is null)
 65                      {
 66                          first = last = new BytesReadOnlySequenceSegment(memory);
 67                      }
 68                      else
 69                      {
 70                          if (last.IsContiguousWith(memory, out nuint contiguousStart, out int contiguousSize))
 71                          {
 72                              last.Replace(GetPhysicalAddressMemory(contiguousStart, contiguousSize));
 73                          }
 74                          else
 75                          {
 76                              last = last.Append(memory);
 77                          }
 78                      }
 79                  }
 80  
 81                  return new ReadOnlySequence<byte>(first, 0, last, (int)(size - last.RunningIndex));
 82              }
 83          }
 84  
 85          public virtual ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
 86          {
 87              if (size == 0)
 88              {
 89                  return ReadOnlySpan<byte>.Empty;
 90              }
 91  
 92              if (tracked)
 93              {
 94                  SignalMemoryTracking(va, (ulong)size, false);
 95              }
 96  
 97              if (IsContiguousAndMapped(va, size))
 98              {
 99                  nuint pa = TranslateVirtualAddressUnchecked(va);
100  
101                  return GetPhysicalAddressSpan(pa, size);
102              }
103              else
104              {
105                  Span<byte> data = new byte[size];
106  
107                  Read(va, data);
108  
109                  return data;
110              }
111          }
112  
113          public virtual WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
114          {
115              if (size == 0)
116              {
117                  return new WritableRegion(null, va, Memory<byte>.Empty);
118              }
119  
120              if (tracked)
121              {
122                  SignalMemoryTracking(va, (ulong)size, true);
123              }
124  
125              if (IsContiguousAndMapped(va, size))
126              {
127                  nuint pa = TranslateVirtualAddressUnchecked(va);
128  
129                  return new WritableRegion(null, va, GetPhysicalAddressMemory(pa, size));
130              }
131              else
132              {
133                  MemoryOwner<byte> memoryOwner = MemoryOwner<byte>.Rent(size);
134  
135                  Read(va, memoryOwner.Span);
136  
137                  return new WritableRegion(this, va, memoryOwner);
138              }
139          }
140  
141          public abstract bool IsMapped(ulong va);
142  
143          public virtual void MapForeign(ulong va, nuint hostPointer, ulong size)
144          {
145              throw new NotSupportedException();
146          }
147  
148          public virtual T Read<T>(ulong va) where T : unmanaged
149          {
150              return MemoryMarshal.Cast<byte, T>(GetSpan(va, Unsafe.SizeOf<T>()))[0];
151          }
152  
153          public virtual void Read(ulong va, Span<byte> data)
154          {
155              if (data.Length == 0)
156              {
157                  return;
158              }
159  
160              AssertValidAddressAndSize(va, data.Length);
161  
162              int offset = 0, size;
163  
164              if ((va & PageMask) != 0)
165              {
166                  nuint pa = TranslateVirtualAddressChecked(va);
167  
168                  size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
169  
170                  GetPhysicalAddressSpan(pa, size).CopyTo(data[..size]);
171  
172                  offset += size;
173              }
174  
175              for (; offset < data.Length; offset += size)
176              {
177                  nuint pa = TranslateVirtualAddressChecked(va + (ulong)offset);
178  
179                  size = Math.Min(data.Length - offset, PageSize);
180  
181                  GetPhysicalAddressSpan(pa, size).CopyTo(data.Slice(offset, size));
182              }
183          }
184  
185          public virtual T ReadTracked<T>(ulong va) where T : unmanaged
186          {
187              SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), false);
188  
189              return Read<T>(va);
190          }
191  
192          public virtual void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
193          {
194              // No default implementation
195          }
196  
197          public virtual void Write(ulong va, ReadOnlySpan<byte> data)
198          {
199              if (data.Length == 0)
200              {
201                  return;
202              }
203  
204              SignalMemoryTracking(va, (ulong)data.Length, true);
205  
206              WriteImpl(va, data);
207          }
208  
209          public virtual void Write<T>(ulong va, T value) where T : unmanaged
210          {
211              Write(va, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
212          }
213  
214          public virtual void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
215          {
216              if (data.Length == 0)
217              {
218                  return;
219              }
220  
221              WriteImpl(va, data);
222          }
223  
224          public virtual bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
225          {
226              if (data.Length == 0)
227              {
228                  return false;
229              }
230  
231              if (IsContiguousAndMapped(va, data.Length))
232              {
233                  SignalMemoryTracking(va, (ulong)data.Length, false);
234  
235                  nuint pa = TranslateVirtualAddressChecked(va);
236  
237                  var target = GetPhysicalAddressSpan(pa, data.Length);
238  
239                  bool changed = !data.SequenceEqual(target);
240  
241                  if (changed)
242                  {
243                      data.CopyTo(target);
244                  }
245  
246                  return changed;
247              }
248              else
249              {
250                  Write(va, data);
251  
252                  return true;
253              }
254          }
255  
256          /// <summary>
257          /// Ensures the combination of virtual address and size is part of the addressable space.
258          /// </summary>
259          /// <param name="va">Virtual address of the range</param>
260          /// <param name="size">Size of the range in bytes</param>
261          /// <exception cref="InvalidMemoryRegionException">Throw when the memory region specified outside the addressable space</exception>
262          protected void AssertValidAddressAndSize(ulong va, ulong size)
263          {
264              if (!ValidateAddressAndSize(va, size))
265              {
266                  throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}");
267              }
268          }
269  
270          /// <summary>
271          /// Ensures the combination of virtual address and size is part of the addressable space.
272          /// </summary>
273          /// <param name="va">Virtual address of the range</param>
274          /// <param name="size">Size of the range in bytes</param>
275          /// <exception cref="InvalidMemoryRegionException">Throw when the memory region specified outside the addressable space</exception>
276          [MethodImpl(MethodImplOptions.AggressiveInlining)]
277          protected void AssertValidAddressAndSize(ulong va, int size)
278              => AssertValidAddressAndSize(va, (ulong)size);
279  
280          /// <summary>
281          /// Computes the number of pages in a virtual address range.
282          /// </summary>
283          /// <param name="va">Virtual address of the range</param>
284          /// <param name="size">Size of the range</param>
285          /// <param name="startVa">The virtual address of the beginning of the first page</param>
286          /// <remarks>This function does not differentiate between allocated and unallocated pages.</remarks>
287          [MethodImpl(MethodImplOptions.AggressiveInlining)]
288          protected static int GetPagesCount(ulong va, ulong size, out ulong startVa)
289          {
290              // WARNING: Always check if ulong does not overflow during the operations.
291              startVa = va & ~(ulong)PageMask;
292              ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask;
293  
294              return (int)(vaSpan / PageSize);
295          }
296  
297          protected abstract Memory<byte> GetPhysicalAddressMemory(nuint pa, int size);
298  
299          protected abstract Span<byte> GetPhysicalAddressSpan(nuint pa, int size);
300  
301          [MethodImpl(MethodImplOptions.AggressiveInlining)]
302          protected bool IsContiguous(ulong va, int size) => IsContiguous(va, (ulong)size);
303  
304          protected virtual bool IsContiguous(ulong va, ulong size)
305          {
306              if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size))
307              {
308                  return false;
309              }
310  
311              int pages = GetPagesCount(va, size, out va);
312  
313              for (int page = 0; page < pages - 1; page++)
314              {
315                  if (!ValidateAddress(va + PageSize))
316                  {
317                      return false;
318                  }
319  
320                  if (TranslateVirtualAddressUnchecked(va) + PageSize != TranslateVirtualAddressUnchecked(va + PageSize))
321                  {
322                      return false;
323                  }
324  
325                  va += PageSize;
326              }
327  
328              return true;
329          }
330  
331          [MethodImpl(MethodImplOptions.AggressiveInlining)]
332          protected bool IsContiguousAndMapped(ulong va, int size)
333              => IsContiguous(va, size) && IsMapped(va);
334  
335          protected abstract nuint TranslateVirtualAddressChecked(ulong va);
336  
337          protected abstract nuint TranslateVirtualAddressUnchecked(ulong va);
338  
339          /// <summary>
340          /// Checks if the virtual address is part of the addressable space.
341          /// </summary>
342          /// <param name="va">Virtual address</param>
343          /// <returns>True if the virtual address is part of the addressable space</returns>
344          [MethodImpl(MethodImplOptions.AggressiveInlining)]
345          protected bool ValidateAddress(ulong va)
346          {
347              return va < AddressSpaceSize;
348          }
349  
350          /// <summary>
351          /// Checks if the combination of virtual address and size is part of the addressable space.
352          /// </summary>
353          /// <param name="va">Virtual address of the range</param>
354          /// <param name="size">Size of the range in bytes</param>
355          /// <returns>True if the combination of virtual address and size is part of the addressable space</returns>
356          protected bool ValidateAddressAndSize(ulong va, ulong size)
357          {
358              ulong endVa = va + size;
359              return endVa >= va && endVa >= size && endVa <= AddressSpaceSize;
360          }
361  
362          protected static void ThrowInvalidMemoryRegionException(string message)
363              => throw new InvalidMemoryRegionException(message);
364  
365          protected static void ThrowMemoryNotContiguous()
366              => throw new MemoryNotContiguousException();
367  
368          protected virtual void WriteImpl(ulong va, ReadOnlySpan<byte> data)
369          {
370              AssertValidAddressAndSize(va, data.Length);
371  
372              if (IsContiguousAndMapped(va, data.Length))
373              {
374                  nuint pa = TranslateVirtualAddressUnchecked(va);
375  
376                  data.CopyTo(GetPhysicalAddressSpan(pa, data.Length));
377              }
378              else
379              {
380                  int offset = 0, size;
381  
382                  if ((va & PageMask) != 0)
383                  {
384                      nuint pa = TranslateVirtualAddressChecked(va);
385  
386                      size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
387  
388                      data[..size].CopyTo(GetPhysicalAddressSpan(pa, size));
389  
390                      offset += size;
391                  }
392  
393                  for (; offset < data.Length; offset += size)
394                  {
395                      nuint pa = TranslateVirtualAddressChecked(va + (ulong)offset);
396  
397                      size = Math.Min(data.Length - offset, PageSize);
398  
399                      data.Slice(offset, size).CopyTo(GetPhysicalAddressSpan(pa, size));
400                  }
401              }
402          }
403  
404      }
405  }