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