From 2d2d59c16193fd1422572455b44fc5669f139ffe Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 27 Jun 2024 14:42:30 +0800 Subject: [PATCH] Convert some more array FCalls to managed (#102739) * 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 Co-authored-by: Jan Kotas --- .../src/System/Array.CoreCLR.cs | 250 +++++++----------- .../src/System/Enum.CoreCLR.cs | 9 +- .../Runtime/CompilerServices/CastHelpers.cs | 2 +- .../RuntimeHelpers.CoreCLR.cs | 121 ++++++++- .../src/System/RuntimeHandles.cs | 6 +- .../classlibnative/bcltype/arraynative.cpp | 200 -------------- .../classlibnative/bcltype/arraynative.h | 19 +- .../RuntimeHelpers.NativeAot.cs | 2 +- src/coreclr/vm/comutilnative.cpp | 12 + src/coreclr/vm/comutilnative.h | 1 + src/coreclr/vm/ecalllist.h | 9 +- src/coreclr/vm/qcallentrypoints.cpp | 1 + src/coreclr/vm/reflectioninvocation.cpp | 43 +-- src/coreclr/vm/reflectioninvocation.h | 5 - src/coreclr/vm/runtimehandles.h | 1 + .../src/Resources/Strings.resx | 8 +- .../src/System/Reflection/InvokeUtils.cs | 180 +++++++++++++ .../CompilerServices/RuntimeHelpers.cs | 33 ++- .../src/System/ThrowHelper.cs | 12 - .../System.Runtime.Tests/System/ArrayTests.cs | 9 + .../CompilerServices/RuntimeHelpers.Mono.cs | 6 +- 21 files changed, 497 insertions(+), 432 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs index 075d826e1f73f..2efcb649844af 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs @@ -105,7 +105,7 @@ private static unsafe void CopySlow(Array sourceArray, int sourceIndex, Array de AssignArrayEnum r = CanAssignArrayType(srcTH, destTH); if (r == AssignArrayEnum.AssignWrongType) - ThrowHelper.ThrowArrayTypeMismatchException_CantAssignType(); + throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_CantAssignType); if (length > 0) { @@ -176,7 +176,7 @@ private static unsafe void CopyImplUnBoxEachElement(Array sourceArray, int sourc } else if (obj is null || RuntimeHelpers.GetMethodTable(obj) != pDestMT) { - ThrowHelper.ThrowInvalidCastException_DownCastArrayElement(); + throw new InvalidCastException(SR.InvalidCast_DownCastArrayElement); } else if (pDestMT->ContainsGCPointers) { @@ -246,150 +246,9 @@ private static unsafe void CopyImplPrimitiveWiden(Array sourceArray, int sourceI for (int i = 0; i < length; i++) { - ref byte srcElement = ref Unsafe.Add(ref srcData, (nuint)i * srcElSize); - ref byte destElement = ref Unsafe.Add(ref data, (nuint)i * destElSize); - - switch (srcElType) - { - case CorElementType.ELEMENT_TYPE_U1: - switch (destElType) - { - case CorElementType.ELEMENT_TYPE_CHAR: - case CorElementType.ELEMENT_TYPE_I2: - case CorElementType.ELEMENT_TYPE_U2: - Unsafe.As(ref destElement) = srcElement; break; - case CorElementType.ELEMENT_TYPE_I4: - case CorElementType.ELEMENT_TYPE_U4: - Unsafe.As(ref destElement) = srcElement; break; - case CorElementType.ELEMENT_TYPE_I8: - case CorElementType.ELEMENT_TYPE_U8: - Unsafe.As(ref destElement) = srcElement; break; - case CorElementType.ELEMENT_TYPE_R4: - Unsafe.As(ref destElement) = srcElement; break; - case CorElementType.ELEMENT_TYPE_R8: - Unsafe.As(ref destElement) = srcElement; break; - default: - Debug.Fail("Array.Copy from U1 to another type hit unsupported widening conversion"); break; - } - break; - - case CorElementType.ELEMENT_TYPE_I1: - switch (destElType) - { - case CorElementType.ELEMENT_TYPE_I2: - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - case CorElementType.ELEMENT_TYPE_I4: - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - case CorElementType.ELEMENT_TYPE_I8: - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - case CorElementType.ELEMENT_TYPE_R4: - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - case CorElementType.ELEMENT_TYPE_R8: - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - default: - Debug.Fail("Array.Copy from I1 to another type hit unsupported widening conversion"); break; - } - break; - - case CorElementType.ELEMENT_TYPE_U2: - case CorElementType.ELEMENT_TYPE_CHAR: - switch (destElType) - { - case CorElementType.ELEMENT_TYPE_U2: - case CorElementType.ELEMENT_TYPE_CHAR: - // U2 and CHAR are identical in conversion - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - case CorElementType.ELEMENT_TYPE_I4: - case CorElementType.ELEMENT_TYPE_U4: - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - case CorElementType.ELEMENT_TYPE_I8: - case CorElementType.ELEMENT_TYPE_U8: - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - case CorElementType.ELEMENT_TYPE_R4: - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - case CorElementType.ELEMENT_TYPE_R8: - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - default: - Debug.Fail("Array.Copy from U2 to another type hit unsupported widening conversion"); break; - } - break; - - case CorElementType.ELEMENT_TYPE_I2: - switch (destElType) - { - case CorElementType.ELEMENT_TYPE_I4: - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - case CorElementType.ELEMENT_TYPE_I8: - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - case CorElementType.ELEMENT_TYPE_R4: - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - case CorElementType.ELEMENT_TYPE_R8: - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - default: - Debug.Fail("Array.Copy from I2 to another type hit unsupported widening conversion"); break; - } - break; - - case CorElementType.ELEMENT_TYPE_U4: - switch (destElType) - { - case CorElementType.ELEMENT_TYPE_I8: - case CorElementType.ELEMENT_TYPE_U8: - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - case CorElementType.ELEMENT_TYPE_R4: - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - case CorElementType.ELEMENT_TYPE_R8: - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - default: - Debug.Fail("Array.Copy from U4 to another type hit unsupported widening conversion"); break; - } - break; - - case CorElementType.ELEMENT_TYPE_I4: - switch (destElType) - { - case CorElementType.ELEMENT_TYPE_I8: - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - case CorElementType.ELEMENT_TYPE_R4: - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - case CorElementType.ELEMENT_TYPE_R8: - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - default: - Debug.Fail("Array.Copy from I4 to another type hit unsupported widening conversion"); break; - } - break; - - case CorElementType.ELEMENT_TYPE_U8: - switch (destElType) - { - case CorElementType.ELEMENT_TYPE_R4: - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - case CorElementType.ELEMENT_TYPE_R8: - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - default: - Debug.Fail("Array.Copy from U8 to another type hit unsupported widening conversion"); break; - } - break; - - case CorElementType.ELEMENT_TYPE_I8: - switch (destElType) - { - case CorElementType.ELEMENT_TYPE_R4: - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - case CorElementType.ELEMENT_TYPE_R8: - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - default: - Debug.Fail("Array.Copy from I8 to another type hit unsupported widening conversion"); break; - } - break; - - case CorElementType.ELEMENT_TYPE_R4: - Debug.Assert(destElType == CorElementType.ELEMENT_TYPE_R8); - Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; - - default: - Debug.Fail("Fell through outer switch in PrimitiveWiden! Unknown primitive type for source array!"); break; - } + InvokeUtils.PrimitiveWiden(ref srcData, ref data, srcElType, destElType); + srcData = ref Unsafe.AddByteOffset(ref srcData, srcElSize); + data = ref Unsafe.AddByteOffset(ref data, destElSize); } } @@ -554,8 +413,103 @@ private unsafe nint GetFlattenedIndex(ReadOnlySpan indices) return result; } - [MethodImpl(MethodImplOptions.InternalCall)] - private extern void InternalSetValue(object? value, nint flattenedIndex); + private unsafe void InternalSetValue(object? value, nint flattenedIndex) + { + MethodTable* pMethodTable = RuntimeHelpers.GetMethodTable(this); + + TypeHandle arrayElementTypeHandle = pMethodTable->GetArrayElementTypeHandle(); + + // Legacy behavior (this handles pointers and function pointers) + if (arrayElementTypeHandle.IsTypeDesc) + { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.Arg_TypeNotSupported); + } + + Debug.Assert((nuint)flattenedIndex < NativeLength); + + ref byte arrayDataRef = ref MemoryMarshal.GetArrayDataReference(this); + + MethodTable* pElementMethodTable = arrayElementTypeHandle.AsMethodTable(); + + if (value == null) + { + // Null is the universal zero... + if (pElementMethodTable->IsValueType) + { + ref byte offsetDataRef = ref Unsafe.Add(ref arrayDataRef, flattenedIndex * pMethodTable->ComponentSize); + nuint elementSize = pElementMethodTable->GetNumInstanceFieldBytes(); + if (pElementMethodTable->ContainsGCPointers) + SpanHelpers.ClearWithReferences(ref Unsafe.As(ref offsetDataRef), elementSize / (nuint)sizeof(IntPtr)); + else + SpanHelpers.ClearWithoutReferences(ref offsetDataRef, elementSize); + } + else + { + Unsafe.Add(ref Unsafe.As(ref arrayDataRef), (nuint)flattenedIndex) = null; + } + } + else if (!pElementMethodTable->IsValueType) + { + if (pElementMethodTable != TypeHandle.TypeHandleOf().AsMethodTable() // Everything is compatible with Object + && CastHelpers.IsInstanceOfAny(pElementMethodTable, value) == null) + throw new InvalidCastException(SR.InvalidCast_StoreArrayElement); + + Unsafe.Add(ref Unsafe.As(ref arrayDataRef), (nuint)flattenedIndex) = value; + } + else + { + // value class or primitive type + + ref byte offsetDataRef = ref Unsafe.Add(ref arrayDataRef, flattenedIndex * pMethodTable->ComponentSize); + if (CastHelpers.IsInstanceOfAny(pElementMethodTable, value) != null) + { + if (pElementMethodTable->IsNullable) + { + RuntimeHelpers.Unbox_Nullable(ref offsetDataRef, pElementMethodTable, value); + } + else + { + nuint elementSize = pElementMethodTable->GetNumInstanceFieldBytes(); + if (pElementMethodTable->ContainsGCPointers) + { + Buffer.BulkMoveWithWriteBarrier(ref offsetDataRef, ref value.GetRawData(), elementSize); + } + else + { + SpanHelpers.Memmove(ref offsetDataRef, ref value.GetRawData(), elementSize); + } + } + } + else + { + // Allow enum -> primitive conversion, disallow primitive -> enum conversion + MethodTable* pValueMethodTable = RuntimeHelpers.GetMethodTable(value); + + // Array.SetValue() does *not* permit conversion from a primitive to an Enum. + if (!pValueMethodTable->IsPrimitive || !pElementMethodTable->IsTruePrimitive) + throw new InvalidCastException(SR.InvalidCast_StoreArrayElement); + + CorElementType srcType = pValueMethodTable->GetPrimitiveCorElementType(); + CorElementType targetType = pElementMethodTable->GetPrimitiveCorElementType(); + + // Get a properly widened type + if (!InvokeUtils.CanPrimitiveWiden(srcType, targetType)) + throw new ArgumentException(SR.Arg_PrimWiden); + + if (srcType == targetType) + { + // Primitive types are always tightly packed in array, using ComponentSize is sufficient. + SpanHelpers.Memmove(ref offsetDataRef, ref value.GetRawData(), pMethodTable->ComponentSize); + } + else + { + InvokeUtils.PrimitiveWiden(ref value.GetRawData(), ref offsetDataRef, srcType, targetType); + } + } + } + + GC.KeepAlive(this); // Keep the method table alive + } public int Length => checked((int)Unsafe.As(this).Length); diff --git a/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs index 9c92874f8ec27..066ca50e60497 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs @@ -14,14 +14,11 @@ 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; } @@ -29,7 +26,7 @@ private static unsafe CorElementType InternalGetCorElementType(RuntimeType rt) [MethodImpl(MethodImplOptions.AggressiveInlining)] private unsafe CorElementType InternalGetCorElementType() { - CorElementType elementType = InternalGetCorElementType(RuntimeHelpers.GetMethodTable(this)); + CorElementType elementType = RuntimeHelpers.GetMethodTable(this)->GetPrimitiveCorElementType(); GC.KeepAlive(this); return elementType; } @@ -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); diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/CastHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/CastHelpers.cs index c2333f3854eed..28bfcdf7de495 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/CastHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/CastHelpers.cs @@ -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) { diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs index 338d5c9e0357e..052f6970a298e 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs @@ -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(ref Unsafe.As(ref src), array.Length), + new Span(ref Unsafe.As(ref dst), array.Length)); + break; + case sizeof(uint): + BinaryPrimitives.ReverseEndianness( + new ReadOnlySpan(ref Unsafe.As(ref src), array.Length), + new Span(ref Unsafe.As(ref dst), array.Length)); + break; + case sizeof(ulong): + BinaryPrimitives.ReverseEndianness( + new ReadOnlySpan(ref Unsafe.As(ref src), array.Length), + new Span(ref Unsafe.As(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 @@ -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; @@ -684,6 +784,13 @@ public TypeHandle GetArrayElementTypeHandle() [MethodImpl(MethodImplOptions.InternalCall)] public extern uint GetNumInstanceFieldBytes(); + + /// + /// Get the representing primitive-like type. Enums are represented by underlying type. + /// + /// This method should only be called when returns . + [MethodImpl(MethodImplOptions.InternalCall)] + public extern CorElementType GetPrimitiveCorElementType(); } // Subset of src\vm\methodtable.h diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs index 74f6f5beeb3b6..a7fcf364f36d9 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs @@ -1089,7 +1089,7 @@ public RuntimeFieldInfoStub(RuntimeFieldHandleInternal fieldHandle, object keepa } [NonVersionable] - public unsafe struct RuntimeFieldHandle : IEquatable, ISerializable + public unsafe partial struct RuntimeFieldHandle : IEquatable, ISerializable { // Returns handle for interop with EE. The handle is guaranteed to be non-null. internal RuntimeFieldHandle GetNativeHandle() @@ -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); diff --git a/src/coreclr/classlibnative/bcltype/arraynative.cpp b/src/coreclr/classlibnative/bcltype/arraynative.cpp index 02ff0360ac4b7..7a42245f76888 100644 --- a/src/coreclr/classlibnative/bcltype/arraynative.cpp +++ b/src/coreclr/classlibnative/bcltype/arraynative.cpp @@ -365,203 +365,3 @@ void QCALLTYPE Array_CreateInstance(QCall::TypeHandle pTypeHnd, INT32 rank, INT3 Done: ; END_QCALL; } - -FCIMPL3(void, ArrayNative::SetValue, ArrayBase* refThisUNSAFE, Object* objUNSAFE, INT_PTR flattenedIndex) -{ - FCALL_CONTRACT; - - BASEARRAYREF refThis(refThisUNSAFE); - OBJECTREF obj(objUNSAFE); - - TypeHandle arrayElementType = refThis->GetArrayElementTypeHandle(); - - // Legacy behavior (this handles pointers and function pointers) - if (arrayElementType.IsTypeDesc()) - { - FCThrowResVoid(kNotSupportedException, W("NotSupported_Type")); - } - - _ASSERTE((SIZE_T)flattenedIndex < refThis->GetNumComponents()); - - MethodTable* pElementTypeMT = arrayElementType.GetMethodTable(); - PREFIX_ASSUME(NULL != pElementTypeMT); - - void* pData = refThis->GetDataPtr() + flattenedIndex * refThis->GetComponentSize(); - - if (obj == NULL) - { - // Null is the universal zero... - if (pElementTypeMT->IsValueType()) - InitValueClass(pData,pElementTypeMT); - else - ClearObjectReference((OBJECTREF*)pData); - } - else - if (arrayElementType == TypeHandle(g_pObjectClass)) - { - // Everything is compatible with Object - SetObjectReference((OBJECTREF*)pData,(OBJECTREF)obj); - } - else - if (!pElementTypeMT->IsValueType()) - { - if (ObjIsInstanceOfCached(OBJECTREFToObject(obj), arrayElementType) != TypeHandle::CanCast) - { - HELPER_METHOD_FRAME_BEGIN_2(refThis, obj); - - if (!ObjIsInstanceOf(OBJECTREFToObject(obj), arrayElementType)) - COMPlusThrow(kInvalidCastException,W("InvalidCast_StoreArrayElement")); - - HELPER_METHOD_FRAME_END(); - - // Refresh pData in case GC moved objects around - pData = refThis->GetDataPtr() + flattenedIndex * refThis->GetComponentSize(); - } - - SetObjectReference((OBJECTREF*)pData,obj); - } - else - { - // value class or primitive type - - if (!pElementTypeMT->UnBoxInto(pData, obj)) - { - HELPER_METHOD_FRAME_BEGIN_2(refThis, obj); - - ARG_SLOT value = 0; - - // Allow enum -> primitive conversion, disallow primitive -> enum conversion - TypeHandle thSrc = obj->GetTypeHandle(); - CorElementType srcType = thSrc.GetVerifierCorElementType(); - CorElementType targetType = arrayElementType.GetSignatureCorElementType(); - - if (!InvokeUtil::IsPrimitiveType(srcType) || !InvokeUtil::IsPrimitiveType(targetType)) - COMPlusThrow(kInvalidCastException, W("InvalidCast_StoreArrayElement")); - - // Get a properly widened type - InvokeUtil::CreatePrimitiveValue(targetType,srcType,obj,&value); - - // Refresh pData in case GC moved objects around - pData = refThis->GetDataPtr() + flattenedIndex * refThis->GetComponentSize(); - - UINT cbSize = CorTypeInfo::Size(targetType); - memcpyNoGCRefs(pData, ArgSlotEndiannessFixup(&value, cbSize), cbSize); - - HELPER_METHOD_FRAME_END(); - } - } -} -FCIMPLEND - -// This method will initialize an array from a TypeHandle to a field. - -FCIMPL2_IV(void, ArrayNative::InitializeArray, ArrayBase* pArrayRef, FCALLRuntimeFieldHandle structField) -{ - FCALL_CONTRACT; - - BASEARRAYREF arr = BASEARRAYREF(pArrayRef); - REFLECTFIELDREF refField = (REFLECTFIELDREF)ObjectToOBJECTREF(FCALL_RFH_TO_REFLECTFIELD(structField)); - HELPER_METHOD_FRAME_BEGIN_2(arr, refField); - - if ((arr == 0) || (refField == NULL)) - COMPlusThrow(kArgumentNullException); - - FieldDesc* pField = (FieldDesc*) refField->GetField(); - - if (!pField->IsRVA()) - COMPlusThrow(kArgumentException); - - // 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 is can be accessed by anyone with the proper - // permissions (C# marks these as assembly visibility, and thus are protected from outside - // snooping) - - if (!CorTypeInfo::IsPrimitiveType(arr->GetArrayElementType()) && !arr->GetArrayElementTypeHandle().IsEnum()) - COMPlusThrow(kArgumentException); - - SIZE_T dwCompSize = arr->GetComponentSize(); - SIZE_T dwElemCnt = arr->GetNumComponents(); - SIZE_T dwTotalSize = dwCompSize * dwElemCnt; - - DWORD size = pField->LoadSize(); - - // make certain you don't go off the end of the rva static - if (dwTotalSize > size) - COMPlusThrow(kArgumentException); - - void *src = pField->GetStaticAddressHandle(NULL); - void *dest = arr->GetDataPtr(); - -#if BIGENDIAN - DWORD i; - switch (dwCompSize) { - case 1: - memcpyNoGCRefs(dest, src, dwElemCnt); - break; - case 2: - for (i = 0; i < dwElemCnt; i++) - *((UINT16*)dest + i) = GET_UNALIGNED_VAL16((UINT16*)src + i); - break; - case 4: - for (i = 0; i < dwElemCnt; i++) - *((UINT32*)dest + i) = GET_UNALIGNED_VAL32((UINT32*)src + i); - break; - case 8: - for (i = 0; i < dwElemCnt; i++) - *((UINT64*)dest + i) = GET_UNALIGNED_VAL64((UINT64*)src + i); - break; - default: - // should not reach here. - UNREACHABLE_MSG("Incorrect primitive type size!"); - break; - } -#else - memcpyNoGCRefs(dest, src, dwTotalSize); -#endif - - HELPER_METHOD_FRAME_END(); -} -FCIMPLEND - -FCIMPL3_VVI(void*, ArrayNative::GetSpanDataFrom, FCALLRuntimeFieldHandle structField, FCALLRuntimeTypeHandle targetTypeUnsafe, INT32* count) -{ - FCALL_CONTRACT; - struct - { - REFLECTFIELDREF refField; - REFLECTCLASSBASEREF refClass; - } gc; - gc.refField = (REFLECTFIELDREF)ObjectToOBJECTREF(FCALL_RFH_TO_REFLECTFIELD(structField)); - gc.refClass = (REFLECTCLASSBASEREF)ObjectToOBJECTREF(FCALL_RTH_TO_REFLECTCLASS(targetTypeUnsafe)); - void* data = NULL; - HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); - - FieldDesc* pField = (FieldDesc*)gc.refField->GetField(); - - if (!pField->IsRVA()) - COMPlusThrow(kArgumentException); - - TypeHandle targetTypeHandle = gc.refClass->GetType(); - if (!CorTypeInfo::IsPrimitiveType(targetTypeHandle.GetSignatureCorElementType()) && !targetTypeHandle.IsEnum()) - COMPlusThrow(kArgumentException); - - DWORD totalSize = pField->LoadSize(); - DWORD targetTypeSize = targetTypeHandle.GetSize(); - - data = pField->GetStaticAddressHandle(NULL); - _ASSERTE(data != NULL); - _ASSERTE(count != NULL); - - if (AlignUp((UINT_PTR)data, targetTypeSize) != (UINT_PTR)data) - COMPlusThrow(kArgumentException); - - *count = (INT32)totalSize / targetTypeSize; - -#if BIGENDIAN - COMPlusThrow(kPlatformNotSupportedException); -#endif - - HELPER_METHOD_FRAME_END(); - return data; -} -FCIMPLEND diff --git a/src/coreclr/classlibnative/bcltype/arraynative.h b/src/coreclr/classlibnative/bcltype/arraynative.h index aeb264a9b28ce..86b125c416bf0 100644 --- a/src/coreclr/classlibnative/bcltype/arraynative.h +++ b/src/coreclr/classlibnative/bcltype/arraynative.h @@ -14,13 +14,7 @@ #define _ARRAYNATIVE_H_ #include "fcall.h" -#include "runtimehandles.h" - -struct FCALLRuntimeFieldHandle -{ - ReflectFieldObject *pFieldDONOTUSEDIRECTLY; -}; -#define FCALL_RFH_TO_REFLECTFIELD(x) (x).pFieldDONOTUSEDIRECTLY +#include "qcall.h" class ArrayNative { @@ -28,17 +22,6 @@ class ArrayNative static FCDECL1(INT32, GetCorElementTypeOfElementType, ArrayBase* arrayUNSAFE); static FCDECL2(FC_BOOL_RET, IsSimpleCopy, ArrayBase* pSrc, ArrayBase* pDst); - - // This set of methods will set a value in an array - static FCDECL3(void, SetValue, ArrayBase* refThisUNSAFE, Object* objUNSAFE, INT_PTR flattenedIndex); - - // This method will initialize an array from a TypeHandle - // to a field. - static FCDECL2_IV(void, InitializeArray, ArrayBase* vArrayRef, FCALLRuntimeFieldHandle structField); - - // This method will acquire data to create a span from a TypeHandle - // to a field. - static FCDECL3_VVI(void*, GetSpanDataFrom, FCALLRuntimeFieldHandle structField, FCALLRuntimeTypeHandle targetTypeUnsafe, INT32* count); }; extern "C" void QCALLTYPE Array_CreateInstance(QCall::TypeHandle pTypeHnd, INT32 rank, INT32* pLengths, INT32* pBounds, BOOL createFromArrayType, QCall::ObjectHandleOnStack retArray); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs index 9b5f23876cfc0..532da20332ab3 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs @@ -28,7 +28,7 @@ public static void InitializeArray(Array array, RuntimeFieldHandle fldHandle) } #pragma warning disable IDE0060 - private static unsafe void* GetSpanDataFrom( + private static unsafe ref byte GetSpanDataFrom( RuntimeFieldHandle fldHandle, RuntimeTypeHandle targetTypeHandle, out int count) diff --git a/src/coreclr/vm/comutilnative.cpp b/src/coreclr/vm/comutilnative.cpp index 58c12cd319bf2..73547a59e4df3 100644 --- a/src/coreclr/vm/comutilnative.cpp +++ b/src/coreclr/vm/comutilnative.cpp @@ -1819,6 +1819,18 @@ FCIMPL1(UINT32, MethodTableNative::GetNumInstanceFieldBytes, MethodTable* mt) } FCIMPLEND +FCIMPL1(CorElementType, MethodTableNative::GetPrimitiveCorElementType, MethodTable* mt) +{ + FCALL_CONTRACT; + + _ASSERTE(mt->IsTruePrimitive() || mt->IsEnum()); + + // MethodTable::GetInternalCorElementType has unnecessary overhead for primitives and enums + // Call EEClass::GetInternalCorElementType directly to avoid it + return mt->GetClass()->GetInternalCorElementType(); +} +FCIMPLEND + extern "C" BOOL QCALLTYPE MethodTable_AreTypesEquivalent(MethodTable* mta, MethodTable* mtb) { QCALL_CONTRACT; diff --git a/src/coreclr/vm/comutilnative.h b/src/coreclr/vm/comutilnative.h index 4a559fe68aa3d..0f00aca48466a 100644 --- a/src/coreclr/vm/comutilnative.h +++ b/src/coreclr/vm/comutilnative.h @@ -247,6 +247,7 @@ extern "C" void QCALLTYPE Interlocked_MemoryBarrierProcessWide(); class MethodTableNative { public: static FCDECL1(UINT32, GetNumInstanceFieldBytes, MethodTable* mt); + static FCDECL1(CorElementType, GetPrimitiveCorElementType, MethodTable* mt); }; extern "C" BOOL QCALLTYPE MethodTable_AreTypesEquivalent(MethodTable* mta, MethodTable* mtb); diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 4a9f2355f395c..d820c4d8e33b2 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -54,10 +54,6 @@ FCFuncStart(gDependentHandleFuncs) FCFuncElement("InternalFree", DependentHandle::InternalFree) FCFuncEnd() -FCFuncStart(gEnumFuncs) - FCFuncElement("InternalGetCorElementType", ReflectionEnum::InternalGetCorElementType) -FCFuncEnd() - FCFuncStart(gObjectFuncs) FCFuncElement("GetType", ObjectNative::GetClass) FCFuncEnd() @@ -362,7 +358,6 @@ FCFuncEnd() FCFuncStart(gArrayFuncs) FCFuncElement("GetCorElementTypeOfElementType", ArrayNative::GetCorElementTypeOfElementType) FCFuncElement("IsSimpleCopy", ArrayNative::IsSimpleCopy) - FCFuncElement("InternalSetValue", ArrayNative::SetValue) FCFuncEnd() FCFuncStart(gBufferFuncs) @@ -439,8 +434,6 @@ FCFuncStart(gMonitorFuncs) FCFuncEnd() FCFuncStart(gRuntimeHelpers) - FCFuncElement("InitializeArray", ArrayNative::InitializeArray) - FCFuncElement("GetSpanDataFrom", ArrayNative::GetSpanDataFrom) FCFuncElement("PrepareDelegate", ReflectionInvocation::PrepareDelegate) FCFuncElement("GetHashCode", ObjectNative::GetHashCode) FCFuncElement("TryGetHashCode", ObjectNative::TryGetHashCode) @@ -455,6 +448,7 @@ FCFuncEnd() FCFuncStart(gMethodTableFuncs) FCFuncElement("GetNumInstanceFieldBytes", MethodTableNative::GetNumInstanceFieldBytes) + FCFuncElement("GetPrimitiveCorElementType", MethodTableNative::GetPrimitiveCorElementType) FCFuncEnd() FCFuncStart(gStubHelperFuncs) @@ -542,7 +536,6 @@ FCClassElement("ComAwareWeakReference", "System", gComAwareWeakReferenceFuncs) FCClassElement("Debugger", "System.Diagnostics", gDiagnosticsDebugger) FCClassElement("Delegate", "System", gDelegateFuncs) FCClassElement("DependentHandle", "System.Runtime", gDependentHandleFuncs) -FCClassElement("Enum", "System", gEnumFuncs) FCClassElement("Environment", "System", gEnvironmentFuncs) FCClassElement("Exception", "System", gExceptionFuncs) FCClassElement("GC", "System", gGCInterfaceFuncs) diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index e76ecb82fdbd3..329989cde6db4 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -132,6 +132,7 @@ static const Entry s_QCall[] = DllImportEntry(RuntimeModule_GetScopeName) DllImportEntry(RuntimeModule_GetFullyQualifiedName) DllImportEntry(RuntimeModule_GetTypes) + DllImportEntry(RuntimeFieldHandle_GetRVAFieldInfo) DllImportEntry(StackFrame_GetMethodDescFromNativeIP) DllImportEntry(ModuleBuilder_GetStringConstant) DllImportEntry(ModuleBuilder_GetTypeRef) diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index c89407275f81e..115e0f85ac96c 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -1259,6 +1259,28 @@ FCIMPL1(void*, RuntimeFieldHandle::GetStaticFieldAddress, ReflectFieldObject *pF } FCIMPLEND +extern "C" BOOL QCALLTYPE RuntimeFieldHandle_GetRVAFieldInfo(FieldDesc* pField, void** address, UINT* size) +{ + QCALL_CONTRACT; + + BOOL ret = FALSE; + + BEGIN_QCALL; + + if (pField != NULL && pField->IsRVA()) + { + Module* pModule = pField->GetModule(); + *address = pModule->GetRvaField(pField->GetOffset()); + *size = pField->LoadSize(); + + ret = TRUE; + } + + END_QCALL; + + return ret; +} + extern "C" void QCALLTYPE ReflectionInvocation_CompileMethod(MethodDesc * pMD) { QCALL_CONTRACT; @@ -1929,25 +1951,6 @@ extern "C" void QCALLTYPE ReflectionSerialization_GetCreateUninitializedObjectIn END_QCALL; } -//************************************************************************************************* -//************************************************************************************************* -//************************************************************************************************* -// ReflectionEnum -//************************************************************************************************* -//************************************************************************************************* -//************************************************************************************************* - -FCIMPL1(INT32, ReflectionEnum::InternalGetCorElementType, MethodTable* pMT) { - FCALL_CONTRACT; - - _ASSERTE(pMT->IsEnum()); - - // MethodTable::GetInternalCorElementType has unnecessary overhead for enums - // Call EEClass::GetInternalCorElementType directly to avoid it - return pMT->GetClass()->GetInternalCorElementType(); -} -FCIMPLEND - //******************************************************************************* struct TempEnumValue { @@ -2116,4 +2119,4 @@ extern "C" void QCALLTYPE ReflectionInvocation_GetBoxInfo( pMT->EnsureInstanceActive(); END_QCALL; -} \ No newline at end of file +} diff --git a/src/coreclr/vm/reflectioninvocation.h b/src/coreclr/vm/reflectioninvocation.h index c26f137cdd927..ff20d72d870f9 100644 --- a/src/coreclr/vm/reflectioninvocation.h +++ b/src/coreclr/vm/reflectioninvocation.h @@ -79,11 +79,6 @@ extern "C" void QCALLTYPE ReflectionInvocation_GetBoxInfo( int32_t* pValueOffset, uint32_t* pValueSize); -class ReflectionEnum { -public: - static FCDECL1(INT32, InternalGetCorElementType, MethodTable* pMT); -}; - extern "C" void QCALLTYPE Enum_GetValuesAndNames(QCall::TypeHandle pEnumType, QCall::ObjectHandleOnStack pReturnValues, QCall::ObjectHandleOnStack pReturnNames, BOOL fGetNames); extern "C" int32_t QCALLTYPE ReflectionInvocation_SizeOf(QCall::TypeHandle pType); diff --git a/src/coreclr/vm/runtimehandles.h b/src/coreclr/vm/runtimehandles.h index 1adb6b25ebc3f..0fd92c45bef33 100644 --- a/src/coreclr/vm/runtimehandles.h +++ b/src/coreclr/vm/runtimehandles.h @@ -309,6 +309,7 @@ class RuntimeFieldHandle { static FCDECL1(FC_BOOL_RET, AcquiresContextFromThis, FieldDesc *pField); static FCDECL1(Object*, GetLoaderAllocator, FieldDesc *pField); }; +extern "C" BOOL QCALLTYPE RuntimeFieldHandle_GetRVAFieldInfo(FieldDesc* pField, void** address, UINT* size); class ModuleHandle { diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index bb7058f936c7c..964449ddbd611 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -4322,4 +4322,10 @@ Emitting debug info is not supported for this member. - + + The field is invalid for initializing array or span. + + + Only array or span of primitive or enum types can be initialized from static data. + + \ No newline at end of file diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs index 5ad0b2729646b..cb83cbefe6dcb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs @@ -123,5 +123,185 @@ private static bool TryConvertPointer(object srcObject, [NotNullWhen(true)] out dstPtr = null; return false; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] // Two callers, one of them is potentially perf sensitive + public static void PrimitiveWiden(ref byte srcElement, ref byte destElement, CorElementType srcElType, CorElementType destElType) + { + switch (srcElType) + { + case CorElementType.ELEMENT_TYPE_U1: + switch (destElType) + { + case CorElementType.ELEMENT_TYPE_CHAR: + case CorElementType.ELEMENT_TYPE_I2: + case CorElementType.ELEMENT_TYPE_U2: + Unsafe.As(ref destElement) = srcElement; break; + case CorElementType.ELEMENT_TYPE_I4: + case CorElementType.ELEMENT_TYPE_U4: + Unsafe.As(ref destElement) = srcElement; break; + case CorElementType.ELEMENT_TYPE_I8: + case CorElementType.ELEMENT_TYPE_U8: + Unsafe.As(ref destElement) = srcElement; break; + case CorElementType.ELEMENT_TYPE_R4: + Unsafe.As(ref destElement) = srcElement; break; + case CorElementType.ELEMENT_TYPE_R8: + Unsafe.As(ref destElement) = srcElement; break; + default: + Debug.Fail("Expected to be unreachable"); break; + } + break; + + case CorElementType.ELEMENT_TYPE_I1: + switch (destElType) + { + case CorElementType.ELEMENT_TYPE_I2: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_I4: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_I8: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_R4: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_R8: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + default: + Debug.Fail("Array.Copy from I1 to another type hit unsupported widening conversion"); break; + } + break; + + case CorElementType.ELEMENT_TYPE_U2: + case CorElementType.ELEMENT_TYPE_CHAR: + switch (destElType) + { + case CorElementType.ELEMENT_TYPE_U2: + case CorElementType.ELEMENT_TYPE_CHAR: + // U2 and CHAR are identical in conversion + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_I4: + case CorElementType.ELEMENT_TYPE_U4: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_I8: + case CorElementType.ELEMENT_TYPE_U8: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_R4: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_R8: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + default: + Debug.Fail("Array.Copy from U2 to another type hit unsupported widening conversion"); break; + } + break; + + case CorElementType.ELEMENT_TYPE_I2: + switch (destElType) + { + case CorElementType.ELEMENT_TYPE_I4: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_I8: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_R4: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_R8: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + default: + Debug.Fail("Array.Copy from I2 to another type hit unsupported widening conversion"); break; + } + break; + + case CorElementType.ELEMENT_TYPE_U4: + switch (destElType) + { + case CorElementType.ELEMENT_TYPE_I8: + case CorElementType.ELEMENT_TYPE_U8: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_R4: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_R8: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + default: + Debug.Fail("Array.Copy from U4 to another type hit unsupported widening conversion"); break; + } + break; + + case CorElementType.ELEMENT_TYPE_I4: + switch (destElType) + { + case CorElementType.ELEMENT_TYPE_I8: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_R4: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_R8: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + default: + Debug.Fail("Array.Copy from I4 to another type hit unsupported widening conversion"); break; + } + break; + + case CorElementType.ELEMENT_TYPE_U8: + switch (destElType) + { + case CorElementType.ELEMENT_TYPE_R4: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_R8: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + default: + Debug.Fail("Array.Copy from U8 to another type hit unsupported widening conversion"); break; + } + break; + + case CorElementType.ELEMENT_TYPE_I8: + switch (destElType) + { + case CorElementType.ELEMENT_TYPE_R4: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_R8: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + default: + Debug.Fail("Array.Copy from I8 to another type hit unsupported widening conversion"); break; + } + break; + + case CorElementType.ELEMENT_TYPE_R4: + Debug.Assert(destElType == CorElementType.ELEMENT_TYPE_R8); + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + + default: + Debug.Fail("Fell through outer switch in PrimitiveWiden! Unknown primitive type for source array!"); break; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CanPrimitiveWiden(CorElementType srcET, CorElementType dstET) + { + // The primitive widen table + // The index represents source type. The value in the table is a bit vector of destination types. + // If corresponding bit is set in the bit vector, source type can be widened into that type. + // All types widen to themselves. + ReadOnlySpan primitiveWidenTable = + [ + 0x00, // ELEMENT_TYPE_END + 0x00, // ELEMENT_TYPE_VOID + 0x0004, // ELEMENT_TYPE_BOOLEAN + 0x3F88, // ELEMENT_TYPE_CHAR (W = U2, CHAR, I4, U4, I8, U8, R4, R8) (U2 == Char) + 0x3550, // ELEMENT_TYPE_I1 (W = I1, I2, I4, I8, R4, R8) + 0x3FE8, // ELEMENT_TYPE_U1 (W = CHAR, U1, I2, U2, I4, U4, I8, U8, R4, R8) + 0x3540, // ELEMENT_TYPE_I2 (W = I2, I4, I8, R4, R8) + 0x3F88, // ELEMENT_TYPE_U2 (W = U2, CHAR, I4, U4, I8, U8, R4, R8) + 0x3500, // ELEMENT_TYPE_I4 (W = I4, I8, R4, R8) + 0x3E00, // ELEMENT_TYPE_U4 (W = U4, I8, R4, R8) + 0x3400, // ELEMENT_TYPE_I8 (W = I8, R4, R8) + 0x3800, // ELEMENT_TYPE_U8 (W = U8, R4, R8) + 0x3000, // ELEMENT_TYPE_R4 (W = R4, R8) + 0x2000, // ELEMENT_TYPE_R8 (W = R8) + ]; + + Debug.Assert(srcET.IsPrimitiveType() && dstET.IsPrimitiveType()); + if ((int)srcET >= primitiveWidenTable.Length) + { + // I or U + return srcET == dstET; + } + return (primitiveWidenTable[(int)srcET] & (1 << (int)dstET)) != 0; + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs index 8bf2cb541f921..f2a14d0d4959f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.Reflection; using System.Runtime.InteropServices; @@ -109,13 +110,43 @@ internal static bool IsPrimitiveType(this CorElementType et) // COR_ELEMENT_TYPE_I1,I2,I4,I8,U1,U2,U4,U8,R4,R8,I,U,CHAR,BOOLEAN => ((1 << (int)et) & 0b_0011_0000_0000_0011_1111_1111_1100) != 0; + private static ReadOnlySpan PrimitiveWidenTable => + [ + 0x00, // ELEMENT_TYPE_END + 0x00, // ELEMENT_TYPE_VOID + 0x0004, // ELEMENT_TYPE_BOOLEAN + 0x3F88, // ELEMENT_TYPE_CHAR (W = U2, CHAR, I4, U4, I8, U8, R4, R8) (U2 == Char) + 0x3550, // ELEMENT_TYPE_I1 (W = I1, I2, I4, I8, R4, R8) + 0x3FE8, // ELEMENT_TYPE_U1 (W = CHAR, U1, I2, U2, I4, U4, I8, U8, R4, R8) + 0x3540, // ELEMENT_TYPE_I2 (W = I2, I4, I8, R4, R8) + 0x3F88, // ELEMENT_TYPE_U2 (W = U2, CHAR, I4, U4, I8, U8, R4, R8) + 0x3500, // ELEMENT_TYPE_I4 (W = I4, I8, R4, R8) + 0x3E00, // ELEMENT_TYPE_U4 (W = U4, I8, R4, R8) + 0x3400, // ELEMENT_TYPE_I8 (W = I8, R4, R8) + 0x3800, // ELEMENT_TYPE_U8 (W = U8, R4, R8) + 0x3000, // ELEMENT_TYPE_R4 (W = R4, R8) + 0x2000, // ELEMENT_TYPE_R8 (W = R8) + ]; + + internal static bool CanPrimitiveWiden(CorElementType srcET, CorElementType dstET) + { + Debug.Assert(srcET.IsPrimitiveType() && dstET.IsPrimitiveType()); + if ((int)srcET >= PrimitiveWidenTable.Length) + { + // I or U + return srcET == dstET; + } + return (PrimitiveWidenTable[(int)srcET] & (1 << (int)dstET)) != 0; + } + /// Provide a fast way to access constant data stored in a module as a ReadOnlySpan{T} /// A field handle that specifies the location of the data to be referred to by the ReadOnlySpan{T}. The Rva of the field must be aligned on a natural boundary of type T /// A ReadOnlySpan{T} of the data stored in the field /// does not refer to a field which is an Rva, is misaligned, or T is of an invalid type. /// This method is intended for compiler use rather than use directly in code. T must be one of byte, sbyte, bool, char, short, ushort, int, uint, long, ulong, float, or double. [Intrinsic] - public static unsafe ReadOnlySpan CreateSpan(RuntimeFieldHandle fldHandle) => new ReadOnlySpan(GetSpanDataFrom(fldHandle, typeof(T).TypeHandle, out int length), length); + public static unsafe ReadOnlySpan CreateSpan(RuntimeFieldHandle fldHandle) + => new ReadOnlySpan(ref Unsafe.As(ref GetSpanDataFrom(fldHandle, typeof(T).TypeHandle, out int length)), length); // The following intrinsics return true if input is a compile-time constant diff --git a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs index d042b39bca143..ef7d3adbc0a1d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs @@ -71,18 +71,6 @@ internal static void ThrowArrayTypeMismatchException() throw new ArrayTypeMismatchException(); } - [DoesNotReturn] - internal static void ThrowArrayTypeMismatchException_CantAssignType() - { - throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_CantAssignType); - } - - [DoesNotReturn] - internal static void ThrowInvalidCastException_DownCastArrayElement() - { - throw new InvalidCastException(SR.InvalidCast_DownCastArrayElement); - } - [DoesNotReturn] internal static void ThrowInvalidTypeWithPointersNotSupported(Type targetType) { diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs index 2850347d59d6a..832d890ab61c2 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs @@ -4153,6 +4153,11 @@ public static void SetValue_Casting() var arr5 = new int[3]; arr5.SetValue(SByteEnum.MinusTwo, new int[] { 1 }); Assert.Equal(-2, arr5[1]); + + // Casting enum to underlying type + var arr6 = new int[3]; + arr6.SetValue(Int32Enum.Case3, new int[] { 1 }); + Assert.Equal(2, arr6[1]); } [Fact] @@ -4169,6 +4174,10 @@ public static void SetValue_Casting_Invalid() // T -> Nullable T must be exact var arr3 = new int?[3]; Assert.Throws(() => arr3.SetValue((short)42, new int[] { 1 })); + + // Converting enum to same size with wrong signed-ness + var arr4 = new uint[3]; + AssertExtensions.Throws(null, () => arr4.SetValue(Int32Enum.Case3, new int[] { 1 })); } [Fact] diff --git a/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs index 016cc3e08568c..891e97640253c 100644 --- a/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs @@ -19,14 +19,14 @@ public static void InitializeArray(Array array, RuntimeFieldHandle fldHandle) InitializeArray(array, fldHandle.Value); } - private static unsafe void* GetSpanDataFrom( + private static unsafe ref byte GetSpanDataFrom( RuntimeFieldHandle fldHandle, RuntimeTypeHandle targetTypeHandle, out int count) { fixed (int *pCount = &count) { - return (void*)GetSpanDataFrom(fldHandle.Value, targetTypeHandle.Value, new IntPtr(pCount)); + return ref GetSpanDataFrom(fldHandle.Value, targetTypeHandle.Value, new IntPtr(pCount)); } } @@ -196,7 +196,7 @@ public static object GetUninitializedObject( private static extern void InitializeArray(Array array, IntPtr fldHandle); [MethodImplAttribute(MethodImplOptions.InternalCall)] - private static extern unsafe IntPtr GetSpanDataFrom( + private static extern unsafe ref byte GetSpanDataFrom( IntPtr fldHandle, IntPtr targetTypeHandle, IntPtr count);