diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h
index b9ebaa08546014..95df5192d353e4 100644
--- a/src/coreclr/inc/corinfo.h
+++ b/src/coreclr/inc/corinfo.h
@@ -2983,7 +2983,7 @@ class ICorStaticInfo
CORINFO_CLASS_HANDLE* vcTypeRet /* OUT */
) = 0;
- // Obtains a list of exact classes for a given base type. Returns 0 if the number of
+ // Obtains a list of exact classes for a given base type. Returns -1 if the number of
// the exact classes is greater than maxExactClasses or if more types might be loaded
// in future.
virtual int getExactClasses(
diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h
index 41e58ca24537b8..e7d744d80bf517 100644
--- a/src/coreclr/inc/jiteeversionguid.h
+++ b/src/coreclr/inc/jiteeversionguid.h
@@ -43,11 +43,11 @@ typedef const GUID *LPCGUID;
#define GUID_DEFINED
#endif // !GUID_DEFINED
-constexpr GUID JITEEVersionIdentifier = { /* 6fd660c7-96be-4832-a84c-4200141f7d08 */
- 0x6fd660c7,
- 0x96be,
- 0x4832,
- {0xa8, 0x4c, 0x42, 0x00, 0x14, 0x1f, 0x7d, 0x08}
+constexpr GUID JITEEVersionIdentifier = { /* bdf34b26-0725-4ad6-9935-40bfd2a4c4fc */
+ 0xbdf34b26,
+ 0x0725,
+ 0x4ad6,
+ {0x99, 0x35, 0x40, 0xbf, 0xd2, 0xa4, 0xc4, 0xfc}
};
//////////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp
index 0db384e5c69a0c..90677f8f4975c1 100644
--- a/src/coreclr/jit/gentree.cpp
+++ b/src/coreclr/jit/gentree.cpp
@@ -14117,6 +14117,18 @@ GenTree* Compiler::gtFoldTypeCompare(GenTree* tree)
return tree;
}
+ // Check if an object of this type can even exist
+ if (info.compCompHnd->getExactClasses(clsHnd, 0, nullptr) == 0)
+ {
+ JITDUMP("Runtime reported %p (%s) is never allocated\n", dspPtr(clsHnd), eeGetClassName(clsHnd));
+
+ const bool operatorIsEQ = (oper == GT_EQ);
+ const int compareResult = operatorIsEQ ? 0 : 1;
+ JITDUMP("Runtime reports comparison is known at jit time: %u\n", compareResult);
+ GenTree* result = gtNewIconNode(compareResult);
+ return result;
+ }
+
// We're good to go.
JITDUMP("Optimizing compare of obj.GetType()"
" and type-from-handle to compare method table pointer\n");
diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp
index 605c318ec98b86..e3f239de43a24c 100644
--- a/src/coreclr/jit/importer.cpp
+++ b/src/coreclr/jit/importer.cpp
@@ -5380,6 +5380,32 @@ GenTree* Compiler::impOptimizeCastClassOrIsInst(GenTree* op1, CORINFO_RESOLVED_T
return nullptr;
}
+ CORINFO_CLASS_HANDLE toClass = pResolvedToken->hClass;
+ if (info.compCompHnd->getExactClasses(toClass, 0, nullptr) == 0)
+ {
+ JITDUMP("\nClass %p (%s) can never be allocated\n", dspPtr(toClass), eeGetClassName(toClass));
+
+ if (!isCastClass)
+ {
+ JITDUMP("Cast will fail, optimizing to return null\n");
+
+ // If the cast was fed by a box, we can remove that too.
+ if (op1->IsBoxedValue())
+ {
+ JITDUMP("Also removing upstream box\n");
+ gtTryRemoveBoxUpstreamEffects(op1);
+ }
+
+ if (gtTreeHasSideEffects(op1, GTF_SIDE_EFFECT))
+ {
+ impAppendTree(op1, CHECK_SPILL_ALL, impCurStmtDI);
+ }
+ return gtNewNull();
+ }
+
+ JITDUMP("Cast will always throw, but not optimizing yet\n");
+ }
+
// See what we know about the type of the object being cast.
bool isExact = false;
bool isNonNull = false;
@@ -5387,7 +5413,6 @@ GenTree* Compiler::impOptimizeCastClassOrIsInst(GenTree* op1, CORINFO_RESOLVED_T
if (fromClass != nullptr)
{
- CORINFO_CLASS_HANDLE toClass = pResolvedToken->hClass;
JITDUMP("\nConsidering optimization of %s from %s%p (%s) to %p (%s)\n", isCastClass ? "castclass" : "isinst",
isExact ? "exact " : "", dspPtr(fromClass), eeGetClassName(fromClass), dspPtr(toClass),
eeGetClassName(toClass));
diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp
index f24750613054a9..63f2f090c6ba06 100644
--- a/src/coreclr/jit/importercalls.cpp
+++ b/src/coreclr/jit/importercalls.cpp
@@ -6588,7 +6588,7 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call,
{
JITDUMP("No exact classes implementing %s\n", eeGetClassName(baseClass))
}
- else if (numExactClasses > maxTypeChecks)
+ else if (numExactClasses < 0 || numExactClasses > maxTypeChecks)
{
JITDUMP("Too many exact classes implementing %s (%d > %d)\n", eeGetClassName(baseClass), numExactClasses,
maxTypeChecks)
diff --git a/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs b/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs
index 6222ec6d649cca..5574a9f8aa100f 100644
--- a/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs
+++ b/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs
@@ -203,13 +203,17 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType
#if !READYTORUN
///
- /// Gets a value indicating whether it might be possible to obtain a constructed type data structure for the given type.
+ /// Gets a value indicating whether it might be possible to obtain a constructed type data structure for the given type
+ /// in this compilation (i.e. is it possible to reference a constructed MethodTable symbol for this).
///
- ///
- /// This is a bit of a hack, but devirtualization manager has a global view of all allocated types
- /// so it can answer this question.
- ///
- public virtual bool CanConstructType(TypeDesc type) => true;
+ public virtual bool CanReferenceConstructedMethodTable(TypeDesc type) => true;
+
+ ///
+ /// Gets a value indicating whether a (potentially canonically-equlivalent) constructed MethodTable could
+ /// exist. This is similar to , but will return true
+ /// for List<__Canon> if a constructed MethodTable for List<object> exists.
+ ///
+ public virtual bool CanReferenceConstructedTypeOrCanonicalFormOfType(TypeDesc type) => true;
public virtual TypeDesc[] GetImplementingClasses(TypeDesc type) => null;
#endif
diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
index 0f87233e90b8b2..9366e1aa65b6d3 100644
--- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
+++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
@@ -4284,7 +4284,7 @@ private HRESULT getPgoInstrumentationResults(CORINFO_METHOD_STRUCT_* ftnHnd, ref
#pragma warning disable SA1001, SA1113, SA1115 // Commas should be spaced correctly
ComputeJitPgoInstrumentationSchema(ObjectToHandle, pgoResultsSchemas, out var nativeSchemas, _cachedMemoryStream
#if !READYTORUN
- , _compilation.CanConstructType
+ , _compilation.CanReferenceConstructedMethodTable
#endif
);
#pragma warning restore SA1001, SA1113, SA1115 // Commas should be spaced correctly
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Compilation.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Compilation.cs
index a4d4a8968af920..0f5a9d3c15293b 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Compilation.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Compilation.cs
@@ -103,9 +103,14 @@ public bool CanInline(MethodDesc caller, MethodDesc callee)
return _inliningPolicy.CanInline(caller, callee);
}
- public bool CanConstructType(TypeDesc type)
+ public bool CanReferenceConstructedMethodTable(TypeDesc type)
{
- return NodeFactory.DevirtualizationManager.CanConstructType(type);
+ return NodeFactory.DevirtualizationManager.CanReferenceConstructedMethodTable(type);
+ }
+
+ public bool CanReferenceConstructedTypeOrCanonicalFormOfType(TypeDesc type)
+ {
+ return NodeFactory.DevirtualizationManager.CanReferenceConstructedTypeOrCanonicalFormOfType(type);
}
public DelegateCreationInfo GetDelegateCtor(TypeDesc delegateType, MethodDesc target, TypeDesc constrainedType, bool followVirtualDispatch)
@@ -261,7 +266,7 @@ public bool NeedsRuntimeLookup(ReadyToRunHelperId lookupKind, object targetOfLoo
public ReadyToRunHelperId GetLdTokenHelperForType(TypeDesc type)
{
- bool canConstructPerWholeProgramAnalysis = NodeFactory.DevirtualizationManager.CanConstructType(type);
+ bool canConstructPerWholeProgramAnalysis = NodeFactory.DevirtualizationManager.CanReferenceConstructedMethodTable(type);
bool creationAllowed = ConstructedEETypeNode.CreationAllowed(type);
return (canConstructPerWholeProgramAnalysis && creationAllowed)
? ReadyToRunHelperId.TypeHandle
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs
index 630de4a7470645..46e3df57efd1ff 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs
@@ -413,7 +413,8 @@ public override DictionaryLayoutNode GetLayout(TypeSystemEntity methodOrType)
private sealed class ScannedDevirtualizationManager : DevirtualizationManager
{
- private HashSet _constructedTypes = new HashSet();
+ private HashSet _constructedMethodTables = new HashSet();
+ private HashSet _canonConstructedMethodTables = new HashSet();
private HashSet _canonConstructedTypes = new HashSet();
private HashSet _unsealedTypes = new HashSet();
private Dictionary> _implementators = new();
@@ -442,7 +443,12 @@ public ScannedDevirtualizationManager(NodeFactory factory, ImmutableArray _constructedTypes.Contains(type);
+ public override bool CanReferenceConstructedMethodTable(TypeDesc type)
+ => _constructedMethodTables.Contains(type);
+
+ public override bool CanReferenceConstructedTypeOrCanonicalFormOfType(TypeDesc type)
+ => _constructedMethodTables.Contains(type) || _canonConstructedMethodTables.Contains(type);
public override TypeDesc[] GetImplementingClasses(TypeDesc type)
{
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs
index aba1e6bbd36080..e1b1b359c0878a 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs
@@ -3280,7 +3280,7 @@ private void updateEntryPointForTailCall(ref CORINFO_CONST_LOOKUP entryPoint)
private int getExactClasses(CORINFO_CLASS_STRUCT_* baseType, int maxExactClasses, CORINFO_CLASS_STRUCT_** exactClsRet)
{
// Not implemented for R2R yet
- return 0;
+ return -1;
}
private bool getStaticFieldContent(CORINFO_FIELD_STRUCT_* fieldHandle, byte* buffer, int bufferSize, int valueOffset, bool ignoreMovableObjects)
diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs
index a0dd7850ae8806..80aef8f4c2118a 100644
--- a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs
+++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs
@@ -72,7 +72,7 @@ public override IEETypeNode NecessaryTypeSymbolIfPossible(TypeDesc type)
// information proving that it isn't, give RyuJIT the constructed symbol even
// though we just need the unconstructed one.
// https://github.com/dotnet/runtimelab/issues/1128
- bool canPotentiallyConstruct = NodeFactory.DevirtualizationManager.CanConstructType(type);
+ bool canPotentiallyConstruct = NodeFactory.DevirtualizationManager.CanReferenceConstructedMethodTable(type);
if (canPotentiallyConstruct)
return _nodeFactory.MaximallyConstructableType(type);
@@ -81,7 +81,7 @@ public override IEETypeNode NecessaryTypeSymbolIfPossible(TypeDesc type)
public FrozenRuntimeTypeNode NecessaryRuntimeTypeIfPossible(TypeDesc type)
{
- bool canPotentiallyConstruct = NodeFactory.DevirtualizationManager.CanConstructType(type);
+ bool canPotentiallyConstruct = NodeFactory.DevirtualizationManager.CanReferenceConstructedMethodTable(type);
if (canPotentiallyConstruct)
return _nodeFactory.SerializedMaximallyConstructableRuntimeTypeObject(type);
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 1e72961928078d..f98d7b0fc06505 100644
--- a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs
+++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs
@@ -2248,14 +2248,42 @@ private void getFieldInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_MET
// and STS::AccessCheck::CanAccess.
}
+ private bool CanNeverHaveInstanceOfSubclassOf(TypeDesc type)
+ {
+ // Don't try to optimize nullable
+ if (type.IsNullable)
+ return false;
+
+ // We don't track unconstructable types very well and they are rare anyway
+ if (!ConstructedEETypeNode.CreationAllowed(type))
+ return false;
+
+ TypeDesc canonType = type.ConvertToCanonForm(CanonicalFormKind.Specific);
+
+ // If we don't have a constructed MethodTable for the exact type or for its template,
+ // this type or any of its subclasses can never be instantiated.
+ return !_compilation.CanReferenceConstructedTypeOrCanonicalFormOfType(type)
+ && (type == canonType || !_compilation.CanReferenceConstructedMethodTable(canonType));
+ }
+
private int getExactClasses(CORINFO_CLASS_STRUCT_* baseType, int maxExactClasses, CORINFO_CLASS_STRUCT_** exactClsRet)
{
MetadataType type = HandleToObject(baseType) as MetadataType;
if (type == null)
+ {
+ return -1;
+ }
+
+ if (CanNeverHaveInstanceOfSubclassOf(type))
{
return 0;
}
+ if (maxExactClasses == 0)
+ {
+ return -1;
+ }
+
// type is already sealed, return it
if (_compilation.IsEffectivelySealed(type))
{
@@ -2266,7 +2294,7 @@ private int getExactClasses(CORINFO_CLASS_STRUCT_* baseType, int maxExactClasses
TypeDesc[] implClasses = _compilation.GetImplementingClasses(type);
if (implClasses == null || implClasses.Length > maxExactClasses)
{
- return 0;
+ return -1;
}
int index = 0;
diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp
index 70c5c627bd404c..6f7952477d0645 100644
--- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp
+++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp
@@ -2787,15 +2787,15 @@ void MethodContext::recGetExactClasses(CORINFO_CLASS_HANDLE baseType, int maxExa
key.A = CastHandle(baseType);
key.B = maxExactClasses;
- Assert(result >= 0);
+ int numResults = result < 0 ? 0 : result;
- DWORDLONG* exactClassesAgnostic = new DWORDLONG[result];
- for (int i = 0; i < result; i++)
+ DWORDLONG* exactClassesAgnostic = new DWORDLONG[numResults];
+ for (int i = 0; i < numResults; i++)
exactClassesAgnostic[i] = CastHandle(exactClsRet[i]);
Agnostic_GetExactClassesResult value;
value.numClasses = result;
- value.classes = GetExactClasses->AddBuffer((unsigned char*)exactClassesAgnostic, (unsigned int)(result * sizeof(DWORDLONG)));
+ value.classes = GetExactClasses->AddBuffer((unsigned char*)exactClassesAgnostic, (unsigned int)(numResults * sizeof(DWORDLONG)));
delete[] exactClassesAgnostic;
diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp
index 05d9365e967964..6a38dcc4e3a08f 100644
--- a/src/coreclr/vm/jitinterface.cpp
+++ b/src/coreclr/vm/jitinterface.cpp
@@ -9663,8 +9663,6 @@ int CEEInfo::getExactClasses (
MODE_ANY;
} CONTRACTL_END;
- int exactClassesCount = 0;
-
JIT_TO_EE_TRANSITION();
// This function is currently implemented only on NativeAOT
@@ -9672,7 +9670,7 @@ int CEEInfo::getExactClasses (
EE_TO_JIT_TRANSITION();
- return exactClassesCount;
+ return -1;
}
/*********************************************************************/
diff --git a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs
index bf4b8639cefa0c..076ce813828c37 100644
--- a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs
+++ b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs
@@ -26,6 +26,7 @@ public static int Run()
TestUnmodifiableStaticFieldOptimization.Run();
TestUnmodifiableInstanceFieldOptimization.Run();
TestGetMethodOptimization.Run();
+ TestTypeOfCodegenBranchElimination.Run();
return 100;
}
@@ -644,6 +645,132 @@ public static void Run()
}
}
+ class TestTypeOfCodegenBranchElimination
+ {
+ class Never1 { }
+ class Never2 { }
+ class Never3 { }
+ class Never4 { }
+ class Never5 { }
+ class Never6 { }
+
+ class Canary1 { }
+ class Canary2 { }
+ class Canary3 { }
+ class Canary4 { }
+ class Canary5 { }
+ class Canary6 { }
+
+ class Maybe1 { }
+
+ class Marker1 { }
+
+ class Atom1 { }
+
+ interface IDynamicCastableImplemented { void A(); }
+ [DynamicInterfaceCastableImplementation]
+ interface IDynamicCastableImplementedImpl : IDynamicCastableImplemented { void IDynamicCastableImplemented.A() { } }
+ class DynamicInterfaceCastable : IDynamicInterfaceCastable
+ {
+ RuntimeTypeHandle IDynamicInterfaceCastable.GetInterfaceImplementation(RuntimeTypeHandle interfaceType) => typeof(IDynamicCastableImplementedImpl).TypeHandle;
+ bool IDynamicInterfaceCastable.IsInterfaceImplemented(RuntimeTypeHandle interfaceType, bool throwIfNotImplemented) => true;
+ }
+
+ [UnconditionalSuppressMessage("AotAnalysis", "IL3050:UnrecognizedAotPattern",
+ Justification = "That's the point")]
+ public static void Run()
+ {
+ if (GetUnknownType().GetType() == typeof(Never1))
+ {
+ Consume(new Canary1());
+ }
+#if !DEBUG
+ ThrowIfPresentWithUsableMethodTable(typeof(TestTypeOfCodegenBranchElimination), nameof(Canary1));
+#endif
+
+ if (GetUnknownType() is Never2)
+ {
+ Consume(new Canary2());
+ }
+#if !DEBUG
+ ThrowIfPresentWithUsableMethodTable(typeof(TestTypeOfCodegenBranchElimination), nameof(Canary2));
+#endif
+
+ IsNever3