From 957ac35b14f2a8e80776c9b4a6bf1a93b1214b72 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Wed, 26 Jul 2023 13:57:46 -0700 Subject: [PATCH 1/5] Refactored AssignmentVariation --- .../src/System/Runtime/TypeCast.cs | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/TypeCast.cs b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/TypeCast.cs index 364a248158e73..8f13dc6de1438 100644 --- a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/TypeCast.cs +++ b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/TypeCast.cs @@ -37,17 +37,22 @@ internal static class TypeCast [Flags] internal enum AssignmentVariation { - Normal = 0, + /// + /// Conversion from an object. In the terminology of ECMA335 the relationship is called "compatible-with". + /// This is the relation used by castclass and isinst (III.4.3). + /// Value types are compatible with Object, ValueType and Enum (if applicable) and implemented interfaces. + /// + BoxedSource = 0, /// - /// Assume the source type is boxed so that value types and enums are compatible with Object, ValueType - /// and Enum (if applicable) + /// Type compatibility of unboxed types. Used for checking compatibility of type parameters. + /// Value types are compatible only if equivalent. /// - BoxedSource = 1, + Unboxed = 1, /// - /// Allow identically sized integral types and enums to be considered equivalent (currently used only for - /// array element types) + /// Allow identically sized integral types and enums to be considered equivalent. + /// Used when checking type compatibility of array element types. /// AllowSizeEquivalence = 2, } @@ -518,7 +523,7 @@ internal static unsafe bool TypeParametersAreCompatible(int arity, // class Foo : ICovariant is ICovariant // class Foo : ICovariant is ICovariant - if (!AreTypesAssignableInternal(pSourceArgType, pTargetArgType, AssignmentVariation.Normal, pVisited)) + if (!AreTypesAssignableInternal(pSourceArgType, pTargetArgType, AssignmentVariation.Unboxed, pVisited)) return false; break; @@ -548,7 +553,7 @@ internal static unsafe bool TypeParametersAreCompatible(int arity, // class Foo : IContravariant is IContravariant // class Foo : IContravariant is IContravariant - if (!AreTypesAssignableInternal(pTargetArgType, pSourceArgType, AssignmentVariation.Normal, pVisited)) + if (!AreTypesAssignableInternal(pTargetArgType, pSourceArgType, AssignmentVariation.Unboxed, pVisited)) return false; break; @@ -600,7 +605,7 @@ public static unsafe bool AreTypesAssignable(MethodTable* pSourceType, MethodTab // equivalent (currently used only for array element types) internal static unsafe bool AreTypesAssignableInternalUncached(MethodTable* pSourceType, MethodTable* pTargetType, AssignmentVariation variation, EETypePairList* pVisited) { - bool fBoxedSource = ((variation & AssignmentVariation.BoxedSource) == AssignmentVariation.BoxedSource); + bool fBoxedSource = (variation == AssignmentVariation.BoxedSource); bool fAllowSizeEquivalence = ((variation & AssignmentVariation.AllowSizeEquivalence) == AssignmentVariation.AllowSizeEquivalence); // From 7cdbbaaddf0b4a8625e6b029690a7e9a55e380b4 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Wed, 26 Jul 2023 18:14:41 -0700 Subject: [PATCH 2/5] refactored similar to coreclr --- .../Runtime/CompilerServices/CastHelpers.cs | 2 + .../src/System/Runtime/RuntimeExports.cs | 18 +- .../src/System/Runtime/TypeCast.cs | 1274 +++++++++-------- .../src/System/Runtime/RuntimeImports.cs | 6 +- .../Internal/Runtime/ReadyToRunConstants.cs | 6 +- .../ILCompiler.Compiler/Compiler/JitHelper.cs | 25 +- .../JitInterface/CorInfoImpl.RyuJit.cs | 21 +- 7 files changed, 702 insertions(+), 650 deletions(-) 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 0292c992a94f3..b41ccd778eb74 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 @@ -333,6 +333,8 @@ internal static unsafe class CastHelpers return ChkCastClassSpecial(toTypeHnd, obj); } + // Optimized helper for classes. Assumes that the trivial cases + // has been taken care of by the inlined check [DebuggerHidden] [StackTraceHidden] [DebuggerStepThrough] diff --git a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/RuntimeExports.cs b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/RuntimeExports.cs index 9a2aea0839130..404ebdc46d40b 100644 --- a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/RuntimeExports.cs +++ b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/RuntimeExports.cs @@ -194,7 +194,7 @@ public static unsafe void RhUnboxAny(object? o, ref byte data, EETypePtr pUnboxT } else { - if (o != null && (TypeCast.IsInstanceOf(ptrUnboxToEEType, o) == null)) + if (o != null && (TypeCast.IsInstanceOfAny(ptrUnboxToEEType, o) == null)) { throw ptrUnboxToEEType->GetClasslibException(ExceptionIDs.InvalidCast); } @@ -392,26 +392,22 @@ internal static unsafe IntPtr RhGetRuntimeHelperForType(MethodTable* pEEType, Ru return (IntPtr)(delegate*)&InternalCalls.RhpNewFast; case RuntimeHelperKind.IsInst: - if (pEEType->IsArray) - return (IntPtr)(delegate*)&TypeCast.IsInstanceOfArray; - else if (pEEType->HasGenericVariance) - return (IntPtr)(delegate*)&TypeCast.IsInstanceOf; + if (pEEType->IsArray || pEEType->HasGenericVariance) + return (IntPtr)(delegate*)&TypeCast.IsInstanceOfAny; else if (pEEType->IsInterface) return (IntPtr)(delegate*)&TypeCast.IsInstanceOfInterface; else if (pEEType->IsParameterizedType || pEEType->IsFunctionPointerType) - return (IntPtr)(delegate*)&TypeCast.IsInstanceOf; // Array handled above; pointers and byrefs handled here + return (IntPtr)(delegate*)&TypeCast.IsInstanceOfAny; // Array handled above; pointers and byrefs handled here else return (IntPtr)(delegate*)&TypeCast.IsInstanceOfClass; case RuntimeHelperKind.CastClass: - if (pEEType->IsArray) - return (IntPtr)(delegate*)&TypeCast.CheckCastArray; - else if (pEEType->HasGenericVariance) - return (IntPtr)(delegate*)&TypeCast.CheckCast; + if (pEEType->IsArray || pEEType->HasGenericVariance) + return (IntPtr)(delegate*)&TypeCast.CheckCastAny; else if (pEEType->IsInterface) return (IntPtr)(delegate*)&TypeCast.CheckCastInterface; else if (pEEType->IsParameterizedType || pEEType->IsFunctionPointerType) - return (IntPtr)(delegate*)&TypeCast.CheckCast; // Array handled above; pointers and byrefs handled here + return (IntPtr)(delegate*)&TypeCast.CheckCastAny; // Array handled above; pointers and byrefs handled here else return (IntPtr)(delegate*)&TypeCast.CheckCastClass; diff --git a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/TypeCast.cs b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/TypeCast.cs index 8f13dc6de1438..d6cc5ecf06a94 100644 --- a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/TypeCast.cs +++ b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/TypeCast.cs @@ -57,6 +57,125 @@ internal enum AssignmentVariation AllowSizeEquivalence = 2, } + // IsInstanceOf test used for unusual cases (naked type parameters, variant generic types) + // Unlike the IsInstanceOfInterface and IsInstanceOfClass functions, + // this test must deal with all kinds of type tests + [RuntimeExport("RhTypeCast_IsInstanceOfAny")] + public static unsafe object? IsInstanceOfAny(MethodTable* pTargetType, object? obj) + { + if (obj != null) + { + MethodTable* mt = obj.GetMethodTable(); + if (mt != pTargetType) + { + CastResult result = s_castCache.TryGet((nuint)mt + (int)AssignmentVariation.BoxedSource, (nuint)pTargetType); + if (result == CastResult.CanCast) + { + // do nothing + } + else if (result == CastResult.CannotCast) + { + obj = null; + } + else + { + goto slowPath; + } + } + } + + return obj; + + slowPath: + // fall through to the slow helper + return IsInstanceOfAny_NoCacheLookup(pTargetType, obj); + } + + [RuntimeExport("RhTypeCast_IsInstanceOfInterface")] + public static unsafe object? IsInstanceOfInterface(MethodTable* pTargetType, object? obj) + { + Debug.Assert(pTargetType->IsInterface); + Debug.Assert(!pTargetType->HasGenericVariance); + + const int unrollSize = 4; + + if (obj != null) + { + MethodTable* mt = obj.GetMethodTable(); + nint interfaceCount = mt->NumInterfaces; + if (interfaceCount != 0) + { + MethodTable** interfaceMap = mt->InterfaceMap; + if (interfaceCount < unrollSize) + { + // If not enough for unrolled, jmp straight to small loop + // as we already know there is one or more interfaces so don't need to check again. + goto few; + } + + do + { + if (interfaceMap[0] == pTargetType || + interfaceMap[1] == pTargetType || + interfaceMap[2] == pTargetType || + interfaceMap[3] == pTargetType) + { + goto done; + } + + interfaceMap += unrollSize; + interfaceCount -= unrollSize; + } while (interfaceCount >= unrollSize); + + if (interfaceCount == 0) + { + // If none remaining, skip the short loop + goto extra; + } + + few: + do + { + if (interfaceMap[0] == pTargetType) + { + goto done; + } + + // Assign next offset + interfaceMap++; + interfaceCount--; + } while (interfaceCount > 0); + + extra: + if (mt->IsIDynamicInterfaceCastable) + { + goto slowPath; + } + } + + obj = null; + } + + done: + + return obj; + + slowPath: + + return IsInstanceOfInterface_Helper(pTargetType, obj); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static unsafe object? IsInstanceOfInterface_Helper(MethodTable* pTargetType, object? obj) + { + // If object type implements IDynamicInterfaceCastable then there's one more way to check whether it implements + // the interface. + if (!IsInstanceOfInterfaceViaIDynamicInterfaceCastable(pTargetType, obj, throwing: false)) + obj = null; + + return obj; + } + [RuntimeExport("RhTypeCast_IsInstanceOfClass")] public static unsafe object? IsInstanceOfClass(MethodTable* pTargetType, object? obj) { @@ -121,6 +240,129 @@ internal enum AssignmentVariation return obj; } + [RuntimeExport("RhTypeCast_IsInstanceOfException")] + public static unsafe bool IsInstanceOfException(MethodTable* pTargetType, object? obj) + { + return IsInstanceOfClass(pTargetType, obj) != null; + } + + // ChkCast test used for unusual cases (naked type parameters, variant generic types) + // Unlike the ChkCastInterface and ChkCastClass functions, + // this test must deal with all kinds of type tests + [RuntimeExport("RhTypeCast_CheckCastAny")] + public static unsafe object CheckCastAny(MethodTable* pTargetType, object obj) + { + CastResult result; + + if (obj != null) + { + MethodTable* mt = obj.GetMethodTable(); + if (mt != pTargetType) + { + result = s_castCache.TryGet((nuint)mt, (nuint)pTargetType); + if (result != CastResult.CanCast) + { + goto slowPath; + } + } + } + + return obj; + + slowPath: + // fall through to the slow helper + object objRet = ChekCastAny_NoCacheLookup(pTargetType, obj); + // Make sure that the fast helper have not lied + Debug.Assert(result != CastResult.CannotCast); + return objRet; + } + + [RuntimeExport("RhTypeCast_CheckCastInterface")] + public static unsafe object CheckCastInterface(MethodTable* pTargetType, object obj) + { + Debug.Assert(pTargetType->IsInterface); + Debug.Assert(!pTargetType->HasGenericVariance); + + const int unrollSize = 4; + + if (obj != null) + { + MethodTable* mt = obj.GetMethodTable(); + nint interfaceCount = mt->NumInterfaces; + if (interfaceCount == 0) + { + goto slowPath; + } + + MethodTable** interfaceMap = mt->InterfaceMap; + if (interfaceCount < unrollSize) + { + // If not enough for unrolled, jmp straight to small loop + // as we already know there is one or more interfaces so don't need to check again. + goto few; + } + + do + { + if (interfaceMap[0] == pTargetType || + interfaceMap[1] == pTargetType || + interfaceMap[2] == pTargetType || + interfaceMap[3] == pTargetType) + { + goto done; + } + + // Assign next offset + interfaceMap += unrollSize; + interfaceCount -= unrollSize; + } while (interfaceCount >= unrollSize); + + if (interfaceCount == 0) + { + // If none remaining, skip the short loop + goto slowPath; + } + + few: + do + { + if (interfaceMap[0] == pTargetType) + { + goto done; + } + + // Assign next offset + interfaceMap++; + interfaceCount--; + } while (interfaceCount > 0); + + goto slowPath; + } + + done: + return obj; + + slowPath: + + return CheckCastInterface_Helper(pTargetType, obj); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static unsafe object CheckCastInterface_Helper(MethodTable* pTargetType, object obj) + { + // If object type implements IDynamicInterfaceCastable then there's one more way to check whether it implements + // the interface. + if (obj.GetMethodTable()->IsIDynamicInterfaceCastable + && IsInstanceOfInterfaceViaIDynamicInterfaceCastable(pTargetType, obj, throwing: true)) + { + return obj; + } + + // Throw the invalid cast exception defined by the classlib, using the input MethodTable* to find the + // correct classlib. + return ThrowInvalidCastException(pTargetType); + } + [RuntimeExport("RhTypeCast_CheckCastClass")] public static unsafe object CheckCastClass(MethodTable* pTargetType, object obj) { @@ -137,6 +379,8 @@ public static unsafe object CheckCastClass(MethodTable* pTargetType, object obj) return CheckCastClassSpecial(pTargetType, obj); } + // Optimized helper for classes. Assumes that the trivial cases + // has been taken care of by the inlined check [RuntimeExport("RhTypeCast_CheckCastClassSpecial")] private static unsafe object CheckCastClassSpecial(MethodTable* pTargetType, object obj) { @@ -201,163 +445,41 @@ private static unsafe object CheckCastClassSpecial(MethodTable* pTargetType, obj return ThrowInvalidCastException(pTargetType); } - [RuntimeExport("RhTypeCast_IsInstanceOfArray")] - public static unsafe object IsInstanceOfArray(MethodTable* pTargetType, object obj) + private static unsafe bool IsInstanceOfInterfaceViaIDynamicInterfaceCastable(MethodTable* pTargetType, object obj, bool throwing) { - if (obj == null) - { - return null; - } + var pfnIsInterfaceImplemented = (delegate*) + pTargetType->GetClasslibFunction(ClassLibFunctionId.IDynamicCastableIsInterfaceImplemented); + return pfnIsInterfaceImplemented(obj, pTargetType, throwing); + } - MethodTable* pObjType = obj.GetMethodTable(); + internal static unsafe bool IsDerived(MethodTable* pDerivedType, MethodTable* pBaseType) + { + Debug.Assert(!pDerivedType->IsArray, "did not expect array type"); + Debug.Assert(!pDerivedType->IsParameterizedType, "did not expect parameterType"); + Debug.Assert(!pDerivedType->IsFunctionPointerType, "did not expect function pointer"); + Debug.Assert(!pBaseType->IsArray, "did not expect array type"); + Debug.Assert(!pBaseType->IsInterface, "did not expect interface type"); + Debug.Assert(!pBaseType->IsParameterizedType, "did not expect parameterType"); + Debug.Assert(!pBaseType->IsFunctionPointerType, "did not expect function pointer"); + Debug.Assert(pBaseType->IsCanonical || pBaseType->IsGenericTypeDefinition, "unexpected MethodTable"); + Debug.Assert(pDerivedType->IsCanonical || pDerivedType->IsGenericTypeDefinition, "unexpected MethodTable"); - Debug.Assert(pTargetType->IsArray, "IsInstanceOfArray called with non-array MethodTable"); + // If a generic type definition reaches this function, then the function should return false unless the types are equivalent. + // This works as the NonArrayBaseType of a GenericTypeDefinition is always null. - // if the types match, we are done - if (pObjType == pTargetType) + do { - return obj; - } + if (pDerivedType == pBaseType) + return true; - // if the object is not an array, we're done - if (!pObjType->IsArray) - { - return null; + pDerivedType = pDerivedType->NonArrayBaseType; } + while (pDerivedType != null); - // compare the array types structurally + return false; + } - if (pObjType->ParameterizedTypeShape != pTargetType->ParameterizedTypeShape) - { - // If the shapes are different, there's one more case to check for: Casting SzArray to MdArray rank 1. - if (!pObjType->IsSzArray || pTargetType->ArrayRank != 1) - { - return null; - } - } - - if (AreTypesAssignableInternal(pObjType->RelatedParameterType, pTargetType->RelatedParameterType, - AssignmentVariation.AllowSizeEquivalence, null)) - { - return obj; - } - - return null; - } - - [RuntimeExport("RhTypeCast_CheckCastArray")] - public static unsafe object CheckCastArray(MethodTable* pTargetEEType, object obj) - { - // a null value can be cast to anything - if (obj == null) - return null; - - object result = IsInstanceOfArray(pTargetEEType, obj); - - if (result == null) - { - // Throw the invalid cast exception defined by the classlib, using the input MethodTable* - // to find the correct classlib. - - return ThrowInvalidCastException(pTargetEEType); - } - - return result; - } - - [RuntimeExport("RhTypeCast_IsInstanceOfInterface")] - public static unsafe object? IsInstanceOfInterface(MethodTable* pTargetType, object? obj) - { - Debug.Assert(pTargetType->IsInterface); - Debug.Assert(!pTargetType->HasGenericVariance); - - const int unrollSize = 4; - - if (obj != null) - { - MethodTable* mt = obj.GetMethodTable(); - nint interfaceCount = mt->NumInterfaces; - if (interfaceCount != 0) - { - MethodTable** interfaceMap = mt->InterfaceMap; - if (interfaceCount < unrollSize) - { - // If not enough for unrolled, jmp straight to small loop - // as we already know there is one or more interfaces so don't need to check again. - goto few; - } - - do - { - if (interfaceMap[0] == pTargetType || - interfaceMap[1] == pTargetType || - interfaceMap[2] == pTargetType || - interfaceMap[3] == pTargetType) - { - goto done; - } - - interfaceMap += unrollSize; - interfaceCount -= unrollSize; - } while (interfaceCount >= unrollSize); - - if (interfaceCount == 0) - { - // If none remaining, skip the short loop - goto extra; - } - - few: - do - { - if (interfaceMap[0] == pTargetType) - { - goto done; - } - - // Assign next offset - interfaceMap++; - interfaceCount--; - } while (interfaceCount > 0); - - extra: - if (mt->IsIDynamicInterfaceCastable) - { - goto slowPath; - } - } - - obj = null; - } - - done: - - return obj; - - slowPath: - - return IsInstanceOfInterface_Helper(pTargetType, obj); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private static unsafe object? IsInstanceOfInterface_Helper(MethodTable* pTargetType, object? obj) - { - // If object type implements IDynamicInterfaceCastable then there's one more way to check whether it implements - // the interface. - if (!IsInstanceOfInterfaceViaIDynamicInterfaceCastable(pTargetType, obj, throwing: false)) - obj = null; - - return obj; - } - - private static unsafe bool IsInstanceOfInterfaceViaIDynamicInterfaceCastable(MethodTable* pTargetType, object obj, bool throwing) - { - var pfnIsInterfaceImplemented = (delegate*) - pTargetType->GetClasslibFunction(ClassLibFunctionId.IDynamicCastableIsInterfaceImplemented); - return pfnIsInterfaceImplemented(obj, pTargetType, throwing); - } - - internal static unsafe bool ImplementsInterface(MethodTable* pObjType, MethodTable* pTargetType, EETypePairList* pVisited) + private static unsafe bool ImplementsInterface(MethodTable* pObjType, MethodTable* pTargetType, EETypePairList* pVisited) { Debug.Assert(!pTargetType->IsParameterizedType, "did not expect parameterized type"); Debug.Assert(!pTargetType->IsFunctionPointerType, "did not expect function pointer type"); @@ -567,388 +689,116 @@ internal static unsafe bool TypeParametersAreCompatible(int arity, return true; } - // - // Determines if a value of the source type can be assigned to a location of the target type. - // It does not handle IDynamicInterfaceCastable, and cannot since we do not have an actual object instance here. - // This routine assumes that the source type is boxed, i.e. a value type source is presumed to be - // compatible with Object and ValueType and an enum source is additionally compatible with Enum. - // - [RuntimeExport("RhTypeCast_AreTypesAssignable")] - public static unsafe bool AreTypesAssignable(MethodTable* pSourceType, MethodTable* pTargetType) + [RuntimeExport("RhTypeCast_CheckArrayStore")] + public static unsafe void CheckArrayStore(object array, object obj) { - // Special case: Generic Type definitions are not assignable in a mrt sense - // in any way. Assignability of those types is handled by reflection logic. - // Call this case out first and here so that these only somewhat filled in - // types do not leak into the rest of the type casting logic. - if (pTargetType->IsGenericTypeDefinition || pSourceType->IsGenericTypeDefinition) + if (array == null || obj == null) { - return false; + return; } - // Special case: T can be cast to Nullable (where T is a value type). Call this case out here - // since this is only applicable if T is boxed, which is not true for any other callers of - // AreTypesAssignableInternal, so no sense making all the other paths pay the cost of the check. - if (pTargetType->IsNullable && pSourceType->IsValueType && !pSourceType->IsNullable) - { - MethodTable* pNullableType = pTargetType->NullableType; + Debug.Assert(array.GetMethodTable()->IsArray, "first argument must be an array"); - return pSourceType == pNullableType; - } + MethodTable* arrayElemType = array.GetMethodTable()->RelatedParameterType; + if (AreTypesAssignableInternal(obj.GetMethodTable(), arrayElemType, AssignmentVariation.BoxedSource, null)) + return; - return AreTypesAssignableInternal(pSourceType, pTargetType, AssignmentVariation.BoxedSource, null); + // If object type implements IDynamicInterfaceCastable then there's one more way to check whether it implements + // the interface. + if (obj.GetMethodTable()->IsIDynamicInterfaceCastable && IsInstanceOfInterfaceViaIDynamicInterfaceCastable(arrayElemType, obj, throwing: false)) + return; + + // Throw the array type mismatch exception defined by the classlib, using the input array's MethodTable* + // to find the correct classlib. + + throw array.GetMethodTable()->GetClasslibException(ExceptionIDs.ArrayTypeMismatch); } - // Internally callable version of the export method above. Has two additional flags: - // fBoxedSource : assume the source type is boxed so that value types and enums are - // compatible with Object, ValueType and Enum (if applicable) - // fAllowSizeEquivalence : allow identically sized integral types and enums to be considered - // equivalent (currently used only for array element types) - internal static unsafe bool AreTypesAssignableInternalUncached(MethodTable* pSourceType, MethodTable* pTargetType, AssignmentVariation variation, EETypePairList* pVisited) + internal struct ArrayElement { - bool fBoxedSource = (variation == AssignmentVariation.BoxedSource); - bool fAllowSizeEquivalence = ((variation & AssignmentVariation.AllowSizeEquivalence) == AssignmentVariation.AllowSizeEquivalence); + public object Value; + } - // - // Are the types identical? - // - if (pSourceType == pTargetType) - return true; + // + // Array stelem/ldelema helpers with RyuJIT conventions + // + [RuntimeExport("RhpStelemRef")] + public static unsafe void StelemRef(Array array, nint index, object obj) + { + // This is supported only on arrays + Debug.Assert(array.GetMethodTable()->IsArray, "first argument must be an array"); - // - // Handle cast to interface cases. - // - if (pTargetType->IsInterface) +#if INPLACE_RUNTIME + // this will throw appropriate exceptions if array is null or access is out of range. + ref object element = ref Unsafe.As(array)[index].Value; +#else + if (array is null) { - // Value types can only be cast to interfaces if they're boxed. - if (!fBoxedSource && pSourceType->IsValueType) - return false; + // TODO: If both array and obj are null, we're likely going to throw Redhawk's NullReferenceException. + // This should blame the caller. + throw obj.GetMethodTable()->GetClasslibException(ExceptionIDs.NullReference); + } + if ((uint)index >= (uint)array.Length) + { + throw array.GetMethodTable()->GetClasslibException(ExceptionIDs.IndexOutOfRange); + } + ref object rawData = ref Unsafe.As(ref Unsafe.As(array).Data); + ref object element = ref Unsafe.Add(ref rawData, index); +#endif - if (ImplementsInterface(pSourceType, pTargetType, pVisited)) - return true; + MethodTable* elementType = array.GetMethodTable()->RelatedParameterType; - // Are the types compatible due to generic variance? - if (pTargetType->HasGenericVariance && pSourceType->HasGenericVariance) - return TypesAreCompatibleViaGenericVariance(pSourceType, pTargetType, pVisited); + if (obj == null) + goto assigningNull; - return false; - } - if (pSourceType->IsInterface) + if (elementType != obj.GetMethodTable()) + goto notExactMatch; + + doWrite: + InternalCalls.RhpAssignRef(ref element, obj); + return; + + assigningNull: + element = null; + return; + + notExactMatch: +#if INPLACE_RUNTIME + // This optimization only makes sense for inplace runtime where there's only one System.Object. + if (array.GetMethodTable() == MethodTable.Of()) + goto doWrite; +#endif + + StelemRef_Helper(ref element, elementType, obj); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static unsafe void StelemRef_Helper(ref object? element, MethodTable* elementType, object obj) + { + CastResult result = CastCache.TryGet(s_table!, (nuint)obj.GetMethodTable() + (int)AssignmentVariation.BoxedSource, (nuint)elementType); + if (result == CastResult.CanCast) { - // The only non-interface type an interface can be cast to is Object. - return WellKnownEETypes.IsSystemObject(pTargetType); + InternalCalls.RhpAssignRef(ref element, obj); + return; } - // - // Handle cast to array or pointer cases. - // - if (pTargetType->IsParameterizedType) + StelemRef_Helper_NoCacheLookup(ref element, elementType, obj); + } + + private static unsafe void StelemRef_Helper_NoCacheLookup(ref object? element, MethodTable* elementType, object obj) + { + Debug.Assert(obj != null); + + obj = IsInstanceOfAny_NoCacheLookup(elementType, obj); + if (obj != null) { - if (pSourceType->IsParameterizedType - && (pTargetType->ParameterizedTypeShape == pSourceType->ParameterizedTypeShape)) - { - MethodTable* pSourceRelatedParameterType = pSourceType->RelatedParameterType; - // Source type is also a parameterized type. Are the parameter types compatible? - if (pSourceRelatedParameterType->IsPointerType) - { - // If the parameter types are pointers, then only exact matches are correct. - // As we've already compared equality at the start of this function, - // return false as the exact match case has already been handled. - // int** is not compatible with uint**, nor is int*[] oompatible with uint*[]. - return false; - } - else if (pSourceRelatedParameterType->IsByRefType) - { - // Only allow exact matches for ByRef types - same as pointers above. This should - // be unreachable and it's only a defense in depth. ByRefs can't be parameters - // of any parameterized type. - return false; - } - else if (pSourceRelatedParameterType->IsFunctionPointerType) - { - // If the parameter types are function pointers, then only exact matches are correct. - // As we've already compared equality at the start of this function, - // return false as the exact match case has already been handled. - return false; - } - else - { - // Note that using AreTypesAssignableInternal with AssignmentVariation.AllowSizeEquivalence - // here handles array covariance as well as IFoo[] -> Foo[] etc. We are not using - // AssignmentVariation.BoxedSource because int[] is not assignable to object[]. - return AreTypesAssignableInternal(pSourceType->RelatedParameterType, - pTargetType->RelatedParameterType, AssignmentVariation.AllowSizeEquivalence, pVisited); - } - } - - // Can't cast a non-parameter type to a parameter type or a parameter type of different shape to a parameter type - return false; - } - - if (pTargetType->IsFunctionPointerType) - { - // Function pointers only cast if source and target are equivalent types. - return false; - } - - if (pSourceType->IsArray) - { - // Target type is not an array. But we can still cast arrays to Object or System.Array. - return WellKnownEETypes.IsValidArrayBaseType(pTargetType); - } - else if (pSourceType->IsParameterizedType) - { - return false; - } - else if (pSourceType->IsFunctionPointerType) - { - return false; - } - - // - // Handle cast to other (non-interface, non-array) cases. - // - - if (pSourceType->IsValueType) - { - // Certain value types of the same size are treated as equivalent when the comparison is - // between array element types (indicated by fAllowSizeEquivalence). These are integer types - // of the same size (e.g. int and uint) and the base type of enums vs all integer types of the - // same size. - if (fAllowSizeEquivalence && pTargetType->IsPrimitive) - { - if (GetNormalizedIntegralArrayElementType(pSourceType) == GetNormalizedIntegralArrayElementType(pTargetType)) - return true; - - // Non-identical value types aren't equivalent in any other case (since value types are - // sealed). - return false; - } - - // If the source type is a value type but it's not boxed then we've run out of options: the types - // are not identical, the target type isn't an interface and we're not allowed to check whether - // the target type is a parent of this one since value types are sealed and thus the only matches - // would be against Object, ValueType or Enum, all of which are reference types and not compatible - // with non-boxed value types. - if (!fBoxedSource) - return false; - } - - // Sub case of casting between two instantiations of the same delegate type where one or more of - // the type parameters have variance. Only interfaces and delegate types can have variance over - // their type parameters and we know that neither type is an interface due to checks above. - if (pTargetType->HasGenericVariance && pSourceType->HasGenericVariance) - { - // We've dealt with the identical case at the start of this method. And the regular path below - // will handle casting to Object, Delegate and MulticastDelegate. Since we don't support - // deriving from user delegate classes any further all we have to check here is that the - // uninstantiated generic delegate definitions are the same and the type parameters are - // compatible. - return TypesAreCompatibleViaGenericVariance(pSourceType, pTargetType, pVisited); - } - - // Is the source type derived from the target type? - if (IsDerived(pSourceType, pTargetType)) - return true; - - return false; - } - - [RuntimeExport("RhTypeCast_CheckCastInterface")] - public static unsafe object CheckCastInterface(MethodTable* pTargetType, object obj) - { - Debug.Assert(pTargetType->IsInterface); - Debug.Assert(!pTargetType->HasGenericVariance); - - const int unrollSize = 4; - - if (obj != null) - { - MethodTable* mt = obj.GetMethodTable(); - nint interfaceCount = mt->NumInterfaces; - if (interfaceCount == 0) - { - goto slowPath; - } - - MethodTable** interfaceMap = mt->InterfaceMap; - if (interfaceCount < unrollSize) - { - // If not enough for unrolled, jmp straight to small loop - // as we already know there is one or more interfaces so don't need to check again. - goto few; - } - - do - { - if (interfaceMap[0] == pTargetType || - interfaceMap[1] == pTargetType || - interfaceMap[2] == pTargetType || - interfaceMap[3] == pTargetType) - { - goto done; - } - - // Assign next offset - interfaceMap += unrollSize; - interfaceCount -= unrollSize; - } while (interfaceCount >= unrollSize); - - if (interfaceCount == 0) - { - // If none remaining, skip the short loop - goto slowPath; - } - - few: - do - { - if (interfaceMap[0] == pTargetType) - { - goto done; - } - - // Assign next offset - interfaceMap++; - interfaceCount--; - } while (interfaceCount > 0); - - goto slowPath; - } - - done: - return obj; - - slowPath: - - return CheckCastInterface_Helper(pTargetType, obj); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private static unsafe object CheckCastInterface_Helper(MethodTable* pTargetType, object obj) - { - // If object type implements IDynamicInterfaceCastable then there's one more way to check whether it implements - // the interface. - if (obj.GetMethodTable()->IsIDynamicInterfaceCastable - && IsInstanceOfInterfaceViaIDynamicInterfaceCastable(pTargetType, obj, throwing: true)) - { - return obj; - } - - // Throw the invalid cast exception defined by the classlib, using the input MethodTable* to find the - // correct classlib. - return ThrowInvalidCastException(pTargetType); - } - - [RuntimeExport("RhTypeCast_CheckArrayStore")] - public static unsafe void CheckArrayStore(object array, object obj) - { - if (array == null || obj == null) - { - return; - } - - Debug.Assert(array.GetMethodTable()->IsArray, "first argument must be an array"); - - MethodTable* arrayElemType = array.GetMethodTable()->RelatedParameterType; - if (AreTypesAssignableInternal(obj.GetMethodTable(), arrayElemType, AssignmentVariation.BoxedSource, null)) - return; - - // If object type implements IDynamicInterfaceCastable then there's one more way to check whether it implements - // the interface. - if (obj.GetMethodTable()->IsIDynamicInterfaceCastable && IsInstanceOfInterfaceViaIDynamicInterfaceCastable(arrayElemType, obj, throwing: false)) - return; - - // Throw the array type mismatch exception defined by the classlib, using the input array's MethodTable* - // to find the correct classlib. - - throw array.GetMethodTable()->GetClasslibException(ExceptionIDs.ArrayTypeMismatch); - } - - internal struct ArrayElement - { - public object Value; - } - - // - // Array stelem/ldelema helpers with RyuJIT conventions - // - [RuntimeExport("RhpStelemRef")] - public static unsafe void StelemRef(Array array, nint index, object obj) - { - // This is supported only on arrays - Debug.Assert(array.GetMethodTable()->IsArray, "first argument must be an array"); - -#if INPLACE_RUNTIME - // this will throw appropriate exceptions if array is null or access is out of range. - ref object element = ref Unsafe.As(array)[index].Value; -#else - if (array is null) - { - // TODO: If both array and obj are null, we're likely going to throw Redhawk's NullReferenceException. - // This should blame the caller. - throw obj.GetMethodTable()->GetClasslibException(ExceptionIDs.NullReference); - } - if ((uint)index >= (uint)array.Length) - { - throw array.GetMethodTable()->GetClasslibException(ExceptionIDs.IndexOutOfRange); - } - ref object rawData = ref Unsafe.As(ref Unsafe.As(array).Data); - ref object element = ref Unsafe.Add(ref rawData, index); -#endif - - MethodTable* elementType = array.GetMethodTable()->RelatedParameterType; - - if (obj == null) - goto assigningNull; - - if (elementType != obj.GetMethodTable()) - goto notExactMatch; - - doWrite: - InternalCalls.RhpAssignRef(ref element, obj); - return; - - assigningNull: - element = null; - return; - - notExactMatch: -#if INPLACE_RUNTIME - // This optimization only makes sense for inplace runtime where there's only one System.Object. - if (array.GetMethodTable() == MethodTable.Of()) - goto doWrite; -#endif - - StelemRef_Helper(ref element, elementType, obj); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private static unsafe void StelemRef_Helper(ref object element, MethodTable* elementType, object obj) - { - if (AreTypesAssignableInternal(obj.GetMethodTable(), elementType, AssignmentVariation.BoxedSource, null)) - { - InternalCalls.RhpAssignRef(ref element, obj); - } - else - { - // If object type implements IDynamicInterfaceCastable then there's one more way to check whether it implements - // the interface. - if (!obj.GetMethodTable()->IsIDynamicInterfaceCastable || !IsInstanceOfInterfaceViaIDynamicInterfaceCastable(elementType, obj, throwing: false)) - { - // Throw the array type mismatch exception defined by the classlib, using the input array's - // MethodTable* to find the correct classlib. - throw elementType->GetClasslibException(ExceptionIDs.ArrayTypeMismatch); - } InternalCalls.RhpAssignRef(ref element, obj); + return; } - } - // This weird structure is for parity with CoreCLR - allows potentially to be tailcalled - private static unsafe ref object ThrowArrayMismatchException(Array array) - { - // Throw the array type mismatch exception defined by the classlib, using the input array's MethodTable* - // to find the correct classlib. - throw array.GetMethodTable()->GetClasslibException(ExceptionIDs.ArrayTypeMismatch); + // Throw the array type mismatch exception defined by the classlib, using the input array's + // MethodTable* to find the correct classlib. + throw elementType->GetClasslibException(ExceptionIDs.ArrayTypeMismatch); } [RuntimeExport("RhpLdelemaRef")] @@ -983,139 +833,89 @@ public static unsafe ref object LdelemaRef(Array array, nint index, IntPtr eleme return ref ThrowArrayMismatchException(array); } - internal static unsafe bool IsDerived(MethodTable* pDerivedType, MethodTable* pBaseType) + // This weird structure is for parity with CoreCLR - allows potentially to be tailcalled + private static unsafe ref object ThrowArrayMismatchException(Array array) { - Debug.Assert(!pDerivedType->IsArray, "did not expect array type"); - Debug.Assert(!pDerivedType->IsParameterizedType, "did not expect parameterType"); - Debug.Assert(!pDerivedType->IsFunctionPointerType, "did not expect function pointer"); - Debug.Assert(!pBaseType->IsArray, "did not expect array type"); - Debug.Assert(!pBaseType->IsInterface, "did not expect interface type"); - Debug.Assert(!pBaseType->IsParameterizedType, "did not expect parameterType"); - Debug.Assert(!pBaseType->IsFunctionPointerType, "did not expect function pointer"); - Debug.Assert(pBaseType->IsCanonical || pBaseType->IsGenericTypeDefinition, "unexpected MethodTable"); - Debug.Assert(pDerivedType->IsCanonical || pDerivedType->IsGenericTypeDefinition, "unexpected MethodTable"); - - // If a generic type definition reaches this function, then the function should return false unless the types are equivalent. - // This works as the NonArrayBaseType of a GenericTypeDefinition is always null. - - do - { - if (pDerivedType == pBaseType) - return true; - - pDerivedType = pDerivedType->NonArrayBaseType; - } - while (pDerivedType != null); - - return false; + // Throw the array type mismatch exception defined by the classlib, using the input array's MethodTable* + // to find the correct classlib. + throw array.GetMethodTable()->GetClasslibException(ExceptionIDs.ArrayTypeMismatch); } - // this is necessary for shared generic code - Foo may be executing - // for T being an interface, an array or a class - [RuntimeExport("RhTypeCast_IsInstanceOf")] - public static unsafe object IsInstanceOf(MethodTable* pTargetType, object obj) + private static unsafe object IsInstanceOfArray(MethodTable* pTargetType, object obj) { - // @TODO: consider using the cache directly, but beware of IDynamicInterfaceCastable in the interface case - if (pTargetType->IsArray) - return IsInstanceOfArray(pTargetType, obj); - else if (pTargetType->HasGenericVariance) - return IsInstanceOfVariantType(pTargetType, obj); - else if (pTargetType->IsInterface) - return IsInstanceOfInterface(pTargetType, obj); - else if (pTargetType->IsParameterizedType || pTargetType->IsFunctionPointerType) - return null; // We handled arrays above so this is for pointers and byrefs only. - else - return IsInstanceOfClass(pTargetType, obj); - } + MethodTable* pObjType = obj.GetMethodTable(); - private static unsafe object IsInstanceOfVariantType(MethodTable* pTargetType, object obj) - { - if (obj == null) + Debug.Assert(pTargetType->IsArray, "IsInstanceOfArray called with non-array MethodTable"); + + // if the types match, we are done + if (pObjType == pTargetType) { return obj; } - if (!AreTypesAssignableInternal(obj.GetMethodTable(), pTargetType, AssignmentVariation.BoxedSource, null) - && (!obj.GetMethodTable()->IsIDynamicInterfaceCastable - || !IsInstanceOfInterfaceViaIDynamicInterfaceCastable(pTargetType, obj, throwing: false))) + // if the object is not an array, we're done + if (!pObjType->IsArray) { return null; } - return obj; - } - - [RuntimeExport("RhTypeCast_IsInstanceOfException")] - public static unsafe bool IsInstanceOfException(MethodTable* pTargetType, object? obj) - { - // Based on IsInstanceOfClass_Helper - - if (obj == null) - return false; - - MethodTable* pObjType = obj.GetMethodTable(); - - if (pObjType == pTargetType) - return true; - - // arrays can be cast to System.Object and System.Array - if (pObjType->IsArray) - return WellKnownEETypes.IsValidArrayBaseType(pTargetType); + // compare the array types structurally - while (true) + if (pObjType->ParameterizedTypeShape != pTargetType->ParameterizedTypeShape) { - pObjType = pObjType->NonArrayBaseType; - if (pObjType == null) - return false; + // If the shapes are different, there's one more case to check for: Casting SzArray to MdArray rank 1. + if (!pObjType->IsSzArray || pTargetType->ArrayRank != 1) + { + return null; + } + } - if (pObjType == pTargetType) - return true; + if (AreTypesAssignableInternal(pObjType->RelatedParameterType, pTargetType->RelatedParameterType, + AssignmentVariation.AllowSizeEquivalence, null)) + { + return obj; } - } - [RuntimeExport("RhTypeCast_CheckCast")] - public static unsafe object CheckCast(MethodTable* pTargetType, object obj) - { - // @TODO: consider using the cache directly, but beware of IDynamicInterfaceCastable in the interface case - if (pTargetType->IsArray) - return CheckCastArray(pTargetType, obj); - else if (pTargetType->HasGenericVariance) - return CheckCastVariantType(pTargetType, obj); - else if (pTargetType->IsInterface) - return CheckCastInterface(pTargetType, obj); - else if (pTargetType->IsParameterizedType || pTargetType->IsFunctionPointerType) - return CheckCastNonboxableType(pTargetType, obj); - else - return CheckCastClass(pTargetType, obj); + return null; } - private static unsafe object CheckCastVariantType(MethodTable* pTargetType, object obj) + private static unsafe object CheckCastArray(MethodTable* pTargetEEType, object obj) { - if (obj == null) + object result = IsInstanceOfArray(pTargetEEType, obj); + + if (result == null) { - return obj; + // Throw the invalid cast exception defined by the classlib, using the input MethodTable* + // to find the correct classlib. + + return ThrowInvalidCastException(pTargetEEType); } + return result; + } + + private static unsafe object IsInstanceOfVariantType(MethodTable* pTargetType, object obj) + { if (!AreTypesAssignableInternal(obj.GetMethodTable(), pTargetType, AssignmentVariation.BoxedSource, null) && (!obj.GetMethodTable()->IsIDynamicInterfaceCastable - || !IsInstanceOfInterfaceViaIDynamicInterfaceCastable(pTargetType, obj, throwing: true))) + || !IsInstanceOfInterfaceViaIDynamicInterfaceCastable(pTargetType, obj, throwing: false))) { - return ThrowInvalidCastException(pTargetType); + return null; } return obj; } - private static unsafe object CheckCastNonboxableType(MethodTable* pTargetType, object obj) + private static unsafe object CheckCastVariantType(MethodTable* pTargetType, object obj) { - // a null value can be cast to anything - if (obj == null) + if (!AreTypesAssignableInternal(obj.GetMethodTable(), pTargetType, AssignmentVariation.BoxedSource, null) + && (!obj.GetMethodTable()->IsIDynamicInterfaceCastable + || !IsInstanceOfInterfaceViaIDynamicInterfaceCastable(pTargetType, obj, throwing: true))) { - return null; + return ThrowInvalidCastException(pTargetType); } - // Parameterized types are not boxable, so nothing can be an instance of these. - return ThrowInvalidCastException(pTargetType); + return obj; } private static unsafe EETypeElementType GetNormalizedIntegralArrayElementType(MethodTable* type) @@ -1168,6 +968,39 @@ public static bool Exists(EETypePairList* pList, MethodTable* pEEType1, MethodTa } } + // + // Determines if a value of the source type can be assigned to a location of the target type. + // It does not handle IDynamicInterfaceCastable, and cannot since we do not have an actual object instance here. + // This routine assumes that the source type is boxed, i.e. a value type source is presumed to be + // compatible with Object and ValueType and an enum source is additionally compatible with Enum. + // + [RuntimeExport("RhTypeCast_AreTypesAssignable")] + public static unsafe bool AreTypesAssignable(MethodTable* pSourceType, MethodTable* pTargetType) + { + // Special case: Generic Type definitions are not assignable in a mrt sense + // in any way. Assignability of those types is handled by reflection logic. + // Call this case out first and here so that these only somewhat filled in + // types do not leak into the rest of the type casting logic. + if (pTargetType->IsGenericTypeDefinition || pSourceType->IsGenericTypeDefinition) + { + return false; + } + + // Special case: T can be cast to Nullable (where T is a value type). Call this case out here + // since this is only applicable if T is boxed, which is not true for any other callers of + // AreTypesAssignableInternal, so no sense making all the other paths pay the cost of the check. + if (pTargetType->IsNullable && pSourceType->IsValueType && !pSourceType->IsNullable) + { + MethodTable* pNullableType = pTargetType->NullableType; + + return pSourceType == pNullableType; + } + + return AreTypesAssignableInternal(pSourceType, pTargetType, AssignmentVariation.BoxedSource, null); + } + + // Internal recursively callable version of the export method above. + // It keeps track of visited type pairs and returns a failure if type assignability yields a self-dependent cycle. public static unsafe bool AreTypesAssignableInternal(MethodTable* pSourceType, MethodTable* pTargetType, AssignmentVariation variation, EETypePairList* pVisited) { // Important special case -- it breaks infinite recursion @@ -1207,5 +1040,236 @@ private static unsafe bool CacheMiss(MethodTable* pSourceType, MethodTable* pTar return result; } + + internal static unsafe bool AreTypesAssignableInternalUncached(MethodTable* pSourceType, MethodTable* pTargetType, AssignmentVariation variation, EETypePairList* pVisited) + { + bool fBoxedSource = (variation == AssignmentVariation.BoxedSource); + bool fAllowSizeEquivalence = ((variation & AssignmentVariation.AllowSizeEquivalence) == AssignmentVariation.AllowSizeEquivalence); + + // + // Are the types identical? + // + if (pSourceType == pTargetType) + return true; + + // + // Handle cast to interface cases. + // + if (pTargetType->IsInterface) + { + // Value types can only be cast to interfaces if they're boxed. + if (!fBoxedSource && pSourceType->IsValueType) + return false; + + if (ImplementsInterface(pSourceType, pTargetType, pVisited)) + return true; + + // Are the types compatible due to generic variance? + if (pTargetType->HasGenericVariance && pSourceType->HasGenericVariance) + return TypesAreCompatibleViaGenericVariance(pSourceType, pTargetType, pVisited); + + return false; + } + if (pSourceType->IsInterface) + { + // The only non-interface type an interface can be cast to is Object. + return WellKnownEETypes.IsSystemObject(pTargetType); + } + + // + // Handle cast to array or pointer cases. + // + if (pTargetType->IsParameterizedType) + { + if (pSourceType->IsParameterizedType + && (pTargetType->ParameterizedTypeShape == pSourceType->ParameterizedTypeShape)) + { + MethodTable* pSourceRelatedParameterType = pSourceType->RelatedParameterType; + // Source type is also a parameterized type. Are the parameter types compatible? + if (pSourceRelatedParameterType->IsPointerType) + { + // If the parameter types are pointers, then only exact matches are correct. + // As we've already compared equality at the start of this function, + // return false as the exact match case has already been handled. + // int** is not compatible with uint**, nor is int*[] oompatible with uint*[]. + return false; + } + else if (pSourceRelatedParameterType->IsByRefType) + { + // Only allow exact matches for ByRef types - same as pointers above. This should + // be unreachable and it's only a defense in depth. ByRefs can't be parameters + // of any parameterized type. + return false; + } + else if (pSourceRelatedParameterType->IsFunctionPointerType) + { + // If the parameter types are function pointers, then only exact matches are correct. + // As we've already compared equality at the start of this function, + // return false as the exact match case has already been handled. + return false; + } + else + { + // Note that using AreTypesAssignableInternal with AssignmentVariation.AllowSizeEquivalence + // here handles array covariance as well as IFoo[] -> Foo[] etc. We are not using + // AssignmentVariation.BoxedSource because int[] is not assignable to object[]. + return AreTypesAssignableInternal(pSourceType->RelatedParameterType, + pTargetType->RelatedParameterType, AssignmentVariation.AllowSizeEquivalence, pVisited); + } + } + + // Can't cast a non-parameter type to a parameter type or a parameter type of different shape to a parameter type + return false; + } + + if (pTargetType->IsFunctionPointerType) + { + // Function pointers only cast if source and target are equivalent types. + return false; + } + + if (pSourceType->IsArray) + { + // Target type is not an array. But we can still cast arrays to Object or System.Array. + return WellKnownEETypes.IsValidArrayBaseType(pTargetType); + } + else if (pSourceType->IsParameterizedType) + { + return false; + } + else if (pSourceType->IsFunctionPointerType) + { + return false; + } + + // + // Handle cast to other (non-interface, non-array) cases. + // + + if (pSourceType->IsValueType) + { + // Certain value types of the same size are treated as equivalent when the comparison is + // between array element types (indicated by fAllowSizeEquivalence). These are integer types + // of the same size (e.g. int and uint) and the base type of enums vs all integer types of the + // same size. + if (fAllowSizeEquivalence && pTargetType->IsPrimitive) + { + if (GetNormalizedIntegralArrayElementType(pSourceType) == GetNormalizedIntegralArrayElementType(pTargetType)) + return true; + + // Non-identical value types aren't equivalent in any other case (since value types are + // sealed). + return false; + } + + // If the source type is a value type but it's not boxed then we've run out of options: the types + // are not identical, the target type isn't an interface and we're not allowed to check whether + // the target type is a parent of this one since value types are sealed and thus the only matches + // would be against Object, ValueType or Enum, all of which are reference types and not compatible + // with non-boxed value types. + if (!fBoxedSource) + return false; + } + + // Sub case of casting between two instantiations of the same delegate type where one or more of + // the type parameters have variance. Only interfaces and delegate types can have variance over + // their type parameters and we know that neither type is an interface due to checks above. + if (pTargetType->HasGenericVariance && pSourceType->HasGenericVariance) + { + // We've dealt with the identical case at the start of this method. And the regular path below + // will handle casting to Object, Delegate and MulticastDelegate. Since we don't support + // deriving from user delegate classes any further all we have to check here is that the + // uninstantiated generic delegate definitions are the same and the type parameters are + // compatible. + return TypesAreCompatibleViaGenericVariance(pSourceType, pTargetType, pVisited); + } + + // Is the source type derived from the target type? + if (IsDerived(pSourceType, pTargetType)) + return true; + + return false; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static unsafe object? IsInstanceOfAny_NoCacheLookup(MethodTable* pTargetType, object obj) + { + MethodTable* pSourceType = obj.GetMethodTable(); + object? retObj; + if (pTargetType->IsArray) + { + retObj = IsInstanceOfArray(pTargetType, obj); + } + else if (pTargetType->HasGenericVariance) + { + retObj = IsInstanceOfVariantType(pTargetType, obj); + } + else if (pTargetType->IsInterface) + { + retObj = IsInstanceOfInterface(pTargetType, obj); + } + else if (pTargetType->IsParameterizedType || pTargetType->IsFunctionPointerType) + { + // We handled arrays above so this is for pointers and byrefs only. + retObj = null; + } + else + { + retObj = IsInstanceOfClass(pTargetType, obj); + } + + // + // Update the cache + // + if (!pSourceType->IsIDynamicInterfaceCastable) + { + // + // Update the cache + // + nuint sourceAndVariation = (nuint)pSourceType + (uint)AssignmentVariation.BoxedSource; + s_castCache.TrySet(sourceAndVariation, (nuint)pTargetType, retObj != null); + } + + return retObj; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static unsafe object ChekCastAny_NoCacheLookup(MethodTable* pTargetType, object obj) + { + MethodTable* pSourceType = obj.GetMethodTable(); + if (pTargetType->IsArray) + { + obj = CheckCastArray(pTargetType, obj); + } + else if (pTargetType->HasGenericVariance) + { + obj = CheckCastVariantType(pTargetType, obj); + } + else if (pTargetType->IsInterface) + { + obj = CheckCastInterface(pTargetType, obj); + } + else if (pTargetType->IsParameterizedType || pTargetType->IsFunctionPointerType) + { + // We handled arrays above so this is for pointers and byrefs only. + // Nothing can be a boxed instance of these. + return ThrowInvalidCastException(pTargetType); + } + else + { + obj = CheckCastClass(pTargetType, obj); + } + + if (!pSourceType->IsIDynamicInterfaceCastable) + { + // + // Update the cache + // + nuint sourceAndVariation = (nuint)pSourceType + (uint)AssignmentVariation.BoxedSource; + s_castCache.TrySet(sourceAndVariation, (nuint)pTargetType, true); + } + + return obj; + } } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs index 9c652dd69bb9a..a979278840d86 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs @@ -354,11 +354,11 @@ internal static unsafe bool AreTypesAssignable(EETypePtr pSourceType, EETypePtr internal static extern void RhCheckArrayStore(object array, object? obj); [MethodImpl(MethodImplOptions.InternalCall)] - [RuntimeImport(RuntimeLibrary, "RhTypeCast_IsInstanceOf")] - private static extern unsafe object IsInstanceOf(MethodTable* pTargetType, object obj); + [RuntimeImport(RuntimeLibrary, "RhTypeCast_IsInstanceOfAny")] + private static extern unsafe object IsInstanceOfAny(MethodTable* pTargetType, object obj); internal static unsafe object IsInstanceOf(EETypePtr pTargetType, object obj) - => IsInstanceOf(pTargetType.ToPointer(), obj); + => IsInstanceOfAny(pTargetType.ToPointer(), obj); // // calls to runtime for allocation diff --git a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs index da8a01a543adc..63383b7ddfa67 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs @@ -363,13 +363,11 @@ public enum ReadyToRunHelper GetRuntimeType, + CheckCastInterface, CheckCastClass, CheckCastClassSpecial, - CheckInstanceClass, - CheckCastArray, - CheckInstanceArray, - CheckCastInterface, CheckInstanceInterface, + CheckInstanceClass, MonitorEnterStatic, MonitorExitStatic, diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs index bfff591226994..1eed47ef13838 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs @@ -242,34 +242,29 @@ public static void GetEntryPoint(TypeSystemContext context, ReadyToRunHelper id, break; case ReadyToRunHelper.CheckCastAny: - mangledName = "RhTypeCast_CheckCast"; - break; - case ReadyToRunHelper.CheckInstanceAny: - mangledName = "RhTypeCast_IsInstanceOf"; - break; - case ReadyToRunHelper.IsInstanceOfException: - mangledName = "RhTypeCast_IsInstanceOfException"; + mangledName = "RhTypeCast_CheckCastAny"; break; case ReadyToRunHelper.CheckCastInterface: mangledName = "RhTypeCast_CheckCastInterface"; break; - case ReadyToRunHelper.CheckInstanceInterface: - mangledName = "RhTypeCast_IsInstanceOfInterface"; - break; case ReadyToRunHelper.CheckCastClass: mangledName = "RhTypeCast_CheckCastClass"; break; case ReadyToRunHelper.CheckCastClassSpecial: mangledName = "RhTypeCast_CheckCastClassSpecial"; break; + + case ReadyToRunHelper.CheckInstanceAny: + mangledName = "RhTypeCast_IsInstanceOfAny"; + break; + case ReadyToRunHelper.CheckInstanceInterface: + mangledName = "RhTypeCast_IsInstanceOfInterface"; + break; case ReadyToRunHelper.CheckInstanceClass: mangledName = "RhTypeCast_IsInstanceOfClass"; break; - case ReadyToRunHelper.CheckCastArray: - mangledName = "RhTypeCast_CheckCastArray"; - break; - case ReadyToRunHelper.CheckInstanceArray: - mangledName = "RhTypeCast_IsInstanceOfArray"; + case ReadyToRunHelper.IsInstanceOfException: + mangledName = "RhTypeCast_IsInstanceOfException"; break; case ReadyToRunHelper.MonitorEnter: diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs index c23c25b42c118..128f72f8818e4 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs @@ -694,10 +694,11 @@ private ISymbolNode GetHelperFtnUncached(CorInfoHelpFunc ftnNum) break; case CorInfoHelpFunc.CORINFO_HELP_CHKCASTANY: + case CorInfoHelpFunc.CORINFO_HELP_CHKCASTARRAY: id = ReadyToRunHelper.CheckCastAny; break; - case CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOFANY: - id = ReadyToRunHelper.CheckInstanceAny; + case CorInfoHelpFunc.CORINFO_HELP_CHKCASTINTERFACE: + id = ReadyToRunHelper.CheckCastInterface; break; case CorInfoHelpFunc.CORINFO_HELP_CHKCASTCLASS: id = ReadyToRunHelper.CheckCastClass; @@ -705,21 +706,17 @@ private ISymbolNode GetHelperFtnUncached(CorInfoHelpFunc ftnNum) case CorInfoHelpFunc.CORINFO_HELP_CHKCASTCLASS_SPECIAL: id = ReadyToRunHelper.CheckCastClassSpecial; break; - case CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOFCLASS: - id = ReadyToRunHelper.CheckInstanceClass; - break; - case CorInfoHelpFunc.CORINFO_HELP_CHKCASTARRAY: - id = ReadyToRunHelper.CheckCastArray; - break; + + case CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOFANY: case CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOFARRAY: - id = ReadyToRunHelper.CheckInstanceArray; - break; - case CorInfoHelpFunc.CORINFO_HELP_CHKCASTINTERFACE: - id = ReadyToRunHelper.CheckCastInterface; + id = ReadyToRunHelper.CheckInstanceAny; break; case CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOFINTERFACE: id = ReadyToRunHelper.CheckInstanceInterface; break; + case CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOFCLASS: + id = ReadyToRunHelper.CheckInstanceClass; + break; case CorInfoHelpFunc.CORINFO_HELP_MON_ENTER: id = ReadyToRunHelper.MonitorEnter; From 48ed2b10e3ada831650df0f4574d06f1bcec3c5e Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Wed, 26 Jul 2023 18:26:43 -0700 Subject: [PATCH 3/5] fix build --- .../Runtime.Base/src/System/Runtime/TypeCast.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/TypeCast.cs b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/TypeCast.cs index d6cc5ecf06a94..11005c131c7f9 100644 --- a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/TypeCast.cs +++ b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/TypeCast.cs @@ -773,9 +773,9 @@ public static unsafe void StelemRef(Array array, nint index, object obj) } [MethodImpl(MethodImplOptions.NoInlining)] - private static unsafe void StelemRef_Helper(ref object? element, MethodTable* elementType, object obj) + private static unsafe void StelemRef_Helper(ref object element, MethodTable* elementType, object obj) { - CastResult result = CastCache.TryGet(s_table!, (nuint)obj.GetMethodTable() + (int)AssignmentVariation.BoxedSource, (nuint)elementType); + CastResult result = s_castCache.TryGet((nuint)obj.GetMethodTable() + (int)AssignmentVariation.BoxedSource, (nuint)elementType); if (result == CastResult.CanCast) { InternalCalls.RhpAssignRef(ref element, obj); @@ -785,12 +785,10 @@ private static unsafe void StelemRef_Helper(ref object? element, MethodTable* el StelemRef_Helper_NoCacheLookup(ref element, elementType, obj); } - private static unsafe void StelemRef_Helper_NoCacheLookup(ref object? element, MethodTable* elementType, object obj) + private static unsafe void StelemRef_Helper_NoCacheLookup(ref object element, MethodTable* elementType, object obj) { - Debug.Assert(obj != null); - - obj = IsInstanceOfAny_NoCacheLookup(elementType, obj); - if (obj != null) + object? castedObj = IsInstanceOfAny_NoCacheLookup(elementType, obj); + if (castedObj != null) { InternalCalls.RhpAssignRef(ref element, obj); return; From 67143871e55c5d59e3c2cf286e266e9f7b559a6f Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Sun, 6 Aug 2023 14:25:12 -0700 Subject: [PATCH 4/5] simpler selection of runtime helpers for casts --- .../Runtime.Base/src/System/Runtime/RuntimeExports.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/RuntimeExports.cs b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/RuntimeExports.cs index 404ebdc46d40b..359c091936358 100644 --- a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/RuntimeExports.cs +++ b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/RuntimeExports.cs @@ -392,22 +392,18 @@ internal static unsafe IntPtr RhGetRuntimeHelperForType(MethodTable* pEEType, Ru return (IntPtr)(delegate*)&InternalCalls.RhpNewFast; case RuntimeHelperKind.IsInst: - if (pEEType->IsArray || pEEType->HasGenericVariance) + if (pEEType->HasGenericVariance || pEEType->IsParameterizedType || pEEType->IsFunctionPointerType) return (IntPtr)(delegate*)&TypeCast.IsInstanceOfAny; else if (pEEType->IsInterface) return (IntPtr)(delegate*)&TypeCast.IsInstanceOfInterface; - else if (pEEType->IsParameterizedType || pEEType->IsFunctionPointerType) - return (IntPtr)(delegate*)&TypeCast.IsInstanceOfAny; // Array handled above; pointers and byrefs handled here else return (IntPtr)(delegate*)&TypeCast.IsInstanceOfClass; case RuntimeHelperKind.CastClass: - if (pEEType->IsArray || pEEType->HasGenericVariance) + if (pEEType->HasGenericVariance || pEEType->IsParameterizedType || pEEType->IsFunctionPointerType) return (IntPtr)(delegate*)&TypeCast.CheckCastAny; else if (pEEType->IsInterface) return (IntPtr)(delegate*)&TypeCast.CheckCastInterface; - else if (pEEType->IsParameterizedType || pEEType->IsFunctionPointerType) - return (IntPtr)(delegate*)&TypeCast.CheckCastAny; // Array handled above; pointers and byrefs handled here else return (IntPtr)(delegate*)&TypeCast.CheckCastClass; From 3f40da77225572d7bda2aa151482dbf0a5589fbb Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:47:23 -0700 Subject: [PATCH 5/5] add a codegen test --- .../baseservices/typeequivalence/impl/Impls.cs | 6 ++++++ .../typeequivalence/simple/Simple.cs | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/tests/baseservices/typeequivalence/impl/Impls.cs b/src/tests/baseservices/typeequivalence/impl/Impls.cs index 71d5bedb9749e..dd0ce12eec9ba 100644 --- a/src/tests/baseservices/typeequivalence/impl/Impls.cs +++ b/src/tests/baseservices/typeequivalence/impl/Impls.cs @@ -19,6 +19,12 @@ public static object Create() } } +[TypeIdentifier("MyScope", "MyTypeId")] +public struct EquivalentValueType +{ + public int A; +} + /// /// Implementation of interfaces that have no impact on inputs. /// diff --git a/src/tests/baseservices/typeequivalence/simple/Simple.cs b/src/tests/baseservices/typeequivalence/simple/Simple.cs index be39e4c4ecf24..7595199a120e1 100644 --- a/src/tests/baseservices/typeequivalence/simple/Simple.cs +++ b/src/tests/baseservices/typeequivalence/simple/Simple.cs @@ -11,6 +11,12 @@ using Xunit; using TypeEquivalenceTypes; +[TypeIdentifier("MyScope", "MyTypeId")] +public struct EquivalentValueType +{ + public int A; +} + public class Simple { private class EmptyType2 : IEmptyType @@ -268,6 +274,17 @@ private static void LoadInvalidType() Console.WriteLine($"-- {typeof(ValueTypeWithInstanceMethod).Name}"); } + private static void TestCastsOptimizations() + { + string otherTypeName = $"{typeof(EquivalentValueType).FullName},{typeof(EmptyType).Assembly.GetName().Name}"; + Type otherEquivalentValueType = Type.GetType(otherTypeName); + + // ensure that an instance of otherEquivalentValueType can cast to EquivalentValueType + object otherEquivalentValueTypeInstance = Activator.CreateInstance(otherEquivalentValueType); + Assert.True(otherEquivalentValueTypeInstance is EquivalentValueType); + EquivalentValueType inst = (EquivalentValueType)otherEquivalentValueTypeInstance; + } + public static int Main() { if (!OperatingSystem.IsWindows()) @@ -286,6 +303,7 @@ public static int Main() TestGenericInterfaceEquivalence(); TestTypeEquivalenceWithTypePunning(); TestLoadingValueTypesWithMethod(); + TestCastsOptimizations(); } catch (Exception e) {