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(new object()); + [MethodImpl(MethodImplOptions.NoInlining)] + static void IsNever3(object o) + { + if (typeof(T) == typeof(Never3)) + { + Consume(new Canary3()); + } + } +#if false // This optimization is disabled for now, don't check. + ThrowIfPresentWithUsableMethodTable(typeof(TestTypeOfCodegenBranchElimination), nameof(Canary3)); +#endif + + // ********* + + if (GetUnknownType().GetType() == typeof(Never4)) + { + Consume(new Canary4()); + } +#if !DEBUG + ThrowIfPresentWithUsableMethodTable(typeof(TestTypeOfCodegenBranchElimination), nameof(Canary4)); +#endif + + if (GetUnknownType() is Never5) + { + Consume(new Canary5()); + } +#if !DEBUG + ThrowIfPresentWithUsableMethodTable(typeof(TestTypeOfCodegenBranchElimination), nameof(Canary5)); +#endif + + IsNever6(new object()); + [MethodImpl(MethodImplOptions.NoInlining)] + static void IsNever6(object o) + { + if (typeof(T) == typeof(Never6)) + { + Consume(new Canary6()); + } + } +#if false // This optimization is disabled for now, don't check. + ThrowIfPresentWithUsableMethodTable(typeof(TestTypeOfCodegenBranchElimination), nameof(Canary6)); +#endif + + // ************ + + Activator.CreateInstance(typeof(Maybe1<>).MakeGenericType(GetAtom1())); + + if (GetUnknownType().GetType() == typeof(Maybe1)) + { + // This should not be optimized away because Maybe1 is possible + // with the type loader template for MakeGeneric above. + Consume(new Marker1()); + } + ThrowIfNotPresent(typeof(TestTypeOfCodegenBranchElimination), nameof(Marker1)); + + // ************ + + if (GetDynamicInterfaceCastableType() is not IDynamicCastableImplemented) + throw new Exception(); + + [MethodImpl(MethodImplOptions.NoInlining)] + static object GetDynamicInterfaceCastableType() => new DynamicInterfaceCastable(); + + [MethodImpl(MethodImplOptions.NoInlining)] + static void Consume(object o) { } + + [MethodImpl(MethodImplOptions.NoInlining)] + static object GetUnknownType() => new object(); + + [MethodImpl(MethodImplOptions.NoInlining)] + static Type GetAtom1() => typeof(Atom1); + } + } + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern", Justification = "That's the point")] private static Type GetTypeSecretly(Type testType, string typeName) => testType.GetNestedType(typeName, BindingFlags.NonPublic | BindingFlags.Public);