Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fold "x ==/!= null" on NativeAOT for non-existent classes #111478

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
// <T> ... foo(T x) { ... if ((object)x == null) ...
Expand Down
15 changes: 15 additions & 0 deletions src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,13 +208,28 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType
/// </summary>
public virtual bool CanReferenceConstructedMethodTable(TypeDesc type) => true;

/// <summary>
/// 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.
/// </summary>
public virtual bool CanInterfaceBeImplementedByConstructedMethodTable(TypeDesc type) => true;

/// <summary>
/// Gets a value indicating whether a (potentially canonically-equlivalent) constructed MethodTable could
/// exist. This is similar to <see cref="CanReferenceConstructedMethodTable"/>, but will return true
/// for List&lt;__Canon&gt; if a constructed MethodTable for List&lt;object&gt; exists.
/// </summary>
public virtual bool CanReferenceConstructedTypeOrCanonicalFormOfType(TypeDesc type) => true;

/// <summary>
/// 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 <see cref="CanInterfaceBeImplementedByConstructedMethodTable"/> but will return true for
/// IInterface&lt;__Canon&gt; if IInterface&lt;String&gt; could be implemented.
/// </summary>
public virtual bool CanInterfaceOrCanonicalFormOfItBeImplementedByConstructedMethodTable(TypeDesc type) => true;


public virtual TypeDesc[] GetImplementingClasses(TypeDesc type) => null;
#endif
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
23 changes: 23 additions & 0 deletions src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,8 @@ private sealed class ScannedDevirtualizationManager : DevirtualizationManager
private HashSet<TypeDesc> _constructedMethodTables = new HashSet<TypeDesc>();
private HashSet<TypeDesc> _canonConstructedMethodTables = new HashSet<TypeDesc>();
private HashSet<TypeDesc> _canonConstructedTypes = new HashSet<TypeDesc>();
private HashSet<TypeDesc> _implementedInterfaces = new HashSet<TypeDesc>();
private HashSet<TypeDesc> _canonImplementedInterfaces = new HashSet<TypeDesc>();
private HashSet<TypeDesc> _unsealedTypes = new HashSet<TypeDesc>();
private Dictionary<TypeDesc, HashSet<TypeDesc>> _implementators = new();
private HashSet<TypeDesc> _disqualifiedTypes = new();
Expand Down Expand Up @@ -507,6 +509,13 @@ public ScannedDevirtualizationManager(NodeFactory factory, ImmutableArray<Depend
{
RecordImplementation(baseInterface, type);
}

if (_implementedInterfaces.Add(baseInterface))
{
TypeDesc canonBaseInterface = baseInterface.ConvertToCanonForm(CanonicalFormKind.Specific);
if (baseInterface != canonBaseInterface)
_canonImplementedInterfaces.Add(canonBaseInterface);
}
}

// Record all base types of this class
Expand Down Expand Up @@ -729,13 +738,27 @@ public override bool CanReferenceConstructedMethodTable(TypeDesc type)
return _constructedMethodTables.Contains(type);
}

public override bool CanInterfaceBeImplementedByConstructedMethodTable(TypeDesc type)
{
Debug.Assert(type.NormalizeInstantiation() == type);
Debug.Assert(type.IsInterface);
return _implementedInterfaces.Contains(type);
}

public override bool CanReferenceConstructedTypeOrCanonicalFormOfType(TypeDesc type)
{
Debug.Assert(type.NormalizeInstantiation() == type);
Debug.Assert(ConstructedEETypeNode.CreationAllowed(type));
return _constructedMethodTables.Contains(type) || _canonConstructedMethodTables.Contains(type);
}

public override bool CanInterfaceOrCanonicalFormOfItBeImplementedByConstructedMethodTable(TypeDesc type)
{
Debug.Assert(type.NormalizeInstantiation() == type);
Debug.Assert(type.IsInterface);
return _implementedInterfaces.Contains(type) || _canonImplementedInterfaces.Contains(type);
}

public override TypeDesc[] GetImplementingClasses(TypeDesc type)
{
if (_disqualifiedTypes.Contains(type))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2244,8 +2244,25 @@ private bool CanNeverHaveInstanceOfSubclassOf(TypeDesc type)
if (!ConstructedEETypeNode.CreationAllowed(type))
return false;

// TODO: rather conservative
if (type.HasVariance)
return false;

TypeDesc canonType = type.ConvertToCanonForm(CanonicalFormKind.Specific);

// Interface optimization may be able to remove MethodTable of interfaces if they were never actually
// used to call/cast. Since getExactClasses can be called for things like "is a location of this
// type ever going to be non-null", we need to make sure the ability to optimize the interface MethodTable away
// doesn't lead to saying "this will always be null".
if (type.IsInterface)
{
if (_compilation.CanInterfaceOrCanonicalFormOfItBeImplementedByConstructedMethodTable(type)
|| (type != canonType && _compilation.CanInterfaceBeImplementedByConstructedMethodTable(canonType)))
{
return false;
}
}

// 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public static int Run()
TestTypeIsEnum.Run();
TestTypeIsValueType.Run();
TestBranchesInGenericCodeRemoval.Run();
TestCanBeNonNull.Run();
TestUnmodifiableStaticFieldOptimization.Run();
TestUnmodifiableInstanceFieldOptimization.Run();
TestGetMethodOptimization.Run();
Expand Down Expand Up @@ -716,6 +717,105 @@ public static void Run()
}
}

class TestCanBeNonNull
{
interface INever1;
interface INever2<T>;
interface INever3<T>;
interface INever3_Variant<in T>;

class ImplementsINever3Object : INever3<object>;
class ImplementsINever3_VariantObject : INever3_Variant<object>;

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<T>(INever2<T> 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<string> inst)
{
if (inst != null)
{
return new Canary4();
}
return null;
}

[MethodImpl(MethodImplOptions.NoInlining)]
static object AcceptINever3_VariantString(INever3_Variant<string> 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<object>(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
Expand Down
Loading