aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Memory/WindowsShared/PlaceholderList.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Memory/WindowsShared/PlaceholderList.cs')
-rw-r--r--Ryujinx.Memory/WindowsShared/PlaceholderList.cs291
1 files changed, 291 insertions, 0 deletions
diff --git a/Ryujinx.Memory/WindowsShared/PlaceholderList.cs b/Ryujinx.Memory/WindowsShared/PlaceholderList.cs
new file mode 100644
index 00000000..be8cef9c
--- /dev/null
+++ b/Ryujinx.Memory/WindowsShared/PlaceholderList.cs
@@ -0,0 +1,291 @@
+using Ryujinx.Memory.Range;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Memory.WindowsShared
+{
+ /// <summary>
+ /// A specialized list used for keeping track of Windows 10's memory placeholders.
+ /// This is used to make splitting a large placeholder into equally small
+ /// granular chunks much easier, while avoiding slowdown due to a large number of
+ /// placeholders by coalescing adjacent granular placeholders after they are unused.
+ /// </summary>
+ class PlaceholderList
+ {
+ private class PlaceholderBlock : IRange
+ {
+ public ulong Address { get; }
+ public ulong Size { get; private set; }
+ public ulong EndAddress { get; private set; }
+ public bool IsGranular { get; set; }
+
+ public PlaceholderBlock(ulong id, ulong size, bool isGranular)
+ {
+ Address = id;
+ Size = size;
+ EndAddress = id + size;
+ IsGranular = isGranular;
+ }
+
+ public bool OverlapsWith(ulong address, ulong size)
+ {
+ return Address < address + size && address < EndAddress;
+ }
+
+ public void ExtendTo(ulong end)
+ {
+ EndAddress = end;
+ Size = end - Address;
+ }
+ }
+
+ private RangeList<PlaceholderBlock> _placeholders;
+ private PlaceholderBlock[] _foundBlocks = new PlaceholderBlock[32];
+
+ /// <summary>
+ /// Create a new list to manage placeholders.
+ /// Note that a size is measured in granular placeholders.
+ /// If the placeholder granularity is 65536 bytes, then a 65536 region will be covered by 1 placeholder granularity.
+ /// </summary>
+ /// <param name="size">Size measured in granular placeholders</param>
+ public PlaceholderList(ulong size)
+ {
+ _placeholders = new RangeList<PlaceholderBlock>();
+
+ _placeholders.Add(new PlaceholderBlock(0, size, false));
+ }
+
+ /// <summary>
+ /// Ensure that the given range of placeholders is granular.
+ /// </summary>
+ /// <param name="id">Start of the range, measured in granular placeholders</param>
+ /// <param name="size">Size of the range, measured in granular placeholders</param>
+ /// <param name="splitPlaceholderCallback">Callback function to run when splitting placeholders, calls with (start, middle)</param>
+ public void EnsurePlaceholders(ulong id, ulong size, Action<ulong, ulong> splitPlaceholderCallback)
+ {
+ // Search 1 before and after the placeholders, as we may need to expand/join granular regions surrounding the requested area.
+
+ ulong endId = id + size;
+ ulong searchStartId = id == 0 ? 0 : (id - 1);
+ int blockCount = _placeholders.FindOverlapsNonOverlapping(searchStartId, (endId - searchStartId) + 1, ref _foundBlocks);
+
+ PlaceholderBlock first = _foundBlocks[0];
+ PlaceholderBlock last = _foundBlocks[blockCount - 1];
+ bool overlapStart = first.EndAddress >= id && id != 0;
+ bool overlapEnd = last.Address <= endId;
+
+ for (int i = 0; i < blockCount; i++)
+ {
+ // Go through all non-granular blocks in the range and create placeholders.
+ PlaceholderBlock block = _foundBlocks[i];
+
+ if (block.Address <= id && block.EndAddress >= endId && block.IsGranular)
+ {
+ return; // The region we're searching for is already granular.
+ }
+
+ if (!block.IsGranular)
+ {
+ ulong placeholderStart = Math.Max(block.Address, id);
+ ulong placeholderEnd = Math.Min(block.EndAddress - 1, endId);
+
+ if (placeholderStart != block.Address && placeholderStart != block.EndAddress)
+ {
+ splitPlaceholderCallback(block.Address, placeholderStart - block.Address);
+ }
+
+ for (ulong j = placeholderStart; j < placeholderEnd; j++)
+ {
+ splitPlaceholderCallback(j, 1);
+ }
+ }
+
+ if (!((block == first && overlapStart) || (block == last && overlapEnd)))
+ {
+ // Remove blocks that will be replaced
+ _placeholders.Remove(block);
+ }
+ }
+
+ if (overlapEnd)
+ {
+ if (!(first == last && overlapStart))
+ {
+ _placeholders.Remove(last);
+ }
+
+ if (last.IsGranular)
+ {
+ endId = last.EndAddress;
+ }
+ else if (last.EndAddress != endId)
+ {
+ _placeholders.Add(new PlaceholderBlock(endId, last.EndAddress - endId, false));
+ }
+ }
+
+ if (overlapStart && first.IsGranular)
+ {
+ first.ExtendTo(endId);
+ }
+ else
+ {
+ if (overlapStart)
+ {
+ first.ExtendTo(id);
+ }
+
+ _placeholders.Add(new PlaceholderBlock(id, endId - id, true));
+ }
+
+ ValidateList();
+ }
+
+ /// <summary>
+ /// Coalesces placeholders in a given region, as they are not being used.
+ /// This assumes that the region only contains placeholders - all views and allocations must have been replaced with placeholders.
+ /// </summary>
+ /// <param name="id">Start of the range, measured in granular placeholders</param>
+ /// <param name="size">Size of the range, measured in granular placeholders</param>
+ /// <param name="coalescePlaceholderCallback">Callback function to run when coalescing two placeholders, calls with (start, end)</param>
+ public void RemovePlaceholders(ulong id, ulong size, Action<ulong, ulong> coalescePlaceholderCallback)
+ {
+ ulong endId = id + size;
+ int blockCount = _placeholders.FindOverlapsNonOverlapping(id, size, ref _foundBlocks);
+
+ PlaceholderBlock first = _foundBlocks[0];
+ PlaceholderBlock last = _foundBlocks[blockCount - 1];
+
+ // All granular blocks must have non-granular blocks surrounding them, unless they start at 0.
+ // We must extend the non-granular blocks into the granular ones. This does mean that we need to search twice.
+
+ if (first.IsGranular || last.IsGranular)
+ {
+ ulong surroundStart = Math.Max(0, (first.IsGranular && first.Address != 0) ? first.Address - 1 : id);
+ blockCount = _placeholders.FindOverlapsNonOverlapping(
+ surroundStart,
+ (last.IsGranular ? last.EndAddress + 1 : endId) - surroundStart,
+ ref _foundBlocks);
+
+ first = _foundBlocks[0];
+ last = _foundBlocks[blockCount - 1];
+ }
+
+ if (first == last)
+ {
+ return; // Already coalesced.
+ }
+
+ PlaceholderBlock extendBlock = id == 0 ? null : first;
+ bool newBlock = false;
+ for (int i = extendBlock == null ? 0 : 1; i < blockCount; i++)
+ {
+ // Go through all granular blocks in the range and extend placeholders.
+ PlaceholderBlock block = _foundBlocks[i];
+
+ ulong blockEnd = block.EndAddress;
+ ulong extendFrom;
+ ulong extent = Math.Min(blockEnd, endId);
+
+ if (block.Address < id && blockEnd > id)
+ {
+ block.ExtendTo(id);
+ extendBlock = null;
+ }
+ else
+ {
+ _placeholders.Remove(block);
+ }
+
+ if (extendBlock == null)
+ {
+ extendFrom = id;
+ extendBlock = new PlaceholderBlock(id, extent - id, false);
+ _placeholders.Add(extendBlock);
+
+ if (blockEnd > extent)
+ {
+ _placeholders.Add(new PlaceholderBlock(extent, blockEnd - extent, true));
+
+ // Skip the next non-granular block, and extend from that into the granular block afterwards.
+ // (assuming that one is still in the requested range)
+
+ if (i + 1 < blockCount)
+ {
+ extendBlock = _foundBlocks[i + 1];
+ }
+
+ i++;
+ }
+
+ newBlock = true;
+ }
+ else
+ {
+ extendFrom = extendBlock.Address;
+ extendBlock.ExtendTo(block.IsGranular ? extent : block.EndAddress);
+ }
+
+ if (block.IsGranular)
+ {
+ ulong placeholderStart = Math.Max(block.Address, id);
+ ulong placeholderEnd = extent;
+
+ if (newBlock)
+ {
+ placeholderStart++;
+ newBlock = false;
+ }
+
+ for (ulong j = placeholderStart; j < placeholderEnd; j++)
+ {
+ coalescePlaceholderCallback(extendFrom, (j + 1) - extendFrom);
+ }
+
+ if (extent < block.EndAddress)
+ {
+ _placeholders.Add(new PlaceholderBlock(placeholderEnd, block.EndAddress - placeholderEnd, true));
+ ValidateList();
+ return;
+ }
+ }
+ else
+ {
+ coalescePlaceholderCallback(extendFrom, block.EndAddress - extendFrom);
+ }
+ }
+
+ ValidateList();
+ }
+
+ /// <summary>
+ /// Ensure that the placeholder list is valid.
+ /// A valid list should not have any gaps between the placeholders,
+ /// and there may be no placehonders with the same IsGranular value next to each other.
+ /// </summary>
+ [Conditional("DEBUG")]
+ private void ValidateList()
+ {
+ bool isGranular = false;
+ bool first = true;
+ ulong lastAddress = 0;
+
+ foreach (var placeholder in _placeholders)
+ {
+ if (placeholder.Address != lastAddress)
+ {
+ throw new InvalidOperationException("Gap in placeholder list.");
+ }
+
+ if (isGranular == placeholder.IsGranular && !first)
+ {
+ throw new InvalidOperationException("Placeholder list not alternating.");
+ }
+
+ first = false;
+ isGranular = placeholder.IsGranular;
+ lastAddress = placeholder.EndAddress;
+ }
+ }
+ }
+}