// 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."); }