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 unsafe class NonOverlappingRangeList : RangeListBase where T : class, INonOverlappingRange { 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); } /// /// 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; } Items[index] = rangeItem; return true; } return false; } /// /// Updates an item's end address on the list. Address must be the same. /// /// The RangeItem to be updated /// True if the item was located and updated, false otherwise protected override bool Update(RangeItem item) { int index = BinarySearch(item.Address); RangeItem rangeItem = new(item.Value) { Previous = item.Previous, Next = item.Next }; if (index > 0) { Items[index - 1].Next = rangeItem; } if (index < Count - 1) { Items[index + 1].Previous = rangeItem; } Items[index] = rangeItem; return true; } [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)) { 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, int endIndex) = BinarySearchEdges(startItem.Address, endItem.EndAddress); if (endIndex < Count) { Items[endIndex].Previous = startIndex > 0 ? Items[startIndex - 1] : null; } if (startIndex > 0) { Items[startIndex - 1].Next = endIndex < Count ? Items[endIndex] : null; } if (endIndex < Count) { Array.Copy(Items, endIndex, Items, startIndex, Count - endIndex); } Count -= endIndex - startIndex; } /// /// 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; } int endIndex = startIndex; while (Items[endIndex] is not null && Items[endIndex].Address < address + size) { if (endIndex == Count - 1) { break; } 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; 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) = FindOverlapsAsNodes(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) { int index = BinarySearchLeftEdge(address, address + size); if (index < 0) { return null; } 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) { int index = BinarySearch(address, address + size); if (index < 0) { return null; } 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) FindOverlapsAsNodes(ulong address, ulong size) { (int index, int endIndex) = BinarySearchEdges(address, address + size); if (index < 0) { return (null, null); } return (Items[index], Items[endIndex - 1]); } public RangeItem[] FindOverlapsAsArray(ulong address, ulong size) { (int index, int endIndex) = BinarySearchEdges(address, address + size); RangeItem[] result; if (index < 0) { result = []; } else { result = new RangeItem[endIndex - index]; Array.Copy(Items, index, result, 0, endIndex - index); } return result; } public Span> FindOverlapsAsSpan(ulong address, ulong size) { (int index, int endIndex) = BinarySearchEdges(address, address + size); Span> result; if (index < 0) { result = []; } else { result = Items.AsSpan().Slice(index, endIndex - index); } return result; } public override IEnumerator GetEnumerator() { for (int i = 0; i < Count; i++) { yield return Items[i].Value; } } } }