mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-05-01 22:57:43 +02:00
125 lines
3.9 KiB
C#
125 lines
3.9 KiB
C#
using Ryujinx.Common;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
|
|
namespace Ryujinx.Memory
|
|
{
|
|
public delegate void PageInitDelegate(Span<byte> page);
|
|
|
|
public class SparseMemoryBlock : IDisposable
|
|
{
|
|
private const ulong MapGranularity = 1UL << 17;
|
|
|
|
private readonly PageInitDelegate _pageInit;
|
|
|
|
private readonly Lock _lock = new();
|
|
private readonly ulong _pageSize;
|
|
private readonly MemoryBlock _reservedBlock;
|
|
private readonly List<MemoryBlock> _mappedBlocks;
|
|
private ulong _mappedBlockUsage;
|
|
|
|
private readonly ulong[] _mappedPageBitmap;
|
|
|
|
public MemoryBlock Block => _reservedBlock;
|
|
|
|
public SparseMemoryBlock(ulong size, PageInitDelegate pageInit, MemoryBlock fill)
|
|
{
|
|
_pageSize = MemoryBlock.GetPageSize();
|
|
_reservedBlock = new MemoryBlock(size, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible);
|
|
_mappedBlocks = [];
|
|
_pageInit = pageInit;
|
|
|
|
int pages = (int)BitUtils.DivRoundUp(size, _pageSize);
|
|
int bitmapEntries = BitUtils.DivRoundUp(pages, 64);
|
|
_mappedPageBitmap = new ulong[bitmapEntries];
|
|
|
|
if (fill != null)
|
|
{
|
|
// Fill the block with mappings from the fill block.
|
|
|
|
if (fill.Size % _pageSize != 0)
|
|
{
|
|
throw new ArgumentException("Fill memory block should be page aligned.", nameof(fill));
|
|
}
|
|
|
|
int repeats = (int)BitUtils.DivRoundUp(size, fill.Size);
|
|
|
|
ulong offset = 0;
|
|
for (int i = 0; i < repeats; i++)
|
|
{
|
|
_reservedBlock.MapView(fill, 0, offset, Math.Min(fill.Size, size - offset));
|
|
offset += fill.Size;
|
|
}
|
|
}
|
|
|
|
// If a fill block isn't provided, the pages that aren't EnsureMapped are unmapped.
|
|
// The caller can rely on signal handler to fill empty pages instead.
|
|
}
|
|
|
|
private void MapPage(ulong pageOffset)
|
|
{
|
|
// Take a page from the latest mapped block.
|
|
MemoryBlock block = _mappedBlocks.LastOrDefault();
|
|
|
|
if (block == null || _mappedBlockUsage == MapGranularity)
|
|
{
|
|
// Need to map some more memory.
|
|
|
|
block = new MemoryBlock(MapGranularity, MemoryAllocationFlags.Mirrorable);
|
|
|
|
_mappedBlocks.Add(block);
|
|
|
|
_mappedBlockUsage = 0;
|
|
}
|
|
|
|
_pageInit(block.GetSpan(_mappedBlockUsage, (int)_pageSize));
|
|
_reservedBlock.MapView(block, _mappedBlockUsage, pageOffset, _pageSize);
|
|
|
|
_mappedBlockUsage += _pageSize;
|
|
}
|
|
|
|
public void EnsureMapped(ulong offset)
|
|
{
|
|
int pageIndex = (int)(offset / _pageSize);
|
|
int bitmapIndex = pageIndex >> 6;
|
|
|
|
ref ulong entry = ref _mappedPageBitmap[bitmapIndex];
|
|
ulong bit = 1UL << (pageIndex & 63);
|
|
|
|
if ((Volatile.Read(ref entry) & bit) == 0)
|
|
{
|
|
// Not mapped.
|
|
|
|
lock (_lock)
|
|
{
|
|
// Check the bit while locked to make sure that this only happens once.
|
|
|
|
ulong lockedEntry = Volatile.Read(ref entry);
|
|
|
|
if ((lockedEntry & bit) == 0)
|
|
{
|
|
MapPage(offset & ~(_pageSize - 1));
|
|
|
|
lockedEntry |= bit;
|
|
|
|
Interlocked.Exchange(ref entry, lockedEntry);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_reservedBlock.Dispose();
|
|
|
|
foreach (MemoryBlock block in _mappedBlocks)
|
|
{
|
|
block.Dispose();
|
|
}
|
|
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
}
|
|
}
|