diff options
| author | TSR Berry <20988865+TSRBerry@users.noreply.github.com> | 2023-04-08 01:22:00 +0200 |
|---|---|---|
| committer | Mary <thog@protonmail.com> | 2023-04-27 23:51:14 +0200 |
| commit | cee712105850ac3385cd0091a923438167433f9f (patch) | |
| tree | 4a5274b21d8b7f938c0d0ce18736d3f2993b11b1 /src/ARMeilleure/Common | |
| parent | cd124bda587ef09668a971fa1cac1c3f0cfc9f21 (diff) | |
Move solution and projects to src
Diffstat (limited to 'src/ARMeilleure/Common')
| -rw-r--r-- | src/ARMeilleure/Common/AddressTable.cs | 252 | ||||
| -rw-r--r-- | src/ARMeilleure/Common/Allocator.cs | 24 | ||||
| -rw-r--r-- | src/ARMeilleure/Common/ArenaAllocator.cs | 187 | ||||
| -rw-r--r-- | src/ARMeilleure/Common/BitMap.cs | 222 | ||||
| -rw-r--r-- | src/ARMeilleure/Common/BitUtils.cs | 57 | ||||
| -rw-r--r-- | src/ARMeilleure/Common/Counter.cs | 98 | ||||
| -rw-r--r-- | src/ARMeilleure/Common/EntryTable.cs | 188 | ||||
| -rw-r--r-- | src/ARMeilleure/Common/EnumUtils.cs | 12 | ||||
| -rw-r--r-- | src/ARMeilleure/Common/NativeAllocator.cs | 27 |
9 files changed, 1067 insertions, 0 deletions
diff --git a/src/ARMeilleure/Common/AddressTable.cs b/src/ARMeilleure/Common/AddressTable.cs new file mode 100644 index 00000000..9db2d00d --- /dev/null +++ b/src/ARMeilleure/Common/AddressTable.cs @@ -0,0 +1,252 @@ +using ARMeilleure.Diagnostics; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Common +{ + /// <summary> + /// Represents a table of guest address to a value. + /// </summary> + /// <typeparam name="TEntry">Type of the value</typeparam> + unsafe class AddressTable<TEntry> : IDisposable where TEntry : unmanaged + { + /// <summary> + /// Represents a level in an <see cref="AddressTable{TEntry}"/>. + /// </summary> + public readonly struct Level + { + /// <summary> + /// Gets the index of the <see cref="Level"/> in the guest address. + /// </summary> + public int Index { get; } + + /// <summary> + /// Gets the length of the <see cref="Level"/> in the guest address. + /// </summary> + public int Length { get; } + + /// <summary> + /// Gets the mask which masks the bits used by the <see cref="Level"/>. + /// </summary> + public ulong Mask => ((1ul << Length) - 1) << Index; + + /// <summary> + /// Initializes a new instance of the <see cref="Level"/> structure with the specified + /// <paramref name="index"/> and <paramref name="length"/>. + /// </summary> + /// <param name="index">Index of the <see cref="Level"/></param> + /// <param name="length">Length of the <see cref="Level"/></param> + public Level(int index, int length) + { + (Index, Length) = (index, length); + } + + /// <summary> + /// Gets the value of the <see cref="Level"/> from the specified guest <paramref name="address"/>. + /// </summary> + /// <param name="address">Guest address</param> + /// <returns>Value of the <see cref="Level"/> from the specified guest <paramref name="address"/></returns> + public int GetValue(ulong address) + { + return (int)((address & Mask) >> Index); + } + } + + private bool _disposed; + private TEntry** _table; + private readonly List<IntPtr> _pages; + + /// <summary> + /// Gets the bits used by the <see cref="Levels"/> of the <see cref="AddressTable{TEntry}"/> instance. + /// </summary> + public ulong Mask { get; } + + /// <summary> + /// Gets the <see cref="Level"/>s used by the <see cref="AddressTable{TEntry}"/> instance. + /// </summary> + public Level[] Levels { get; } + + /// <summary> + /// Gets or sets the default fill value of newly created leaf pages. + /// </summary> + public TEntry Fill { get; set; } + + /// <summary> + /// Gets the base address of the <see cref="EntryTable{TEntry}"/>. + /// </summary> + /// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception> + public IntPtr Base + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_pages) + { + return (IntPtr)GetRootPage(); + } + } + } + + /// <summary> + /// Constructs a new instance of the <see cref="AddressTable{TEntry}"/> class with the specified list of + /// <see cref="Level"/>. + /// </summary> + /// <exception cref="ArgumentNullException"><paramref name="levels"/> is null</exception> + /// <exception cref="ArgumentException">Length of <paramref name="levels"/> is less than 2</exception> + public AddressTable(Level[] levels) + { + ArgumentNullException.ThrowIfNull(levels); + + if (levels.Length < 2) + { + throw new ArgumentException("Table must be at least 2 levels deep.", nameof(levels)); + } + + _pages = new List<IntPtr>(capacity: 16); + + Levels = levels; + Mask = 0; + + foreach (var level in Levels) + { + Mask |= level.Mask; + } + } + + /// <summary> + /// Determines if the specified <paramref name="address"/> is in the range of the + /// <see cref="AddressTable{TEntry}"/>. + /// </summary> + /// <param name="address">Guest address</param> + /// <returns><see langword="true"/> if is valid; otherwise <see langword="false"/></returns> + public bool IsValid(ulong address) + { + return (address & ~Mask) == 0; + } + + /// <summary> + /// Gets a reference to the value at the specified guest <paramref name="address"/>. + /// </summary> + /// <param name="address">Guest address</param> + /// <returns>Reference to the value at the specified guest <paramref name="address"/></returns> + /// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception> + /// <exception cref="ArgumentException"><paramref name="address"/> is not mapped</exception> + public ref TEntry GetValue(ulong address) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + if (!IsValid(address)) + { + throw new ArgumentException($"Address 0x{address:X} is not mapped onto the table.", nameof(address)); + } + + lock (_pages) + { + return ref GetPage(address)[Levels[^1].GetValue(address)]; + } + } + + /// <summary> + /// Gets the leaf page for the specified guest <paramref name="address"/>. + /// </summary> + /// <param name="address">Guest address</param> + /// <returns>Leaf page for the specified guest <paramref name="address"/></returns> + private TEntry* GetPage(ulong address) + { + TEntry** page = GetRootPage(); + + for (int i = 0; i < Levels.Length - 1; i++) + { + ref Level level = ref Levels[i]; + ref TEntry* nextPage = ref page[level.GetValue(address)]; + + if (nextPage == null) + { + ref Level nextLevel = ref Levels[i + 1]; + + nextPage = i == Levels.Length - 2 ? + (TEntry*)Allocate(1 << nextLevel.Length, Fill, leaf: true) : + (TEntry*)Allocate(1 << nextLevel.Length, IntPtr.Zero, leaf: false); + } + + page = (TEntry**)nextPage; + } + + return (TEntry*)page; + } + + /// <summary> + /// Lazily initialize and get the root page of the <see cref="AddressTable{TEntry}"/>. + /// </summary> + /// <returns>Root page of the <see cref="AddressTable{TEntry}"/></returns> + private TEntry** GetRootPage() + { + if (_table == null) + { + _table = (TEntry**)Allocate(1 << Levels[0].Length, fill: IntPtr.Zero, leaf: false); + } + + return _table; + } + + /// <summary> + /// Allocates a block of memory of the specified type and length. + /// </summary> + /// <typeparam name="T">Type of elements</typeparam> + /// <param name="length">Number of elements</param> + /// <param name="fill">Fill value</param> + /// <param name="leaf"><see langword="true"/> if leaf; otherwise <see langword="false"/></param> + /// <returns>Allocated block</returns> + private IntPtr Allocate<T>(int length, T fill, bool leaf) where T : unmanaged + { + var size = sizeof(T) * length; + var page = (IntPtr)NativeAllocator.Instance.Allocate((uint)size); + var span = new Span<T>((void*)page, length); + + span.Fill(fill); + + _pages.Add(page); + + TranslatorEventSource.Log.AddressTableAllocated(size, leaf); + + return page; + } + + /// <summary> + /// Releases all resources used by the <see cref="AddressTable{TEntry}"/> instance. + /// </summary> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases all unmanaged and optionally managed resources used by the <see cref="AddressTable{TEntry}"/> + /// instance. + /// </summary> + /// <param name="disposing"><see langword="true"/> to dispose managed resources also; otherwise just unmanaged resouces</param> + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + foreach (var page in _pages) + { + Marshal.FreeHGlobal(page); + } + + _disposed = true; + } + } + + /// <summary> + /// Frees resources used by the <see cref="AddressTable{TEntry}"/> instance. + /// </summary> + ~AddressTable() + { + Dispose(false); + } + } +} diff --git a/src/ARMeilleure/Common/Allocator.cs b/src/ARMeilleure/Common/Allocator.cs new file mode 100644 index 00000000..247a8e8b --- /dev/null +++ b/src/ARMeilleure/Common/Allocator.cs @@ -0,0 +1,24 @@ +using System; + +namespace ARMeilleure.Common +{ + unsafe abstract class Allocator : IDisposable + { + public T* Allocate<T>(ulong count = 1) where T : unmanaged + { + return (T*)Allocate(count * (uint)sizeof(T)); + } + + public abstract void* Allocate(ulong size); + + public abstract void Free(void* block); + + protected virtual void Dispose(bool disposing) { } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/ARMeilleure/Common/ArenaAllocator.cs b/src/ARMeilleure/Common/ArenaAllocator.cs new file mode 100644 index 00000000..bce6794a --- /dev/null +++ b/src/ARMeilleure/Common/ArenaAllocator.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace ARMeilleure.Common +{ + unsafe sealed class ArenaAllocator : Allocator + { + private class PageInfo + { + public byte* Pointer; + public byte Unused; + public int UnusedCounter; + } + + private int _lastReset; + private ulong _index; + private int _pageIndex; + private PageInfo _page; + private List<PageInfo> _pages; + private readonly ulong _pageSize; + private readonly uint _pageCount; + private readonly List<IntPtr> _extras; + + public ArenaAllocator(uint pageSize, uint pageCount) + { + _lastReset = Environment.TickCount; + + // Set _index to pageSize so that the first allocation goes through the slow path. + _index = pageSize; + _pageIndex = -1; + + _page = null; + _pages = new List<PageInfo>(); + _pageSize = pageSize; + _pageCount = pageCount; + + _extras = new List<IntPtr>(); + } + + public Span<T> AllocateSpan<T>(ulong count) where T : unmanaged + { + return new Span<T>(Allocate<T>(count), (int)count); + } + + public override void* Allocate(ulong size) + { + if (_index + size <= _pageSize) + { + byte* result = _page.Pointer + _index; + + _index += size; + + return result; + } + + return AllocateSlow(size); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void* AllocateSlow(ulong size) + { + if (size > _pageSize) + { + void* extra = NativeAllocator.Instance.Allocate(size); + + _extras.Add((IntPtr)extra); + + return extra; + } + + if (_index + size > _pageSize) + { + _index = 0; + _pageIndex++; + } + + if (_pageIndex < _pages.Count) + { + _page = _pages[_pageIndex]; + _page.Unused = 0; + } + else + { + _page = new PageInfo(); + _page.Pointer = (byte*)NativeAllocator.Instance.Allocate(_pageSize); + + _pages.Add(_page); + } + + byte* result = _page.Pointer + _index; + + _index += size; + + return result; + } + + public override void Free(void* block) { } + + public void Reset() + { + _index = _pageSize; + _pageIndex = -1; + _page = null; + + // Free excess pages that was allocated. + while (_pages.Count > _pageCount) + { + NativeAllocator.Instance.Free(_pages[_pages.Count - 1].Pointer); + + _pages.RemoveAt(_pages.Count - 1); + } + + // Free extra blocks that are not page-sized + foreach (IntPtr ptr in _extras) + { + NativeAllocator.Instance.Free((void*)ptr); + } + + _extras.Clear(); + + // Free pooled pages that has not been used in a while. Remove pages at the back first, because we try to + // keep the pages at the front alive, since they're more likely to be hot and in the d-cache. + bool removing = true; + + // If arena is used frequently, keep pages for longer. Otherwise keep pages for a shorter amount of time. + int now = Environment.TickCount; + int count = (now - _lastReset) switch { + >= 5000 => 0, + >= 2500 => 50, + >= 1000 => 100, + >= 10 => 1500, + _ => 5000 + }; + + for (int i = _pages.Count - 1; i >= 0; i--) + { + PageInfo page = _pages[i]; + + if (page.Unused == 0) + { + page.UnusedCounter = 0; + } + + page.UnusedCounter += page.Unused; + page.Unused = 1; + + // If page not used after `count` resets, remove it. + if (removing && page.UnusedCounter >= count) + { + NativeAllocator.Instance.Free(page.Pointer); + + _pages.RemoveAt(i); + } + else + { + removing = false; + } + } + + _lastReset = now; + } + + protected override void Dispose(bool disposing) + { + if (_pages != null) + { + foreach (PageInfo info in _pages) + { + NativeAllocator.Instance.Free(info.Pointer); + } + + foreach (IntPtr ptr in _extras) + { + NativeAllocator.Instance.Free((void*)ptr); + } + + _pages = null; + } + } + + ~ArenaAllocator() + { + Dispose(false); + } + } +} diff --git a/src/ARMeilleure/Common/BitMap.cs b/src/ARMeilleure/Common/BitMap.cs new file mode 100644 index 00000000..27ef031f --- /dev/null +++ b/src/ARMeilleure/Common/BitMap.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace ARMeilleure.Common +{ + unsafe class BitMap : IEnumerable<int>, IDisposable + { + private const int IntSize = 64; + private const int IntMask = IntSize - 1; + + private int _count; + private long* _masks; + private readonly Allocator _allocator; + + public BitMap(Allocator allocator) + { + _allocator = allocator; + } + + public BitMap(Allocator allocator, int capacity) : this(allocator) + { + EnsureCapacity(capacity); + } + + public bool Set(int bit) + { + EnsureCapacity(bit + 1); + + int wordIndex = bit / IntSize; + int wordBit = bit & IntMask; + + long wordMask = 1L << wordBit; + + if ((_masks[wordIndex] & wordMask) != 0) + { + return false; + } + + _masks[wordIndex] |= wordMask; + + return true; + } + + public void Clear(int bit) + { + EnsureCapacity(bit + 1); + + int wordIndex = bit / IntSize; + int wordBit = bit & IntMask; + + long wordMask = 1L << wordBit; + + _masks[wordIndex] &= ~wordMask; + } + + public bool IsSet(int bit) + { + EnsureCapacity(bit + 1); + + int wordIndex = bit / IntSize; + int wordBit = bit & IntMask; + + return (_masks[wordIndex] & (1L << wordBit)) != 0; + } + + public int FindFirstUnset() + { + for (int index = 0; index < _count; index++) + { + long mask = _masks[index]; + + if (mask != -1L) + { + return BitOperations.TrailingZeroCount(~mask) + index * IntSize; + } + } + + return _count * IntSize; + } + + public bool Set(BitMap map) + { + EnsureCapacity(map._count * IntSize); + + bool modified = false; + + for (int index = 0; index < _count; index++) + { + long newValue = _masks[index] | map._masks[index]; + + if (_masks[index] != newValue) + { + _masks[index] = newValue; + + modified = true; + } + } + + return modified; + } + + public bool Clear(BitMap map) + { + EnsureCapacity(map._count * IntSize); + + bool modified = false; + + for (int index = 0; index < _count; index++) + { + long newValue = _masks[index] & ~map._masks[index]; + + if (_masks[index] != newValue) + { + _masks[index] = newValue; + + modified = true; + } + } + + return modified; + } + + private void EnsureCapacity(int size) + { + int count = (size + IntMask) / IntSize; + + if (count > _count) + { + var oldMask = _masks; + var oldSpan = new Span<long>(_masks, _count); + + _masks = _allocator.Allocate<long>((uint)count); + _count = count; + + var newSpan = new Span<long>(_masks, _count); + + oldSpan.CopyTo(newSpan); + newSpan.Slice(oldSpan.Length).Clear(); + + _allocator.Free(oldMask); + } + } + + public void Dispose() + { + if (_masks != null) + { + _allocator.Free(_masks); + + _masks = null; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator<int> IEnumerable<int>.GetEnumerator() + { + return GetEnumerator(); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + public struct Enumerator : IEnumerator<int> + { + private long _index; + private long _mask; + private int _bit; + private readonly BitMap _map; + + public int Current => (int)_index * IntSize + _bit; + object IEnumerator.Current => Current; + + public Enumerator(BitMap map) + { + _index = -1; + _mask = 0; + _bit = 0; + _map = map; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + if (_mask != 0) + { + _mask &= ~(1L << _bit); + } + + // Manually hoist these loads, because RyuJIT does not. + long count = (uint)_map._count; + long* masks = _map._masks; + + while (_mask == 0) + { + if (++_index >= count) + { + return false; + } + + _mask = masks[_index]; + } + + _bit = BitOperations.TrailingZeroCount(_mask); + + return true; + } + + public void Reset() { } + + public void Dispose() { } + } + } +}
\ No newline at end of file diff --git a/src/ARMeilleure/Common/BitUtils.cs b/src/ARMeilleure/Common/BitUtils.cs new file mode 100644 index 00000000..e7697ff3 --- /dev/null +++ b/src/ARMeilleure/Common/BitUtils.cs @@ -0,0 +1,57 @@ +using System; +using System.Numerics; + +namespace ARMeilleure.Common +{ + static class BitUtils + { + private static ReadOnlySpan<sbyte> HbsNibbleLut => new sbyte[] { -1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3 }; + + public static long FillWithOnes(int bits) + { + return bits == 64 ? -1L : (1L << bits) - 1; + } + + public static int HighestBitSet(int value) + { + return 31 - BitOperations.LeadingZeroCount((uint)value); + } + + public static int HighestBitSetNibble(int value) + { + return HbsNibbleLut[value]; + } + + public static long Replicate(long bits, int size) + { + long output = 0; + + for (int bit = 0; bit < 64; bit += size) + { + output |= bits << bit; + } + + return output; + } + + public static int RotateRight(int bits, int shift, int size) + { + return (int)RotateRight((uint)bits, shift, size); + } + + public static uint RotateRight(uint bits, int shift, int size) + { + return (bits >> shift) | (bits << (size - shift)); + } + + public static long RotateRight(long bits, int shift, int size) + { + return (long)RotateRight((ulong)bits, shift, size); + } + + public static ulong RotateRight(ulong bits, int shift, int size) + { + return (bits >> shift) | (bits << (size - shift)); + } + } +} diff --git a/src/ARMeilleure/Common/Counter.cs b/src/ARMeilleure/Common/Counter.cs new file mode 100644 index 00000000..d7210d15 --- /dev/null +++ b/src/ARMeilleure/Common/Counter.cs @@ -0,0 +1,98 @@ +using System; + +namespace ARMeilleure.Common +{ + /// <summary> + /// Represents a numeric counter which can be used for instrumentation of compiled code. + /// </summary> + /// <typeparam name="T">Type of the counter</typeparam> + class Counter<T> : IDisposable where T : unmanaged + { + private bool _disposed; + /// <summary> + /// Index in the <see cref="EntryTable{T}"/> + /// </summary> + private readonly int _index; + private readonly EntryTable<T> _countTable; + + /// <summary> + /// Initializes a new instance of the <see cref="Counter{T}"/> class from the specified + /// <see cref="EntryTable{T}"/> instance and index. + /// </summary> + /// <param name="countTable"><see cref="EntryTable{T}"/> instance</param> + /// <exception cref="ArgumentNullException"><paramref name="countTable"/> is <see langword="null"/></exception> + /// <exception cref="ArgumentException"><typeparamref name="T"/> is unsupported</exception> + public Counter(EntryTable<T> countTable) + { + if (typeof(T) != typeof(byte) && typeof(T) != typeof(sbyte) && + typeof(T) != typeof(short) && typeof(T) != typeof(ushort) && + typeof(T) != typeof(int) && typeof(T) != typeof(uint) && + typeof(T) != typeof(long) && typeof(T) != typeof(ulong) && + typeof(T) != typeof(nint) && typeof(T) != typeof(nuint) && + typeof(T) != typeof(float) && typeof(T) != typeof(double)) + { + throw new ArgumentException("Counter does not support the specified type."); + } + + _countTable = countTable ?? throw new ArgumentNullException(nameof(countTable)); + _index = countTable.Allocate(); + } + + /// <summary> + /// Gets a reference to the value of the counter. + /// </summary> + /// <exception cref="ObjectDisposedException"><see cref="Counter{T}"/> instance was disposed</exception> + /// <remarks> + /// This can refer to freed memory if the owning <see cref="EntryTable{TEntry}"/> is disposed. + /// </remarks> + public ref T Value + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + return ref _countTable.GetValue(_index); + } + } + + /// <summary> + /// Releases all resources used by the <see cref="Counter{T}"/> instance. + /// </summary> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases all unmanaged and optionally managed resources used by the <see cref="Counter{T}"/> instance. + /// </summary> + /// <param name="disposing"><see langword="true"/> to dispose managed resources also; otherwise just unmanaged resources</param> + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + try + { + // The index into the EntryTable is essentially an unmanaged resource since we allocate and free the + // resource ourselves. + _countTable.Free(_index); + } + catch (ObjectDisposedException) + { + // Can happen because _countTable may be disposed before the Counter instance. + } + + _disposed = true; + } + } + + /// <summary> + /// Frees resources used by the <see cref="Counter{T}"/> instance. + /// </summary> + ~Counter() + { + Dispose(false); + } + } +} diff --git a/src/ARMeilleure/Common/EntryTable.cs b/src/ARMeilleure/Common/EntryTable.cs new file mode 100644 index 00000000..6f205797 --- /dev/null +++ b/src/ARMeilleure/Common/EntryTable.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Numerics; + +namespace ARMeilleure.Common +{ + /// <summary> + /// Represents an expandable table of the type <typeparamref name="TEntry"/>, whose entries will remain at the same + /// address through out the table's lifetime. + /// </summary> + /// <typeparam name="TEntry">Type of the entry in the table</typeparam> + class EntryTable<TEntry> : IDisposable where TEntry : unmanaged + { + private bool _disposed; + private int _freeHint; + private readonly int _pageCapacity; // Number of entries per page. + private readonly int _pageLogCapacity; + private readonly Dictionary<int, IntPtr> _pages; + private readonly BitMap _allocated; + + /// <summary> + /// Initializes a new instance of the <see cref="EntryTable{TEntry}"/> class with the desired page size in + /// bytes. + /// </summary> + /// <param name="pageSize">Desired page size in bytes</param> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="pageSize"/> is less than 0</exception> + /// <exception cref="ArgumentException"><typeparamref name="TEntry"/>'s size is zero</exception> + /// <remarks> + /// The actual page size may be smaller or larger depending on the size of <typeparamref name="TEntry"/>. + /// </remarks> + public unsafe EntryTable(int pageSize = 4096) + { + if (pageSize < 0) + { + throw new ArgumentOutOfRangeException(nameof(pageSize), "Page size cannot be negative."); + } + + if (sizeof(TEntry) == 0) + { + throw new ArgumentException("Size of TEntry cannot be zero."); + } + + _allocated = new BitMap(NativeAllocator.Instance); + _pages = new Dictionary<int, IntPtr>(); + _pageLogCapacity = BitOperations.Log2((uint)(pageSize / sizeof(TEntry))); + _pageCapacity = 1 << _pageLogCapacity; + } + + /// <summary> + /// Allocates an entry in the <see cref="EntryTable{TEntry}"/>. + /// </summary> + /// <returns>Index of entry allocated in the table</returns> + /// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception> + public int Allocate() + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_allocated) + { + if (_allocated.IsSet(_freeHint)) + { + _freeHint = _allocated.FindFirstUnset(); + } + + int index = _freeHint++; + var page = GetPage(index); + + _allocated.Set(index); + + GetValue(page, index) = default; + + return index; + } + } + + /// <summary> + /// Frees the entry at the specified <paramref name="index"/>. + /// </summary> + /// <param name="index">Index of entry to free</param> + /// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception> + public void Free(int index) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_allocated) + { + if (_allocated.IsSet(index)) + { + _allocated.Clear(index); + + _freeHint = index; + } + } + } + + /// <summary> + /// Gets a reference to the entry at the specified allocated <paramref name="index"/>. + /// </summary> + /// <param name="index">Index of the entry</param> + /// <returns>Reference to the entry at the specified <paramref name="index"/></returns> + /// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception> + /// <exception cref="ArgumentException">Entry at <paramref name="index"/> is not allocated</exception> + public ref TEntry GetValue(int index) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_allocated) + { + if (!_allocated.IsSet(index)) + { + throw new ArgumentException("Entry at the specified index was not allocated", nameof(index)); + } + + var page = GetPage(index); + + return ref GetValue(page, index); + } + } + + /// <summary> + /// Gets a reference to the entry at using the specified <paramref name="index"/> from the specified + /// <paramref name="page"/>. + /// </summary> + /// <param name="page">Page to use</param> + /// <param name="index">Index to use</param> + /// <returns>Reference to the entry</returns> + private ref TEntry GetValue(Span<TEntry> page, int index) + { + return ref page[index & (_pageCapacity - 1)]; + } + + /// <summary> + /// Gets the page for the specified <see cref="index"/>. + /// </summary> + /// <param name="index">Index to use</param> + /// <returns>Page for the specified <see cref="index"/></returns> + private unsafe Span<TEntry> GetPage(int index) + { + var pageIndex = (int)((uint)(index & ~(_pageCapacity - 1)) >> _pageLogCapacity); + + if (!_pages.TryGetValue(pageIndex, out IntPtr page)) + { + page = (IntPtr)NativeAllocator.Instance.Allocate((uint)sizeof(TEntry) * (uint)_pageCapacity); + + _pages.Add(pageIndex, page); + } + + return new Span<TEntry>((void*)page, _pageCapacity); + } + + /// <summary> + /// Releases all resources used by the <see cref="EntryTable{TEntry}"/> instance. + /// </summary> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases all unmanaged and optionally managed resources used by the <see cref="EntryTable{TEntry}"/> + /// instance. + /// </summary> + /// <param name="disposing"><see langword="true"/> to dispose managed resources also; otherwise just unmanaged resouces</param> + protected unsafe virtual void Dispose(bool disposing) + { + if (!_disposed) + { + _allocated.Dispose(); + + foreach (var page in _pages.Values) + { + NativeAllocator.Instance.Free((void*)page); + } + + _disposed = true; + } + } + + /// <summary> + /// Frees resources used by the <see cref="EntryTable{TEntry}"/> instance. + /// </summary> + ~EntryTable() + { + Dispose(false); + } + } +} diff --git a/src/ARMeilleure/Common/EnumUtils.cs b/src/ARMeilleure/Common/EnumUtils.cs new file mode 100644 index 00000000..2a4aa645 --- /dev/null +++ b/src/ARMeilleure/Common/EnumUtils.cs @@ -0,0 +1,12 @@ +using System; + +namespace ARMeilleure.Common +{ + static class EnumUtils + { + public static int GetCount(Type enumType) + { + return Enum.GetNames(enumType).Length; + } + } +} diff --git a/src/ARMeilleure/Common/NativeAllocator.cs b/src/ARMeilleure/Common/NativeAllocator.cs new file mode 100644 index 00000000..71c04a9b --- /dev/null +++ b/src/ARMeilleure/Common/NativeAllocator.cs @@ -0,0 +1,27 @@ +using System; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Common +{ + unsafe sealed class NativeAllocator : Allocator + { + public static NativeAllocator Instance { get; } = new(); + + public override void* Allocate(ulong size) + { + void* result = (void*)Marshal.AllocHGlobal((IntPtr)size); + + if (result == null) + { + throw new OutOfMemoryException(); + } + + return result; + } + + public override void Free(void* block) + { + Marshal.FreeHGlobal((IntPtr)block); + } + } +} |
