Skip to content

Commit 315c31c

Browse files
authored
JIT: redundant branch destructure dominating and/or (#69291)
If a branch predicate `p` is dominated by another branch with predicate `AND(p, ..)` or `OR(p, ...)` we may be able to infer the value of `p`. This is useful on its own, and should help unblock #62689.
1 parent fdcedd8 commit 315c31c

File tree

7 files changed

+529
-62
lines changed

7 files changed

+529
-62
lines changed

src/coreclr/jit/compiler.h

+12-10
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,17 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
7070
* Forward declarations
7171
*/
7272

73-
struct InfoHdr; // defined in GCInfo.h
74-
struct escapeMapping_t; // defined in fgdiagnostic.cpp
75-
class emitter; // defined in emit.h
76-
struct ShadowParamVarInfo; // defined in GSChecks.cpp
77-
struct InitVarDscInfo; // defined in register_arg_convention.h
78-
class FgStack; // defined in fgbasic.cpp
79-
class Instrumentor; // defined in fgprofile.cpp
80-
class SpanningTreeVisitor; // defined in fgprofile.cpp
81-
class CSE_DataFlow; // defined in OptCSE.cpp
82-
class OptBoolsDsc; // defined in optimizer.cpp
73+
struct InfoHdr; // defined in GCInfo.h
74+
struct escapeMapping_t; // defined in fgdiagnostic.cpp
75+
class emitter; // defined in emit.h
76+
struct ShadowParamVarInfo; // defined in GSChecks.cpp
77+
struct InitVarDscInfo; // defined in register_arg_convention.h
78+
class FgStack; // defined in fgbasic.cpp
79+
class Instrumentor; // defined in fgprofile.cpp
80+
class SpanningTreeVisitor; // defined in fgprofile.cpp
81+
class CSE_DataFlow; // defined in OptCSE.cpp
82+
class OptBoolsDsc; // defined in optimizer.cpp
83+
struct RelopImplicationInfo; // defined in redundantbranchopts.cpp
8384
#ifdef DEBUG
8485
struct IndentStack;
8586
#endif
@@ -6900,6 +6901,7 @@ class Compiler
69006901
bool optRedundantBranch(BasicBlock* const block);
69016902
bool optJumpThread(BasicBlock* const block, BasicBlock* const domBlock, bool domIsSameRelop);
69026903
bool optReachable(BasicBlock* const fromBlock, BasicBlock* const toBlock, BasicBlock* const excludedBlock);
6904+
void optRelopImpliesRelop(RelopImplicationInfo* rii);
69036905

69046906
/**************************************************************************
69056907
* Value/Assertion propagation

src/coreclr/jit/jitconfigvalues.h

+1
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,7 @@ CONFIG_INTEGER(JitDoLoopHoisting, W("JitDoLoopHoisting"), 1) // Perform loop h
400400
CONFIG_INTEGER(JitDoLoopInversion, W("JitDoLoopInversion"), 1) // Perform loop inversion on "for/while" loops
401401
CONFIG_INTEGER(JitDoRangeAnalysis, W("JitDoRangeAnalysis"), 1) // Perform range check analysis
402402
CONFIG_INTEGER(JitDoRedundantBranchOpts, W("JitDoRedundantBranchOpts"), 1) // Perform redundant branch optimizations
403+
403404
CONFIG_INTEGER(JitDoSsa, W("JitDoSsa"), 1) // Perform Static Single Assignment (SSA) numbering on the variables
404405
CONFIG_INTEGER(JitDoValueNumber, W("JitDoValueNumber"), 1) // Perform value numbering on method expressions
405406

src/coreclr/jit/redundantbranchopts.cpp

+202-52
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,161 @@ PhaseStatus Compiler::optRedundantBranches()
7272
return visitor.madeChanges ? PhaseStatus::MODIFIED_EVERYTHING : PhaseStatus::MODIFIED_NOTHING;
7373
}
7474

75+
static const ValueNumStore::VN_RELATION_KIND s_vnRelations[] = {ValueNumStore::VN_RELATION_KIND::VRK_Same,
76+
ValueNumStore::VN_RELATION_KIND::VRK_Reverse,
77+
ValueNumStore::VN_RELATION_KIND::VRK_Swap,
78+
ValueNumStore::VN_RELATION_KIND::VRK_SwapReverse};
79+
80+
//------------------------------------------------------------------------
81+
// RelopImplicationInfo
82+
//
83+
// Describes information needed to check for and describe the
84+
// inferences between two relops.
85+
//
86+
struct RelopImplicationInfo
87+
{
88+
// Dominating relop, whose value may be determined by control flow
89+
ValueNum domCmpNormVN = ValueNumStore::NoVN;
90+
// Dominated relop, whose value we would like to determine
91+
ValueNum treeNormVN = ValueNumStore::NoVN;
92+
// Relationship between the two relops, if any
93+
ValueNumStore::VN_RELATION_KIND vnRelation = ValueNumStore::VN_RELATION_KIND::VRK_Same;
94+
// Can we draw an inference?
95+
bool canInfer = false;
96+
// If canInfer and ominating relop is true, can we infer value of dominated relop?
97+
bool canInferFromTrue = true;
98+
// If canInfer and dominating relop is false, can we infer value of dominated relop?
99+
bool canInferFromFalse = true;
100+
// Reverse the sense of the inference
101+
bool reverseSense = false;
102+
};
103+
104+
//------------------------------------------------------------------------
105+
// optRedundantBranch: try and optimize a possibly redundant branch
106+
//
107+
// Arguments:
108+
// rii - struct with relop implication information
109+
//
110+
// Returns:
111+
// No return value.
112+
// Sets rii->canInfer and other fields, if inference is possible.
113+
//
114+
// Notes:
115+
//
116+
// First looks for exact or similar relations.
117+
//
118+
// If that fails, then looks for cases where the user or optOptimizeBools
119+
// has combined two distinct predicates with a boolean AND, OR, or has wrapped
120+
// a predicate in NOT.
121+
//
122+
// This will be expressed as {NE/EQ}({AND/OR/NOT}(...), 0).
123+
// If the operator is EQ then a true {AND/OR} result implies
124+
// a false taken branch, so we need to invert the sense of our
125+
// inferences.
126+
//
127+
// Note we could also infer the tree relop's value from other
128+
// dominating relops, for example, (x >= 0) dominating (x > 0).
129+
// That is left as a future enhancement.
130+
//
131+
void Compiler::optRelopImpliesRelop(RelopImplicationInfo* rii)
132+
{
133+
assert(!rii->canInfer);
134+
135+
// Look for related VNs
136+
//
137+
for (auto vnRelation : s_vnRelations)
138+
{
139+
const ValueNum relatedVN = vnStore->GetRelatedRelop(rii->domCmpNormVN, vnRelation);
140+
if ((relatedVN != ValueNumStore::NoVN) && (relatedVN == rii->treeNormVN))
141+
{
142+
rii->canInfer = true;
143+
rii->vnRelation = vnRelation;
144+
return;
145+
}
146+
}
147+
148+
// VNs are not directly related. See if dominating
149+
// compare encompasses a related VN.
150+
//
151+
VNFuncApp funcApp;
152+
if (!vnStore->GetVNFunc(rii->domCmpNormVN, &funcApp))
153+
{
154+
return;
155+
}
156+
157+
genTreeOps const oper = genTreeOps(funcApp.m_func);
158+
159+
// Look for {EQ,NE}({AND,OR,NOT}, 0)
160+
//
161+
if (!GenTree::StaticOperIs(oper, GT_EQ, GT_NE))
162+
{
163+
return;
164+
}
165+
166+
const ValueNum constantVN = funcApp.m_args[1];
167+
if (constantVN != vnStore->VNZeroForType(TYP_INT))
168+
{
169+
return;
170+
}
171+
172+
const ValueNum predVN = funcApp.m_args[0];
173+
VNFuncApp predFuncApp;
174+
if (!vnStore->GetVNFunc(predVN, &predFuncApp))
175+
{
176+
return;
177+
}
178+
179+
genTreeOps const predOper = genTreeOps(predFuncApp.m_func);
180+
181+
if (!GenTree::StaticOperIs(predOper, GT_AND, GT_OR, GT_NOT))
182+
{
183+
return;
184+
}
185+
186+
// Dominating compare is {EQ,NE}({AND,OR,NOT}, 0).
187+
//
188+
// See if one of {AND,OR,NOT} operands is related.
189+
//
190+
for (unsigned int i = 0; (i < predFuncApp.m_arity) && !rii->canInfer; i++)
191+
{
192+
ValueNum pVN = predFuncApp.m_args[i];
193+
194+
for (auto vnRelation : s_vnRelations)
195+
{
196+
const ValueNum relatedVN = vnStore->GetRelatedRelop(pVN, vnRelation);
197+
198+
if ((relatedVN != ValueNumStore::NoVN) && (relatedVN == rii->treeNormVN))
199+
{
200+
rii->vnRelation = vnRelation;
201+
rii->canInfer = true;
202+
203+
// If dom predicate is wrapped in EQ(*,0) then a true dom
204+
// predicate implies a false branch outcome, and vice versa.
205+
//
206+
// And if the dom predicate is GT_NOT we reverse yet again.
207+
//
208+
rii->reverseSense = (oper == GT_EQ) ^ (predOper == GT_NOT);
209+
210+
// We only get partial knowledge in these cases.
211+
//
212+
// AND(p1,p2) = true ==> both p1 and p2 must be true
213+
// AND(p1,p2) = false ==> don't know p1 or p2
214+
// OR(p1,p2) = true ==> don't know p1 or p2
215+
// OR(p1,p2) = false ==> both p1 and p2 must be false
216+
//
217+
if (predOper != GT_NOT)
218+
{
219+
rii->canInferFromFalse = rii->reverseSense ^ (predOper == GT_OR);
220+
rii->canInferFromTrue = rii->reverseSense ^ (predOper == GT_AND);
221+
}
222+
223+
JITDUMP("Inferring predicate value from %s\n", GenTree::OpName(predOper));
224+
return;
225+
}
226+
}
227+
}
228+
}
229+
75230
//------------------------------------------------------------------------
76231
// optRedundantBranch: try and optimize a possibly redundant branch
77232
//
@@ -120,12 +275,25 @@ bool Compiler::optRedundantBranch(BasicBlock* const block)
120275
return false;
121276
}
122277

123-
const ValueNumStore::VN_RELATION_KIND vnRelations[] = {ValueNumStore::VN_RELATION_KIND::VRK_Same,
124-
ValueNumStore::VN_RELATION_KIND::VRK_Reverse,
125-
ValueNumStore::VN_RELATION_KIND::VRK_Swap,
126-
ValueNumStore::VN_RELATION_KIND::VRK_SwapReverse};
278+
// Unpack the tree's VN
279+
//
280+
ValueNum treeNormVN;
281+
vnStore->VNUnpackExc(tree->GetVN(VNK_Liberal), &treeNormVN, &treeExcVN);
127282

128-
while (domBlock != nullptr)
283+
// If the treeVN is a constant, we optimize directly.
284+
//
285+
// Note the inferencing we do below is not valid for constant VNs,
286+
// so handling/avoiding this case up front is a correctness requirement.
287+
//
288+
if (vnStore->IsVNConstant(treeNormVN))
289+
{
290+
291+
relopValue = (treeNormVN == vnStore->VNZeroForType(TYP_INT)) ? 0 : 1;
292+
JITDUMP("Relop [%06u] " FMT_BB " has known value %s\n ", dspTreeID(tree), block->bbNum,
293+
relopValue == 0 ? "false" : "true");
294+
}
295+
296+
while ((relopValue == -1) && (domBlock != nullptr))
129297
{
130298
// Check the current dominator
131299
//
@@ -141,34 +309,15 @@ bool Compiler::optRedundantBranch(BasicBlock* const block)
141309
// We can use liberal VNs here, as bounds checks are not yet
142310
// manifest explicitly as relops.
143311
//
144-
// Look for an exact match and also try the various swapped/reversed forms.
145-
//
146-
ValueNum treeNormVN;
147-
vnStore->VNUnpackExc(tree->GetVN(VNK_Liberal), &treeNormVN, &treeExcVN);
148-
ValueNum domCmpNormVN;
149-
vnStore->VNUnpackExc(domCmpTree->GetVN(VNK_Liberal), &domCmpNormVN, &domCmpExcVN);
150-
ValueNumStore::VN_RELATION_KIND vnRelationMatch = ValueNumStore::VN_RELATION_KIND::VRK_Same;
151-
bool matched = false;
152-
153-
for (auto vnRelation : vnRelations)
154-
{
155-
const ValueNum relatedVN = vnStore->GetRelatedRelop(domCmpNormVN, vnRelation);
156-
if ((relatedVN != ValueNumStore::NoVN) && (relatedVN == treeNormVN))
157-
{
158-
vnRelationMatch = vnRelation;
159-
matched = true;
160-
break;
161-
}
162-
}
312+
RelopImplicationInfo rii;
313+
rii.treeNormVN = treeNormVN;
314+
vnStore->VNUnpackExc(domCmpTree->GetVN(VNK_Liberal), &rii.domCmpNormVN, &domCmpExcVN);
163315

164-
// Note we could also infer the tree relop's value from relops higher in the dom tree
165-
// that involve the same operands but are not swaps or reverses.
166-
//
167-
// For example, (x >= 0) dominating (x > 0).
316+
// See if knowing the value of domCmpNormVN implies knowing the value of treeNormVN.
168317
//
169-
// That is left as a future enhancement.
170-
//
171-
if (matched)
318+
optRelopImpliesRelop(&rii);
319+
320+
if (rii.canInfer)
172321
{
173322
// If we have a long skinny dominator tree we may scale poorly,
174323
// and in particular reachability (below) is costly. Give up if
@@ -184,20 +333,23 @@ bool Compiler::optRedundantBranch(BasicBlock* const block)
184333
// Is there a unique path from the dominating compare?
185334
//
186335
JITDUMP("\nDominator " FMT_BB " of " FMT_BB " has relop with %s liberal VN\n", domBlock->bbNum,
187-
block->bbNum, ValueNumStore::VNRelationString(vnRelationMatch));
336+
block->bbNum, ValueNumStore::VNRelationString(rii.vnRelation));
188337
DISPTREE(domCmpTree);
189338
JITDUMP(" Redundant compare; current relop:\n");
190339
DISPTREE(tree);
191340

192-
const bool domIsSameRelop = (vnRelationMatch == ValueNumStore::VN_RELATION_KIND::VRK_Same) ||
193-
(vnRelationMatch == ValueNumStore::VN_RELATION_KIND::VRK_Swap);
341+
const bool domIsSameRelop = (rii.vnRelation == ValueNumStore::VN_RELATION_KIND::VRK_Same) ||
342+
(rii.vnRelation == ValueNumStore::VN_RELATION_KIND::VRK_Swap);
194343

195344
BasicBlock* const trueSuccessor = domBlock->bbJumpDest;
196345
BasicBlock* const falseSuccessor = domBlock->bbNext;
197-
const bool trueReaches = optReachable(trueSuccessor, block, domBlock);
198-
const bool falseReaches = optReachable(falseSuccessor, block, domBlock);
199346

200-
if (trueReaches && falseReaches)
347+
// If we can trace the flow from the dominating relop, we can infer its value.
348+
//
349+
const bool trueReaches = optReachable(trueSuccessor, block, domBlock);
350+
const bool falseReaches = optReachable(falseSuccessor, block, domBlock);
351+
352+
if (trueReaches && falseReaches && rii.canInferFromTrue && rii.canInferFromFalse)
201353
{
202354
// Both dominating compare outcomes reach the current block so we can't infer the
203355
// value of the relop.
@@ -212,22 +364,26 @@ bool Compiler::optRedundantBranch(BasicBlock* const block)
212364
return true;
213365
}
214366
}
215-
else if (trueReaches)
367+
else if (trueReaches && !falseReaches && rii.canInferFromTrue)
216368
{
217369
// Taken jump in dominator reaches, fall through doesn't; relop must be true/false.
218370
//
219-
JITDUMP("Jump successor " FMT_BB " of " FMT_BB " reaches, relop must be %s\n",
220-
domBlock->bbJumpDest->bbNum, domBlock->bbNum, domIsSameRelop ? "true" : "false");
221-
relopValue = domIsSameRelop ? 1 : 0;
371+
const bool relopIsTrue = rii.reverseSense ^ domIsSameRelop;
372+
JITDUMP("Jump successor " FMT_BB " of " FMT_BB " reaches, relop [%06u] must be %s\n",
373+
domBlock->bbJumpDest->bbNum, domBlock->bbNum, dspTreeID(tree),
374+
relopIsTrue ? "true" : "false");
375+
relopValue = relopIsTrue ? 1 : 0;
222376
break;
223377
}
224-
else if (falseReaches)
378+
else if (falseReaches && !trueReaches && rii.canInferFromFalse)
225379
{
226380
// Fall through from dominator reaches, taken jump doesn't; relop must be false/true.
227381
//
228-
JITDUMP("Fall through successor " FMT_BB " of " FMT_BB " reaches, relop must be %s\n",
229-
domBlock->bbNext->bbNum, domBlock->bbNum, domIsSameRelop ? "false" : "true");
230-
relopValue = domIsSameRelop ? 0 : 1;
382+
const bool relopIsFalse = rii.reverseSense ^ domIsSameRelop;
383+
JITDUMP("Fall through successor " FMT_BB " of " FMT_BB " reaches, relop [%06u] must be %s\n",
384+
domBlock->bbNext->bbNum, domBlock->bbNum, dspTreeID(tree),
385+
relopIsFalse ? "false" : "true");
386+
relopValue = relopIsFalse ? 0 : 1;
231387
break;
232388
}
233389
else
@@ -822,7 +978,6 @@ bool Compiler::optRedundantRelop(BasicBlock* const block)
822978
//
823979
if (stmt == block->firstStmt())
824980
{
825-
JITDUMP(" -- no, no prior stmt\n");
826981
return false;
827982
}
828983

@@ -877,11 +1032,6 @@ bool Compiler::optRedundantRelop(BasicBlock* const block)
8771032
ValueNumStore::VN_RELATION_KIND candidateVnRelation = ValueNumStore::VN_RELATION_KIND::VRK_Same;
8781033
bool sideEffect = false;
8791034

880-
const ValueNumStore::VN_RELATION_KIND vnRelations[] = {ValueNumStore::VN_RELATION_KIND::VRK_Same,
881-
ValueNumStore::VN_RELATION_KIND::VRK_Reverse,
882-
ValueNumStore::VN_RELATION_KIND::VRK_Swap,
883-
ValueNumStore::VN_RELATION_KIND::VRK_SwapReverse};
884-
8851035
// We need to keep track of which locals might be killed by
8861036
// the trees between the expression we want to forward substitute
8871037
// and the jump.
@@ -1012,7 +1162,7 @@ bool Compiler::optRedundantRelop(BasicBlock* const block)
10121162
bool matched = false;
10131163
ValueNumStore::VN_RELATION_KIND vnRelationMatch = ValueNumStore::VN_RELATION_KIND::VRK_Same;
10141164

1015-
for (auto vnRelation : vnRelations)
1165+
for (auto vnRelation : s_vnRelations)
10161166
{
10171167
const ValueNum relatedVN = vnStore->GetRelatedRelop(domCmpVN, vnRelation);
10181168
if ((relatedVN != ValueNumStore::NoVN) && (relatedVN == treeVN))

0 commit comments

Comments
 (0)