Added UnsafeMultiHashMap

This commit is contained in:
2026-03-08 15:38:00 +09:00
parent 37d548085e
commit 21e755a56e
40 changed files with 619 additions and 156 deletions

View File

@@ -0,0 +1,362 @@
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)
{
return Unsafe.ReadUnaligned<Vector128<byte>>(ref Unsafe.AddByteOffset(ref start, offset));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<byte> LoadVector256(ref byte start, nuint offset)
{
return Unsafe.ReadUnaligned<Vector256<byte>>(ref Unsafe.AddByteOffset(ref start, offset));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static nuint GetByteVector128SpanLength(nuint offset, int length)
{
return (uint)((length - (int)offset) & ~(Vector128<byte>.Count - 1));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static nuint GetByteVector256SpanLength(nuint offset, int length)
{
return (uint)((length - (int)offset) & ~(Vector256<byte>.Count - 1));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static nuint GetByteVector512SpanLength(nuint offset, int length)
{
return (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;
}
}
}