using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Threading; namespace Ryujinx.Memory.Range { /// /// A range list that assumes ranges are non-overlapping, with list items that can be split in two to avoid overlaps. /// /// Type of the range. public class NonOverlappingRangeList : RangeListBase where T : INonOverlappingRange { private readonly Dictionary> _quickAccess = new(AddressEqualityComparer.Comparer); private readonly Dictionary> _fastQuickAccess = new(AddressEqualityComparer.Comparer); public readonly ReaderWriterLockSlim Lock = new(); /// /// Creates a new non-overlapping range list. /// public NonOverlappingRangeList() { } /// /// Creates a new non-overlapping range list. /// /// The initial size of the backing array public NonOverlappingRangeList(int backingInitialSize) : base(backingInitialSize) { } /// /// Adds a new item to the list. /// /// The item to be added public override void Add(T item) { int index = BinarySearch(item.Address); if (index < 0) { index = ~index; } RangeItem rangeItem = new(item); Insert(index, rangeItem); _quickAccess.Add(item.Address, rangeItem); } /// /// Updates an item's end address on the list. Address must be the same. /// /// The item to be updated /// True if the item was located and updated, false otherwise protected override bool Update(T item) { int index = BinarySearch(item.Address); if (index >= 0 && Items[index].Value.Equals(item)) { RangeItem rangeItem = new(item) { Previous = Items[index].Previous, Next = Items[index].Next }; if (index > 0) { Items[index - 1].Next = rangeItem; } if (index < Count - 1) { Items[index + 1].Previous = rangeItem; } foreach (ulong addr in Items[index].QuickAccessAddresses) { _quickAccess.Remove(addr); _fastQuickAccess.Remove(addr); } Items[index] = rangeItem; _quickAccess[item.Address] = rangeItem; return true; } return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void Insert(int index, RangeItem item) { Debug.Assert(item.Address != item.EndAddress); if (Count + 1 > Items.Length) { Array.Resize(ref Items, Items.Length + BackingGrowthSize); } if (index >= Count) { if (index == Count) { if (index != 0) { item.Previous = Items[index - 1]; Items[index - 1].Next = item; } Items[index] = item; Count++; } } else { Array.Copy(Items, index, Items, index + 1, Count - index); Items[index] = item; if (index != 0) { item.Previous = Items[index - 1]; Items[index - 1].Next = item; } item.Next = Items[index + 1]; Items[index + 1].Previous = item; Count++; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void RemoveAt(int index) { if (index < Count - 1) { Items[index + 1].Previous = index > 0 ? Items[index - 1] : null; } if (index > 0) { Items[index - 1].Next = index < Count - 1 ? Items[index + 1] : null; } if (index < --Count) { Array.Copy(Items, index + 1, Items, index, Count - index); } } /// /// Removes an item from the list. /// /// The item to be removed /// True if the item was removed, or false if it was not found [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Remove(T item) { int index = BinarySearch(item.Address); if (index >= 0 && Items[index].Value.Equals(item)) { _quickAccess.Remove(item.Address); foreach (ulong addr in Items[index].QuickAccessAddresses) { _quickAccess.Remove(addr); _fastQuickAccess.Remove(addr); } RemoveAt(index); return true; } return false; } /// /// Removes a range of items from the item list /// /// The first item in the range of items to be removed /// The last item in the range of items to be removed public override void RemoveRange(RangeItem startItem, RangeItem endItem) { if (startItem is null) { return; } if (startItem == endItem) { Remove(startItem.Value); return; } int startIndex = BinarySearch(startItem.Address); int endIndex = BinarySearch(endItem.Address); if (endIndex < Count - 1) { Items[endIndex + 1].Previous = startIndex > 0 ? Items[startIndex - 1] : null; } if (startIndex > 0) { Items[startIndex - 1].Next = endIndex < Count - 1 ? Items[endIndex + 1] : null; } if (endIndex < Count - 1) { Array.Copy(Items, endIndex + 1, Items, startIndex, Count - endIndex - 1); } Count -= endIndex - startIndex + 1; while (startItem != endItem.Next) { _quickAccess.Remove(startItem.Address); foreach (ulong addr in startItem.QuickAccessAddresses) { _quickAccess.Remove(addr); _fastQuickAccess.Remove(addr); } startItem = startItem.Next; } } /// /// Removes a range of items from the item list /// /// Start address of the range /// Size of the range public void RemoveRange(ulong address, ulong size) { int startIndex = BinarySearchLeftEdge(address, address + size); if (startIndex < 0) { return; } RangeItem startItem = Items[startIndex]; int endIndex = startIndex; while (startItem is not null && startItem.Address < address + size) { _quickAccess.Remove(startItem.Address); foreach (ulong addr in startItem.QuickAccessAddresses) { _quickAccess.Remove(addr); _fastQuickAccess.Remove(addr); } startItem = startItem.Next; endIndex++; } if (endIndex < Count - 1) { Items[endIndex + 1].Previous = startIndex > 0 ? Items[startIndex - 1] : null; } if (startIndex > 0) { Items[startIndex - 1].Next = endIndex < Count - 1 ? Items[endIndex + 1] : null; } if (endIndex < Count - 1) { Array.Copy(Items, endIndex + 1, Items, startIndex, Count - endIndex - 1); } Count -= endIndex - startIndex + 1; } /// /// Clear all ranges. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { Lock.EnterWriteLock(); Count = 0; _quickAccess.Clear(); _fastQuickAccess.Clear(); Lock.ExitWriteLock(); } /// /// Finds a list of regions that cover the desired (address, size) range. /// If this range starts or ends in the middle of an existing region, it is split and only the relevant part is added. /// If there is no matching region, or there is a gap, then new regions are created with the factory. /// Regions are added to the list in address ascending order. /// /// List to add found regions to /// Start address of the search region /// Size of the search region /// Factory for creating new ranges public void GetOrAddRegions(out List list, ulong address, ulong size, Func factory) { // (regarding the specific case this generalized function is used for) // A new region may be split into multiple parts if multiple virtual regions have mapped to it. // For instance, while a virtual mapping could cover 0-2 in physical space, the space 0-1 may have already been reserved... // So we need to return both the split 0-1 and 1-2 ranges. Lock.EnterWriteLock(); (RangeItem first, RangeItem last) = FindOverlaps(address, size); list = new List(); if (first is null) { // The region is fully unmapped. Create and add it to the range list. T region = factory(address, size); list.Add(region); Add(region); } else { ulong lastAddress = address; ulong endAddress = address + size; RangeItem current = first; while (last is not null && current is not null && current.Address < endAddress) { T region = current.Value; if (first == last && region.Address == address && region.Size == size) { // Exact match, no splitting required. list.Add(region); Lock.ExitWriteLock(); return; } if (lastAddress < region.Address) { // There is a gap between this region and the last. We need to fill it. T fillRegion = factory(lastAddress, region.Address - lastAddress); list.Add(fillRegion); Add(fillRegion); } if (region.Address < address) { // Split the region around our base address and take the high half. region = Split(region, address); } if (region.EndAddress > address + size) { // Split the region around our end address and take the low half. Split(region, address + size); } list.Add(region); lastAddress = region.EndAddress; current = current.Next; } if (lastAddress < endAddress) { // There is a gap between this region and the end. We need to fill it. T fillRegion = factory(lastAddress, endAddress - lastAddress); list.Add(fillRegion); Add(fillRegion); } } Lock.ExitWriteLock(); } /// /// Splits a region around a target point and updates the region list. /// The original region's size is modified, but its address stays the same. /// A new region starting from the split address is added to the region list and returned. /// /// The region to split /// The address to split with /// The new region (high part) [MethodImpl(MethodImplOptions.AggressiveInlining)] private T Split(T region, ulong splitAddress) { T newRegion = (T)region.Split(splitAddress); Update(region); Add(newRegion); return newRegion; } /// /// Gets an item on the list overlapping the specified memory range. /// /// Start address of the range /// Size in bytes of the range /// The leftmost overlapping item, or null if none is found [MethodImpl(MethodImplOptions.AggressiveInlining)] public override RangeItem FindOverlap(ulong address, ulong size) { if (_quickAccess.TryGetValue(address, out RangeItem overlap)) { return overlap; } int index = BinarySearchLeftEdge(address, address + size); if (index < 0) { return null; } if (Items[index].Address < address) { _quickAccess.TryAdd(address, Items[index]); Items[index].QuickAccessAddresses.Add(address); } return Items[index]; } /// /// Gets an item on the list overlapping the specified memory range. /// /// Start address of the range /// Size in bytes of the range /// The overlapping item, or null if none is found [MethodImpl(MethodImplOptions.AggressiveInlining)] public override RangeItem FindOverlapFast(ulong address, ulong size) { if (_quickAccess.TryGetValue(address, out RangeItem overlap) || _fastQuickAccess.TryGetValue(address, out overlap)) { return overlap; } int index = BinarySearch(address, address + size); if (index < 0) { return null; } if (Items[index].Address < address) { _quickAccess.TryAdd(address, Items[index]); } else { _fastQuickAccess.TryAdd(address, Items[index]); } Items[index].QuickAccessAddresses.Add(address); return Items[index]; } /// /// Gets all items on the list overlapping the specified memory range. /// /// Start address of the range /// Size in bytes of the range /// The first and last overlapping items, or null if none are found [MethodImpl(MethodImplOptions.AggressiveInlining)] public (RangeItem, RangeItem) FindOverlaps(ulong address, ulong size) { if (_quickAccess.TryGetValue(address, out RangeItem overlap)) { if (overlap.Next is null || overlap.Next.Address >= address + size) { return (overlap, overlap); } return (overlap, Items[BinarySearchRightEdge(address, address + size)]); } (int index, int endIndex) = BinarySearchEdges(address, address + size); if (index < 0) { return (null, null); } if (Items[index].Address < address) { _quickAccess.TryAdd(address, Items[index]); Items[index].QuickAccessAddresses.Add(address); } return (Items[index], Items[endIndex - 1]); } public override IEnumerator GetEnumerator() { for (int i = 0; i < Count; i++) { yield return Items[i].Value; } } } }