mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-07-29 05:37:11 +02:00
Move solution and projects to src
This commit is contained in:
parent
cd124bda58
commit
cee7121058
3466 changed files with 55 additions and 55 deletions
252
src/ARMeilleure/Common/AddressTable.cs
Normal file
252
src/ARMeilleure/Common/AddressTable.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
24
src/ARMeilleure/Common/Allocator.cs
Normal file
24
src/ARMeilleure/Common/Allocator.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
187
src/ARMeilleure/Common/ArenaAllocator.cs
Normal file
187
src/ARMeilleure/Common/ArenaAllocator.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
222
src/ARMeilleure/Common/BitMap.cs
Normal file
222
src/ARMeilleure/Common/BitMap.cs
Normal file
|
@ -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() { }
|
||||
}
|
||||
}
|
||||
}
|
57
src/ARMeilleure/Common/BitUtils.cs
Normal file
57
src/ARMeilleure/Common/BitUtils.cs
Normal file
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
98
src/ARMeilleure/Common/Counter.cs
Normal file
98
src/ARMeilleure/Common/Counter.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
188
src/ARMeilleure/Common/EntryTable.cs
Normal file
188
src/ARMeilleure/Common/EntryTable.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
12
src/ARMeilleure/Common/EnumUtils.cs
Normal file
12
src/ARMeilleure/Common/EnumUtils.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace ARMeilleure.Common
|
||||
{
|
||||
static class EnumUtils
|
||||
{
|
||||
public static int GetCount(Type enumType)
|
||||
{
|
||||
return Enum.GetNames(enumType).Length;
|
||||
}
|
||||
}
|
||||
}
|
27
src/ARMeilleure/Common/NativeAllocator.cs
Normal file
27
src/ARMeilleure/Common/NativeAllocator.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue