Fix source only package issue in nuget

This commit is contained in:
2026-02-22 12:36:51 +09:00
parent 49dc44605b
commit ffac1c643e
35 changed files with 69 additions and 8 deletions

View File

@@ -0,0 +1,582 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Utilities;
public unsafe struct HashMapHelper<TKey> : IDisposable
where TKey : unmanaged, IEquatable<TKey>
{
internal unsafe struct Enumerator
{
public HashMapHelper<TKey>* buffer;
public int index;
public int bucketIndex;
public int nextIndex;
public Enumerator(HashMapHelper<TKey>* data)
{
buffer = data;
index = -1;
bucketIndex = 0;
nextIndex = -1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
return buffer->MoveNext(ref bucketIndex, ref nextIndex, out index);
}
public void Reset()
{
index = -1;
bucketIndex = 0;
nextIndex = -1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public KeyValuePair<TKey, TValue> GetCurrent<TValue>()
where TValue : unmanaged
{
return new KeyValuePair<TKey, TValue>(buffer->_keys[index], UnsafeUtility.ReadArrayElementRef<TValue>(buffer->_buffer, index));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TKey GetCurrentKey()
{
if (index != -1)
{
return buffer->_keys[index];
}
return default;
}
public void Dispose()
{
}
}
// This buffer has 4 parts: TValue, TKey, Next, Buckets.
private byte* _buffer;
internal TKey* _keys;
internal int* _next;
internal int* _buckets;
private int _count;
private int _capacity;
private int _bucketCapacity;
private int _allocatedIndex;
private int _firstFreeIndex;
private readonly int _alignment;
private readonly int _sizeOfTValue;
private readonly int _log2MinGrowth;
private MemoryHandle _memoryHandle;
private AllocationHandle _allocationHandle;
public const int MINIMAL_CAPACITY = 64;
public readonly byte* Buffer => _buffer;
public readonly int Count => _count;
public readonly int Capacity => _capacity;
public readonly bool IsEmpty => !IsCreated || _count == 0;
public readonly bool IsCreated
{
get
{
if (_buffer != null)
{
if (_allocationHandle.IsValid != null)
{
return _allocationHandle.IsValid(_allocationHandle.State, _memoryHandle);
}
else
{
return true;
}
}
return false;
}
}
private static int CalculateDataSize(int capacity, int bucketCapacity, int sizeOfTValue, out int outKeyOffset, out int outNextOffset, out int outBucketOffset)
{
var sizeOfTKey = sizeof(TKey);
var sizeOfInt = sizeof(int);
var valuesSize = sizeOfTValue * capacity;
var keysSize = sizeOfTKey * capacity;
var nextSize = sizeOfInt * capacity;
var bucketSize = sizeOfInt * bucketCapacity;
var totalSize = valuesSize + keysSize + nextSize + bucketSize;
outKeyOffset = 0 + valuesSize;
outNextOffset = outKeyOffset + keysSize;
outBucketOffset = outNextOffset + nextSize;
return totalSize;
}
public HashMapHelper(int capacity, int sizeOfTValue, int alignOfTValue, uint minGrowth, AllocationHandle handle, AllocationOption allocationOption)
{
if (capacity <= 0)
{
throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than zero.");
}
if (sizeOfTValue < 0 || alignOfTValue < 0)
{
throw new ArgumentOutOfRangeException(nameof(sizeOfTValue), "Size or alignment of TValue can not be less than zero.");
}
_capacity = CalcCapacityCeilPow2(capacity);
_bucketCapacity = _capacity * 2;
var alignOfKey = (int)AlignOf<TKey>();
var alignOfInt = (int)AlignOf<int>();
var maxDataAlign = Math.Max(Math.Max(alignOfTValue, alignOfKey), alignOfInt);
_alignment = maxDataAlign;
_sizeOfTValue = sizeOfTValue;
_log2MinGrowth = BitOperations.Log2(minGrowth);
_allocationHandle = handle;
var totalSize = CalculateDataSize(_capacity, _bucketCapacity, sizeOfTValue,
out var keyOffset, out var nextOffset, out var bucketOffset);
AllocateBuffer(totalSize, keyOffset, nextOffset, bucketOffset, allocationOption);
Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("ENABLE_COLLECTION_CHECKS")]
private readonly void ThrowIfNotCreated()
{
if (!IsCreated)
{
throw new InvalidOperationException("The HashMapHelper is not created.");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int CeilPow2(int x)
{
x -= 1;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
return x + 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly int CalcCapacityCeilPow2(int capacity)
{
capacity = Math.Max(Math.Max(1, _count), capacity);
var newCapacity = Math.Max(capacity, 1 << _log2MinGrowth);
var result = CeilPow2(newCapacity);
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly int GetBucket(in TKey key)
{
var h = (uint)key.GetHashCode();
return (int)(h & (uint)(_bucketCapacity - 1));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly void CheckIndexOutOfBounds(int idx)
{
if ((uint)idx >= (uint)_capacity)
{
throw new InvalidOperationException($"Index {idx} is out of bounds for the hash map with capacity {_capacity}.");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void AllocateBuffer(int totalSize, int keyOffset, int nextOffset, int bucketOffset, AllocationOption allocationOption)
{
if (_allocationHandle.Alloc == null)
{
throw new InvalidOperationException("Target allocation handle does not support allocation.");
}
MemoryHandle memHandle;
var buf = (byte*)_allocationHandle.Alloc(_allocationHandle.State, (uint)totalSize, (nuint)_alignment, allocationOption, &memHandle);
_buffer = buf;
_keys = (TKey*)(_buffer + keyOffset);
_next = (int*)(_buffer + nextOffset);
_buckets = (int*)(_buffer + bucketOffset);
_memoryHandle = memHandle;
}
private void ResizeExact(int newCapacity, int newBucketCapacity)
{
var totalSize = CalculateDataSize(newCapacity, newBucketCapacity, _sizeOfTValue,
out var keyOffset, out var nextOffset, out var bucketOffset);
var oldBuffer = _buffer;
var oldKeys = _keys;
var oldNext = _next;
var oldBuckets = _buckets;
var oldBucketCapacity = _bucketCapacity;
var oldMemoryHandle = _memoryHandle;
AllocateBuffer(totalSize, keyOffset, nextOffset, bucketOffset, AllocationOption.None);
_capacity = newCapacity;
_bucketCapacity = newBucketCapacity;
Clear();
for (int i = 0, num = oldBucketCapacity; i < num; ++i)
{
for (var idx = oldBuckets[i]; idx != -1; idx = oldNext[idx])
{
var newIdx = TryAdd(oldKeys[idx]);
MemCpy(_buffer + _sizeOfTValue * newIdx, oldBuffer + _sizeOfTValue * idx, (nuint)_sizeOfTValue);
}
}
if (_allocationHandle.Free != null)
{
_allocationHandle.Free(_allocationHandle.State, oldBuffer, oldMemoryHandle);
}
}
public void Resize(int newCapacity)
{
ThrowIfNotCreated();
newCapacity = Math.Max(newCapacity, _count);
var newBucketCapacity = CeilPow2(newCapacity * 2);
if (_capacity == newCapacity && _bucketCapacity == newBucketCapacity)
{
return;
}
ResizeExact(newCapacity, newBucketCapacity);
}
public void TrimExcess()
{
ThrowIfNotCreated();
var capacity = CalcCapacityCeilPow2(_count);
ResizeExact(capacity, capacity * 2);
}
public int Find(in TKey key)
{
ThrowIfNotCreated();
if (_allocatedIndex <= 0)
{
return -1;
}
// First find the slot based on the hash
var bucket = GetBucket(key);
var entryIdx = _buckets[bucket];
if ((uint)entryIdx < (uint)_capacity)
{
var nextPtrs = _next;
while (!UnsafeUtility.ReadArrayElement<TKey>(_keys, entryIdx).Equals(key))
{
entryIdx = nextPtrs[entryIdx];
if ((uint)entryIdx >= (uint)_capacity)
{
return -1;
}
}
return entryIdx;
}
return -1;
}
public int TryAdd(in TKey key)
{
ThrowIfNotCreated();
var k = key;
if (Find(in key) != -1)
{
return -1;
}
// Allocate an entry from the free list
int idx;
int* next;
if (_allocatedIndex >= _capacity && _firstFreeIndex < 0)
{
var newCap = CalcCapacityCeilPow2(_capacity + (1 << _log2MinGrowth));
Resize(newCap);
}
idx = _firstFreeIndex;
if (idx >= 0)
{
_firstFreeIndex = _next[idx];
}
else
{
idx = _allocatedIndex++;
}
CheckIndexOutOfBounds(idx);
UnsafeUtility.WriteArrayElement(_keys, idx, key);
var bucket = GetBucket(key);
// Add the index to the hash-map
next = _next;
next[idx] = _buckets[bucket];
_buckets[bucket] = idx;
_count++;
return idx;
}
public int TryRemove(in TKey key)
{
ThrowIfNotCreated();
if (_capacity == 0)
{
return -1;
}
var removed = 0;
// First find the slot based on the hash
var bucket = GetBucket(key);
var prevEntry = -1;
var entryIdx = _buckets[bucket];
while (entryIdx >= 0 && entryIdx < _capacity)
{
if (UnsafeUtility.ReadArrayElement<TKey>(_keys, entryIdx).Equals(key))
{
removed++;
// Found matching element, remove it
if (prevEntry < 0)
{
_buckets[bucket] = _next[entryIdx];
}
else
{
_next[prevEntry] = _next[entryIdx];
}
// And free the index
var nextIdx = _next[entryIdx];
_next[entryIdx] = _firstFreeIndex;
_firstFreeIndex = entryIdx;
entryIdx = nextIdx;
break;
}
else
{
prevEntry = entryIdx;
entryIdx = _next[entryIdx];
}
}
_count -= removed;
return 0 != removed ? removed : -1;
}
public bool TryGetValue<TValue>(in TKey key, out TValue item)
where TValue : unmanaged
{
ThrowIfNotCreated();
var idx = Find(key);
if (idx != -1)
{
item = UnsafeUtility.ReadArrayElement<TValue>(_buffer, idx);
return true;
}
item = default;
return false;
}
public ref TValue GetValueRef<TValue>(in TKey key, out bool exists)
where TValue : unmanaged
{
ThrowIfNotCreated();
var idx = Find(key);
if (idx != -1)
{
exists = true;
return ref UnsafeUtility.ReadArrayElementRef<TValue>(_buffer, idx);
}
exists = false;
return ref Unsafe.NullRef<TValue>();
}
public bool MoveNextSearch(ref int bucketIndex, ref int nextIndex, out int index)
{
ThrowIfNotCreated();
for (int i = bucketIndex, num = _bucketCapacity; i < num; ++i)
{
var idx = _buckets[i];
if (idx != -1)
{
index = idx;
bucketIndex = i + 1;
nextIndex = _next[idx];
return true;
}
}
index = -1;
bucketIndex = _bucketCapacity;
nextIndex = -1;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext(ref int bucketIndex, ref int nextIndex, out int index)
{
ThrowIfNotCreated();
if (nextIndex != -1)
{
index = nextIndex;
nextIndex = _next[nextIndex];
return true;
}
return MoveNextSearch(ref bucketIndex, ref nextIndex, out index);
}
internal UnsafeArray<TKey> GetKeyArray(Allocator allocator)
{
ThrowIfNotCreated();
var result = new UnsafeArray<TKey>(_count, allocator);
for (int i = 0, count = 0, max = result.Count, capacity = _bucketCapacity; i < capacity && count < max; i++)
{
var bucket = _buckets[i];
while (bucket != -1)
{
result[count++] = UnsafeUtility.ReadArrayElement<TKey>(_keys, bucket);
bucket = _next[bucket];
}
}
return result;
}
internal UnsafeArray<TValue> GetValueArray<TValue>(Allocator allocator)
where TValue : unmanaged
{
ThrowIfNotCreated();
var result = new UnsafeArray<TValue>(_count, allocator);
for (int i = 0, count = 0, max = result.Count, capacity = _bucketCapacity; i < capacity && count < max; ++i)
{
var bucket = _buckets[i];
while (bucket != -1)
{
result[count++] = UnsafeUtility.ReadArrayElement<TValue>(_buffer, bucket);
bucket = _next[bucket];
}
}
return result;
}
public UnsafeArray<KeyValuePair<TKey, TValue>> GetKeyValueArrays<TValue>(Allocator allocator)
where TValue : unmanaged
{
ThrowIfNotCreated();
var result = new UnsafeArray<KeyValuePair<TKey, TValue>>(_count, allocator);
for (int i = 0, count = 0, max = result.Count, capacity = _bucketCapacity; i < capacity && count < max; i++)
{
var bucket = _buckets[i];
while (bucket != -1)
{
result[count] = new(UnsafeUtility.ReadArrayElement<TKey>(_keys, bucket),
UnsafeUtility.ReadArrayElement<TValue>(_buffer, bucket));
count++;
bucket = _next[bucket];
}
}
return result;
}
public void Clear()
{
ThrowIfNotCreated();
MemSet(_buckets, 0xff, (nuint)_bucketCapacity * sizeof(int));
MemSet(_next, 0xff, (nuint)_capacity * sizeof(int));
_count = 0;
_firstFreeIndex = -1;
_allocatedIndex = 0;
}
public void Dispose()
{
if (!IsCreated)
{
return;
}
if (_allocationHandle.Free != null)
{
_allocationHandle.Free(_allocationHandle.State, _buffer, _memoryHandle);
}
_buffer = null;
_keys = null;
_next = null;
_buckets = null;
_count = 0;
_capacity = 0;
_bucketCapacity = 0;
}
}

View File

@@ -0,0 +1,352 @@
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace Misaki.HighPerformance.LowLevel.Utilities;
public static unsafe partial class MemoryUtility
{
[DoesNotReturn]
private static void ThrowMustBeNullTerminatedString()
{
throw new ArgumentException("Arg_MustBeNullTerminatedString");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<byte> LoadVector128(ref byte start, nuint offset)
=> Unsafe.ReadUnaligned<Vector128<byte>>(ref Unsafe.AddByteOffset(ref start, offset));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<byte> LoadVector256(ref byte start, nuint offset)
=> Unsafe.ReadUnaligned<Vector256<byte>>(ref Unsafe.AddByteOffset(ref start, offset));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static nuint GetByteVector128SpanLength(nuint offset, int length)
=> (uint)((length - (int)offset) & ~(Vector128<byte>.Count - 1));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static nuint GetByteVector256SpanLength(nuint offset, int length)
=> (uint)((length - (int)offset) & ~(Vector256<byte>.Count - 1));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static nuint GetByteVector512SpanLength(nuint offset, int length)
=> (uint)((length - (int)offset) & ~(Vector512<byte>.Count - 1));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe nuint UnalignedCountVector128(byte* searchSpace)
{
var unaligned = (nint)searchSpace & (Vector128<byte>.Count - 1);
return (uint)((Vector128<byte>.Count - unaligned) & (Vector128<byte>.Count - 1));
}
/// <summary>
/// Searches for the first occurrence of a null byte (0x00) in a given byte array.
/// </summary>
/// <param name="searchSpace">A pointer to the byte array where the search will be performed.</param>
/// <returns>Returns the index of the first null byte found in the array..</returns>
/// <exception cref="ArgumentException">Thrown if the byte array is not null-terminated.</exception>"
public static unsafe int IndexOfNullByte(byte* searchSpace)
{
const int Length = int.MaxValue;
const uint uValue = 0; // Use uint for comparisons to avoid unnecessary 8->32 extensions
nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations
var lengthToExamine = (nuint)(uint)Length;
if (Vector128.IsHardwareAccelerated)
{
// Avx2 branch also operates on Sse2 sizes, so check is combined.
lengthToExamine = UnalignedCountVector128(searchSpace);
}
SequentialScan:
while (lengthToExamine >= 8)
{
lengthToExamine -= 8;
if (uValue == searchSpace[offset])
goto Found;
if (uValue == searchSpace[offset + 1])
goto Found1;
if (uValue == searchSpace[offset + 2])
goto Found2;
if (uValue == searchSpace[offset + 3])
goto Found3;
if (uValue == searchSpace[offset + 4])
goto Found4;
if (uValue == searchSpace[offset + 5])
goto Found5;
if (uValue == searchSpace[offset + 6])
goto Found6;
if (uValue == searchSpace[offset + 7])
goto Found7;
offset += 8;
}
if (lengthToExamine >= 4)
{
lengthToExamine -= 4;
if (uValue == searchSpace[offset])
goto Found;
if (uValue == searchSpace[offset + 1])
goto Found1;
if (uValue == searchSpace[offset + 2])
goto Found2;
if (uValue == searchSpace[offset + 3])
goto Found3;
offset += 4;
}
while (lengthToExamine > 0)
{
lengthToExamine -= 1;
if (uValue == searchSpace[offset])
goto Found;
offset += 1;
}
// We get past SequentialScan only if IsHardwareAccelerated is true; and remain length is greater than Vector length.
// However, we still have the redundant check to allow the JIT to see that the code is unreachable and eliminate it when the platform does not
// have hardware accelerated. After processing Vector lengths we return to SequentialScan to finish any remaining.
if (Vector512.IsHardwareAccelerated)
{
if (offset < Length)
{
if ((((uint)searchSpace + offset) & (nuint)(Vector256<byte>.Count - 1)) != 0)
{
// Invert currently aligned to Vector256 (is aligned to Vector128); this can cause a problem for searches
// with no upper bound e.g. String.strlen.
// Start with a check on Vector128 to align to Vector256, before moving to processing Vector256.
// This ensures we do not fault across memory pages while searching for an end of string.
var search = Vector128.Load(searchSpace + offset);
// Same method as below
var matches = Vector128.Equals(Vector128<byte>.Zero, search).ExtractMostSignificantBits();
if (matches == 0)
{
// Zero flags set so no matches
offset += (nuint)Vector128<byte>.Count;
}
else
{
// Find bitflag offset of first match and add to current offset
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
}
}
if ((((uint)searchSpace + offset) & (nuint)(Vector512<byte>.Count - 1)) != 0)
{
// Invert currently aligned to Vector512 (is aligned to Vector256); this can cause a problem for searches
// with no upper bound e.g. String.strlen.
// Start with a check on Vector256 to align to Vector512, before moving to processing Vector256.
// This ensures we do not fault across memory pages while searching for an end of string.
var search = Vector256.Load(searchSpace + offset);
// Same method as below
var matches = Vector256.Equals(Vector256<byte>.Zero, search).ExtractMostSignificantBits();
if (matches == 0)
{
// Zero flags set so no matches
offset += (nuint)Vector256<byte>.Count;
}
else
{
// Find bitflag offset of first match and add to current offset
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
}
}
lengthToExamine = GetByteVector512SpanLength(offset, Length);
if (lengthToExamine > offset)
{
do
{
var search = Vector512.Load(searchSpace + offset);
var matches = Vector512.Equals(Vector512<byte>.Zero, search).ExtractMostSignificantBits();
// Note that MoveMask has converted the equal vector elements into a set of bit flags,
// So the bit position in 'matches' corresponds to the element offset.
if (matches == 0)
{
// Zero flags set so no matches
offset += (nuint)Vector512<byte>.Count;
continue;
}
// Find bitflag offset of first match and add to current offset
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
} while (lengthToExamine > offset);
}
lengthToExamine = GetByteVector256SpanLength(offset, Length);
if (lengthToExamine > offset)
{
var search = Vector256.Load(searchSpace + offset);
// Same method as above
var matches = Vector256.Equals(Vector256<byte>.Zero, search).ExtractMostSignificantBits();
if (matches == 0)
{
// Zero flags set so no matches
offset += (nuint)Vector256<byte>.Count;
}
else
{
// Find bitflag offset of first match and add to current offset
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
}
}
lengthToExamine = GetByteVector128SpanLength(offset, Length);
if (lengthToExamine > offset)
{
var search = Vector128.Load(searchSpace + offset);
// Same method as above
var matches = Vector128.Equals(Vector128<byte>.Zero, search).ExtractMostSignificantBits();
if (matches == 0)
{
// Zero flags set so no matches
offset += (nuint)Vector128<byte>.Count;
}
else
{
// Find bitflag offset of first match and add to current offset
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
}
}
if (offset < Length)
{
lengthToExamine = (Length - offset);
goto SequentialScan;
}
}
}
else if (Vector256.IsHardwareAccelerated)
{
if (offset < Length)
{
if ((((uint)searchSpace + offset) & (nuint)(Vector256<byte>.Count - 1)) != 0)
{
// Invert currently aligned to Vector256 (is aligned to Vector128); this can cause a problem for searches
// with no upper bound e.g. String.strlen.
// Start with a check on Vector128 to align to Vector256, before moving to processing Vector256.
// This ensures we do not fault across memory pages while searching for an end of string.
var search = Vector128.Load(searchSpace + offset);
// Same method as below
var matches = Vector128.Equals(Vector128<byte>.Zero, search).ExtractMostSignificantBits();
if (matches == 0)
{
// Zero flags set so no matches
offset += (nuint)Vector128<byte>.Count;
}
else
{
// Find bitflag offset of first match and add to current offset
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
}
}
lengthToExamine = GetByteVector256SpanLength(offset, Length);
if (lengthToExamine > offset)
{
do
{
var search = Vector256.Load(searchSpace + offset);
var matches = Vector256.Equals(Vector256<byte>.Zero, search).ExtractMostSignificantBits();
// Note that MoveMask has converted the equal vector elements into a set of bit flags,
// So the bit position in 'matches' corresponds to the element offset.
if (matches == 0)
{
// Zero flags set so no matches
offset += (nuint)Vector256<byte>.Count;
continue;
}
// Find bitflag offset of first match and add to current offset
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
} while (lengthToExamine > offset);
}
lengthToExamine = GetByteVector128SpanLength(offset, Length);
if (lengthToExamine > offset)
{
var search = Vector128.Load(searchSpace + offset);
// Same method as above
var matches = Vector128.Equals(Vector128<byte>.Zero, search).ExtractMostSignificantBits();
if (matches == 0)
{
// Zero flags set so no matches
offset += (nuint)Vector128<byte>.Count;
}
else
{
// Find bitflag offset of first match and add to current offset
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
}
}
if (offset < Length)
{
lengthToExamine = (Length - offset);
goto SequentialScan;
}
}
}
else if (Vector128.IsHardwareAccelerated)
{
if (offset < Length)
{
lengthToExamine = GetByteVector128SpanLength(offset, Length);
while (lengthToExamine > offset)
{
var search = Vector128.Load(searchSpace + offset);
// Same method as above
var compareResult = Vector128.Equals(Vector128<byte>.Zero, search);
if (compareResult == Vector128<byte>.Zero)
{
// Zero flags set so no matches
offset += (nuint)Vector128<byte>.Count;
continue;
}
// Find bitflag offset of first match and add to current offset
var matches = compareResult.ExtractMostSignificantBits();
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
}
if (offset < Length)
{
lengthToExamine = (Length - offset);
goto SequentialScan;
}
}
}
ThrowMustBeNullTerminatedString();
Found: // Workaround for https://github.com/dotnet/runtime/issues/8795
return (int)offset;
Found1:
return (int)(offset + 1);
Found2:
return (int)(offset + 2);
Found3:
return (int)(offset + 3);
Found4:
return (int)(offset + 4);
Found5:
return (int)(offset + 5);
Found6:
return (int)(offset + 6);
Found7:
return (int)(offset + 7);
}
}

View File

@@ -0,0 +1,296 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.LowLevel.Utilities;
public static unsafe partial class MemoryUtility
{
[StructLayout(LayoutKind.Sequential)]
private struct AlignOfHelper<T>
where T : struct
{
public byte dummy;
public T data;
}
/// <summary>
/// Allocates a block of memory of the specified size in bytes.
/// </summary>
/// <param name="size">Specifies the number of bytes to allocate in memory.</param>
/// <returns>Returns a pointer to the allocated memory block.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* Malloc(nuint size)
{
try
{
#if NET6_0_OR_GREATER
return NativeMemory.Alloc(size);
#else
return Marshal.AllocHGlobal((IntPtr)size).ToPointer();
#endif
}
catch (Exception)
{
return null;
}
}
/// <summary>
/// Allocates a block of memory of the specified size in bytes and initializes it to zero.
/// </summary>
/// <param name="size">Specifies the number of bytes to allocate in memory.</param>
/// <returns>Returns a pointer to the allocated and zero-initialized memory block.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* Calloc(nuint size)
{
try
{
#if NET6_0_OR_GREATER
return NativeMemory.AllocZeroed(size);
#else
var ptr = Marshal.AllocHGlobal((IntPtr)size).ToPointer();
Unsafe.InitBlock(ptr, 0, (uint)size);
return ptr;
#endif
}
catch (Exception)
{
return null;
}
}
/// <summary>
/// Allocates a block of memory with a specified size and alignment.
/// </summary>
/// <param name="size">Specifies the total number of bytes to allocate for the memory block.</param>
/// <param name="alignment">Defines the required alignment for the allocated memory address.</param>
/// <returns>Returns a pointer to the allocated memory block.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* AlignedAlloc(nuint size, nuint alignment)
{
try
{
#if NET6_0_OR_GREATER
return NativeMemory.AlignedAlloc(size, alignment);
#else
return Marshal.AllocHGlobal((IntPtr)(size + alignment - 1)).ToPointer();
#endif
}
catch (Exception)
{
return null;
}
}
/// <summary>
/// Resizes a previously allocated memory block to a new size. It returns a pointer to the reallocated memory.
/// </summary>
/// <param name="ptr">The pointer to the memory block that needs to be resized.</param>
/// <param name="size">The new size for the memory block after resizing.</param>
/// <returns>A pointer to the reallocated memory block, or null if the operation fails.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* Realloc(void* ptr, nuint size)
{
try
{
#if NET6_0_OR_GREATER
return NativeMemory.Realloc(ptr, size);
#else
return Marshal.ReAllocHGlobal((IntPtr)ptr, (IntPtr)size).ToPointer();
#endif
}
catch (Exception)
{
return null;
}
}
/// <summary>
/// Reallocates memory to a specified size with a given alignment. It returns a pointer to the newly allocated
/// memory.
/// </summary>
/// <param name="ptr">The pointer to the existing memory block that needs to be reallocated.</param>
/// <param name="size">The new size for the memory allocation.</param>
/// <param name="alignment">The required alignment for the new memory allocation.</param>
/// <returns>A pointer to the reallocated memory block, or null if the allocation fails.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* AlignedRealloc(void* ptr, nuint size, nuint alignment)
{
try
{
#if NET6_0_OR_GREATER
return NativeMemory.AlignedRealloc(ptr, size, alignment);
#else
var newPtr = Marshal.AllocHGlobal((IntPtr)(size + alignment - 1)).ToPointer();
if (newPtr == null)
{
return null;
}
if (ptr != null)
{
Unsafe.CopyBlock(newPtr, ptr, (uint)size);
Marshal.FreeHGlobal((IntPtr)ptr);
}
return newPtr;
#endif
}
catch (Exception)
{
return null;
}
}
/// <summary>
/// Releases the allocated memory pointed to by the given pointer. This helps in managing memory usage effectively.
/// </summary>
/// <param name="ptr">The pointer to the memory block that needs to be freed.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Free(void* ptr)
{
#if NET6_0_OR_GREATER
NativeMemory.Free(ptr);
#else
Marshal.FreeHGlobal((IntPtr)ptr);
#endif
}
/// <summary>
/// Releases memory that was allocated with alignment requirements. It ensures proper deallocation of aligned memory
/// blocks.
/// </summary>
/// <param name="ptr">The pointer to the memory block that needs to be freed.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AlignedFree(void* ptr)
{
#if NET6_0_OR_GREATER
NativeMemory.AlignedFree(ptr);
#else
Marshal.FreeHGlobal((IntPtr)ptr);
#endif
}
/// <summary>
/// Clears a block of memory by setting it to zero. It initializes a specified number of bytes at a given memory
/// address.
/// </summary>
/// <param name="ptr">Specifies the memory address where the clearing operation will begin.</param>
/// <param name="size">Indicates the number of bytes to be cleared in the memory block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void MemClear(void* ptr, nuint size)
{
#if NET6_0_OR_GREATER
NativeMemory.Clear(ptr, size);
#else
Unsafe.InitBlock(ptr, 0, (uint)size);
#endif
}
/// <summary>
/// Sets a block of memory to a specified byte value for a given size.
/// </summary>
/// <param name="ptr">The memory address where the byte value will be set.</param>
/// <param name="size">The number of bytes to set to the specified value.</param>
/// <param name="value">The byte value to which the memory block will be initialized.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void MemSet(void* ptr, byte value, nuint size)
{
#if NET6_0_OR_GREATER
NativeMemory.Fill(ptr, size, value);
#else
Unsafe.InitBlock(ptr, value, (uint)size);
#endif
}
/// <summary>
/// Copies a block of memory from a source location to a destination location.
/// </summary>
/// <param name="source">Indicates the memory address from which data will be copied.</param>
/// <param name="destination">Specifies the memory address where the copied data will be stored.</param>
/// <param name="size">Defines the number of bytes to be copied from the source to the destination.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void MemCpy(void* destination, void* source, nuint size)
{
#if NET6_0_OR_GREATER
NativeMemory.Copy(source, destination, size);
#else
Unsafe.CopyBlock(destination, source, (uint)size);
#endif
}
/// <summary>
/// Moves a block of memory from a source location to a destination location, handling overlapping regions correctly.
/// </summary>
/// <param name="destination">Indicates the memory address where the data will be moved to.</param>
/// <param name="source">Specifies the memory address from which data will be moved.</param>
/// <param name="size">Defines the number of bytes to be moved from the source to the destination.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void MemMove(void* destination, void* source, nuint size)
{
#if NET6_0_OR_GREATER
// NativeMemory.Copy use memmove internally.
NativeMemory.Copy(source, destination, size);
#else
Unsafe.CopyBlock(destination, source, (uint)size);
#endif
}
/// <summary>
/// Compares two blocks of memory byte by byte for a specified length.
/// </summary>
/// <param name="ptr1">A pointer to the first block of memory to compare.</param>
/// <param name="ptr2">A pointer to the second block of memory to compare.</param>
/// <param name="size">The number of bytes to compare. Must not exceed the length of either memory block.</param>
/// <returns>A signed integer that indicates the relative order of the memory blocks: less than zero if the first differing
/// byte in ptr1 is less than the corresponding byte in ptr2; zero if all compared bytes are equal; greater than
/// zero if the first differing byte in ptr1 is greater than the corresponding byte in ptr2.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int MemCmp(void* ptr1, void* ptr2, nuint size)
{
if (ptr1 == ptr2)
{
return 0;
}
var span1 = new ReadOnlySpan<byte>(ptr1, (int)size);
var span2 = new ReadOnlySpan<byte>(ptr2, (int)size);
return span1.SequenceCompareTo(span2);
}
/// <summary>
/// Calculates the size in bytes of a specified unmanaged type.
/// </summary>
/// <typeparam name="T">Represents an unmanaged type for which the size is being calculated.</typeparam>
/// <returns>Returns the size of the specified type as an unsigned integer.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static nuint SizeOf<T>()
where T : unmanaged
{
return (nuint)sizeof(T);
}
/// <summary>
/// Calculates the alignment size of a specified unmanaged type.
/// </summary>
/// <typeparam name="T">Represents an unmanaged type for which the alignment size is being calculated.</typeparam>
/// <returns>Returns the difference in size between a helper structure and the specified type.</returns>
public static nuint AlignOf<T>()
where T : unmanaged
{
return (nuint)(sizeof(AlignOfHelper<T>) - sizeof(T));
}
/// <summary>
/// Calculates the alignment size of a specified struct.
/// </summary>
/// <typeparam name="T">Represents a value type that is used to determine the alignment size.</typeparam>
/// <returns>Returns the size difference in bytes as an integer.</returns>
public static int MarshalAlignOf<T>()
where T : struct
{
return Marshal.SizeOf<AlignOfHelper<T>>() - Marshal.SizeOf<T>();
}
}

View File

@@ -0,0 +1,174 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.LowLevel.Utilities;
/// <summary>
/// Provides extension methods for copying elements between unsafe collections and spans, converting collections to
/// arrays or lists, and searching for values.
/// </summary>
public static unsafe class UnsafeCollectionExtensions
{
/// <summary>
/// Copies elements from a source UnsafeCollection to a destination Span, ensuring both have the same size.
/// </summary>
/// <typeparam name="C">The type of the destination collection, which must implement <see cref="IUnsafeCollection{T}"/>.</typeparam>
/// <typeparam name="T">Specifies the type of elements being copied, which must be unmanaged.</typeparam>
/// <param name="source">Represents the source collection from which elements are copied.</param>
/// <param name="destination">Represents the target span where elements are copied to.</param>
/// <exception cref="ArgumentException">Thrown when the sizes of the source collection and destination span do not match.</exception>
public static void CopyTo<C, T>(this C source, Span<T> destination)
where C: IUnsafeCollection<T> where T : unmanaged
{
if (source.Count > destination.Length)
{
throw new ArgumentException("Source collection is larger than the destination span.");
}
fixed (T* pDest = destination)
{
MemCpy(pDest, source.GetUnsafePtr(), (uint)(source.Count * sizeof(T)));
}
}
/// <summary>
/// Copies a range of elements from a source collection to a destination span, ensuring both are adequately sized.
/// </summary>
/// <typeparam name="C">The type of the destination collection, which must implement <see cref="IUnsafeCollection{T}"/>.</typeparam>
/// <typeparam name="T">Specifies the type of elements being copied, which must be a value type.</typeparam>
/// <param name="source">The collection from which elements are copied.</param>
/// <param name="destination">The span where the elements will be copied to.</param>
/// <param name="sourceIndex">The starting index in the source collection for the copy operation.</param>
/// <param name="destinationIndex">The starting index in the destination span where the elements will be placed.</param>
/// <param name="length">The number of elements to copy from the source to the destination.</param>
/// <exception cref="ArgumentException">Thrown when the specified range exceeds the bounds of the source collection or destination span.</exception>
public static void CopyTo<C, T>(this C source, Span<T> destination, uint sourceIndex, uint destinationIndex, uint length)
where C : IUnsafeCollection<T> where T : unmanaged
{
if (sourceIndex + length > source.Count || destinationIndex + length > destination.Length)
{
throw new ArgumentException("Source collection or destination span is too small for the specified range.");
}
fixed (T* pDest = destination)
{
MemCpy(pDest + destinationIndex, (byte*)source.GetUnsafePtr() + sourceIndex * sizeof(T), (uint)(length * sizeof(T)));
}
}
/// <summary>
/// Copies elements from a source span to a destination unsafe collection, ensuring both have the same size.
/// </summary>
/// <typeparam name="C">The type of the destination collection, which must implement <see cref="IUnsafeCollection{T}"/>.</typeparam>
/// <typeparam name="T">Specifies the type of elements being copied, which must be unmanaged.</typeparam>
/// <param name="destination">Represents the unsafe collection that will receive the copied elements.</param>
/// <param name="source">Represents the span containing the elements to be copied to the unsafe collection.</param>
/// <exception cref="ArgumentException">Thrown when the source span and destination collection have different sizes.</exception>
public static void CopyFrom<C, T>(this C destination, ReadOnlySpan<T> source)
where C : IUnsafeCollection<T> where T : unmanaged
{
if (destination.Count < source.Length)
{
throw new ArgumentException("Destination collection is smaller than the source span.");
}
fixed (T* pSrc = source)
{
MemCpy(destination.GetUnsafePtr(), pSrc, (uint)(source.Length * sizeof(T)));
}
}
/// <summary>
/// Copies a specified range of elements from a source span to a destination collection.
/// </summary>
/// <typeparam name="C">The type of the destination collection, which must implement <see cref="IUnsafeCollection{T}"/>.</typeparam>
/// <typeparam name="T">Represents the type of elements being copied, which must be unmanaged.</typeparam>
/// <param name="destination">The collection where elements will be copied to.</param>
/// <param name="source">The span containing the elements to be copied.</param>
/// <param name="sourceIndex">The starting index in the source span from which to begin copying.</param>
/// <param name="destinationIndex">The starting index in the destination collection where the elements will be placed.</param>
/// <param name="length">The number of elements to copy from the source span to the destination collection.</param>
/// <exception cref="ArgumentException">Thrown when the specified range exceeds the bounds of the source span or destination collection.</exception>
public static void CopyFrom<C, T>(this C destination, ReadOnlySpan<T> source, uint sourceIndex, uint destinationIndex, uint length)
where C : IUnsafeCollection<T> where T : unmanaged
{
if (sourceIndex + length > source.Length || destinationIndex + length > destination.Count)
{
throw new ArgumentException("Source span or destination collection is too small for the specified range.");
}
fixed (T* pSrc = source)
{
MemCpy((byte*)destination.GetUnsafePtr() + destinationIndex * sizeof(T), pSrc + sourceIndex, (uint)(length * sizeof(T)));
}
}
/// <summary>
/// Converts a managed array to an UnsafeArray by copying its elements to unmanaged memory.
/// </summary>
/// <typeparam name="T">The type of elements in the array, which must be unmanaged.</typeparam>
/// <param name="source">The managed array to convert.</param>
/// <param name="allocator">The allocator to use for memory allocation of the UnsafeArray.</param>
/// <returns>A new UnsafeArray containing a copy of the source array elements.</returns>
public static UnsafeArray<T> ToUnsafeArray<T>(this T[] source, Allocator allocator)
where T : unmanaged
{
var array = new UnsafeArray<T>(source.Length, allocator);
fixed (T* pSrc = source)
{
MemCpy(array.GetUnsafePtr(), pSrc, (uint)(source.Length * sizeof(T)));
}
return array;
}
/// <summary>
/// Converts a managed List to an UnsafeList by copying its elements to unmanaged memory.
/// </summary>
/// <typeparam name="T">The type of elements in the list, which must be unmanaged.</typeparam>
/// <param name="source">The managed List to convert.</param>
/// <param name="allocator">The allocator to use for memory allocation of the UnsafeList.</param>
/// <returns>A new UnsafeList containing a copy of the source list elements.</returns>
public static UnsafeList<T> ToUnsafeList<T>(this List<T> source, Allocator allocator)
where T : unmanaged
{
var list = new UnsafeList<T>(source.Count, allocator);
fixed (T* pSrc = CollectionsMarshal.AsSpan(source))
{
MemCpy(list.GetUnsafePtr(), pSrc, (uint)(source.Count * sizeof(T)));
}
return list;
}
/// <summary>
/// Creates a new array containing all elements from the specified unsafe collection.
/// </summary>
/// <typeparam name="C">The type of the source collection, which must implement <see cref="IUnsafeCollection{T}"/>.</typeparam>
/// <typeparam name="T">The type of elements contained in the collection. Must be an unmanaged type.</typeparam>
/// <param name="source">The collection whose elements will be copied to the new array. Must not be null.</param>
/// <returns>An array containing all elements from <paramref name="source"/> in their current order.</returns>
public static T[] ToArray<C, T>(this C source)
where C : IUnsafeCollection<T> where T : unmanaged
{
return new Span<T>(source.GetUnsafePtr(), source.Count).ToArray();
}
/// <summary>
/// Creates a new <see cref="List{T}"/> containing the elements of the specified unsafe collection.
/// </summary>
/// <typeparam name="C">The type of the source collection, which must implement <see cref="IUnsafeCollection{T}"/>.</typeparam>
/// <typeparam name="T">The type of elements contained in the collection. Must be an unmanaged type.</typeparam>
/// <param name="source">The unsafe collection whose elements are to be copied to the new list. Must not be null.</param>
/// <returns>A <see cref="List{T}"/> containing all elements from <paramref name="source"/> in their original order.</returns>
public static List<T> ToList<C, T>(this C source)
where C : IUnsafeCollection<T> where T : unmanaged
{
var list = new List<T>(source.Count);
var span = new Span<T>(source.GetUnsafePtr(), source.Count);
span.CopyTo(CollectionsMarshal.AsSpan(list));
return list;
}
}

View File

@@ -0,0 +1,174 @@
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Utilities;
public static unsafe class UnsafeUtility
{
/// <summary>
/// Converts a pointer to a reference of a specified type.
/// </summary>
/// <typeparam name="T">Specifies the type of the reference to be created from the pointer.</typeparam>
/// <param name="ptr">Represents the memory address to be converted into a reference.</param>
/// <returns>Returns a reference of the specified type pointing to the given memory address.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T AsRef<T>(void* ptr)
where T : unmanaged
{
return ref *(T*)ptr;
}
/// <summary>
/// Returns the address of a specified variable in memory.
/// </summary>
/// <typeparam name="T">Represents the type of the variable whose address is being retrieved.</typeparam>
/// <param name="value">The variable whose memory address is to be obtained.</param>
/// <returns>A pointer to the memory address of the specified variable.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* AddressOf<T>(ref T value)
where T : unmanaged
{
return Unsafe.AsPointer(ref value);
}
/// <summary>
/// Reads an element from an unmanaged array at a specified index using a pointer.
/// </summary>
/// <typeparam name="T">Specifies the type of elements in the unmanaged array.</typeparam>
/// <param name="ptr">Points to the start of the unmanaged array from which the element is read.</param>
/// <param name="index">Indicates the position of the element to be accessed within the array.</param>
/// <returns>Returns a pointer to the element located at the specified index.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T* ReadArrayElementUnsafe<T>(void* ptr, nint index)
where T : unmanaged
{
return (T*)((byte*)ptr + index * sizeof(T));
}
/// <summary>
/// Reads an element from an unmanaged array at a specified index using a pointer.
/// </summary>
/// <typeparam name="T">Specifies the type of elements in the unmanaged array.</typeparam>
/// <param name="ptr">Points to the start of the unmanaged array from which the element is read.</param>
/// <param name="index">Indicates the position of the element to be accessed within the array.</param>
/// <returns>Returns a pointer to the element located at the specified index.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T* ReadArrayElementUnsafe<T>(void* ptr, nuint index)
where T : unmanaged
{
return (T*)((byte*)ptr + index * (nuint)sizeof(T));
}
/// <summary>
/// Reads an element from an unmanaged array using a pointer and index, returning a reference to the element.
/// </summary>
/// <typeparam name="T">Specifies the type of the elements in the unmanaged array.</typeparam>
/// <param name="ptr">Points to the start of the unmanaged array from which the element is read.</param>
/// <param name="index">Indicates the position of the element to be accessed in the array.</param>
/// <returns>A reference to the specified element in the unmanaged array.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T ReadArrayElementRef<T>(void* ptr, nint index)
where T : unmanaged
{
return ref AsRef<T>(ReadArrayElementUnsafe<T>(ptr, index));
}
/// <summary>
/// Reads an element from an unmanaged array using a pointer and index, returning a reference to the element.
/// </summary>
/// <typeparam name="T">Specifies the type of the elements in the unmanaged array.</typeparam>
/// <param name="ptr">Points to the start of the unmanaged array from which the element is read.</param>
/// <param name="index">Indicates the position of the element to be accessed in the array.</param>
/// <returns>A reference to the specified element in the unmanaged array.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T ReadArrayElementRef<T>(void* ptr, nuint index)
where T : unmanaged
{
return ref AsRef<T>(ReadArrayElementUnsafe<T>(ptr, index));
}
/// <summary>
/// Reads an element from an array at a specified index using a pointer to the array.
/// </summary>
/// <typeparam name="T">Specifies the type of the elements in the array, which must be unmanaged.</typeparam>
/// <param name="ptr">Points to the start of the array from which an element will be read.</param>
/// <param name="index">Indicates the position of the element to be accessed within the array.</param>
/// <returns>The element located at the specified index in the array.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T ReadArrayElement<T>(void* ptr, nint index)
where T : unmanaged
{
return *ReadArrayElementUnsafe<T>(ptr, index);
}
/// <summary>
/// Reads an element from an array at a specified index using a pointer to the array.
/// </summary>
/// <typeparam name="T">Specifies the type of the elements in the array, which must be unmanaged.</typeparam>
/// <param name="ptr">Points to the start of the array from which an element will be read.</param>
/// <param name="index">Indicates the position of the element to be accessed within the array.</param>
/// <returns>The element located at the specified index in the array.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T ReadArrayElement<T>(void* ptr, nuint index)
where T : unmanaged
{
return *ReadArrayElementUnsafe<T>(ptr, index);
}
/// <summary>
/// Writes a value to a specified index of an unmanaged array using a pointer.
/// </summary>
/// <typeparam name="T">Specifies the type of the value being written to the array, which must be an unmanaged type.</typeparam>
/// <param name="ptr">Points to the beginning of the unmanaged array where the value will be written.</param>
/// <param name="index">Indicates the position in the array where the value should be stored.</param>
/// <param name="value">Represents the value to be written to the specified index of the array.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteArrayElement<T>(void* ptr, nint index, T value)
where T : unmanaged
{
*ReadArrayElementUnsafe<T>(ptr, index) = value;
}
/// <summary>
/// Writes a value to a specified index of an unmanaged array using a pointer.
/// </summary>
/// <typeparam name="T">Specifies the type of the value being written to the array, which must be an unmanaged type.</typeparam>
/// <param name="ptr">Points to the beginning of the unmanaged array where the value will be written.</param>
/// <param name="index">Indicates the position in the array where the value should be stored.</param>
/// <param name="value">Represents the value to be written to the specified index of the array.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteArrayElement<T>(void* ptr, nuint index, T value)
where T : unmanaged
{
*ReadArrayElementUnsafe<T>(ptr, index) = value;
}
/// <summary>
/// Returns a pointer to the first element of the specified span. This method enables direct, unsafe access to the underlying data of the span.
/// </summary>
/// <typeparam name="T">The type of elements in the span. Must be an unmanaged type.</typeparam>
/// <param name="span">The span whose underlying data pointer is to be obtained.</param>
/// <returns>A pointer to the first element of the span. If the span is empty, the returned pointer is undefined and must not be dereferenced.</returns>
public static T* GetUnsafePtr<T>(this Span<T> span)
where T : unmanaged
{
fixed (T* ptr = span)
{
return ptr;
}
}
/// <summary>
/// Returns a pointer to the first element of the specified span. This method enables direct, unsafe access to the underlying data of the span.
/// </summary>
/// <typeparam name="T">The type of elements in the span. Must be an unmanaged type.</typeparam>
/// <param name="span">The span whose underlying data pointer is to be obtained.</param>
/// <returns>A pointer to the first element of the span. If the span is empty, the returned pointer is undefined and must not be dereferenced.</returns>
public static T* GetUnsafePtr<T>(this ReadOnlySpan<T> span)
where T : unmanaged
{
fixed (T* ptr = span)
{
return ptr;
}
}
}