Skip to content

Commit 91d15ff

Browse files
authored
JIT: teach VN to fold type comparisons (#72136)
Teach VN to fold some comparisons involving calls to `TypeHandleToRuntimeType`. Closes #71909.
1 parent dfd6efc commit 91d15ff

File tree

6 files changed

+348
-2
lines changed

6 files changed

+348
-2
lines changed

src/coreclr/jit/smallhash.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,26 @@ struct HashTableInfo<unsigned>
7575
}
7676
};
7777

78+
#ifdef HOST_64BIT
79+
//------------------------------------------------------------------------
80+
// HashTableInfo<ssize_t>: specialized version of HashTableInfo for ssize_t-
81+
// typed keys.
82+
template <>
83+
struct HashTableInfo<ssize_t>
84+
{
85+
static bool Equals(ssize_t x, ssize_t y)
86+
{
87+
return x == y;
88+
}
89+
90+
static unsigned GetHashCode(ssize_t key)
91+
{
92+
// Return the key itself
93+
return (unsigned)key;
94+
}
95+
};
96+
#endif
97+
7898
//------------------------------------------------------------------------
7999
// HashTableBase: base type for HashTable and SmallHashTable. This class
80100
// provides the vast majority of the implementation. The

src/coreclr/jit/valuenum.cpp

Lines changed: 126 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,7 @@ ValueNumStore::ValueNumStore(Compiler* comp, CompAllocator alloc)
439439
, m_intCnsMap(nullptr)
440440
, m_longCnsMap(nullptr)
441441
, m_handleMap(nullptr)
442+
, m_embeddedToCompileTimeHandleMap(alloc)
442443
, m_floatCnsMap(nullptr)
443444
, m_doubleCnsMap(nullptr)
444445
, m_byrefCnsMap(nullptr)
@@ -2135,6 +2136,22 @@ ValueNum ValueNumStore::VNForFunc(var_types typ, VNFunc func, ValueNum arg0VN, V
21352136

21362137
ValueNum resultVN = NoVN;
21372138

2139+
// Even if the argVNs differ, if both operands runtime types constructed from handles,
2140+
// we can sometimes also fold.
2141+
//
2142+
// The case where the arg VNs are equal is handled by EvalUsingMathIdentity below.
2143+
// This is the VN analog of gtFoldTypeCompare.
2144+
//
2145+
const genTreeOps oper = genTreeOps(func);
2146+
if ((arg0VN != arg1VN) && GenTree::StaticOperIs(oper, GT_EQ, GT_NE))
2147+
{
2148+
resultVN = VNEvalFoldTypeCompare(typ, func, arg0VN, arg1VN);
2149+
if (resultVN != NoVN)
2150+
{
2151+
return resultVN;
2152+
}
2153+
}
2154+
21382155
// We canonicalize commutative operations.
21392156
// (Perhaps should eventually handle associative/commutative [AC] ops -- but that gets complicated...)
21402157
if (VNFuncIsCommutative(func))
@@ -3651,6 +3668,108 @@ ValueNum ValueNumStore::EvalBitCastForConstantArgs(var_types dstType, ValueNum a
36513668
}
36523669
}
36533670

3671+
//------------------------------------------------------------------------
3672+
// VNEvalFoldTypeCompare:
3673+
//
3674+
// Arguments:
3675+
// type - The result type
3676+
// func - The function
3677+
// arg0VN - VN of the first argument
3678+
// arg1VN - VN of the second argument
3679+
//
3680+
// Return Value:
3681+
// NoVN if this is not a foldable type compare
3682+
// Simplified (perhaps constant) VN if it is foldable.
3683+
//
3684+
// Notes:
3685+
// Value number counterpart to gtFoldTypeCompare
3686+
// Doesn't handle all the cases (yet).
3687+
//
3688+
// (EQ/NE (TypeHandleToRuntimeType x) (TypeHandleToRuntimeType y)) == (EQ/NE x y)
3689+
//
3690+
ValueNum ValueNumStore::VNEvalFoldTypeCompare(var_types type, VNFunc func, ValueNum arg0VN, ValueNum arg1VN)
3691+
{
3692+
const genTreeOps oper = genTreeOps(func);
3693+
assert(GenTree::StaticOperIs(oper, GT_EQ, GT_NE));
3694+
3695+
VNFuncApp arg0Func;
3696+
const bool arg0IsFunc = GetVNFunc(arg0VN, &arg0Func);
3697+
3698+
if (!arg0IsFunc || (arg0Func.m_func != VNF_TypeHandleToRuntimeType))
3699+
{
3700+
return NoVN;
3701+
}
3702+
3703+
VNFuncApp arg1Func;
3704+
const bool arg1IsFunc = GetVNFunc(arg1VN, &arg1Func);
3705+
3706+
if (!arg1IsFunc || (arg1Func.m_func != VNF_TypeHandleToRuntimeType))
3707+
{
3708+
return NoVN;
3709+
}
3710+
3711+
// Only re-express as handle equality when we have known
3712+
// class handles and the VM agrees comparing these gives the same
3713+
// result as comparing the runtime types.
3714+
//
3715+
// Note that VN actually tracks the value of embedded handle;
3716+
// we need to pass the VM the associated the compile time handles,
3717+
// in case they differ (say for prejitting or AOT).
3718+
//
3719+
ValueNum handle0 = arg0Func.m_args[0];
3720+
if (!IsVNHandle(handle0))
3721+
{
3722+
return NoVN;
3723+
}
3724+
3725+
ValueNum handle1 = arg1Func.m_args[0];
3726+
if (!IsVNHandle(handle1))
3727+
{
3728+
return NoVN;
3729+
}
3730+
3731+
assert(GetHandleFlags(handle0) == GTF_ICON_CLASS_HDL);
3732+
assert(GetHandleFlags(handle1) == GTF_ICON_CLASS_HDL);
3733+
3734+
const ssize_t handleVal0 = ConstantValue<ssize_t>(handle0);
3735+
const ssize_t handleVal1 = ConstantValue<ssize_t>(handle1);
3736+
ssize_t compileTimeHandle0;
3737+
ssize_t compileTimeHandle1;
3738+
3739+
// These mappings should always exist.
3740+
//
3741+
const bool found0 = m_embeddedToCompileTimeHandleMap.TryGetValue(handleVal0, &compileTimeHandle0);
3742+
const bool found1 = m_embeddedToCompileTimeHandleMap.TryGetValue(handleVal1, &compileTimeHandle1);
3743+
assert(found0 && found1);
3744+
3745+
// We may see null compile time handles for some constructed class handle cases.
3746+
// We should fix the construction if possible. But just skip those cases for now.
3747+
//
3748+
if ((compileTimeHandle0 == 0) || (compileTimeHandle1 == 0))
3749+
{
3750+
return NoVN;
3751+
}
3752+
3753+
JITDUMP("Asking runtime to compare %p (%s) and %p (%s) for equality\n", dspPtr(compileTimeHandle0),
3754+
m_pComp->eeGetClassName(CORINFO_CLASS_HANDLE(compileTimeHandle0)), dspPtr(compileTimeHandle1),
3755+
m_pComp->eeGetClassName(CORINFO_CLASS_HANDLE(compileTimeHandle1)));
3756+
3757+
ValueNum result = NoVN;
3758+
const TypeCompareState s =
3759+
m_pComp->info.compCompHnd->compareTypesForEquality(CORINFO_CLASS_HANDLE(compileTimeHandle0),
3760+
CORINFO_CLASS_HANDLE(compileTimeHandle1));
3761+
if (s != TypeCompareState::May)
3762+
{
3763+
const bool typesAreEqual = (s == TypeCompareState::Must);
3764+
const bool operatorIsEQ = (oper == GT_EQ);
3765+
const int compareResult = operatorIsEQ ^ typesAreEqual ? 0 : 1;
3766+
JITDUMP("Runtime reports comparison is known at jit time: %u\n", compareResult);
3767+
result = VNForIntCon(compareResult);
3768+
}
3769+
3770+
return result;
3771+
}
3772+
36543773
//------------------------------------------------------------------------
36553774
// VNEvalCanFoldBinaryFunc: Can the given binary function be constant-folded?
36563775
//
@@ -7929,8 +8048,13 @@ void Compiler::fgValueNumberTreeConst(GenTree* tree)
79298048
case TYP_BOOL:
79308049
if (tree->IsIconHandle())
79318050
{
7932-
tree->gtVNPair.SetBoth(
7933-
vnStore->VNForHandle(ssize_t(tree->AsIntConCommon()->IconValue()), tree->GetIconHandleFlag()));
8051+
const ssize_t embeddedHandle = tree->AsIntCon()->IconValue();
8052+
tree->gtVNPair.SetBoth(vnStore->VNForHandle(embeddedHandle, tree->GetIconHandleFlag()));
8053+
if (tree->GetIconHandleFlag() == GTF_ICON_CLASS_HDL)
8054+
{
8055+
const ssize_t compileTimeHandle = tree->AsIntCon()->gtCompileTimeHandle;
8056+
vnStore->AddToEmbeddedHandleMap(embeddedHandle, compileTimeHandle);
8057+
}
79348058
}
79358059
else if ((typ == TYP_LONG) || (typ == TYP_ULONG))
79368060
{

src/coreclr/jit/valuenum.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,9 @@ class ValueNumStore
314314
// Returns "true" iff "vnf" should be folded by evaluating the func with constant arguments.
315315
bool VNEvalShouldFold(var_types typ, VNFunc func, ValueNum arg0VN, ValueNum arg1VN);
316316

317+
// Value number a type comparison
318+
ValueNum VNEvalFoldTypeCompare(var_types type, VNFunc func, ValueNum arg0VN, ValueNum arg1VN);
319+
317320
// return vnf(v0)
318321
template <typename T>
319322
static T EvalOp(VNFunc vnf, T v0);
@@ -458,6 +461,11 @@ class ValueNumStore
458461
// that happens to be the same...
459462
ValueNum VNForHandle(ssize_t cnsVal, GenTreeFlags iconFlags);
460463

464+
void AddToEmbeddedHandleMap(ssize_t embeddedHandle, ssize_t compileTimeHandle)
465+
{
466+
m_embeddedToCompileTimeHandleMap.AddOrUpdate(embeddedHandle, compileTimeHandle);
467+
}
468+
461469
// And the single constant for an object reference type.
462470
static ValueNum VNForNull()
463471
{
@@ -1380,6 +1388,9 @@ class ValueNumStore
13801388
return m_handleMap;
13811389
}
13821390

1391+
typedef SmallHashTable<ssize_t, ssize_t> EmbeddedToCompileTimeHandleMap;
1392+
EmbeddedToCompileTimeHandleMap m_embeddedToCompileTimeHandleMap;
1393+
13831394
struct LargePrimitiveKeyFuncsFloat : public JitLargePrimitiveKeyFuncs<float>
13841395
{
13851396
static bool Equals(float x, float y)
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Runtime.CompilerServices;
7+
8+
public enum Enum1 : int { A }
9+
public enum Enum2 : uint { A }
10+
11+
class TypeTestFolding
12+
{
13+
[MethodImpl(MethodImplOptions.NoInlining)]
14+
static void SideEffect() { }
15+
16+
//static bool True0() => typeof(delegate*<int, double>) == typeof(delegate* unmanaged<float, void*, void>);
17+
//static bool True1()
18+
//{
19+
// var t0 = typeof(delegate*<int, double>);
20+
// SideEffect();
21+
// var t1 = typeof(delegate* unmanaged<float, void*, void>);
22+
// return t0 == t1;
23+
//}
24+
25+
static bool True2() => typeof(TypeTestFolding) == typeof(TypeTestFolding);
26+
static bool True3()
27+
{
28+
var t0 = typeof(TypeTestFolding);
29+
SideEffect();
30+
var t1 = typeof(TypeTestFolding);
31+
return t0 == t1;
32+
}
33+
34+
static bool True4() => typeof(ValueTuple<TypeTestFolding>) == typeof(ValueTuple<TypeTestFolding>);
35+
static bool True5()
36+
{
37+
var t0 = typeof(ValueTuple<TypeTestFolding>);
38+
SideEffect();
39+
var t1 = typeof(ValueTuple<TypeTestFolding>);
40+
return t0 == t1;
41+
}
42+
43+
//static bool True6() => typeof(delegate*<int>) == typeof(nint);
44+
//static bool True7()
45+
//{
46+
// var t0 = typeof(delegate*<int>);
47+
// SideEffect();
48+
// var t1 = typeof(nint);
49+
// return t0 == t1;
50+
//}
51+
52+
static bool False0() => typeof(List<object>) == typeof(List<string>);
53+
static bool False1()
54+
{
55+
var t0 = typeof(List<object>);
56+
SideEffect();
57+
var t1 = typeof(List<string>);
58+
return t0 == t1;
59+
}
60+
61+
static bool False2() => typeof(int) == typeof(Enum1);
62+
static bool False3()
63+
{
64+
var t0 = typeof(int);
65+
SideEffect();
66+
var t1 = typeof(Enum1);
67+
return t0 == t1;
68+
}
69+
70+
static bool False4() => typeof(Enum1) == typeof(Enum2);
71+
static bool False5()
72+
{
73+
var t0 = typeof(Enum1);
74+
SideEffect();
75+
var t1 = typeof(Enum2);
76+
return t0 == t1;
77+
}
78+
79+
static bool False6() => typeof(int?) == typeof(uint?);
80+
static bool False7()
81+
{
82+
var t0 = typeof(int?);
83+
SideEffect();
84+
var t1 = typeof(uint?);
85+
return t0 == t1;
86+
}
87+
88+
static bool False8() => typeof(int?) == typeof(Enum1?);
89+
static bool False9()
90+
{
91+
var t0 = typeof(int?);
92+
SideEffect();
93+
var t1 = typeof(Enum1?);
94+
return t0 == t1;
95+
}
96+
97+
static bool False10() => typeof(ValueTuple<TypeTestFolding>) == typeof(ValueTuple<string>);
98+
static bool False11()
99+
{
100+
var t0 = typeof(ValueTuple<TypeTestFolding>);
101+
SideEffect();
102+
var t1 = typeof(ValueTuple<string>);
103+
return t0 == t1;
104+
}
105+
106+
//static bool False12() => typeof(delegate*<int>[]) == typeof(delegate*<float>[]);
107+
//static bool False13()
108+
//{
109+
// var t0 = typeof(delegate*<int>[]);
110+
// SideEffect();
111+
// var t1 = typeof(delegate*<float>[]);
112+
// return t0 == t1;
113+
//}
114+
115+
static bool False14() => typeof(int[]) == typeof(uint[]);
116+
static bool False15()
117+
{
118+
var t0 = typeof(int[]);
119+
SideEffect();
120+
var t1 = typeof(uint[]);
121+
return t0 == t1;
122+
}
123+
124+
//static bool False16() => typeof(delegate*<int>) == typeof(IntPtr);
125+
//static bool False17()
126+
//{
127+
// var t0 = typeof(delegate*<int>);
128+
// SideEffect();
129+
// var t1 = typeof(UIntPtr);
130+
// return t0 == t1;
131+
//}
132+
133+
unsafe static int Main()
134+
{
135+
delegate*<bool>[] trueFuncs = new delegate*<bool>[] { &True2, &True3, &True4, &True5 };
136+
delegate*<bool>[] falseFuncs = new delegate*<bool>[] { &False0, &False1, &False2, &False3, &False4, &False5,
137+
&False6, &False7, &False8, &False9, &False10, &False11,
138+
&False14, &False15 };
139+
140+
int result = 100;
141+
int trueCount = 0;
142+
int falseCount = 0;
143+
144+
foreach (var tf in trueFuncs)
145+
{
146+
if (!tf())
147+
{
148+
Console.WriteLine($"True{trueCount} failed");
149+
result++;
150+
}
151+
trueCount++;
152+
}
153+
154+
foreach (var ff in falseFuncs)
155+
{
156+
if (ff())
157+
{
158+
Console.WriteLine($"False{falseCount} failed");
159+
result++;
160+
}
161+
falseCount++;
162+
}
163+
164+
Console.WriteLine($"Ran {trueCount + falseCount} tests; result {result}");
165+
return result;
166+
}
167+
}
168+
169+
170+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<OutputType>Exe</OutputType>
4+
</PropertyGroup>
5+
<PropertyGroup>
6+
<DebugType>None</DebugType>
7+
<Optimize>True</Optimize>
8+
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
9+
</PropertyGroup>
10+
<ItemGroup>
11+
<Compile Include="$(MSBuildProjectName).cs" />
12+
</ItemGroup>
13+
</Project>

0 commit comments

Comments
 (0)