// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/MemoryMarshal.cs
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace FisheryLib;
///
/// Provides a collection of methods for interoperating with , ,
/// , and .
///
internal static class MemoryMarshal
{
///
/// Casts a Span of one primitive type to Span of bytes.
/// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety.
///
/// The source slice, of type .
///
/// Thrown when contains pointers.
///
///
/// Thrown if the Length property of the new Span would exceed int.MaxValue.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe Span AsBytes(Span span)
where T : struct
{
if (RuntimeHelpers.IsReferenceOrContainsReferences())
ThrowInvalidTypeWithPointersNotSupported(typeof(T));
return new Span(
Unsafe.AsPointer(ref GetReference(span)),
checked(span.Length * Unsafe.SizeOf()));
}
///
/// Casts a ReadOnlySpan of one primitive type to ReadOnlySpan of bytes.
/// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety.
///
/// The source slice, of type .
///
/// Thrown when contains pointers.
///
///
/// Thrown if the Length property of the new Span would exceed int.MaxValue.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ReadOnlySpan AsBytes(ReadOnlySpan span)
where T : struct
{
if (RuntimeHelpers.IsReferenceOrContainsReferences())
ThrowInvalidTypeWithPointersNotSupported(typeof(T));
return new ReadOnlySpan(
Unsafe.AsPointer(ref GetReference(span)),
checked(span.Length * Unsafe.SizeOf()));
}
/// Creates a from a .
/// The .
/// A representing the same memory as the , but writable.
///
/// must be used with extreme caution. is used
/// to represent immutable data and other memory that is not meant to be written to; instances created
/// by should not be written to. The method exists to enable variables typed
/// as but only used for reading to store a .
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory AsMemory(ReadOnlyMemory memory) =>
Unsafe.As, Memory>(ref memory);
///
/// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to the location where the 0th element
/// would have been stored. Such a reference may or may not be null. It can be used for pinning but must never be dereferenced.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T GetReference(Span span) => ref span.DangerousGetPinnableReference();
///
/// Returns a reference to the 0th element of the ReadOnlySpan. If the ReadOnlySpan is empty, returns a reference to the location where the 0th element
/// would have been stored. Such a reference may or may not be null. It can be used for pinning but must never be dereferenced.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T GetReference(ReadOnlySpan span) => ref span.DangerousGetPinnableReference();
///
/// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to fake non-null pointer. Such a reference can be used
/// for pinning but must never be dereferenced. This is useful for interop with methods that do not accept null pointers for zero-sized buffers.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe ref T GetNonNullPinnableReference(Span span) => ref (span.Length != 0) ? ref span.DangerousGetPinnableReference() : ref Unsafe.AsRef((void*)1);
///
/// Returns a reference to the 0th element of the ReadOnlySpan. If the ReadOnlySpan is empty, returns a reference to fake non-null pointer. Such a reference
/// can be used for pinning but must never be dereferenced. This is useful for interop with methods that do not accept null pointers for zero-sized buffers.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe ref T GetNonNullPinnableReference(ReadOnlySpan span) => ref (span.Length != 0) ? ref span.DangerousGetPinnableReference() : ref Unsafe.AsRef((void*)1);
///
/// Casts a Span of one primitive type to another primitive type .
/// These types may not contain pointers or references. This is checked at runtime in order to preserve type safety.
///
///
/// Supported only for platforms that support misaligned memory access or when the memory block is aligned by other means.
///
/// The source slice, of type .
///
/// Thrown when or contains pointers.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span Cast(Span span)
where TFrom : struct
where TTo : struct
{
if (RuntimeHelpers.IsReferenceOrContainsReferences())
ThrowInvalidTypeWithPointersNotSupported(typeof(TFrom));
if (RuntimeHelpers.IsReferenceOrContainsReferences())
ThrowInvalidTypeWithPointersNotSupported(typeof(TTo));
// Use unsigned integers - unsigned division by constant (especially by power of 2)
// and checked casts are faster and smaller.
var fromSize = (uint)Unsafe.SizeOf();
var toSize = (uint)Unsafe.SizeOf();
var fromLength = (uint)span.Length;
int toLength;
if (fromSize == toSize)
{
// Special case for same size types - `(ulong)fromLength * (ulong)fromSize / (ulong)toSize`
// should be optimized to just `length` but the JIT doesn't do that today.
toLength = (int)fromLength;
}
else if (fromSize == 1)
{
// Special case for byte sized TFrom - `(ulong)fromLength * (ulong)fromSize / (ulong)toSize`
// becomes `(ulong)fromLength / (ulong)toSize` but the JIT can't narrow it down to `int`
// and can't eliminate the checked cast. This also avoids a 32 bit specific issue,
// the JIT can't eliminate long multiply by 1.
toLength = (int)(fromLength / toSize);
}
else
{
// Ensure that casts are done in such a way that the JIT is able to "see"
// the uint->ulong casts and the multiply together so that on 32 bit targets
// 32x32to64 multiplication is used.
var toLengthUInt64 = (ulong)fromLength * (ulong)fromSize / (ulong)toSize;
toLength = checked((int)toLengthUInt64);
}
unsafe
{
return new Span(
Unsafe.AsPointer(ref span.DangerousGetPinnableReference()),
toLength);
}
}
///
/// Casts a ReadOnlySpan of one primitive type to another primitive type .
/// These types may not contain pointers or references. This is checked at runtime in order to preserve type safety.
///
///
/// Supported only for platforms that support misaligned memory access or when the memory block is aligned by other means.
///
/// The source slice, of type .
///
/// Thrown when or contains pointers.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan Cast(ReadOnlySpan span)
where TFrom : struct
where TTo : struct
{
if (RuntimeHelpers.IsReferenceOrContainsReferences())
ThrowInvalidTypeWithPointersNotSupported(typeof(TFrom));
if (RuntimeHelpers.IsReferenceOrContainsReferences())
ThrowInvalidTypeWithPointersNotSupported(typeof(TTo));
// Use unsigned integers - unsigned division by constant (especially by power of 2)
// and checked casts are faster and smaller.
var fromSize = (uint)Unsafe.SizeOf();
var toSize = (uint)Unsafe.SizeOf();
var fromLength = (uint)span.Length;
int toLength;
if (fromSize == toSize)
{
// Special case for same size types - `(ulong)fromLength * (ulong)fromSize / (ulong)toSize`
// should be optimized to just `length` but the JIT doesn't do that today.
toLength = (int)fromLength;
}
else if (fromSize == 1)
{
// Special case for byte sized TFrom - `(ulong)fromLength * (ulong)fromSize / (ulong)toSize`
// becomes `(ulong)fromLength / (ulong)toSize` but the JIT can't narrow it down to `int`
// and can't eliminate the checked cast. This also avoids a 32 bit specific issue,
// the JIT can't eliminate long multiply by 1.
toLength = (int)(fromLength / toSize);
}
else
{
// Ensure that casts are done in such a way that the JIT is able to "see"
// the uint->ulong casts and the multiply together so that on 32 bit targets
// 32x32to64 multiplication is used.
var toLengthUInt64 = (ulong)fromLength * (ulong)fromSize / (ulong)toSize;
toLength = checked((int)toLengthUInt64);
}
unsafe
{
return new ReadOnlySpan(
Unsafe.AsPointer(ref GetReference(span)),
toLength);
}
}
///
/// Creates a new span over a portion of a regular managed object. This can be useful
/// if part of a managed object represents a "fixed array." This is dangerous because the
/// is not checked.
///
/// A reference to data.
/// The number of elements the memory contains.
/// A span representing the specified reference and length.
/// The lifetime of the returned span will not be validated for safety by span-aware languages.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe Span CreateSpan(ref T reference, int length) => new(Unsafe.AsPointer(ref reference), length);
///
/// Creates a new read-only span over a portion of a regular managed object. This can be useful
/// if part of a managed object represents a "fixed array." This is dangerous because the
/// is not checked.
///
/// A reference to data.
/// The number of elements the memory contains.
/// A read-only span representing the specified reference and length.
/// The lifetime of the returned span will not be validated for safety by span-aware languages.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ReadOnlySpan CreateReadOnlySpan(ref T reference, int length) => new(Unsafe.AsPointer(ref reference), length);
/*
/// Creates a new read-only span for a null-terminated string.
/// The pointer to the null-terminated string of characters.
/// A read-only span representing the specified null-terminated string, or an empty span if the pointer is null.
/// The returned span does not include the null terminator.
/// The string is longer than .
public static unsafe ReadOnlySpan CreateReadOnlySpanFromNullTerminated(char* value) =>
value != null ? new ReadOnlySpan(value, string.wcslen(value)) :
default;
/// Creates a new read-only span for a null-terminated UTF8 string.
/// The pointer to the null-terminated string of bytes.
/// A read-only span representing the specified null-terminated string, or an empty span if the pointer is null.
/// The returned span does not include the null terminator, nor does it validate the well-formedness of the UTF8 data.
/// The string is longer than .
public static unsafe ReadOnlySpan CreateReadOnlySpanFromNullTerminated(byte* value) =>
value != null ? new ReadOnlySpan(value, string.strlen(value)) :
default;
///
/// Get an array segment from the underlying memory.
/// If unable to get the array segment, return false with a default array segment.
///
public static bool TryGetArray(ReadOnlyMemory memory, out ArraySegment segment)
{
object? obj = memory.GetObjectStartLength(out int index, out int length);
// As an optimization, we skip the "is string?" check below if typeof(T) is not char,
// as Memory / ROM can't possibly contain a string instance in this case.
if (obj != null && !(
(typeof(T) == typeof(char) && obj.GetType() == typeof(string))
))
{
if (RuntimeHelpers.ObjectHasComponentSize(obj))
{
// The object has a component size, which means it's variable-length, but we already
// checked above that it's not a string. The only remaining option is that it's a T[]
// or a U[] which is blittable to a T[] (e.g., int[] and uint[]).
// The array may be prepinned, so remove the high bit from the start index in the line below.
// The ArraySegment ctor will perform bounds checking on index & length.
segment = new ArraySegment(Unsafe.As(obj), index & ReadOnlyMemory.RemoveFlagsBitMask, length);
return true;
}
else
{
// The object isn't null, and it's not variable-length, so the only remaining option
// is MemoryManager. The ArraySegment ctor will perform bounds checking on index & length.
Debug.Assert(obj is MemoryManager);
if (Unsafe.As>(obj).TryGetArray(out ArraySegment tempArraySegment))
{
segment = new ArraySegment(tempArraySegment.Array!, tempArraySegment.Offset + index, length);
return true;
}
}
}
// If we got to this point, the object is null, or it's a string, or it's a MemoryManager
// which isn't backed by an array. We'll quickly homogenize all zero-length Memory instances
// to an empty array for the purposes of reporting back to our caller.
if (length == 0)
{
segment = ArraySegment.Empty;
return true;
}
// Otherwise, there's *some* data, but it's not convertible to T[].
segment = default;
return false;
}
///
/// Gets an from the underlying read-only memory.
/// If unable to get the type, returns false.
///
/// The element type of the .
/// The type of to try and retrieve.
/// The memory to get the manager for.
/// The returned manager of the .
/// A indicating if it was successful.
public static bool TryGetMemoryManager(ReadOnlyMemory memory, [NotNullWhen(true)] out TManager? manager)
where TManager : MemoryManager
{
TManager? localManager; // Use register for null comparison rather than byref
manager = localManager = memory.GetObjectStartLength(out _, out _) as TManager;
#pragma warning disable 8762 // "Parameter 'manager' may not have a null value when exiting with 'true'."
return localManager != null;
#pragma warning restore 8762
}
///
/// Gets an and , from the underlying read-only memory.
/// If unable to get the type, returns false.
///
/// The element type of the .
/// The type of to try and retrieve.
/// The memory to get the manager for.
/// The returned manager of the .
/// The offset from the start of the that the represents.
/// The length of the that the represents.
/// A indicating if it was successful.
public static bool TryGetMemoryManager(ReadOnlyMemory memory, [NotNullWhen(true)] out TManager? manager, out int start, out int length)
where TManager : MemoryManager
{
TManager? localManager; // Use register for null comparison rather than byref
manager = localManager = memory.GetObjectStartLength(out start, out length) as TManager;
Debug.Assert(length >= 0);
if (localManager == null)
{
start = default;
length = default;
return false;
}
#pragma warning disable 8762 // "Parameter 'manager' may not have a null value when exiting with 'true'."
return true;
#pragma warning restore 8762
}*/
///
/// Creates an view of the given to allow
/// the to be used in existing APIs that take an .
///
/// The element type of the .
/// The ReadOnlyMemory to view as an
/// An view of the given
public static IEnumerable ToEnumerable(ReadOnlyMemory memory)
{
for (var i = 0; i < memory.Length; i++)
yield return memory.Span[i];
}
/*
/// Attempts to get the underlying from a .
/// The memory that may be wrapping a object.
/// The string.
/// The starting location in .
/// The number of items in .
///
public static bool TryGetString(ReadOnlyMemory memory, [NotNullWhen(true)] out string? text, out int start, out int length)
{
if (memory.GetObjectStartLength(out int offset, out int count) is string s)
{
Debug.Assert(offset >= 0);
Debug.Assert(count >= 0);
text = s;
start = offset;
length = count;
return true;
}
else
{
text = null;
start = 0;
length = 0;
return false;
}
}*/
///
/// Reads a structure of type T out of a read-only span of bytes.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Read(ReadOnlySpan source)
where T : struct
{
if (RuntimeHelpers.IsReferenceOrContainsReferences())
{
ThrowInvalidTypeWithPointersNotSupported(typeof(T));
}
if (Unsafe.SizeOf() > source.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
return Unsafe.ReadUnaligned(ref GetReference(source));
}
///
/// Reads a structure of type T out of a span of bytes.
/// If the span is too small to contain the type T, return false.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryRead(ReadOnlySpan source, out T value)
where T : struct
{
if (RuntimeHelpers.IsReferenceOrContainsReferences())
{
ThrowInvalidTypeWithPointersNotSupported(typeof(T));
}
if (Unsafe.SizeOf() > (uint)source.Length)
{
value = default;
return false;
}
value = Unsafe.ReadUnaligned(ref GetReference(source));
return true;
}
///
/// Writes a structure of type T into a span of bytes.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Write(Span destination, ref T value)
where T : struct
{
if (RuntimeHelpers.IsReferenceOrContainsReferences())
{
ThrowInvalidTypeWithPointersNotSupported(typeof(T));
}
if ((uint)Unsafe.SizeOf() > (uint)destination.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
Unsafe.WriteUnaligned(ref GetReference(destination), value);
}
///
/// Writes a structure of type T into a span of bytes.
/// If the span is too small to contain the type T, return false.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryWrite(Span destination, ref T value)
where T : struct
{
if (RuntimeHelpers.IsReferenceOrContainsReferences())
{
ThrowInvalidTypeWithPointersNotSupported(typeof(T));
}
if (Unsafe.SizeOf() > (uint)destination.Length)
{
return false;
}
Unsafe.WriteUnaligned(ref GetReference(destination), value);
return true;
}
///
/// Re-interprets a span of bytes as a reference to structure of type T.
/// The type may not contain pointers or references. This is checked at runtime in order to preserve type safety.
///
///
/// Supported only for platforms that support misaligned memory access or when the memory block is aligned by other means.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T AsRef(Span span)
where T : struct
{
if (RuntimeHelpers.IsReferenceOrContainsReferences())
{
ThrowInvalidTypeWithPointersNotSupported(typeof(T));
}
if (Unsafe.SizeOf() > (uint)span.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
return ref Unsafe.As(ref GetReference(span));
}
///
/// Re-interprets a span of bytes as a reference to structure of type T.
/// The type may not contain pointers or references. This is checked at runtime in order to preserve type safety.
///
///
/// Supported only for platforms that support misaligned memory access or when the memory block is aligned by other means.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref readonly T AsRef(ReadOnlySpan span)
where T : struct
{
if (RuntimeHelpers.IsReferenceOrContainsReferences())
{
ThrowInvalidTypeWithPointersNotSupported(typeof(T));
}
if (Unsafe.SizeOf() > (uint)span.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
return ref Unsafe.As(ref GetReference(span));
}
///
/// Creates a new memory over the portion of the pre-pinned target array beginning
/// at 'start' index and ending at 'end' index (exclusive).
///
/// The pre-pinned target array.
/// The index at which to begin the memory.
/// The number of items in the memory.
/// This method should only be called on an array that is already pinned and
/// that array should not be unpinned while the returned Memory is still in use.
/// Calling this method on an unpinned array could result in memory corruption.
/// Returns default when is null.
/// Thrown when is covariant and array's type is not exactly T[].
///
/// Thrown when the specified or end index is not in the range (<0 or >=Length).
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory CreateFromPinnedArray(T[]? array, int start, int length)
{
if (array == null)
{
if (start != 0 || length != 0)
ThrowHelper.ThrowArgumentOutOfRangeException();
return default;
}
if (!typeof(T).IsValueType && array.GetType() != typeof(T[]))
ThrowHelper.ThrowArrayTypeMismatchException();
if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
ThrowHelper.ThrowArgumentOutOfRangeException();
// Before using _index, check if _index < 0, then 'and' it with RemoveFlagsBitMask
return new Memory(/*(object)*/array, start | (1 << 31), length);
}
[DoesNotReturn]
private static void ThrowInvalidTypeWithPointersNotSupported(Type targetType)
=> throw new ArgumentException($"Cannot use type '{targetType}'.Only value types without pointers or references are supported.");
}