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)
{