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 8c48595d4e2d3c..169270737b7380 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs @@ -249,147 +249,152 @@ private static unsafe void CopyImplPrimitiveWiden(Array sourceArray, int sourceI 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; + PrimitiveWiden(ref srcElement, ref destElement, srcElType, destElType); + } + } - default: - Debug.Fail("Fell through outer switch in PrimitiveWiden! Unknown primitive type for source array!"); break; - } + private 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("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; } } @@ -554,8 +559,100 @@ 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); + if (pElementMethodTable->ContainsGCPointers) + SpanHelpers.ClearWithReferences(ref Unsafe.As(ref offsetDataRef), pElementMethodTable->GetNumInstanceFieldBytes()); + else + SpanHelpers.ClearWithoutReferences(ref offsetDataRef, pElementMethodTable->GetNumInstanceFieldBytes()); + } + else + { + ref object? elementRef = ref Unsafe.As(ref arrayDataRef); + ref object? offsetElementRef = ref Unsafe.Add(ref elementRef, (nuint)flattenedIndex); + offsetElementRef = null; + } + } + else if (TypeHandle.AreSameType(arrayElementTypeHandle, TypeHandle.TypeHandleOf())) + { + // Everything is compatible with Object + ref object? elementRef = ref Unsafe.As(ref arrayDataRef); + ref object? offsetElementRef = ref Unsafe.Add(ref elementRef, (nuint)flattenedIndex); + offsetElementRef = value; + } + else if (!pElementMethodTable->IsValueType) + { + if (CastHelpers.IsInstanceOfAny(pElementMethodTable, value) == null) + { + ThrowHelper.ThrowInvalidCastException_StoreArrayElement(); + } + + ref object? elementRef = ref Unsafe.As(ref arrayDataRef); + ref object? offsetElementRef = ref Unsafe.Add(ref elementRef, (nuint)flattenedIndex); + offsetElementRef = 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 if (pElementMethodTable->ContainsGCPointers) + { + Buffer.BulkMoveWithWriteBarrier(ref offsetDataRef, ref value.GetRawData(), pElementMethodTable->GetNumInstanceFieldBytes()); + } + else + { + SpanHelpers.Memmove(ref offsetDataRef, ref value.GetRawData(), pElementMethodTable->GetNumInstanceFieldBytes()); + } + } + else + { + // Allow enum -> primitive conversion, disallow primitive -> enum conversion + MethodTable* thSrc = RuntimeHelpers.GetMethodTable(value); + CorElementType srcType = thSrc->GetVerifierCorElementType(); + CorElementType targetType = pElementMethodTable->GetVerifierCorElementType(); + + if (!srcType.IsPrimitiveType() || !targetType.IsPrimitiveType() || !pElementMethodTable->IsTruePrimitive) + ThrowHelper.ThrowInvalidCastException_StoreArrayElement(); + + // Get a properly widened type + if (!RuntimeHelpers.CanPrimitiveWiden(srcType, targetType)) + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_PrimitiveWiden); + + 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/Runtime/CompilerServices/CastHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/CastHelpers.cs index 0c640dd1c2f0a4..14c8a959cda5b0 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 936f9e786be9b0..415e0ad612929e 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,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.Buffers.Binary; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; @@ -8,7 +9,6 @@ using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; -using System.Threading; namespace System.Runtime.CompilerServices { @@ -652,6 +652,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; @@ -681,6 +683,9 @@ public TypeHandle GetArrayElementTypeHandle() [MethodImpl(MethodImplOptions.InternalCall)] public extern uint GetNumInstanceFieldBytes(); + + [MethodImpl(MethodImplOptions.InternalCall)] + public extern CorElementType GetVerifierCorElementType(); } // Subset of src\vm\methodtable.h @@ -706,9 +711,9 @@ public bool CanCompareBitsOrUseFastGetHashCode } /// - /// A type handle, which can wrap either a pointer to a TypeDesc or to a . + /// A type handle, which can wrap either a pointer to a or to a . /// - internal unsafe struct TypeHandle + internal readonly unsafe partial struct TypeHandle { // Subset of src\vm\typehandle.h @@ -750,11 +755,67 @@ public bool IsTypeDesc return (MethodTable*)m_asTAddr; } + /// + /// Gets the pointer wrapped by the current instance. + /// + /// This is only safe to call if returned . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TypeDesc* AsTypeDesc() + { + Debug.Assert(IsTypeDesc); + + return (TypeDesc*)((nint)m_asTAddr - 2); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TypeHandle TypeHandleOf() { return new TypeHandle((void*)RuntimeTypeHandle.ToIntPtr(typeof(T).TypeHandle)); } + + public static bool AreSameType(TypeHandle left, TypeHandle right) => left.m_asTAddr == right.m_asTAddr; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanCastTo(TypeHandle destTH) + { + CastResult result = CastCache.TryGet(CastHelpers.s_table!, (nuint)m_asTAddr, (nuint)destTH.m_asTAddr); + + if (result != CastResult.MaybeCast) + return result == CastResult.CanCast; + + return CanCastTo_NoCacheLookup(m_asTAddr, destTH.m_asTAddr); + } + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "TypeHandle_CanCastTo")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool CanCastTo_NoCacheLookup(void* fromTypeHnd, void* toTypeHnd); + + public bool IsValueType + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return IsTypeDesc + ? AsTypeDesc()->GetInternalCorElementType() == CorElementType.ELEMENT_TYPE_VALUETYPE + : AsMethodTable()->IsValueType; + } + } + + public bool IsInterface => !IsTypeDesc && AsMethodTable()->IsInterface; + + public CorElementType GetVerifierCorElementType() => IsTypeDesc + ? AsTypeDesc()->GetInternalCorElementType() + : AsMethodTable()->GetVerifierCorElementType(); + } + + internal unsafe struct TypeDesc + { + private readonly int m_typeAndFlags; + + // This is the ELEMENT_TYPE* that would be used in the type sig for this type + // For enums this is the underlying type + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CorElementType GetInternalCorElementType() => (CorElementType)(m_typeAndFlags & 0xFF); } // Helper structs used for tail calls via helper. diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs index 74f6f5beeb3b63..7eddc635ef76bd 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() @@ -1191,7 +1191,10 @@ internal static RuntimeType GetApproxDeclaringType(IRuntimeFieldInfo field) internal static extern int GetInstanceFieldOffset(RtFieldInfo field); [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr GetStaticFieldAddress(RtFieldInfo field); + internal static extern IntPtr GetStaticFieldAddress(IRuntimeFieldInfo field); + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeFieldHandle_GetFieldSize")] + internal static partial uint GetFieldSize(QCallFieldHandle field); [MethodImpl(MethodImplOptions.InternalCall)] internal static extern int GetToken(RtFieldInfo field); diff --git a/src/coreclr/vm/comutilnative.cpp b/src/coreclr/vm/comutilnative.cpp index a3c9d0a848cdff..1823ffa2c07ace 100644 --- a/src/coreclr/vm/comutilnative.cpp +++ b/src/coreclr/vm/comutilnative.cpp @@ -1787,6 +1787,13 @@ FCIMPL1(UINT32, MethodTableNative::GetNumInstanceFieldBytes, MethodTable* mt) } FCIMPLEND +FCIMPL1(CorElementType, MethodTableNative::GetVerifierCorElementType, MethodTable* mt) +{ + FCALL_CONTRACT; + return mt->GetVerifierCorElementType(); +} +FCIMPLEND + extern "C" BOOL QCALLTYPE MethodTable_AreTypesEquivalent(MethodTable* mta, MethodTable* mtb) { QCALL_CONTRACT; @@ -1802,6 +1809,21 @@ extern "C" BOOL QCALLTYPE MethodTable_AreTypesEquivalent(MethodTable* mta, Metho return bResult; } +extern "C" BOOL QCALLTYPE TypeHandle_CanCastTo(void* fromTypeHnd, void* toTypeHnd) +{ + QCALL_CONTRACT; + + BOOL ret = false; + + BEGIN_QCALL; + + ret = TypeHandle::FromPtr(fromTypeHnd).CanCastTo(TypeHandle::FromPtr(toTypeHnd)); + + END_QCALL; + + return ret; +} + static MethodTable * g_pStreamMT; static WORD g_slotBeginRead, g_slotEndRead; static WORD g_slotBeginWrite, g_slotEndWrite; diff --git a/src/coreclr/vm/comutilnative.h b/src/coreclr/vm/comutilnative.h index 3e64207564c847..fb1038d7f3f5db 100644 --- a/src/coreclr/vm/comutilnative.h +++ b/src/coreclr/vm/comutilnative.h @@ -244,10 +244,12 @@ extern "C" void QCALLTYPE Interlocked_MemoryBarrierProcessWide(); class MethodTableNative { public: static FCDECL1(UINT32, GetNumInstanceFieldBytes, MethodTable* mt); + static FCDECL1(CorElementType, GetVerifierCorElementType, MethodTable* mt); }; extern "C" BOOL QCALLTYPE MethodTable_AreTypesEquivalent(MethodTable* mta, MethodTable* mtb); extern "C" BOOL QCALLTYPE MethodTable_CanCompareBitsOrUseFastGetHashCode(MethodTable* mt); +extern "C" BOOL QCALLTYPE TypeHandle_CanCastTo(void* fromTypeHnd, void* toTypeHnd); extern "C" INT32 QCALLTYPE ValueType_GetHashCodeStrategy(MethodTable* mt, QCall::ObjectHandleOnStack objHandle, UINT32* fieldOffset, UINT32* fieldSize, MethodTable** fieldMT); class StreamNative { diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 98a38b8603419c..f06fc007e2ccb1 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -456,6 +456,7 @@ FCFuncEnd() FCFuncStart(gMethodTableFuncs) FCFuncElement("GetNumInstanceFieldBytes", MethodTableNative::GetNumInstanceFieldBytes) + FCFuncElement("GetVerifierCorElementType", MethodTableNative::GetVerifierCorElementType) FCFuncEnd() FCFuncStart(gStubHelperFuncs) diff --git a/src/coreclr/vm/qcall.h b/src/coreclr/vm/qcall.h index e3154c7b1334c5..b63d2ffa7beac5 100644 --- a/src/coreclr/vm/qcall.h +++ b/src/coreclr/vm/qcall.h @@ -293,6 +293,24 @@ class QCall } }; + struct FieldHandle + { + Object ** m_ppObject; + FieldDesc * m_pField; + + operator FieldDesc * () + { + LIMITED_METHOD_CONTRACT; + return m_pField; + } + + FieldDesc * operator -> () + { + LIMITED_METHOD_CONTRACT; + return m_pField; + } + }; + struct LoaderAllocatorHandle { LoaderAllocator * m_pLoaderAllocator; diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 4008a5957eaac8..ff02d1cac1af01 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -103,6 +103,7 @@ static const Entry s_QCall[] = DllImportEntry(QCall_FreeGCHandleForTypeHandle) DllImportEntry(MethodTable_AreTypesEquivalent) DllImportEntry(MethodTable_CanCompareBitsOrUseFastGetHashCode) + DllImportEntry(TypeHandle_CanCastTo) DllImportEntry(ValueType_GetHashCodeStrategy) DllImportEntry(RuntimeTypeHandle_MakePointer) DllImportEntry(RuntimeTypeHandle_MakeByRef) @@ -132,6 +133,7 @@ static const Entry s_QCall[] = DllImportEntry(RuntimeModule_GetScopeName) DllImportEntry(RuntimeModule_GetFullyQualifiedName) DllImportEntry(RuntimeModule_GetTypes) + DllImportEntry(RuntimeFieldHandle_GetFieldSize) DllImportEntry(StackFrame_GetMethodDescFromNativeIP) DllImportEntry(ModuleBuilder_GetStringConstant) DllImportEntry(ModuleBuilder_GetTypeRef) diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index a98dafd62a558b..52c211fe74c8de 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -1246,17 +1246,32 @@ FCIMPL1(void*, RuntimeFieldHandle::GetStaticFieldAddress, ReflectFieldObject *pF // IsFastPathSupported needs to checked before calling this method. _ASSERTE(IsFastPathSupportedHelper(pFieldDesc)); - PTR_BYTE base = 0; - if (!pFieldDesc->IsRVA()) + if (pFieldDesc->IsRVA()) { - // For RVA the base is ignored and offset is used. - base = pFieldDesc->GetBase(); + Module* pModule = pFieldDesc->GetModule(); + return pModule->GetRvaField(pFieldDesc->GetOffset()); + } + else + { + PTR_BYTE base = pFieldDesc->GetBase(); + return PTR_VOID(base + pFieldDesc->GetOffset()); } - - return PTR_VOID(base + pFieldDesc->GetOffset()); } FCIMPLEND +extern "C" UINT QCALLTYPE RuntimeFieldHandle_GetFieldSize(QCall::FieldHandle pField) +{ + QCALL_CONTRACT; + + UINT ret = 0; + + BEGIN_QCALL; + ret = pField->LoadSize(); + END_QCALL; + + return ret; +} + extern "C" void QCALLTYPE ReflectionInvocation_CompileMethod(MethodDesc * pMD) { QCALL_CONTRACT; diff --git a/src/coreclr/vm/runtimehandles.h b/src/coreclr/vm/runtimehandles.h index 1adb6b25ebc3f4..a444636618bc11 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" UINT QCALLTYPE RuntimeFieldHandle_GetFieldSize(QCall::FieldHandle pField); 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 79653d2eb426a5..b9d10848b5a3df 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. + + + The array must be array of primitive or enum type. + + \ No newline at end of file diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/QCallHandles.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/QCallHandles.cs index 240e9932bf6244..e6ab54d12e5d7f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/QCallHandles.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/QCallHandles.cs @@ -95,4 +95,25 @@ internal QCallTypeHandle(ref RuntimeTypeHandle rth) _handle = rth.Value; } } + + // Wraps IRuntimeFieldInfo into a handle. Used to pass IRuntimeFieldInfo to native code without letting it be collected + internal unsafe ref struct QCallFieldHandle + { + private void* _ptr; + private IntPtr _handle; + +#if CORECLR + internal QCallFieldHandle(ref IRuntimeFieldInfo field) + { + _ptr = Unsafe.AsPointer(ref field); + _handle = field.Value.Value; + } +#endif + + internal QCallFieldHandle(ref RuntimeFieldHandle rth) + { + _ptr = Unsafe.AsPointer(ref rth); + _handle = rth.Value; + } + } } 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 8bf2cb541f921d..d1efa61d6cecf2 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,6 +110,35 @@ 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 diff --git a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs index d042b39bca1436..c3ce5b98e0a037 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs @@ -83,6 +83,12 @@ internal static void ThrowInvalidCastException_DownCastArrayElement() throw new InvalidCastException(SR.InvalidCast_DownCastArrayElement); } + [DoesNotReturn] + internal static void ThrowInvalidCastException_StoreArrayElement() + { + throw new InvalidCastException(SR.InvalidCast_StoreArrayElement); + } + [DoesNotReturn] internal static void ThrowInvalidTypeWithPointersNotSupported(Type targetType) { @@ -1246,6 +1252,12 @@ private static string GetResourceString(ExceptionResource resource) return SR.Format_ExpectedAsciiDigit; case ExceptionResource.Argument_HasToBeArrayClass: return SR.Argument_HasToBeArrayClass; + case ExceptionResource.Arg_PrimitiveWiden: + return SR.Arg_PrimWiden; + case ExceptionResource.Argument_MustBePrimitiveArray: + return SR.Argument_MustBePrimitiveArray; + case ExceptionResource.Argument_BadFieldForInitializeArray: + return SR.Argument_BadFieldForInitializeArray; case ExceptionResource.InvalidOperation_IncompatibleComparer: return SR.InvalidOperation_IncompatibleComparer; default: @@ -1445,6 +1457,9 @@ internal enum ExceptionResource Format_UnclosedFormatItem, Format_ExpectedAsciiDigit, Argument_HasToBeArrayClass, + Arg_PrimitiveWiden, + Argument_MustBePrimitiveArray, + Argument_BadFieldForInitializeArray, InvalidOperation_IncompatibleComparer, } }