.NET-Community-Toolkit/CommunityToolkit.HighPerfor.../Helpers/Internals/RuntimeHelpers.cs

281 lines
11 KiB
C#

// 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;
#if !NETSTANDARD2_1_OR_GREATER
using System.Reflection;
#endif
using System.Runtime.CompilerServices;
namespace CommunityToolkit.HighPerformance.Helpers.Internals;
/// <summary>
/// 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.
/// </summary>
internal static class RuntimeHelpers
{
/// <summary>
/// Converts a length of items from one size to another (rounding towards zero).
/// </summary>
/// <typeparam name="TFrom">The source type of items.</typeparam>
/// <typeparam name="TTo">The target type of items.</typeparam>
/// <param name="length">The input length to convert.</param>
/// <returns>The converted length for the specified argument and types.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int ConvertLength<TFrom, TTo>(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);
}
}
/// <summary>
/// Gets the length of a given array as a native integer.
/// </summary>
/// <typeparam name="T">The type of values in the array.</typeparam>
/// <param name="array">The input <see cref="Array"/> instance.</param>
/// <returns>The total length of <paramref name="array"/> as a native integer.</returns>
/// <remarks>
/// This method is needed because this expression is not inlined correctly if the target array
/// is only visible as a non-generic <see cref="Array"/> instance, because the C# compiler will
/// not be able to emit the <see langword="ldlen"/> opcode instead of calling the right method.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static nint GetArrayNativeLength<T>(T[] array)
{
return (nint)array.LongLength;
}
/// <summary>
/// Gets the length of a given array as a native integer.
/// </summary>
/// <param name="array">The input <see cref="Array"/> instance.</param>
/// <returns>The total length of <paramref name="array"/> as a native integer.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static nint GetArrayNativeLength(Array array)
{
return (nint)array.LongLength;
}
#if !NETCOREAPP3_1_OR_GREATER
/// <summary>
/// Gets the byte offset to the first <typeparamref name="T"/> element in a SZ array.
/// </summary>
/// <typeparam name="T">The type of values in the array.</typeparam>
/// <returns>The byte offset to the first <typeparamref name="T"/> element in a SZ array.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IntPtr GetArrayDataByteOffset<T>()
{
return TypeInfo<T>.ArrayDataByteOffset;
}
#endif
/// <summary>
/// Gets the byte offset to the first <typeparamref name="T"/> element in a 2D array.
/// </summary>
/// <typeparam name="T">The type of values in the array.</typeparam>
/// <returns>The byte offset to the first <typeparamref name="T"/> element in a 2D array.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IntPtr GetArray2DDataByteOffset<T>()
{
return TypeInfo<T>.Array2DDataByteOffset;
}
/// <summary>
/// Gets the byte offset to the first <typeparamref name="T"/> element in a 3D array.
/// </summary>
/// <typeparam name="T">The type of values in the array.</typeparam>
/// <returns>The byte offset to the first <typeparamref name="T"/> element in a 3D array.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IntPtr GetArray3DDataByteOffset<T>()
{
return TypeInfo<T>.Array3DDataByteOffset;
}
#if !NETSTANDARD2_1_OR_GREATER
/// <summary>
/// Gets a byte offset describing a portable pinnable reference. This can either be an
/// interior pointer into some object data (described with a valid <see cref="object"/> reference
/// and a reference to some of its data), or a raw pointer (described with a <see langword="null"/>
/// reference to an <see cref="object"/>, and a reference that is assumed to refer to pinned data).
/// </summary>
/// <typeparam name="T">The type of field being referenced.</typeparam>
/// <param name="obj">The input <see cref="object"/> hosting the target field.</param>
/// <param name="data">A reference to a target field of type <typeparamref name="T"/> within <paramref name="obj"/>.</param>
/// <returns>
/// The <see cref="IntPtr"/> value representing the offset to the target field from the start of the object data
/// for the parameter <paramref name="obj"/>, or the value of the raw pointer passed as a tracked reference.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe IntPtr GetObjectDataOrReferenceByteOffset<T>(object? obj, ref T data)
{
if (obj is null)
{
return (IntPtr)Unsafe.AsPointer(ref data);
}
return ObjectMarshal.DangerousGetObjectDataByteOffset(obj, ref data);
}
/// <summary>
/// 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 <see cref="object"/> reference
/// and a byte offset into its data), or a raw pointer (described with a <see langword="null"/>
/// reference to an <see cref="object"/>, and a byte offset representing the value of the raw pointer).
/// </summary>
/// <typeparam name="T">The type of reference to retrieve.</typeparam>
/// <param name="obj">The input <see cref="object"/> hosting the target field.</param>
/// <param name="offset">The input byte offset for the <typeparamref name="T"/> reference to retrieve.</param>
/// <returns>A <typeparamref name="T"/> reference matching the given parameters.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref T GetObjectDataAtOffsetOrPointerReference<T>(object? obj, IntPtr offset)
{
if (obj is null)
{
return ref Unsafe.AsRef<T>((void*)offset);
}
return ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(obj, offset);
}
/// <summary>
/// Checks whether or not a given type is a reference type or contains references.
/// </summary>
/// <typeparam name="T">The type to check.</typeparam>
/// <returns>Whether or not <typeparamref name="T"/> respects the <see langword="unmanaged"/> constraint.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsReferenceOrContainsReferences<T>()
{
return TypeInfo<T>.IsReferenceOrContainsReferences;
}
/// <summary>
/// Implements the logic for <see cref="IsReferenceOrContainsReferences{T}"/>.
/// </summary>
/// <param name="type">The current type to check.</param>
/// <returns>Whether or not <paramref name="type"/> is a reference type or contains references.</returns>
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<T>
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
/// <summary>
/// A private generic class to preload type info for arbitrary runtime types.
/// </summary>
/// <typeparam name="T">The type to load info for.</typeparam>
private static class TypeInfo<T>
{
/// <summary>
/// The byte offset to the first <typeparamref name="T"/> element in a SZ array.
/// </summary>
public static readonly IntPtr ArrayDataByteOffset = MeasureArrayDataByteOffset();
/// <summary>
/// The byte offset to the first <typeparamref name="T"/> element in a 2D array.
/// </summary>
public static readonly IntPtr Array2DDataByteOffset = MeasureArray2DDataByteOffset();
/// <summary>
/// The byte offset to the first <typeparamref name="T"/> element in a 3D array.
/// </summary>
public static readonly IntPtr Array3DDataByteOffset = MeasureArray3DDataByteOffset();
#if !NETSTANDARD2_1_OR_GREATER
/// <summary>
/// Indicates whether <typeparamref name="T"/> does not respect the <see langword="unmanaged"/> constraint.
/// </summary>
public static readonly bool IsReferenceOrContainsReferences = IsReferenceOrContainsReferences(typeof(T));
#endif
/// <summary>
/// Computes the value for <see cref="ArrayDataByteOffset"/>.
/// </summary>
/// <returns>The value of <see cref="ArrayDataByteOffset"/> for the current runtime.</returns>
private static IntPtr MeasureArrayDataByteOffset()
{
T[]? array = new T[1];
return ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array[0]);
}
/// <summary>
/// Computes the value for <see cref="Array2DDataByteOffset"/>.
/// </summary>
/// <returns>The value of <see cref="Array2DDataByteOffset"/> for the current runtime.</returns>
private static IntPtr MeasureArray2DDataByteOffset()
{
T[,]? array = new T[1, 1];
return ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array[0, 0]);
}
/// <summary>
/// Computes the value for <see cref="Array3DDataByteOffset"/>.
/// </summary>
/// <returns>The value of <see cref="Array3DDataByteOffset"/> for the current runtime.</returns>
private static IntPtr MeasureArray3DDataByteOffset()
{
T[,,]? array = new T[1, 1, 1];
return ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array[0, 0, 0]);
}
}
}