Skip to content

Commit

Permalink
Convert some more array FCalls to managed (#102739)
Browse files Browse the repository at this point in the history
* Convert InternalSetValue to managed

* Implement InitializeArray in managed

* Implement GetSpanDataFrom in managed

* Remove FCall for GetCorElementTypeOfElementType

* Move PrimitiveWiden to InvokeUtils

* Apply suggestions from code review

Co-authored-by: Aaron Robinson <[email protected]>
Co-authored-by: Jan Kotas <[email protected]>
  • Loading branch information
3 people authored Jun 27, 2024
1 parent f920dac commit 2d2d59c
Show file tree
Hide file tree
Showing 21 changed files with 497 additions and 432 deletions.
250 changes: 102 additions & 148 deletions src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs

Large diffs are not rendered by default.

9 changes: 3 additions & 6 deletions src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,19 @@ public abstract partial class Enum
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Enum_GetValuesAndNames")]
private static partial void GetEnumValuesAndNames(QCallTypeHandle enumType, ObjectHandleOnStack values, ObjectHandleOnStack names, Interop.BOOL getNames);

[MethodImpl(MethodImplOptions.InternalCall)]
private static extern unsafe CorElementType InternalGetCorElementType(MethodTable* pMT);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe CorElementType InternalGetCorElementType(RuntimeType rt)
{
Debug.Assert(rt.IsActualEnum);
CorElementType elementType = InternalGetCorElementType((MethodTable*)rt.GetUnderlyingNativeHandle());
CorElementType elementType = rt.GetNativeTypeHandle().AsMethodTable()->GetPrimitiveCorElementType();
GC.KeepAlive(rt);
return elementType;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe CorElementType InternalGetCorElementType()
{
CorElementType elementType = InternalGetCorElementType(RuntimeHelpers.GetMethodTable(this));
CorElementType elementType = RuntimeHelpers.GetMethodTable(this)->GetPrimitiveCorElementType();
GC.KeepAlive(this);
return elementType;
}
Expand Down Expand Up @@ -71,7 +68,7 @@ internal static unsafe RuntimeType InternalGetUnderlyingType(RuntimeType enumTyp
// Sanity check the last element in the table
Debug.Assert(s_underlyingTypes[(int)CorElementType.ELEMENT_TYPE_U] == typeof(nuint));

RuntimeType? underlyingType = s_underlyingTypes[(int)InternalGetCorElementType((MethodTable*)enumType.GetUnderlyingNativeHandle())];
RuntimeType? underlyingType = s_underlyingTypes[(int)enumType.GetNativeTypeHandle().AsMethodTable()->GetPrimitiveCorElementType()];
GC.KeepAlive(enumType);

Debug.Assert(underlyingType != null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ internal static unsafe class CastHelpers
// Unlike the IsInstanceOfInterface and IsInstanceOfClass functions,
// this test must deal with all kinds of type tests
[DebuggerHidden]
private static object? IsInstanceOfAny(void* toTypeHnd, object? obj)
internal static object? IsInstanceOfAny(void* toTypeHnd, object? obj)
{
if (obj != null)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,126 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers.Binary;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Threading;

namespace System.Runtime.CompilerServices
{
public static partial class RuntimeHelpers
{
[Intrinsic]
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void InitializeArray(Array array, RuntimeFieldHandle fldHandle);
public static unsafe void InitializeArray(Array array, RuntimeFieldHandle fldHandle)
{
if (array is null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);

[MethodImpl(MethodImplOptions.InternalCall)]
private static extern unsafe void* GetSpanDataFrom(
if (fldHandle.IsNullHandle())
throw new ArgumentException(SR.Argument_InvalidHandle);

IRuntimeFieldInfo fldInfo = fldHandle.GetRuntimeFieldInfo();

if (!RuntimeFieldHandle.GetRVAFieldInfo(fldInfo.Value, out void* address, out uint size))
throw new ArgumentException(SR.Argument_BadFieldForInitializeArray);

// Note that we do not check that the field is actually in the PE file that is initializing
// the array. Basically, the data being published can be accessed by anyone with the proper
// permissions (C# marks these as assembly visibility, and thus are protected from outside
// snooping)

MethodTable* pMT = GetMethodTable(array);
TypeHandle elementTH = pMT->GetArrayElementTypeHandle();

if (elementTH.IsTypeDesc || !elementTH.AsMethodTable()->IsPrimitive) // Enum is included
throw new ArgumentException(SR.Argument_BadArrayForInitializeArray);

nuint totalSize = pMT->ComponentSize * array.NativeLength;

// make certain you don't go off the end of the rva static
if (totalSize > size)
throw new ArgumentException(SR.Argument_BadFieldForInitializeArray);

ref byte src = ref *(byte*)address; // Ref is extending the lifetime of the static field.
GC.KeepAlive(fldInfo);

ref byte dst = ref MemoryMarshal.GetArrayDataReference(array);

Debug.Assert(!elementTH.AsMethodTable()->ContainsGCPointers);

if (BitConverter.IsLittleEndian)
{
SpanHelpers.Memmove(ref dst, ref src, totalSize);
}
else
{
switch (pMT->ComponentSize)
{
case sizeof(byte):
SpanHelpers.Memmove(ref dst, ref src, totalSize);
break;
case sizeof(ushort):
BinaryPrimitives.ReverseEndianness(
new ReadOnlySpan<ushort>(ref Unsafe.As<byte, ushort>(ref src), array.Length),
new Span<ushort>(ref Unsafe.As<byte, ushort>(ref dst), array.Length));
break;
case sizeof(uint):
BinaryPrimitives.ReverseEndianness(
new ReadOnlySpan<uint>(ref Unsafe.As<byte, uint>(ref src), array.Length),
new Span<uint>(ref Unsafe.As<byte, uint>(ref dst), array.Length));
break;
case sizeof(ulong):
BinaryPrimitives.ReverseEndianness(
new ReadOnlySpan<ulong>(ref Unsafe.As<byte, ulong>(ref src), array.Length),
new Span<ulong>(ref Unsafe.As<byte, ulong>(ref dst), array.Length));
break;
default:
Debug.Fail("Incorrect primitive type size!");
break;
}
}
}

private static unsafe ref byte GetSpanDataFrom(
RuntimeFieldHandle fldHandle,
RuntimeTypeHandle targetTypeHandle,
out int count);
out int count)
{
if (fldHandle.IsNullHandle())
throw new ArgumentException(SR.Argument_InvalidHandle);

IRuntimeFieldInfo fldInfo = fldHandle.GetRuntimeFieldInfo();

if (!RuntimeFieldHandle.GetRVAFieldInfo(fldInfo.Value, out void* data, out uint totalSize))
throw new ArgumentException(SR.Argument_BadFieldForInitializeArray);

TypeHandle th = targetTypeHandle.GetNativeTypeHandle();
Debug.Assert(!th.IsTypeDesc); // TypeDesc can't be used as generic parameter
MethodTable* targetMT = th.AsMethodTable();

if (!targetMT->IsPrimitive) // Enum is included
throw new ArgumentException(SR.Argument_BadArrayForInitializeArray);

uint targetTypeSize = targetMT->GetNumInstanceFieldBytes();
Debug.Assert(uint.IsPow2(targetTypeSize));

if (((nuint)data & (targetTypeSize - 1)) != 0)
throw new ArgumentException(SR.Argument_BadFieldForInitializeArray);

if (!BitConverter.IsLittleEndian)
{
throw new PlatformNotSupportedException();
}

count = (int)(totalSize / targetTypeSize);
ref byte dataRef = ref *(byte*)data; // Ref is extending the lifetime of the static field.
GC.KeepAlive(fldInfo);

return ref dataRef;
}

// GetObjectValue is intended to allow value classes to be manipulated as 'Object'
// but have aliasing behavior of a value class. The intent is that you would use
Expand Down Expand Up @@ -655,6 +753,8 @@ public int MultiDimensionalArrayRank
// Warning! UNLIKE the similarly named Reflection api, this method also returns "true" for Enums.
public bool IsPrimitive => (Flags & enum_flag_Category_Mask) is enum_flag_Category_PrimitiveValueType or enum_flag_Category_TruePrimitive;

public bool IsTruePrimitive => (Flags & enum_flag_Category_Mask) is enum_flag_Category_TruePrimitive;

public bool HasInstantiation => (Flags & enum_flag_HasComponentSize) == 0 && (Flags & enum_flag_GenericsMask) != enum_flag_GenericsMask_NonGeneric;

public bool IsGenericTypeDefinition => (Flags & (enum_flag_HasComponentSize | enum_flag_GenericsMask)) == enum_flag_GenericsMask_TypicalInst;
Expand Down Expand Up @@ -684,6 +784,13 @@ public TypeHandle GetArrayElementTypeHandle()

[MethodImpl(MethodImplOptions.InternalCall)]
public extern uint GetNumInstanceFieldBytes();

/// <summary>
/// Get the <see cref="CorElementType"/> representing primitive-like type. Enums are represented by underlying type.
/// </summary>
/// <remarks>This method should only be called when <see cref="IsPrimitive"/> returns <see langword="true"/>.</remarks>
[MethodImpl(MethodImplOptions.InternalCall)]
public extern CorElementType GetPrimitiveCorElementType();
}

// Subset of src\vm\methodtable.h
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1089,7 +1089,7 @@ public RuntimeFieldInfoStub(RuntimeFieldHandleInternal fieldHandle, object keepa
}

[NonVersionable]
public unsafe struct RuntimeFieldHandle : IEquatable<RuntimeFieldHandle>, ISerializable
public unsafe partial struct RuntimeFieldHandle : IEquatable<RuntimeFieldHandle>, ISerializable
{
// Returns handle for interop with EE. The handle is guaranteed to be non-null.
internal RuntimeFieldHandle GetNativeHandle()
Expand Down Expand Up @@ -1193,6 +1193,10 @@ internal static RuntimeType GetApproxDeclaringType(IRuntimeFieldInfo field)
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern IntPtr GetStaticFieldAddress(RtFieldInfo field);

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeFieldHandle_GetRVAFieldInfo")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool GetRVAFieldInfo(RuntimeFieldHandleInternal field, out void* address, out uint size);

[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern int GetToken(RtFieldInfo field);

Expand Down
Loading

0 comments on commit 2d2d59c

Please sign in to comment.