diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 9154da487bd1fa..0ab30837412ff7 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -14485,6 +14485,23 @@ GenTree* Compiler::gtFoldExprSpecial(GenTree* tree) case GT_EQ: case GT_NE: { + // For "obj ==/!= null" we can fold the comparison to true/false if VM tells us that the object cannot + // be non-null. Example: "obj == null" -> true if obj's type is FooCls and FooCls is an abstract class + // without subclasses. + // + // IsTargetAbi check is not necessary here, but it improves TP for runtimes where this optimization is + // not possible anyway. + if ((val == 0) && op->TypeIs(TYP_REF) && IsTargetAbi(CORINFO_NATIVEAOT_ABI)) + { + bool isExact, isNonNull; + CORINFO_CLASS_HANDLE opCls = gtGetClassHandle(op, &isExact, &isNonNull); + if ((opCls != NO_CLASS_HANDLE) && (info.compCompHnd->getExactClasses(opCls, 0, nullptr) == 0)) + { + op = gtWrapWithSideEffects(NewMorphedIntConNode(tree->OperIs(GT_EQ) ? 1 : 0), op, GTF_ALL_EFFECT); + goto DONE_FOLD; + } + } + // Optimize boxed value classes; these are always false. This IL is // generated when a generic value is tested against null: // ... foo(T x) { ... if ((object)x == null) ... diff --git a/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs b/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs index 5574a9f8aa100f..7d1c3be1de42a9 100644 --- a/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs +++ b/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs @@ -208,6 +208,12 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType /// public virtual bool CanReferenceConstructedMethodTable(TypeDesc type) => true; + /// + /// Gets a value indicating whether it might be possible that the interface type is implemented by a constructed type data structure + /// in this compilation. Note that the MethodTable for the interface itself could still be optimized away. + /// + public virtual bool CanInterfaceBeImplementedByConstructedMethodTable(TypeDesc type) => true; + /// /// Gets a value indicating whether a (potentially canonically-equlivalent) constructed MethodTable could /// exist. This is similar to , but will return true @@ -215,6 +221,15 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType /// public virtual bool CanReferenceConstructedTypeOrCanonicalFormOfType(TypeDesc type) => true; + /// + /// Gets a value indicating whether it might be possible that the interface type is implemented by a constructed type data structure + /// in this compilation. Note that the MethodTable for the interface itself could still be optimized away. + /// Similar to but will return true for + /// IInterface<__Canon> if IInterface<String> could be implemented. + /// + public virtual bool CanInterfaceOrCanonicalFormOfItBeImplementedByConstructedMethodTable(TypeDesc type) => true; + + public virtual TypeDesc[] GetImplementingClasses(TypeDesc type) => null; #endif } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Compilation.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Compilation.cs index 6a542d8d00a748..4673b0f7b18421 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Compilation.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Compilation.cs @@ -108,11 +108,21 @@ public bool CanReferenceConstructedMethodTable(TypeDesc type) return NodeFactory.DevirtualizationManager.CanReferenceConstructedMethodTable(type.NormalizeInstantiation()); } + public bool CanInterfaceBeImplementedByConstructedMethodTable(TypeDesc type) + { + return NodeFactory.DevirtualizationManager.CanInterfaceBeImplementedByConstructedMethodTable(type.NormalizeInstantiation()); + } + public bool CanReferenceConstructedTypeOrCanonicalFormOfType(TypeDesc type) { return NodeFactory.DevirtualizationManager.CanReferenceConstructedTypeOrCanonicalFormOfType(type.NormalizeInstantiation()); } + public bool CanInterfaceOrCanonicalFormOfItBeImplementedByConstructedMethodTable(TypeDesc type) + { + return NodeFactory.DevirtualizationManager.CanInterfaceOrCanonicalFormOfItBeImplementedByConstructedMethodTable(type.NormalizeInstantiation()); + } + public DelegateCreationInfo GetDelegateCtor(TypeDesc delegateType, MethodDesc target, TypeDesc constrainedType, bool followVirtualDispatch) { // If we're creating a delegate to a virtual method that cannot be overridden, devirtualize. diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs index 4d9328f1a34eef..954ae7b92b256e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs @@ -438,6 +438,8 @@ private sealed class ScannedDevirtualizationManager : DevirtualizationManager private HashSet _constructedMethodTables = new HashSet(); private HashSet _canonConstructedMethodTables = new HashSet(); private HashSet _canonConstructedTypes = new HashSet(); + private HashSet _implementedInterfaces = new HashSet(); + private HashSet _canonImplementedInterfaces = new HashSet(); private HashSet _unsealedTypes = new HashSet(); private Dictionary> _implementators = new(); private HashSet _disqualifiedTypes = new(); @@ -507,6 +509,13 @@ public ScannedDevirtualizationManager(NodeFactory factory, ImmutableArray; + interface INever3; + interface INever3_Variant; + + class ImplementsINever3Object : INever3; + class ImplementsINever3_VariantObject : INever3_Variant; + + interface IImplemented; + class Implements : IImplemented; + + class Canary1; + class Canary2; + class Canary3; + class Canary4; + class Canary5; + + [MethodImpl(MethodImplOptions.NoInlining)] + static object AcceptINever(INever1 inst) + { + if (inst != null) + { + return new Canary1(); + } + return null; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static object AcceptINever2(INever2 inst) + { + if (inst != null) + { + return new Canary2(); + } + return null; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static object AcceptIImplemented(IImplemented inst) + { + if (inst != null) + { + return new Canary3(); + } + return null; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static object AcceptINever3String(INever3 inst) + { + if (inst != null) + { + return new Canary4(); + } + return null; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static object AcceptINever3_VariantString(INever3_Variant inst) + { + if (inst != null) + { + return new Canary5(); + } + return null; + } + + public static void Run() + { + if (AcceptINever(null) != null) + throw new Exception(); +#if !DEBUG + ThrowIfPresentWithUsableMethodTable(typeof(TestCanBeNonNull), nameof(Canary1)); +#endif + + if (AcceptINever2(null) != null) + throw new Exception(); +#if !DEBUG + ThrowIfPresentWithUsableMethodTable(typeof(TestCanBeNonNull), nameof(Canary2)); +#endif + + if (AcceptIImplemented(new Implements()) == null) + throw new Exception(); + ThrowIfNotPresent(typeof(TestCanBeNonNull), nameof(Canary3)); + + new ImplementsINever3Object().ToString(); + AcceptINever3String(null); +#if !DEBUG + ThrowIfPresentWithUsableMethodTable(typeof(TestCanBeNonNull), nameof(Canary4)); +#endif + + if (AcceptINever3_VariantString(new ImplementsINever3_VariantObject()) == null) + throw new Exception(); + ThrowIfNotPresent(typeof(TestCanBeNonNull), nameof(Canary5)); + } + } + class TestUnmodifiableStaticFieldOptimization { static class ClassWithNotReadOnlyField