Skip to content

Commit 21cfd9f

Browse files
Fold always false type checks (#99761)
If we have a program like: ```csharp class Never { } class Program { static void Main() { Test(null); } [MethodImpl(MethodImplOptions.NoInlining)] static void Test(object o) { if (o is Never) Console.WriteLine("Hello!"); if (o.GetType() == typeof(Never)) Console.WriteLine("Hello!"); } } ``` We know these checks are never going to be true thanks to the whole program view. Fold these to `false`.
1 parent 186c994 commit 21cfd9f

File tree

15 files changed

+241
-32
lines changed

15 files changed

+241
-32
lines changed

src/coreclr/inc/corinfo.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2983,7 +2983,7 @@ class ICorStaticInfo
29832983
CORINFO_CLASS_HANDLE* vcTypeRet /* OUT */
29842984
) = 0;
29852985

2986-
// Obtains a list of exact classes for a given base type. Returns 0 if the number of
2986+
// Obtains a list of exact classes for a given base type. Returns -1 if the number of
29872987
// the exact classes is greater than maxExactClasses or if more types might be loaded
29882988
// in future.
29892989
virtual int getExactClasses(

src/coreclr/inc/jiteeversionguid.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@ typedef const GUID *LPCGUID;
4343
#define GUID_DEFINED
4444
#endif // !GUID_DEFINED
4545

46-
constexpr GUID JITEEVersionIdentifier = { /* 6fd660c7-96be-4832-a84c-4200141f7d08 */
47-
0x6fd660c7,
48-
0x96be,
49-
0x4832,
50-
{0xa8, 0x4c, 0x42, 0x00, 0x14, 0x1f, 0x7d, 0x08}
46+
constexpr GUID JITEEVersionIdentifier = { /* bdf34b26-0725-4ad6-9935-40bfd2a4c4fc */
47+
0xbdf34b26,
48+
0x0725,
49+
0x4ad6,
50+
{0x99, 0x35, 0x40, 0xbf, 0xd2, 0xa4, 0xc4, 0xfc}
5151
};
5252

5353
//////////////////////////////////////////////////////////////////////////////////////////////////////////

src/coreclr/jit/gentree.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14117,6 +14117,18 @@ GenTree* Compiler::gtFoldTypeCompare(GenTree* tree)
1411714117
return tree;
1411814118
}
1411914119

14120+
// Check if an object of this type can even exist
14121+
if (info.compCompHnd->getExactClasses(clsHnd, 0, nullptr) == 0)
14122+
{
14123+
JITDUMP("Runtime reported %p (%s) is never allocated\n", dspPtr(clsHnd), eeGetClassName(clsHnd));
14124+
14125+
const bool operatorIsEQ = (oper == GT_EQ);
14126+
const int compareResult = operatorIsEQ ? 0 : 1;
14127+
JITDUMP("Runtime reports comparison is known at jit time: %u\n", compareResult);
14128+
GenTree* result = gtNewIconNode(compareResult);
14129+
return result;
14130+
}
14131+
1412014132
// We're good to go.
1412114133
JITDUMP("Optimizing compare of obj.GetType()"
1412214134
" and type-from-handle to compare method table pointer\n");

src/coreclr/jit/importer.cpp

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5380,14 +5380,39 @@ GenTree* Compiler::impOptimizeCastClassOrIsInst(GenTree* op1, CORINFO_RESOLVED_T
53805380
return nullptr;
53815381
}
53825382

5383+
CORINFO_CLASS_HANDLE toClass = pResolvedToken->hClass;
5384+
if (info.compCompHnd->getExactClasses(toClass, 0, nullptr) == 0)
5385+
{
5386+
JITDUMP("\nClass %p (%s) can never be allocated\n", dspPtr(toClass), eeGetClassName(toClass));
5387+
5388+
if (!isCastClass)
5389+
{
5390+
JITDUMP("Cast will fail, optimizing to return null\n");
5391+
5392+
// If the cast was fed by a box, we can remove that too.
5393+
if (op1->IsBoxedValue())
5394+
{
5395+
JITDUMP("Also removing upstream box\n");
5396+
gtTryRemoveBoxUpstreamEffects(op1);
5397+
}
5398+
5399+
if (gtTreeHasSideEffects(op1, GTF_SIDE_EFFECT))
5400+
{
5401+
impAppendTree(op1, CHECK_SPILL_ALL, impCurStmtDI);
5402+
}
5403+
return gtNewNull();
5404+
}
5405+
5406+
JITDUMP("Cast will always throw, but not optimizing yet\n");
5407+
}
5408+
53835409
// See what we know about the type of the object being cast.
53845410
bool isExact = false;
53855411
bool isNonNull = false;
53865412
CORINFO_CLASS_HANDLE fromClass = gtGetClassHandle(op1, &isExact, &isNonNull);
53875413

53885414
if (fromClass != nullptr)
53895415
{
5390-
CORINFO_CLASS_HANDLE toClass = pResolvedToken->hClass;
53915416
JITDUMP("\nConsidering optimization of %s from %s%p (%s) to %p (%s)\n", isCastClass ? "castclass" : "isinst",
53925417
isExact ? "exact " : "", dspPtr(fromClass), eeGetClassName(fromClass), dspPtr(toClass),
53935418
eeGetClassName(toClass));

src/coreclr/jit/importercalls.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6596,7 +6596,7 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call,
65966596
{
65976597
JITDUMP("No exact classes implementing %s\n", eeGetClassName(baseClass))
65986598
}
6599-
else if (numExactClasses > maxTypeChecks)
6599+
else if (numExactClasses < 0 || numExactClasses > maxTypeChecks)
66006600
{
66016601
JITDUMP("Too many exact classes implementing %s (%d > %d)\n", eeGetClassName(baseClass), numExactClasses,
66026602
maxTypeChecks)

src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -203,13 +203,17 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType
203203

204204
#if !READYTORUN
205205
/// <summary>
206-
/// Gets a value indicating whether it might be possible to obtain a constructed type data structure for the given type.
206+
/// Gets a value indicating whether it might be possible to obtain a constructed type data structure for the given type
207+
/// in this compilation (i.e. is it possible to reference a constructed MethodTable symbol for this).
207208
/// </summary>
208-
/// <remarks>
209-
/// This is a bit of a hack, but devirtualization manager has a global view of all allocated types
210-
/// so it can answer this question.
211-
/// </remarks>
212-
public virtual bool CanConstructType(TypeDesc type) => true;
209+
public virtual bool CanReferenceConstructedMethodTable(TypeDesc type) => true;
210+
211+
/// <summary>
212+
/// Gets a value indicating whether a (potentially canonically-equlivalent) constructed MethodTable could
213+
/// exist. This is similar to <see cref="CanReferenceConstructedMethodTable"/>, but will return true
214+
/// for List&lt;__Canon&gt; if a constructed MethodTable for List&lt;object&gt; exists.
215+
/// </summary>
216+
public virtual bool CanReferenceConstructedTypeOrCanonicalFormOfType(TypeDesc type) => true;
213217

214218
public virtual TypeDesc[] GetImplementingClasses(TypeDesc type) => null;
215219
#endif

src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4284,7 +4284,7 @@ private HRESULT getPgoInstrumentationResults(CORINFO_METHOD_STRUCT_* ftnHnd, ref
42844284
#pragma warning disable SA1001, SA1113, SA1115 // Commas should be spaced correctly
42854285
ComputeJitPgoInstrumentationSchema(ObjectToHandle, pgoResultsSchemas, out var nativeSchemas, _cachedMemoryStream
42864286
#if !READYTORUN
4287-
, _compilation.CanConstructType
4287+
, _compilation.CanReferenceConstructedMethodTable
42884288
#endif
42894289
);
42904290
#pragma warning restore SA1001, SA1113, SA1115 // Commas should be spaced correctly

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Compilation.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,14 @@ public bool CanInline(MethodDesc caller, MethodDesc callee)
103103
return _inliningPolicy.CanInline(caller, callee);
104104
}
105105

106-
public bool CanConstructType(TypeDesc type)
106+
public bool CanReferenceConstructedMethodTable(TypeDesc type)
107107
{
108-
return NodeFactory.DevirtualizationManager.CanConstructType(type);
108+
return NodeFactory.DevirtualizationManager.CanReferenceConstructedMethodTable(type);
109+
}
110+
111+
public bool CanReferenceConstructedTypeOrCanonicalFormOfType(TypeDesc type)
112+
{
113+
return NodeFactory.DevirtualizationManager.CanReferenceConstructedTypeOrCanonicalFormOfType(type);
109114
}
110115

111116
public DelegateCreationInfo GetDelegateCtor(TypeDesc delegateType, MethodDesc target, TypeDesc constrainedType, bool followVirtualDispatch)
@@ -261,7 +266,7 @@ public bool NeedsRuntimeLookup(ReadyToRunHelperId lookupKind, object targetOfLoo
261266

262267
public ReadyToRunHelperId GetLdTokenHelperForType(TypeDesc type)
263268
{
264-
bool canConstructPerWholeProgramAnalysis = NodeFactory.DevirtualizationManager.CanConstructType(type);
269+
bool canConstructPerWholeProgramAnalysis = NodeFactory.DevirtualizationManager.CanReferenceConstructedMethodTable(type);
265270
bool creationAllowed = ConstructedEETypeNode.CreationAllowed(type);
266271
return (canConstructPerWholeProgramAnalysis && creationAllowed)
267272
? ReadyToRunHelperId.TypeHandle

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,8 @@ public override DictionaryLayoutNode GetLayout(TypeSystemEntity methodOrType)
413413

414414
private sealed class ScannedDevirtualizationManager : DevirtualizationManager
415415
{
416-
private HashSet<TypeDesc> _constructedTypes = new HashSet<TypeDesc>();
416+
private HashSet<TypeDesc> _constructedMethodTables = new HashSet<TypeDesc>();
417+
private HashSet<TypeDesc> _canonConstructedMethodTables = new HashSet<TypeDesc>();
417418
private HashSet<TypeDesc> _canonConstructedTypes = new HashSet<TypeDesc>();
418419
private HashSet<TypeDesc> _unsealedTypes = new HashSet<TypeDesc>();
419420
private Dictionary<TypeDesc, HashSet<TypeDesc>> _implementators = new();
@@ -442,7 +443,12 @@ public ScannedDevirtualizationManager(NodeFactory factory, ImmutableArray<Depend
442443

443444
if (type != null)
444445
{
445-
_constructedTypes.Add(type);
446+
_constructedMethodTables.Add(type);
447+
TypeDesc canonForm = type.ConvertToCanonForm(CanonicalFormKind.Specific);
448+
if (canonForm != type)
449+
{
450+
_canonConstructedMethodTables.Add(canonForm);
451+
}
446452

447453
if (type.IsInterface)
448454
{
@@ -687,7 +693,11 @@ protected override MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefTyp
687693
return result;
688694
}
689695

690-
public override bool CanConstructType(TypeDesc type) => _constructedTypes.Contains(type);
696+
public override bool CanReferenceConstructedMethodTable(TypeDesc type)
697+
=> _constructedMethodTables.Contains(type);
698+
699+
public override bool CanReferenceConstructedTypeOrCanonicalFormOfType(TypeDesc type)
700+
=> _constructedMethodTables.Contains(type) || _canonConstructedMethodTables.Contains(type);
691701

692702
public override TypeDesc[] GetImplementingClasses(TypeDesc type)
693703
{

src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3280,7 +3280,7 @@ private void updateEntryPointForTailCall(ref CORINFO_CONST_LOOKUP entryPoint)
32803280
private int getExactClasses(CORINFO_CLASS_STRUCT_* baseType, int maxExactClasses, CORINFO_CLASS_STRUCT_** exactClsRet)
32813281
{
32823282
// Not implemented for R2R yet
3283-
return 0;
3283+
return -1;
32843284
}
32853285

32863286
private bool getStaticFieldContent(CORINFO_FIELD_STRUCT_* fieldHandle, byte* buffer, int bufferSize, int valueOffset, bool ignoreMovableObjects)

0 commit comments

Comments
 (0)