/ src / Ryujinx.HLE / HOS / Kernel / Memory / KPageHeap.cs
KPageHeap.cs
  1  using Ryujinx.Common;
  2  using System;
  3  
  4  namespace Ryujinx.HLE.HOS.Kernel.Memory
  5  {
  6      class KPageHeap
  7      {
  8          private class Block
  9          {
 10              private readonly KPageBitmap _bitmap = new();
 11              private ulong _heapAddress;
 12              private ulong _endOffset;
 13  
 14              public int Shift { get; private set; }
 15              public int NextShift { get; private set; }
 16              public ulong Size => 1UL << Shift;
 17              public int PagesCount => (int)(Size / KPageTableBase.PageSize);
 18              public int FreeBlocksCount => _bitmap.BitsCount;
 19              public int FreePagesCount => FreeBlocksCount * PagesCount;
 20  
 21              public ArraySegment<ulong> Initialize(ulong address, ulong size, int blockShift, int nextBlockShift, ArraySegment<ulong> bitStorage)
 22              {
 23                  Shift = blockShift;
 24                  NextShift = nextBlockShift;
 25  
 26                  ulong endAddress = address + size;
 27  
 28                  ulong align = nextBlockShift != 0
 29                      ? 1UL << nextBlockShift
 30                      : 1UL << blockShift;
 31  
 32                  address = BitUtils.AlignDown(address, align);
 33                  endAddress = BitUtils.AlignUp(endAddress, align);
 34  
 35                  _heapAddress = address;
 36                  _endOffset = (endAddress - address) / (1UL << blockShift);
 37  
 38                  return _bitmap.Initialize(bitStorage, _endOffset);
 39              }
 40  
 41              public ulong PushBlock(ulong address)
 42              {
 43                  ulong offset = (address - _heapAddress) >> Shift;
 44  
 45                  _bitmap.SetBit(offset);
 46  
 47                  if (NextShift != 0)
 48                  {
 49                      int diff = 1 << (NextShift - Shift);
 50  
 51                      offset = BitUtils.AlignDown(offset, (ulong)diff);
 52  
 53                      if (_bitmap.ClearRange(offset, diff))
 54                      {
 55                          return _heapAddress + (offset << Shift);
 56                      }
 57                  }
 58  
 59                  return 0;
 60              }
 61  
 62              public ulong PopBlock(bool random)
 63              {
 64                  long sOffset = (long)_bitmap.FindFreeBlock(random);
 65  
 66                  if (sOffset < 0L)
 67                  {
 68                      return 0;
 69                  }
 70  
 71                  ulong offset = (ulong)sOffset;
 72  
 73                  _bitmap.ClearBit(offset);
 74  
 75                  return _heapAddress + (offset << Shift);
 76              }
 77  
 78              public static int CalculateManagementOverheadSize(ulong regionSize, int currBlockShift, int nextBlockShift)
 79              {
 80                  ulong currBlockSize = 1UL << currBlockShift;
 81                  ulong nextBlockSize = 1UL << nextBlockShift;
 82                  ulong align = nextBlockShift != 0 ? nextBlockSize : currBlockSize;
 83                  return KPageBitmap.CalculateManagementOverheadSize((align * 2 + BitUtils.AlignUp(regionSize, align)) / currBlockSize);
 84              }
 85          }
 86  
 87          private static readonly int[] _memoryBlockPageShifts = { 12, 16, 21, 22, 25, 29, 30 };
 88  
 89  #pragma warning disable IDE0052 // Remove unread private member
 90          private readonly ulong _heapAddress;
 91          private readonly ulong _heapSize;
 92          private ulong _usedSize;
 93  #pragma warning restore IDE0052
 94          private readonly int _blocksCount;
 95          private readonly Block[] _blocks;
 96  
 97          public KPageHeap(ulong address, ulong size) : this(address, size, _memoryBlockPageShifts)
 98          {
 99          }
100  
101          public KPageHeap(ulong address, ulong size, int[] blockShifts)
102          {
103              _heapAddress = address;
104              _heapSize = size;
105              _blocksCount = blockShifts.Length;
106              _blocks = new Block[_memoryBlockPageShifts.Length];
107  
108              var currBitmapStorage = new ArraySegment<ulong>(new ulong[CalculateManagementOverheadSize(size, blockShifts)]);
109  
110              for (int i = 0; i < blockShifts.Length; i++)
111              {
112                  int currBlockShift = blockShifts[i];
113                  int nextBlockShift = i != blockShifts.Length - 1 ? blockShifts[i + 1] : 0;
114  
115                  _blocks[i] = new Block();
116  
117                  currBitmapStorage = _blocks[i].Initialize(address, size, currBlockShift, nextBlockShift, currBitmapStorage);
118              }
119          }
120  
121          public void UpdateUsedSize()
122          {
123              _usedSize = _heapSize - (GetFreePagesCount() * KPageTableBase.PageSize);
124          }
125  
126          public ulong GetFreePagesCount()
127          {
128              ulong freeCount = 0;
129  
130              for (int i = 0; i < _blocksCount; i++)
131              {
132                  freeCount += (ulong)_blocks[i].FreePagesCount;
133              }
134  
135              return freeCount;
136          }
137  
138          public ulong AllocateBlock(int index, bool random)
139          {
140              ulong neededSize = _blocks[index].Size;
141  
142              for (int i = index; i < _blocksCount; i++)
143              {
144                  ulong address = _blocks[i].PopBlock(random);
145  
146                  if (address != 0)
147                  {
148                      ulong allocatedSize = _blocks[i].Size;
149  
150                      if (allocatedSize > neededSize)
151                      {
152                          Free(address + neededSize, (allocatedSize - neededSize) / KPageTableBase.PageSize);
153                      }
154  
155                      return address;
156                  }
157              }
158  
159              return 0;
160          }
161  
162          private void FreeBlock(ulong block, int index)
163          {
164              do
165              {
166                  block = _blocks[index++].PushBlock(block);
167              }
168              while (block != 0);
169          }
170  
171          public void Free(ulong address, ulong pagesCount)
172          {
173              if (pagesCount == 0)
174              {
175                  return;
176              }
177  
178              int bigIndex = _blocksCount - 1;
179  
180              ulong start = address;
181              ulong end = address + pagesCount * KPageTableBase.PageSize;
182              ulong beforeStart = start;
183              ulong beforeEnd = start;
184              ulong afterStart = end;
185              ulong afterEnd = end;
186  
187              while (bigIndex >= 0)
188              {
189                  ulong blockSize = _blocks[bigIndex].Size;
190  
191                  ulong bigStart = BitUtils.AlignUp(start, blockSize);
192                  ulong bigEnd = BitUtils.AlignDown(end, blockSize);
193  
194                  if (bigStart < bigEnd)
195                  {
196                      for (ulong block = bigStart; block < bigEnd; block += blockSize)
197                      {
198                          FreeBlock(block, bigIndex);
199                      }
200  
201                      beforeEnd = bigStart;
202                      afterStart = bigEnd;
203  
204                      break;
205                  }
206  
207                  bigIndex--;
208              }
209  
210              for (int i = bigIndex - 1; i >= 0; i--)
211              {
212                  ulong blockSize = _blocks[i].Size;
213  
214                  while (beforeStart + blockSize <= beforeEnd)
215                  {
216                      beforeEnd -= blockSize;
217                      FreeBlock(beforeEnd, i);
218                  }
219              }
220  
221              for (int i = bigIndex - 1; i >= 0; i--)
222              {
223                  ulong blockSize = _blocks[i].Size;
224  
225                  while (afterStart + blockSize <= afterEnd)
226                  {
227                      FreeBlock(afterStart, i);
228                      afterStart += blockSize;
229                  }
230              }
231          }
232  
233          public static int GetAlignedBlockIndex(ulong pagesCount, ulong alignPages)
234          {
235              ulong targetPages = Math.Max(pagesCount, alignPages);
236  
237              for (int i = 0; i < _memoryBlockPageShifts.Length; i++)
238              {
239                  if (targetPages <= GetBlockPagesCount(i))
240                  {
241                      return i;
242                  }
243              }
244  
245              return -1;
246          }
247  
248          public static int GetBlockIndex(ulong pagesCount)
249          {
250              for (int i = _memoryBlockPageShifts.Length - 1; i >= 0; i--)
251              {
252                  if (pagesCount >= GetBlockPagesCount(i))
253                  {
254                      return i;
255                  }
256              }
257  
258              return -1;
259          }
260  
261          public static ulong GetBlockSize(int index)
262          {
263              return 1UL << _memoryBlockPageShifts[index];
264          }
265  
266          public static ulong GetBlockPagesCount(int index)
267          {
268              return GetBlockSize(index) / KPageTableBase.PageSize;
269          }
270  
271          private static int CalculateManagementOverheadSize(ulong regionSize, int[] blockShifts)
272          {
273              int overheadSize = 0;
274  
275              for (int i = 0; i < blockShifts.Length; i++)
276              {
277                  int currBlockShift = blockShifts[i];
278                  int nextBlockShift = i != blockShifts.Length - 1 ? blockShifts[i + 1] : 0;
279                  overheadSize += Block.CalculateManagementOverheadSize(regionSize, currBlockShift, nextBlockShift);
280              }
281  
282              return BitUtils.AlignUp(overheadSize, KPageTableBase.PageSize);
283          }
284      }
285  }