using Ryujinx.Common; using System; using System.Collections.Generic; using System.Linq; using System.Threading; namespace Ryujinx.Memory { public delegate void PageInitDelegate(Span 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 _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); } } }