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 7386b1689866a..01743710316e7 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs @@ -253,7 +253,7 @@ private static unsafe void CopyImplUnBoxEachElement(Array sourceArray, int sourc if (pDestMT->IsNullable) { - RuntimeHelpers.Unbox_Nullable(ref dest, pDestMT, obj); + CastHelpers.Unbox_Nullable(ref dest, pDestMT, obj); } else if (obj is null || RuntimeHelpers.GetMethodTable(obj) != pDestMT) { @@ -518,11 +518,16 @@ private unsafe void InternalSetValue(object? value, nint flattenedIndex) if (pElementMethodTable->IsValueType) { ref byte offsetDataRef = ref Unsafe.Add(ref arrayDataRef, flattenedIndex * pMethodTable->ComponentSize); - nuint elementSize = pElementMethodTable->GetNumInstanceFieldBytes(); if (pElementMethodTable->ContainsGCPointers) + { + nuint elementSize = pElementMethodTable->GetNumInstanceFieldBytesIfContainsGCPointers(); SpanHelpers.ClearWithReferences(ref Unsafe.As(ref offsetDataRef), elementSize / (nuint)sizeof(IntPtr)); + } else + { + nuint elementSize = pElementMethodTable->GetNumInstanceFieldBytes(); SpanHelpers.ClearWithoutReferences(ref offsetDataRef, elementSize); + } } else { @@ -546,17 +551,18 @@ private unsafe void InternalSetValue(object? value, nint flattenedIndex) { if (pElementMethodTable->IsNullable) { - RuntimeHelpers.Unbox_Nullable(ref offsetDataRef, pElementMethodTable, value); + CastHelpers.Unbox_Nullable(ref offsetDataRef, pElementMethodTable, value); } else { - nuint elementSize = pElementMethodTable->GetNumInstanceFieldBytes(); if (pElementMethodTable->ContainsGCPointers) { + nuint elementSize = pElementMethodTable->GetNumInstanceFieldBytesIfContainsGCPointers(); Buffer.BulkMoveWithWriteBarrier(ref offsetDataRef, ref value.GetRawData(), elementSize); } else { + nuint elementSize = pElementMethodTable->GetNumInstanceFieldBytes(); SpanHelpers.Memmove(ref offsetDataRef, ref value.GetRawData(), elementSize); } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs index 664f218be6633..957197f0f5b9d 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs @@ -458,7 +458,7 @@ internal static unsafe bool InternalEqualTypes(object a, object b) { if (a.GetType() == b.GetType()) return true; - +#if FEATURE_TYPEEQUIVALENCE MethodTable* pMTa = RuntimeHelpers.GetMethodTable(a); MethodTable* pMTb = RuntimeHelpers.GetMethodTable(b); @@ -474,6 +474,9 @@ internal static unsafe bool InternalEqualTypes(object a, object b) GC.KeepAlive(b); return ret; +#else + return false; +#endif // FEATURE_TYPEEQUIVALENCE } // Used by the ctor. Do not call directly. 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 28bfcdf7de495..202a339b68025 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 @@ -2,26 +2,34 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; namespace System.Runtime.CompilerServices { [StackTraceHidden] [DebuggerStepThrough] - internal static unsafe class CastHelpers + internal static unsafe partial class CastHelpers { // In coreclr the table is allocated and written to on the native side. internal static int[]? s_table; + [LibraryImport(RuntimeHelpers.QCall)] + internal static partial void ThrowInvalidCastException(void* fromTypeHnd, void* toTypeHnd); + + [DoesNotReturn] + internal static void ThrowInvalidCastException(object fromType, void* toTypeHnd) + { + ThrowInvalidCastException(RuntimeHelpers.GetMethodTable(fromType), toTypeHnd); + throw null!; // Provide hint to the inliner that this method does not return + } + [MethodImpl(MethodImplOptions.InternalCall)] private static extern object IsInstanceOfAny_NoCacheLookup(void* toTypeHnd, object obj); [MethodImpl(MethodImplOptions.InternalCall)] private static extern object ChkCastAny_NoCacheLookup(void* toTypeHnd, object obj); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern ref byte Unbox_Helper(void* toTypeHnd, object obj); - [MethodImpl(MethodImplOptions.InternalCall)] private static extern void WriteBarrier(ref object? dst, object? obj); @@ -365,7 +373,7 @@ internal static unsafe class CastHelpers } [DebuggerHidden] - private static ref byte Unbox(void* toTypeHnd, object obj) + private static ref byte Unbox(MethodTable* toTypeHnd, object obj) { // This will throw NullReferenceException if obj is null. if (RuntimeHelpers.GetMethodTable(obj) == toTypeHnd) @@ -492,5 +500,145 @@ private static unsafe void ArrayTypeCheck_Helper(object obj, void* elementType) ThrowArrayMismatchException(); } } + + // Helpers for Unboxing +#if FEATURE_TYPEEQUIVALENCE + [DebuggerHidden] + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool AreTypesEquivalent(MethodTable* pMTa, MethodTable* pMTb) + { + if (pMTa == pMTb) + { + return true; + } + + if (!pMTa->HasTypeEquivalence || !pMTb->HasTypeEquivalence) + { + return false; + } + + return RuntimeHelpers.AreTypesEquivalent(pMTa, pMTb); + } +#endif // FEATURE_TYPEEQUIVALENCE + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsNullableForType(MethodTable* typeMT, MethodTable* boxedMT) + { + if (!typeMT->IsNullable) + { + return false; + } + + // Normally getting the first generic argument involves checking the PerInstInfo to get the count of generic dictionaries + // in the hierarchy, and then doing a bit of math to find the right dictionary, but since we know this is nullable + // we can do a simple double deference to do the same thing. + Debug.Assert(typeMT->InstantiationArg0() == **typeMT->PerInstInfo); + MethodTable *pMTNullableArg = **typeMT->PerInstInfo; + if (pMTNullableArg == boxedMT) + { + return true; + } + else + { +#if FEATURE_TYPEEQUIVALENCE + return AreTypesEquivalent(pMTNullableArg, boxedMT); +#else + return false; +#endif // FEATURE_TYPEEQUIVALENCE + } + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.NoInlining)] + private static void Unbox_Nullable_NotIsNullableForType(ref byte destPtr, MethodTable* typeMT, object obj) + { + // Also allow true nullables to be unboxed normally. + // This should not happen normally, but can happen in debugger scenarios. + if (typeMT != RuntimeHelpers.GetMethodTable(obj)) + { + CastHelpers.ThrowInvalidCastException(obj, typeMT); + } + Buffer.BulkMoveWithWriteBarrier(ref destPtr, ref RuntimeHelpers.GetRawData(obj), typeMT->GetNullableNumInstanceFieldBytes()); + } + + [DebuggerHidden] + internal static void Unbox_Nullable(ref byte destPtr, MethodTable* typeMT, object? obj) + { + if (obj == null) + { + if (!typeMT->ContainsGCPointers) + { + SpanHelpers.ClearWithoutReferences(ref destPtr, typeMT->GetNullableNumInstanceFieldBytes()); + } + else + { + SpanHelpers.ClearWithReferences(ref Unsafe.As(ref destPtr), typeMT->GetNumInstanceFieldBytesIfContainsGCPointers() / (nuint)sizeof(IntPtr)); + } + } + else + { + if (!IsNullableForType(typeMT, RuntimeHelpers.GetMethodTable(obj))) + { + Unbox_Nullable_NotIsNullableForType(ref destPtr, typeMT, obj); + } + else + { + Unsafe.As(ref destPtr) = true; + ref byte dst = ref Unsafe.Add(ref destPtr, typeMT->NullableValueAddrOffset); + uint valueSize = typeMT->NullableValueSize; + ref byte src = ref RuntimeHelpers.GetRawData(obj); + if (typeMT->ContainsGCPointers) + Buffer.BulkMoveWithWriteBarrier(ref dst, ref src, valueSize); + else + SpanHelpers.Memmove(ref dst, ref src, valueSize); + } + } + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.NoInlining)] + private static ref byte Unbox_Helper(MethodTable* pMT1, object obj) + { + // must be a value type + Debug.Assert(pMT1->IsValueType); + + MethodTable* pMT2 = RuntimeHelpers.GetMethodTable(obj); + if ((!pMT1->IsPrimitive || !pMT2->IsPrimitive || + pMT1->GetPrimitiveCorElementType() != pMT2->GetPrimitiveCorElementType()) +#if FEATURE_TYPEEQUIVALENCE + && !AreTypesEquivalent(pMT1, pMT2) +#endif // FEATURE_TYPEEQUIVALENCE + ) + { + CastHelpers.ThrowInvalidCastException(obj, pMT1); + } + + return ref RuntimeHelpers.GetRawData(obj); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.NoInlining)] + private static void Unbox_TypeTest_Helper(MethodTable *pMT1, MethodTable *pMT2) + { + if ((!pMT1->IsPrimitive || !pMT2->IsPrimitive || + pMT1->GetPrimitiveCorElementType() != pMT2->GetPrimitiveCorElementType()) +#if FEATURE_TYPEEQUIVALENCE + && !AreTypesEquivalent(pMT1, pMT2) +#endif // FEATURE_TYPEEQUIVALENCE + ) + { + CastHelpers.ThrowInvalidCastException(pMT1, pMT2); + } + } + + [DebuggerHidden] + private static void Unbox_TypeTest(MethodTable *pMT1, MethodTable *pMT2) + { + if (pMT1 == pMT2) + return; + else + Unbox_TypeTest_Helper(pMT1, pMT2); + } } } 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 be037686401b3..2d61aa3f0c700 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 @@ -448,9 +448,6 @@ internal static unsafe bool ObjectHasComponentSize(object obj) [MethodImpl(MethodImplOptions.InternalCall)] internal static extern unsafe object? Box(MethodTable* methodTable, ref byte data); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern unsafe void Unbox_Nullable(ref byte destination, MethodTable* toTypeHnd, object? obj); - // Given an object reference, returns its MethodTable*. // // WARNING: The caller has to ensure that MethodTable* does not get unloaded. The most robust way @@ -706,12 +703,41 @@ internal unsafe struct MethodTable [FieldOffset(ElementTypeOffset)] public void* ElementType; + /// + /// The PerInstInfo is used to describe the generic arguments and dictionary of this type. + /// It points at a structure defined as PerInstInfo in C++, which is an array of pointers to generic + /// dictionaries, which then point to the actual type arguments + the contents of the generic dictionary. + /// The size of the PerInstInfo is defined in the negative space of that structure, and the size of the + /// generic dictionary is described in the DictionaryLayout of the associated canonical MethodTable. + /// + [FieldOffset(ElementTypeOffset)] + public MethodTable*** PerInstInfo; + /// /// This interface map used to list out the set of interfaces. Only meaningful if InterfaceCount is non-zero. /// [FieldOffset(InterfaceMapOffset)] public MethodTable** InterfaceMap; + /// + /// This is used to hold the nullable unbox data for nullable value types. + /// + [FieldOffset(InterfaceMapOffset)] +#if TARGET_64BIT + public uint NullableValueAddrOffset; +#else + public byte NullableValueAddrOffset; +#endif + +#if TARGET_64BIT + [FieldOffset(InterfaceMapOffset + 4)] + public uint NullableValueSize; +#else + [FieldOffset(InterfaceMapOffset)] + private uint NullableValueSizeEncoded; + public uint NullableValueSize => NullableValueSizeEncoded >> 8; +#endif + // WFLAGS_LOW_ENUM private const uint enum_flag_GenericsMask = 0x00000030; private const uint enum_flag_GenericsMask_NonGeneric = 0x00000000; // no instantiation @@ -730,6 +756,7 @@ internal unsafe struct MethodTable private const uint enum_flag_Category_Mask = 0x000F0000; private const uint enum_flag_Category_ValueType = 0x00040000; private const uint enum_flag_Category_Nullable = 0x00050000; + private const uint enum_flag_Category_IsPrimitiveMask = 0x000E0000; private const uint enum_flag_Category_PrimitiveValueType = 0x00060000; // sub-category of ValueType, Enum or primitive value type private const uint enum_flag_Category_TruePrimitive = 0x00070000; // sub-category of ValueType, Primitive (ELEMENT_TYPE_I, etc.) private const uint enum_flag_Category_Array = 0x00080000; @@ -780,7 +807,9 @@ internal unsafe struct MethodTable public bool NonTrivialInterfaceCast => (Flags & enum_flag_NonTrivialInterfaceCast) != 0; +#if FEATURE_TYPEEQUIVALENCE public bool HasTypeEquivalence => (Flags & enum_flag_HasTypeEquivalence) != 0; +#endif // FEATURE_TYPEEQUIVALENCE public bool HasFinalizer => (Flags & enum_flag_HasFinalizer) != 0; @@ -815,12 +844,13 @@ public int MultiDimensionalArrayRank public bool IsValueType => (Flags & enum_flag_Category_ValueType_Mask) == enum_flag_Category_ValueType; - public bool IsNullable => (Flags & enum_flag_Category_Mask) == enum_flag_Category_Nullable; + + public bool IsNullable { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return (Flags & enum_flag_Category_Mask) == enum_flag_Category_Nullable; } } public bool IsByRefLike => (Flags & (enum_flag_HasComponentSize | enum_flag_IsByRefLike)) == enum_flag_IsByRefLike; // 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 IsPrimitive => (Flags & enum_flag_Category_IsPrimitiveMask) == enum_flag_Category_PrimitiveValueType; public bool IsTruePrimitive => (Flags & enum_flag_Category_Mask) is enum_flag_Category_TruePrimitive; @@ -877,6 +907,27 @@ public TypeHandle GetArrayElementTypeHandle() /// [MethodImpl(MethodImplOptions.InternalCall)] public extern MethodTable* GetMethodTableMatchingParentClass(MethodTable* parent); + + [MethodImpl(MethodImplOptions.InternalCall)] + public extern MethodTable* InstantiationArg0(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetNullableNumInstanceFieldBytes() + { + Debug.Assert(IsNullable); + Debug.Assert((NullableValueAddrOffset + NullableValueSize) == GetNumInstanceFieldBytes()); + return NullableValueAddrOffset + NullableValueSize; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetNumInstanceFieldBytesIfContainsGCPointers() + { + // If the type ContainsGCPointers, we can compute the size without resorting to loading the BaseSizePadding field from the EEClass + + Debug.Assert(ContainsGCPointers); + Debug.Assert((BaseSize - (nuint)(2 * sizeof(IntPtr)) == GetNumInstanceFieldBytes())); + return BaseSize - (uint)(2 * sizeof(IntPtr)); + } } [StructLayout(LayoutKind.Sequential)] diff --git a/src/coreclr/inc/jithelpers.h b/src/coreclr/inc/jithelpers.h index 3eae84bc90dec..b163f80ab4941 100644 --- a/src/coreclr/inc/jithelpers.h +++ b/src/coreclr/inc/jithelpers.h @@ -114,8 +114,8 @@ DYNAMICJITHELPER(CORINFO_HELP_BOX, JIT_Box, METHOD__NIL) JITHELPER(CORINFO_HELP_BOX_NULLABLE, JIT_Box, METHOD__NIL) DYNAMICJITHELPER(CORINFO_HELP_UNBOX, NULL, METHOD__CASTHELPERS__UNBOX) - JITHELPER(CORINFO_HELP_UNBOX_TYPETEST, JIT_Unbox_TypeTest, METHOD__NIL) - JITHELPER(CORINFO_HELP_UNBOX_NULLABLE, JIT_Unbox_Nullable, METHOD__NIL) + DYNAMICJITHELPER(CORINFO_HELP_UNBOX_TYPETEST,NULL, METHOD__CASTHELPERS__UNBOX_TYPETEST) + DYNAMICJITHELPER(CORINFO_HELP_UNBOX_NULLABLE,NULL, METHOD__CASTHELPERS__UNBOX_NULLABLE) JITHELPER(CORINFO_HELP_GETREFANY, JIT_GetRefAny, METHOD__NIL) DYNAMICJITHELPER(CORINFO_HELP_ARRADDR_ST, NULL, METHOD__CASTHELPERS__STELEMREF) diff --git a/src/coreclr/vm/JitQCallHelpers.h b/src/coreclr/vm/JitQCallHelpers.h index c04ed1e9c1d0a..ff3bfa97981f3 100644 --- a/src/coreclr/vm/JitQCallHelpers.h +++ b/src/coreclr/vm/JitQCallHelpers.h @@ -21,6 +21,7 @@ class MethodDesc; extern "C" void * QCALLTYPE ResolveVirtualFunctionPointer(QCall::ObjectHandleOnStack obj, CORINFO_CLASS_HANDLE classHnd, CORINFO_METHOD_HANDLE methodHnd); extern "C" CORINFO_GENERIC_HANDLE QCALLTYPE GenericHandleWorker(MethodDesc * pMD, MethodTable * pMT, LPVOID signature, DWORD dictionaryIndexAndSlot, Module* pModule); extern "C" void QCALLTYPE InitClassHelper(MethodTable* pMT); +extern "C" void QCALLTYPE ThrowInvalidCastException(CORINFO_CLASS_HANDLE pTargetType, CORINFO_CLASS_HANDLE pSourceType); extern "C" void QCALLTYPE GetThreadStaticsByMethodTable(QCall::ByteRefOnStack refHandle, MethodTable* pMT, bool gcStatic); extern "C" void QCALLTYPE GetThreadStaticsByIndex(QCall::ByteRefOnStack refHandle, uint32_t staticBlockIndex, bool gcStatic); diff --git a/src/coreclr/vm/comutilnative.cpp b/src/coreclr/vm/comutilnative.cpp index 2ef7785294266..123bf906aef7a 100644 --- a/src/coreclr/vm/comutilnative.cpp +++ b/src/coreclr/vm/comutilnative.cpp @@ -1818,6 +1818,14 @@ FCIMPL2(MethodTable*, MethodTableNative::GetMethodTableMatchingParentClass, Meth } FCIMPLEND +FCIMPL1(MethodTable*, MethodTableNative::InstantiationArg0, MethodTable* mt); +{ + FCALL_CONTRACT; + + return mt->GetInstantiation()[0].AsMethodTable(); +} +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 b2acd9a2cb45d..e01e0aa782d34 100644 --- a/src/coreclr/vm/comutilnative.h +++ b/src/coreclr/vm/comutilnative.h @@ -259,6 +259,7 @@ class MethodTableNative { static FCDECL1(UINT32, GetNumInstanceFieldBytes, MethodTable* mt); static FCDECL1(CorElementType, GetPrimitiveCorElementType, MethodTable* mt); static FCDECL2(MethodTable*, GetMethodTableMatchingParentClass, MethodTable* mt, MethodTable* parent); + static FCDECL1(MethodTable*, InstantiationArg0, MethodTable* mt); }; extern "C" BOOL QCALLTYPE MethodTable_AreTypesEquivalent(MethodTable* mta, MethodTable* mtb); diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 518bb532f7d99..32e95970d264c 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -1169,10 +1169,12 @@ DEFINE_METHOD(CASTHELPERS, CHKCASTANY, ChkCastAny, SM_Ptr DEFINE_METHOD(CASTHELPERS, CHKCASTINTERFACE, ChkCastInterface, SM_PtrVoid_Obj_RetObj) DEFINE_METHOD(CASTHELPERS, CHKCASTCLASS, ChkCastClass, SM_PtrVoid_Obj_RetObj) DEFINE_METHOD(CASTHELPERS, CHKCASTCLASSSPECIAL, ChkCastClassSpecial, SM_PtrVoid_Obj_RetObj) -DEFINE_METHOD(CASTHELPERS, UNBOX, Unbox, SM_PtrVoid_Obj_RetRefByte) +DEFINE_METHOD(CASTHELPERS, UNBOX, Unbox, NoSig) DEFINE_METHOD(CASTHELPERS, STELEMREF, StelemRef, SM_ArrObject_IntPtr_Obj_RetVoid) DEFINE_METHOD(CASTHELPERS, LDELEMAREF, LdelemaRef, SM_ArrObject_IntPtr_PtrVoid_RetRefObj) DEFINE_METHOD(CASTHELPERS, ARRAYTYPECHECK, ArrayTypeCheck, SM_Obj_Array_RetVoid) +DEFINE_METHOD(CASTHELPERS, UNBOX_NULLABLE, Unbox_Nullable, NoSig) +DEFINE_METHOD(CASTHELPERS, UNBOX_TYPETEST, Unbox_TypeTest, NoSig) DEFINE_CLASS(VIRTUALDISPATCHHELPERS, CompilerServices, VirtualDispatchHelpers) DEFINE_METHOD(VIRTUALDISPATCHHELPERS, VIRTUALFUNCTIONPOINTER, VirtualFunctionPointer, NoSig) diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 1ea05d6f692d8..2f2ae0d584af1 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -269,8 +269,6 @@ FCFuncEnd() FCFuncStart(gCastHelpers) FCFuncElement("IsInstanceOfAny_NoCacheLookup", ::IsInstanceOfAny_NoCacheLookup) FCFuncElement("ChkCastAny_NoCacheLookup", ::ChkCastAny_NoCacheLookup) - FCFuncElement("Unbox_Helper", ::Unbox_Helper) - FCFuncElement("JIT_Unbox_TypeTest", ::JIT_Unbox_TypeTest) FCFuncElement("WriteBarrier", ::WriteBarrier_Helper) FCFuncEnd() @@ -356,13 +354,13 @@ FCFuncStart(gRuntimeHelpers) FCFuncElement("AllocTailCallArgBufferWorker", TailCallHelp::AllocTailCallArgBufferWorker) FCFuncElement("GetTailCallInfo", TailCallHelp::GetTailCallInfo) FCFuncElement("Box", JIT_Box) - FCFuncElement("Unbox_Nullable", JIT_Unbox_Nullable) FCFuncEnd() FCFuncStart(gMethodTableFuncs) FCFuncElement("GetNumInstanceFieldBytes", MethodTableNative::GetNumInstanceFieldBytes) FCFuncElement("GetPrimitiveCorElementType", MethodTableNative::GetPrimitiveCorElementType) FCFuncElement("GetMethodTableMatchingParentClass", MethodTableNative::GetMethodTableMatchingParentClass) + FCFuncElement("InstantiationArg0", MethodTableNative::InstantiationArg0) FCFuncEnd() FCFuncStart(gStubHelperFuncs) diff --git a/src/coreclr/vm/generics.cpp b/src/coreclr/vm/generics.cpp index 384b1007cf8bd..ad76316877fa7 100644 --- a/src/coreclr/vm/generics.cpp +++ b/src/coreclr/vm/generics.cpp @@ -401,6 +401,10 @@ ClassLoader::CreateTypeHandleForNonCanonicalGenericInstantiation( // Fill in interface map pointer pMT->SetInterfaceMap(wNumInterfaces, pInterfaceMap); + if (pMT->IsNullable()) + { + pMT->SetNullableDetails((UINT16)pOldMT->GetNullableValueAddrOffset(), (UINT16)pOldMT->GetNullableValueSize()); + } // Copy across extra flags for these interfaces as well. We may need additional memory for this. PVOID pExtraInterfaceInfo = NULL; diff --git a/src/coreclr/vm/jithelpers.cpp b/src/coreclr/vm/jithelpers.cpp index f5018882aa2de..88bb4981d6572 100644 --- a/src/coreclr/vm/jithelpers.cpp +++ b/src/coreclr/vm/jithelpers.cpp @@ -1382,172 +1382,6 @@ HCIMPL2(Object*, JIT_Box, CORINFO_CLASS_HANDLE type, void* unboxedData) } HCIMPLEND -/*************************************************************/ -NOINLINE HCIMPL3(VOID, JIT_Unbox_Nullable_Framed, void * destPtr, MethodTable* typeMT, OBJECTREF objRef) -{ - FCALL_CONTRACT; - - HELPER_METHOD_FRAME_BEGIN_1(objRef); - if (!Nullable::UnBox(destPtr, objRef, typeMT)) - { - COMPlusThrowInvalidCastException(&objRef, TypeHandle(typeMT)); - } - HELPER_METHOD_POLL(); - HELPER_METHOD_FRAME_END(); -} -HCIMPLEND - -/*************************************************************/ -HCIMPL3(VOID, JIT_Unbox_Nullable, void * destPtr, CORINFO_CLASS_HANDLE type, Object* obj) -{ - FCALL_CONTRACT; - - TypeHandle typeHnd(type); - _ASSERTE(Nullable::IsNullableType(typeHnd)); - - MethodTable* typeMT = typeHnd.AsMethodTable(); - - OBJECTREF objRef = ObjectToOBJECTREF(obj); - - if (Nullable::UnBoxNoGC(destPtr, objRef, typeMT)) - { - // exact match (type equivalence not needed) - FC_GC_POLL(); - return; - } - - // Fall back to a framed helper that handles type equivalence. - ENDFORBIDGC(); - HCCALL3(JIT_Unbox_Nullable_Framed, destPtr, typeMT, objRef); -} -HCIMPLEND - -/*************************************************************/ -/* framed Unbox helper that handles enums and full-blown type equivalence */ -NOINLINE HCIMPL2(LPVOID, Unbox_Helper_Framed, MethodTable* pMT1, Object* obj) -{ - FCALL_CONTRACT; - - LPVOID result = NULL; - MethodTable* pMT2 = obj->GetMethodTable(); - - OBJECTREF objRef = ObjectToOBJECTREF(obj); - HELPER_METHOD_FRAME_BEGIN_RET_1(objRef); - HELPER_METHOD_POLL(); - - if (pMT1->GetInternalCorElementType() == pMT2->GetInternalCorElementType() && - (pMT1->IsEnum() || pMT1->IsTruePrimitive()) && - (pMT2->IsEnum() || pMT2->IsTruePrimitive())) - { - // we allow enums and their primitive type to be interchangeable - result = objRef->GetData(); - } - else if (pMT1->IsEquivalentTo(pMT2)) - { - // the structures are equivalent - result = objRef->GetData(); - } - else - { - COMPlusThrowInvalidCastException(&objRef, TypeHandle(pMT1)); - } - HELPER_METHOD_FRAME_END(); - - return result; -} -HCIMPLEND - -/*************************************************************/ -/* Unbox helper that handles enums */ -HCIMPL2(LPVOID, Unbox_Helper, CORINFO_CLASS_HANDLE type, Object* obj) -{ - FCALL_CONTRACT; - - TypeHandle typeHnd(type); - // boxable types have method tables - _ASSERTE(!typeHnd.IsTypeDesc()); - - MethodTable* pMT1 = typeHnd.AsMethodTable(); - // must be a value type - _ASSERTE(pMT1->IsValueType()); - - MethodTable* pMT2 = obj->GetMethodTable(); - - // we allow enums and their primitive type to be interchangeable. - // if suspension is requested, defer to the framed helper. - if (pMT1->GetInternalCorElementType() == pMT2->GetInternalCorElementType() && - (pMT1->IsEnum() || pMT1->IsTruePrimitive()) && - (pMT2->IsEnum() || pMT2->IsTruePrimitive()) && - g_TrapReturningThreads == 0) - { - return obj->GetData(); - } - - // Fall back to a framed helper that can also handle GC suspension and type equivalence. - ENDFORBIDGC(); - return HCCALL2(Unbox_Helper_Framed, pMT1, obj); -} -HCIMPLEND - -/* framed Unbox type test helper that handles enums and full-blown type equivalence */ -NOINLINE HCIMPL2(void, JIT_Unbox_TypeTest_Framed, MethodTable* pMT1, MethodTable* pMT2) -{ - FCALL_CONTRACT; - - HELPER_METHOD_FRAME_BEGIN_0(); - HELPER_METHOD_POLL(); - - if (pMT1->GetInternalCorElementType() == pMT2->GetInternalCorElementType() && - (pMT1->IsEnum() || pMT1->IsTruePrimitive()) && - (pMT2->IsEnum() || pMT2->IsTruePrimitive())) - { - // type test test passes - } - else if (pMT1->IsEquivalentTo(pMT2)) - { - // the structures are equivalent - } - else - { - COMPlusThrowInvalidCastException(TypeHandle(pMT2), TypeHandle(pMT1)); - } - HELPER_METHOD_FRAME_END(); -} -HCIMPLEND - -/*************************************************************/ -/* Unbox type test that handles enums */ -HCIMPL2(void, JIT_Unbox_TypeTest, CORINFO_CLASS_HANDLE type, CORINFO_CLASS_HANDLE boxType) -{ - FCALL_CONTRACT; - - TypeHandle typeHnd(type); - // boxable types have method tables - _ASSERTE(!typeHnd.IsTypeDesc()); - - MethodTable* pMT1 = typeHnd.AsMethodTable(); - // must be a value type - _ASSERTE(pMT1->IsValueType()); - - TypeHandle boxTypeHnd(boxType); - MethodTable* pMT2 = boxTypeHnd.AsMethodTable(); - - // we allow enums and their primitive type to be interchangeable. - // if suspension is requested, defer to the framed helper. - if (pMT1->GetInternalCorElementType() == pMT2->GetInternalCorElementType() && - (pMT1->IsEnum() || pMT1->IsTruePrimitive()) && - (pMT2->IsEnum() || pMT2->IsTruePrimitive()) && - g_TrapReturningThreads == 0) - { - return; - } - - // Fall back to a framed helper that can also handle GC suspension and type equivalence. - ENDFORBIDGC(); - HCCALL2(JIT_Unbox_TypeTest_Framed, pMT1, pMT2); -} -HCIMPLEND - /*************************************************************/ HCIMPL2_IV(LPVOID, JIT_GetRefAny, CORINFO_CLASS_HANDLE type, TypedByRef typedByRef) { @@ -1575,6 +1409,21 @@ HCIMPL2(BOOL, JIT_IsInstanceOfException, CORINFO_CLASS_HANDLE type, Object* obj) } HCIMPLEND +extern "C" void QCALLTYPE ThrowInvalidCastException(CORINFO_CLASS_HANDLE pTargetType, CORINFO_CLASS_HANDLE pSourceType) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + TypeHandle targetType(pTargetType); + TypeHandle sourceType(pSourceType); + + GCX_COOP(); + + COMPlusThrowInvalidCastException(sourceType, targetType); + + END_QCALL; +} //======================================================================== // diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index d52b71b9dfa46..6038fbfbbcc49 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -232,13 +232,11 @@ extern "C" FCDECL2(VOID, JIT_WriteBarrierEnsureNonHeapTarget, Object **dst, Obje extern "C" FCDECL2(Object*, ChkCastAny_NoCacheLookup, CORINFO_CLASS_HANDLE type, Object* obj); extern "C" FCDECL2(Object*, IsInstanceOfAny_NoCacheLookup, CORINFO_CLASS_HANDLE type, Object* obj); -extern "C" FCDECL2(LPVOID, Unbox_Helper, CORINFO_CLASS_HANDLE type, Object* obj); -extern "C" FCDECL2(void, JIT_Unbox_TypeTest, CORINFO_CLASS_HANDLE type, CORINFO_CLASS_HANDLE boxType); -extern "C" FCDECL3(void, JIT_Unbox_Nullable, void * destPtr, CORINFO_CLASS_HANDLE type, Object* obj); // ARM64 JIT_WriteBarrier uses speciall ABI and thus is not callable directly // Copied write barriers must be called at a different location extern "C" FCDECL2(VOID, JIT_WriteBarrier_Callable, Object **dst, Object *ref); + #define WriteBarrier_Helper JIT_WriteBarrier_Callable #ifdef TARGET_AMD64 diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 77367c4a9b213..abc1fb94eeaa1 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -1867,11 +1867,21 @@ class MethodTable // the information within MethodTable, and so less code manipulates EEClass // objects directly, because doing so can lead to bugs related to generics. // - // Use m_wBaseSize whenever this is identical to GetNumInstanceFieldBytes. - // We would need to reserve a flag for this. - // inline DWORD GetNumInstanceFieldBytes(); + // Returns the size of the instance fields for a value type, in bytes when + // the type is known to contain GC pointers. This takes advantage of the detail + // that if the type contains GC pointers, the size of the instance fields is aligned + // to pointer sized boundaries. This is only faster if we already have some reason + // to have checked for ContainsGCPointers. + inline DWORD GetNumInstanceFieldBytesIfContainsGCPointers() + { + LIMITED_METHOD_DAC_CONTRACT; + _ASSERTE(ContainsGCPointers()); + _ASSERTE(GetBaseSize() - (DWORD)(2 * sizeof(TADDR)) == GetNumInstanceFieldBytes()); + return GetBaseSize() - (DWORD)(2 * sizeof(TADDR)); + } + int GetFieldAlignmentRequirement(); inline WORD GetNumIntroducedInstanceFields(); @@ -2676,7 +2686,6 @@ class MethodTable OBJECTREF Box(void* data); OBJECTREF FastBox(void** data); #ifndef DACCESS_COMPILE - BOOL UnBoxInto(void *dest, OBJECTREF src); void UnBoxIntoUnchecked(void *dest, OBJECTREF src); #endif @@ -2766,7 +2775,7 @@ class MethodTable } // Returns true if this type is Nullable for some T. - inline BOOL IsNullable() + inline BOOL IsNullable() const { LIMITED_METHOD_DAC_CONTRACT; return GetFlag(enum_flag_Category_Mask) == enum_flag_Category_Nullable; @@ -2982,6 +2991,37 @@ class MethodTable OBJECTREF GetManagedClassObject(); OBJECTREF GetManagedClassObjectIfExists(); + // ------------------------------------------------------------------ + // Details about Nullable MethodTables + // ------------------------------------------------------------------ + UINT32 GetNullableValueAddrOffset() const + { + LIMITED_METHOD_DAC_CONTRACT; + _ASSERTE(IsNullable()); +#ifndef TARGET_64BIT + return *(BYTE*)&m_encodedNullableUnboxData; +#else + return *(UINT32*)&m_encodedNullableUnboxData; +#endif + } + + UINT32 GetNullableValueSize() const + { + LIMITED_METHOD_DAC_CONTRACT; + _ASSERTE(IsNullable()); +#ifndef TARGET_64BIT + return (UINT32)(m_encodedNullableUnboxData >> 8); +#else + return (UINT32)(m_encodedNullableUnboxData >> 32); +#endif + } + + UINT32 GetNullableNumInstanceFieldBytes() const + { + LIMITED_METHOD_DAC_CONTRACT; + _ASSERTE(IsNullable()); + return GetNullableValueAddrOffset() + GetNullableValueSize(); + } // ------------------------------------------------------------------ // Private part of MethodTable @@ -3799,6 +3839,33 @@ public : return (m_dwFlags2 & (DWORD)mask) == (DWORD)flag; } +#ifndef DACCESS_COMPILE + void SetNullableDetails(UINT16 offsetToValueField, UINT32 sizeOfValueField) + { + STANDARD_VM_CONTRACT; + _ASSERTE(IsNullable()); +#ifndef TARGET_64BIT + if (sizeOfValueField > 0xFFFFFF) + { + // We can't encode the size of the value field in the Nullable MethodTable + // because it's too large. This is a limitation of the encoding. It is not expected + // to impact any real customers, as Nullable should only be used on the stack + // where having a 16MB local would always be a significant problem. Especially oh + // a 32-bit machine. + ThrowHR(COR_E_TYPELOAD); + } + if (offsetToValueField > 255) + { + // If we get here something completely unexpected has happened. We don't expect alignment greater than 128 + ThrowHR(COR_E_TYPELOAD); + } + m_encodedNullableUnboxData = ((TADDR)sizeOfValueField << 8) | (TADDR)offsetToValueField; +#else + m_encodedNullableUnboxData = ((TADDR)sizeOfValueField << 32) | (TADDR)offsetToValueField; +#endif + } +#endif // DACCESS_COMPILE + private: // Low WORD is component size for array and string types (HasComponentSize() returns true). // Used for flags otherwise. @@ -3857,7 +3924,12 @@ public : TADDR m_ElementTypeHnd; }; public: - PTR_InterfaceInfo m_pInterfaceMap; + union + { + PTR_InterfaceInfo m_pInterfaceMap; + TADDR m_encodedNullableUnboxData; // Used for Nullable to represent the offset to the value field, and the size of the value field + }; + // VTable slots go here diff --git a/src/coreclr/vm/methodtable.inl b/src/coreclr/vm/methodtable.inl index 7710efe98d048..85e8a5fdda883 100644 --- a/src/coreclr/vm/methodtable.inl +++ b/src/coreclr/vm/methodtable.inl @@ -1241,31 +1241,6 @@ inline OBJECTREF MethodTable::AllocateNoChecks() #ifndef DACCESS_COMPILE -//========================================================================================== -// unbox src into dest, making sure src is of the correct type. - -inline BOOL MethodTable::UnBoxInto(void *dest, OBJECTREF src) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_COOPERATIVE; - } - CONTRACTL_END; - - if (Nullable::IsNullableType(TypeHandle(this))) - return Nullable::UnBoxNoGC(dest, src, this); - else - { - if (src == NULL || src->GetMethodTable() != this) - return FALSE; - - CopyValueClass(dest, src->UnBox(), this); - } - return TRUE; -} - //========================================================================================== // unbox src into dest, No checks are done @@ -1280,9 +1255,7 @@ inline void MethodTable::UnBoxIntoUnchecked(void *dest, OBJECTREF src) CONTRACTL_END; if (Nullable::IsNullableType(TypeHandle(this))) { - BOOL ret; - ret = Nullable::UnBoxNoGC(dest, src, this); - _ASSERTE(ret); + Nullable::UnBoxNoCheck(dest, src, this); } else { diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index f4393ea494220..573a3b2ca4f85 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -1892,6 +1892,13 @@ MethodTableBuilder::BuildMethodTableThrowing( // TODO: fix it so that we emit them in the correct order in the first place. if (pMT->ContainsGCPointers()) { +#ifdef _DEBUG + if (pMT->IsValueType()) + { + DWORD baseSizePadding = pMT->GetClass()->GetBaseSizePadding(); + _ASSERTE(baseSizePadding == (sizeof(TADDR) * 2)); // This is dependended on by GetNumInstanceFieldBytesIfContainsGCPointers. + } +#endif // _DEBUG CGCDesc* gcDesc = CGCDesc::GetCGCDescFromMT(pMT); qsort(gcDesc->GetLowestSeries(), (int)gcDesc->GetNumSeries(), sizeof(CGCDescSeries), compareCGCDescSeries); } @@ -10061,8 +10068,26 @@ void MethodTableBuilder::CheckForSystemTypes() // Pre-compute whether the class is a Nullable so that code:Nullable::IsNullableType is efficient // This is useful to the performance of boxing/unboxing a Nullable if (GetCl() == g_pNullableClass->GetCl()) + { pMT->SetIsNullable(); + // Capure Nullable specific details into the MethodTable for better unboxing performance + FieldDesc* pFDValue = &pMT->GetApproxFieldDescListRaw()[1]; + _ASSERTE(strcmp(pFDValue->GetDebugName(), "value") == 0); + UINT32 offset = pFDValue->GetOffset(); + if (offset > 0xFF) + { + BuildMethodTableThrowException(IDS_CLASSLOAD_FIELDTOOLARGE); + } + + TypeHandle thValueFieldType = pFDValue->GetApproxFieldTypeHandleThrowing(); + if (!thValueFieldType.IsTypeDesc()) // Non-MethodTable cases can only happen when the size doesn't matter, such as for type variables. + { + pMT->SetNullableDetails((UINT8)offset, thValueFieldType.AsMethodTable()->GetNumInstanceFieldBytes()); + _ASSERTE(pMT->GetNullableNumInstanceFieldBytes() == pMT->GetNumInstanceFieldBytes()); + } + } + return; } } diff --git a/src/coreclr/vm/object.cpp b/src/coreclr/vm/object.cpp index 050f16e09ad04..8955474a85cb9 100644 --- a/src/coreclr/vm/object.cpp +++ b/src/coreclr/vm/object.cpp @@ -366,11 +366,12 @@ void STDCALL CopyValueClassUnchecked(void* dest, void* src, MethodTable *pMT) if (pMT->ContainsGCPointers()) { - memmoveGCRefs(dest, src, pMT->GetNumInstanceFieldBytes()); + memmoveGCRefs(dest, src, pMT->GetNumInstanceFieldBytesIfContainsGCPointers()); } else { - switch (pMT->GetNumInstanceFieldBytes()) + DWORD numInstanceFieldBytes = pMT->GetNumInstanceFieldBytes(); + switch (numInstanceFieldBytes) { case 1: *(UINT8*)dest = *(UINT8*)src; @@ -391,7 +392,7 @@ void STDCALL CopyValueClassUnchecked(void* dest, void* src, MethodTable *pMT) break; #endif // !ALIGN_ACCESS default: - memcpyNoGCRefs(dest, src, pMT->GetNumInstanceFieldBytes()); + memcpyNoGCRefs(dest, src, numInstanceFieldBytes); break; } } @@ -1599,19 +1600,6 @@ BOOL Nullable::IsNullableForTypeHelper(MethodTable* nullableMT, MethodTable* par return TypeHandle(paramMT).IsEquivalentTo(nullableMT->GetInstantiation()[0]); } -//=============================================================================== -// Returns true if nullableMT is Nullable for T == paramMT - -BOOL Nullable::IsNullableForTypeHelperNoGC(MethodTable* nullableMT, MethodTable* paramMT) -{ - LIMITED_METHOD_CONTRACT; - if (!nullableMT->IsNullable()) - return FALSE; - - // we require an exact match of the parameter types - return TypeHandle(paramMT) == nullableMT->GetInstantiation()[0]; -} - //=============================================================================== int32_t Nullable::GetValueAddrOffset(MethodTable* nullableMT) { @@ -1619,7 +1607,8 @@ int32_t Nullable::GetValueAddrOffset(MethodTable* nullableMT) _ASSERTE(IsNullableType(nullableMT)); _ASSERTE(strcmp(nullableMT->GetApproxFieldDescListRaw()[1].GetDebugName(), "value") == 0); - return nullableMT->GetApproxFieldDescListRaw()[1].GetOffset(); + _ASSERTE(nullableMT->GetApproxFieldDescListRaw()[1].GetOffset() == nullableMT->GetNullableValueAddrOffset()); + return nullableMT->GetNullableValueAddrOffset(); } CLR_BOOL* Nullable::HasValueAddr(MethodTable* nullableMT) { @@ -1637,7 +1626,8 @@ void* Nullable::ValueAddr(MethodTable* nullableMT) { LIMITED_METHOD_CONTRACT; _ASSERTE(strcmp(nullableMT->GetApproxFieldDescListRaw()[1].GetDebugName(), "value") == 0); - return (((BYTE*) this) + nullableMT->GetApproxFieldDescListRaw()[1].GetOffset()); + _ASSERTE(nullableMT->GetApproxFieldDescListRaw()[1].GetOffset() == nullableMT->GetNullableValueAddrOffset()); + return (((BYTE*) this) + nullableMT->GetNullableValueAddrOffset()); } //=============================================================================== @@ -1736,53 +1726,6 @@ BOOL Nullable::UnBox(void* destPtr, OBJECTREF boxedVal, MethodTable* destMT) return fRet; } -//=============================================================================== -// Special Logic to unbox a boxed T as a nullable -// Does not handle type equivalence (may conservatively return FALSE) -BOOL Nullable::UnBoxNoGC(void* destPtr, OBJECTREF boxedVal, MethodTable* destMT) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_COOPERATIVE; - } - CONTRACTL_END; - Nullable* dest = (Nullable*) destPtr; - - // We should only get here if we are unboxing a T as a Nullable - _ASSERTE(IsNullableType(destMT)); - - // We better have a concrete instantiation, or our field offset asserts are not useful - _ASSERTE(!destMT->ContainsGenericVariables()); - - if (boxedVal == NULL) - { - // Logically we are doing *dest->HasValueAddr(destMT) = false; - // We zero out the whole structure because it may contain GC references - // and these need to be initialized to zero. (could optimize in the non-GC case) - InitValueClass(destPtr, destMT); - } - else - { - if (!IsNullableForTypeNoGC(destMT, boxedVal->GetMethodTable())) - { - // For safety's sake, also allow true nullables to be unboxed normally. - // This should not happen normally, but we want to be robust - if (destMT == boxedVal->GetMethodTable()) - { - CopyValueClass(dest, boxedVal->GetData(), destMT); - return TRUE; - } - return FALSE; - } - - *dest->HasValueAddr(destMT) = true; - CopyValueClass(dest->ValueAddr(destMT), boxedVal->UnBox(), boxedVal->GetMethodTable()); - } - return TRUE; -} - //=============================================================================== // Special Logic to unbox a boxed T as a nullable // Does not do any type checks. @@ -1817,6 +1760,7 @@ void Nullable::UnBoxNoCheck(void* destPtr, OBJECTREF boxedVal, MethodTable* dest // For safety's sake, also allow true nullables to be unboxed normally. // This should not happen normally, but we want to be robust CopyValueClass(dest, boxedVal->GetData(), destMT); + return; } *dest->HasValueAddr(destMT) = true; diff --git a/src/coreclr/vm/object.h b/src/coreclr/vm/object.h index fca89bc39b24a..21a44dbaf875b 100644 --- a/src/coreclr/vm/object.h +++ b/src/coreclr/vm/object.h @@ -2545,14 +2545,12 @@ class Nullable { static void CheckFieldOffsets(TypeHandle nullableType); static BOOL IsNullableType(TypeHandle nullableType); static BOOL IsNullableForType(TypeHandle nullableType, MethodTable* paramMT); - static BOOL IsNullableForTypeNoGC(TypeHandle nullableType, MethodTable* paramMT); static OBJECTREF Box(void* src, MethodTable* nullable); static BOOL UnBox(void* dest, OBJECTREF boxedVal, MethodTable* destMT); static BOOL UnBoxNoGC(void* dest, OBJECTREF boxedVal, MethodTable* destMT); static void UnBoxNoCheck(void* dest, OBJECTREF boxedVal, MethodTable* destMT); static OBJECTREF BoxedNullableNull(TypeHandle nullableType) { return NULL; } - // if 'Obj' is a true boxed nullable, return the form we want (either null or a boxed T) static OBJECTREF NormalizeBox(OBJECTREF obj); @@ -2572,7 +2570,6 @@ class Nullable { private: static BOOL IsNullableForTypeHelper(MethodTable* nullableMT, MethodTable* paramMT); - static BOOL IsNullableForTypeHelperNoGC(MethodTable* nullableMT, MethodTable* paramMT); CLR_BOOL* HasValueAddr(MethodTable* nullableMT); void* ValueAddr(MethodTable* nullableMT); diff --git a/src/coreclr/vm/object.inl b/src/coreclr/vm/object.inl index c78222529c309..0c45e030a9024 100644 --- a/src/coreclr/vm/object.inl +++ b/src/coreclr/vm/object.inl @@ -215,18 +215,6 @@ __forceinline BOOL Nullable::IsNullableForType(TypeHandle type, MethodTable* par return Nullable::IsNullableForTypeHelper(type.AsMethodTable(), paramMT); } -//=============================================================================== -// Returns true if this pMT is Nullable for T == paramMT - -__forceinline BOOL Nullable::IsNullableForTypeNoGC(TypeHandle type, MethodTable* paramMT) -{ - if (type.IsTypeDesc()) - return FALSE; - if (!type.AsMethodTable()->HasInstantiation()) // shortcut, if it is not generic it can't be Nullable - return FALSE; - return Nullable::IsNullableForTypeHelperNoGC(type.AsMethodTable(), paramMT); -} - //=============================================================================== // Returns true if this type is Nullable for some T. diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index cdbf839914cd2..4c6b39a158f45 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -505,6 +505,7 @@ static const Entry s_QCall[] = DllImportEntry(GetThreadStaticsByMethodTable) DllImportEntry(GetThreadStaticsByIndex) DllImportEntry(GenericHandleWorker) + DllImportEntry(ThrowInvalidCastException) }; const void* QCallResolveDllImport(const char* name)