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

+1-1
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

+5-5
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

+12
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

+26-1
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

+1-1
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

+10-6
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

+1-1
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

+8-3
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

+13-3
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

+1-1
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)

src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public override IEETypeNode NecessaryTypeSymbolIfPossible(TypeDesc type)
7272
// information proving that it isn't, give RyuJIT the constructed symbol even
7373
// though we just need the unconstructed one.
7474
// https://github.com/dotnet/runtimelab/issues/1128
75-
bool canPotentiallyConstruct = NodeFactory.DevirtualizationManager.CanConstructType(type);
75+
bool canPotentiallyConstruct = NodeFactory.DevirtualizationManager.CanReferenceConstructedMethodTable(type);
7676
if (canPotentiallyConstruct)
7777
return _nodeFactory.MaximallyConstructableType(type);
7878

@@ -81,7 +81,7 @@ public override IEETypeNode NecessaryTypeSymbolIfPossible(TypeDesc type)
8181

8282
public FrozenRuntimeTypeNode NecessaryRuntimeTypeIfPossible(TypeDesc type)
8383
{
84-
bool canPotentiallyConstruct = NodeFactory.DevirtualizationManager.CanConstructType(type);
84+
bool canPotentiallyConstruct = NodeFactory.DevirtualizationManager.CanReferenceConstructedMethodTable(type);
8585
if (canPotentiallyConstruct)
8686
return _nodeFactory.SerializedMaximallyConstructableRuntimeTypeObject(type);
8787

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

+29-1
Original file line numberDiff line numberDiff line change
@@ -2248,14 +2248,42 @@ private void getFieldInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_MET
22482248
// and STS::AccessCheck::CanAccess.
22492249
}
22502250

2251+
private bool CanNeverHaveInstanceOfSubclassOf(TypeDesc type)
2252+
{
2253+
// Don't try to optimize nullable
2254+
if (type.IsNullable)
2255+
return false;
2256+
2257+
// We don't track unconstructable types very well and they are rare anyway
2258+
if (!ConstructedEETypeNode.CreationAllowed(type))
2259+
return false;
2260+
2261+
TypeDesc canonType = type.ConvertToCanonForm(CanonicalFormKind.Specific);
2262+
2263+
// If we don't have a constructed MethodTable for the exact type or for its template,
2264+
// this type or any of its subclasses can never be instantiated.
2265+
return !_compilation.CanReferenceConstructedTypeOrCanonicalFormOfType(type)
2266+
&& (type == canonType || !_compilation.CanReferenceConstructedMethodTable(canonType));
2267+
}
2268+
22512269
private int getExactClasses(CORINFO_CLASS_STRUCT_* baseType, int maxExactClasses, CORINFO_CLASS_STRUCT_** exactClsRet)
22522270
{
22532271
MetadataType type = HandleToObject(baseType) as MetadataType;
22542272
if (type == null)
2273+
{
2274+
return -1;
2275+
}
2276+
2277+
if (CanNeverHaveInstanceOfSubclassOf(type))
22552278
{
22562279
return 0;
22572280
}
22582281

2282+
if (maxExactClasses == 0)
2283+
{
2284+
return -1;
2285+
}
2286+
22592287
// type is already sealed, return it
22602288
if (_compilation.IsEffectivelySealed(type))
22612289
{
@@ -2266,7 +2294,7 @@ private int getExactClasses(CORINFO_CLASS_STRUCT_* baseType, int maxExactClasses
22662294
TypeDesc[] implClasses = _compilation.GetImplementingClasses(type);
22672295
if (implClasses == null || implClasses.Length > maxExactClasses)
22682296
{
2269-
return 0;
2297+
return -1;
22702298
}
22712299

22722300
int index = 0;

src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp

+4-4
Original file line numberDiff line numberDiff line change
@@ -2787,15 +2787,15 @@ void MethodContext::recGetExactClasses(CORINFO_CLASS_HANDLE baseType, int maxExa
27872787
key.A = CastHandle(baseType);
27882788
key.B = maxExactClasses;
27892789

2790-
Assert(result >= 0);
2790+
int numResults = result < 0 ? 0 : result;
27912791

2792-
DWORDLONG* exactClassesAgnostic = new DWORDLONG[result];
2793-
for (int i = 0; i < result; i++)
2792+
DWORDLONG* exactClassesAgnostic = new DWORDLONG[numResults];
2793+
for (int i = 0; i < numResults; i++)
27942794
exactClassesAgnostic[i] = CastHandle(exactClsRet[i]);
27952795

27962796
Agnostic_GetExactClassesResult value;
27972797
value.numClasses = result;
2798-
value.classes = GetExactClasses->AddBuffer((unsigned char*)exactClassesAgnostic, (unsigned int)(result * sizeof(DWORDLONG)));
2798+
value.classes = GetExactClasses->AddBuffer((unsigned char*)exactClassesAgnostic, (unsigned int)(numResults * sizeof(DWORDLONG)));
27992799

28002800
delete[] exactClassesAgnostic;
28012801

src/coreclr/vm/jitinterface.cpp

+1-3
Original file line numberDiff line numberDiff line change
@@ -9663,16 +9663,14 @@ int CEEInfo::getExactClasses (
96639663
MODE_ANY;
96649664
} CONTRACTL_END;
96659665

9666-
int exactClassesCount = 0;
9667-
96689666
JIT_TO_EE_TRANSITION();
96699667

96709668
// This function is currently implemented only on NativeAOT
96719669
// but can be implemented for CoreCLR as well (e.g. for internal types)
96729670

96739671
EE_TO_JIT_TRANSITION();
96749672

9675-
return exactClassesCount;
9673+
return -1;
96769674
}
96779675

96789676
/*********************************************************************/

0 commit comments

Comments
 (0)