// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // The portable implementation in this type is originally from CoreFX. // See https://github.com/dotnet/corefx/blob/release/2.1/src/System.Memory/src/System/SpanHelpers.cs. using System; using System.Diagnostics.Contracts; #if !NETSTANDARD2_1_OR_GREATER using System.Reflection; #endif using System.Runtime.CompilerServices; namespace CommunityToolkit.HighPerformance.Helpers.Internals; /// /// A helper class that with utility methods for dealing with references, and other low-level details. /// It also contains some APIs that act as polyfills for .NET Standard 2.0 and below. /// internal static class RuntimeHelpers { /// /// Converts a length of items from one size to another (rounding towards zero). /// /// The source type of items. /// The target type of items. /// The input length to convert. /// The converted length for the specified argument and types. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe int ConvertLength(int length) where TFrom : unmanaged where TTo : unmanaged { if (sizeof(TFrom) == sizeof(TTo)) { return length; } else if (sizeof(TFrom) == 1) { return (int)((uint)length / (uint)sizeof(TTo)); } else { ulong targetLength = (ulong)(uint)length * (uint)sizeof(TFrom) / (uint)sizeof(TTo); return checked((int)targetLength); } } /// /// Gets the length of a given array as a native integer. /// /// The type of values in the array. /// The input instance. /// The total length of as a native integer. /// /// This method is needed because this expression is not inlined correctly if the target array /// is only visible as a non-generic instance, because the C# compiler will /// not be able to emit the opcode instead of calling the right method. /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static nint GetArrayNativeLength(T[] array) { return (nint)array.LongLength; } /// /// Gets the length of a given array as a native integer. /// /// The input instance. /// The total length of as a native integer. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static nint GetArrayNativeLength(Array array) { return (nint)array.LongLength; } #if !NETCOREAPP3_1_OR_GREATER /// /// Gets the byte offset to the first element in a SZ array. /// /// The type of values in the array. /// The byte offset to the first element in a SZ array. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static IntPtr GetArrayDataByteOffset() { return TypeInfo.ArrayDataByteOffset; } #endif /// /// Gets the byte offset to the first element in a 2D array. /// /// The type of values in the array. /// The byte offset to the first element in a 2D array. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static IntPtr GetArray2DDataByteOffset() { return TypeInfo.Array2DDataByteOffset; } /// /// Gets the byte offset to the first element in a 3D array. /// /// The type of values in the array. /// The byte offset to the first element in a 3D array. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static IntPtr GetArray3DDataByteOffset() { return TypeInfo.Array3DDataByteOffset; } #if !NETSTANDARD2_1_OR_GREATER /// /// Gets a byte offset describing a portable pinnable reference. This can either be an /// interior pointer into some object data (described with a valid reference /// and a reference to some of its data), or a raw pointer (described with a /// reference to an , and a reference that is assumed to refer to pinned data). /// /// The type of field being referenced. /// The input hosting the target field. /// A reference to a target field of type within . /// /// The value representing the offset to the target field from the start of the object data /// for the parameter , or the value of the raw pointer passed as a tracked reference. /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe IntPtr GetObjectDataOrReferenceByteOffset(object? obj, ref T data) { if (obj is null) { return (IntPtr)Unsafe.AsPointer(ref data); } return ObjectMarshal.DangerousGetObjectDataByteOffset(obj, ref data); } /// /// Gets a reference from data describing a portable pinnable reference. This can either be an /// interior pointer into some object data (described with a valid reference /// and a byte offset into its data), or a raw pointer (described with a /// reference to an , and a byte offset representing the value of the raw pointer). /// /// The type of reference to retrieve. /// The input hosting the target field. /// The input byte offset for the reference to retrieve. /// A reference matching the given parameters. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe ref T GetObjectDataAtOffsetOrPointerReference(object? obj, IntPtr offset) { if (obj is null) { return ref Unsafe.AsRef((void*)offset); } return ref ObjectMarshal.DangerousGetObjectDataReferenceAt(obj, offset); } /// /// Checks whether or not a given type is a reference type or contains references. /// /// The type to check. /// Whether or not respects the constraint. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsReferenceOrContainsReferences() { return TypeInfo.IsReferenceOrContainsReferences; } /// /// Implements the logic for . /// /// The current type to check. /// Whether or not is a reference type or contains references. [Pure] private static bool IsReferenceOrContainsReferences(Type type) { // Common case, for primitive types if (type.IsPrimitive) { return false; } // Explicitly check for pointer types first if (type.IsPointer) { return false; } // Check for value types (this has to be after checking for pointers) if (!type.IsValueType) { return true; } // Check if the type is Nullable if (Nullable.GetUnderlyingType(type) is Type nullableType) { type = nullableType; } if (type.IsEnum) { return false; } // Complex struct, recursively inspect all fields foreach (FieldInfo field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { if (IsReferenceOrContainsReferences(field.FieldType)) { return true; } } return false; } #endif /// /// A private generic class to preload type info for arbitrary runtime types. /// /// The type to load info for. private static class TypeInfo { /// /// The byte offset to the first element in a SZ array. /// public static readonly IntPtr ArrayDataByteOffset = MeasureArrayDataByteOffset(); /// /// The byte offset to the first element in a 2D array. /// public static readonly IntPtr Array2DDataByteOffset = MeasureArray2DDataByteOffset(); /// /// The byte offset to the first element in a 3D array. /// public static readonly IntPtr Array3DDataByteOffset = MeasureArray3DDataByteOffset(); #if !NETSTANDARD2_1_OR_GREATER /// /// Indicates whether does not respect the constraint. /// public static readonly bool IsReferenceOrContainsReferences = IsReferenceOrContainsReferences(typeof(T)); #endif /// /// Computes the value for . /// /// The value of for the current runtime. [Pure] private static IntPtr MeasureArrayDataByteOffset() { T[]? array = new T[1]; return ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array[0]); } /// /// Computes the value for . /// /// The value of for the current runtime. [Pure] private static IntPtr MeasureArray2DDataByteOffset() { T[,]? array = new T[1, 1]; return ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array[0, 0]); } /// /// Computes the value for . /// /// The value of for the current runtime. [Pure] private static IntPtr MeasureArray3DDataByteOffset() { T[,,]? array = new T[1, 1, 1]; return ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array[0, 0, 0]); } } }