Skip to content

Commit 63d9790

Browse files
authored
JIT: some enhancements to redundant branch opts (dotnet#60732)
Handle cases where the dominating compare is the reverse of the compare we're trying to optimize. For example, if `(x > y)` dominates `(y <= x)` we may be able to optimize the dominated compare. Addresses aspects of dotnet#48115.
1 parent 1bc4b61 commit 63d9790

File tree

4 files changed

+217
-23
lines changed

4 files changed

+217
-23
lines changed

src/coreclr/jit/compiler.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7453,7 +7453,7 @@ class Compiler
74537453
//
74547454
PhaseStatus optRedundantBranches();
74557455
bool optRedundantBranch(BasicBlock* const block);
7456-
bool optJumpThread(BasicBlock* const block, BasicBlock* const domBlock);
7456+
bool optJumpThread(BasicBlock* const block, BasicBlock* const domBlock, bool domIsSameRelop);
74577457
bool optReachable(BasicBlock* const fromBlock, BasicBlock* const toBlock, BasicBlock* const excludedBlock);
74587458

74597459
#if ASSERTION_PROP

src/coreclr/jit/redundantbranchopts.cpp

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -128,23 +128,42 @@ bool Compiler::optRedundantBranch(BasicBlock* const block)
128128

129129
if (domCmpTree->OperKind() & GTK_RELOP)
130130
{
131-
// We can use liberal VNs as bounds checks are not yet
131+
// We can use liberal VNs here, as bounds checks are not yet
132132
// manifest explicitly as relops.
133133
//
134-
ValueNum domCmpVN = domCmpTree->GetVN(VNK_Liberal);
135-
136-
// Note we could also infer the tree relop's value from similar relops higher in the dom tree.
137-
// For example, (x >= 0) dominating (x > 0), or (x < 0) dominating (x > 0).
134+
// Look for an exact match and also try the various swapped/reversed forms.
135+
//
136+
const ValueNum treeVN = tree->GetVN(VNK_Liberal);
137+
const ValueNum domCmpVN = domCmpTree->GetVN(VNK_Liberal);
138+
const ValueNum domCmpSwpVN =
139+
vnStore->GetRelatedRelop(domCmpVN, ValueNumStore::VN_RELATION_KIND::VRK_Swap);
140+
const ValueNum domCmpRevVN =
141+
vnStore->GetRelatedRelop(domCmpVN, ValueNumStore::VN_RELATION_KIND::VRK_Reverse);
142+
const ValueNum domCmpSwpRevVN =
143+
vnStore->GetRelatedRelop(domCmpVN, ValueNumStore::VN_RELATION_KIND::VRK_SwapReverse);
144+
145+
const bool matchCmp = ((domCmpVN != ValueNumStore::NoVN) && (domCmpVN == treeVN));
146+
const bool matchSwp = ((domCmpSwpVN != ValueNumStore::NoVN) && (domCmpSwpVN == treeVN));
147+
const bool matchRev = ((domCmpRevVN != ValueNumStore::NoVN) && (domCmpRevVN == treeVN));
148+
const bool matchSwpRev = ((domCmpSwpRevVN != ValueNumStore::NoVN) && (domCmpSwpRevVN == treeVN));
149+
const bool domIsSameRelop = matchCmp || matchSwp;
150+
const bool domIsRevRelop = matchRev || matchSwpRev;
151+
152+
// Note we could also infer the tree relop's value from relops higher in the dom tree
153+
// that involve the same operands but are not swaps or reverses.
154+
//
155+
// For example, (x >= 0) dominating (x > 0).
138156
//
139157
// That is left as a future enhancement.
140158
//
141-
if (domCmpVN == tree->GetVN(VNK_Liberal))
159+
if (domIsSameRelop || domIsRevRelop)
142160
{
143161
// The compare in "tree" is redundant.
144162
// Is there a unique path from the dominating compare?
145163
//
146-
JITDUMP("\nDominator " FMT_BB " of " FMT_BB " has relop with same liberal VN:\n", domBlock->bbNum,
147-
block->bbNum);
164+
JITDUMP("\nDominator " FMT_BB " of " FMT_BB " has %srelop with %s liberal VN\n", domBlock->bbNum,
165+
block->bbNum, matchSwp || matchSwpRev ? "swapped " : "",
166+
domIsSameRelop ? "the same" : "a reverse");
148167
DISPTREE(domCmpTree);
149168
JITDUMP(" Redundant compare; current relop:\n");
150169
DISPTREE(tree);
@@ -162,7 +181,7 @@ bool Compiler::optRedundantBranch(BasicBlock* const block)
162181
// However we may be able to update the flow from block's predecessors so they
163182
// bypass block and instead transfer control to jump's successors (aka jump threading).
164183
//
165-
const bool wasThreaded = optJumpThread(block, domBlock);
184+
const bool wasThreaded = optJumpThread(block, domBlock, domIsSameRelop);
166185

167186
if (wasThreaded)
168187
{
@@ -171,20 +190,20 @@ bool Compiler::optRedundantBranch(BasicBlock* const block)
171190
}
172191
else if (trueReaches)
173192
{
174-
// Taken jump in dominator reaches, fall through doesn't; relop must be true.
193+
// Taken jump in dominator reaches, fall through doesn't; relop must be true/false.
175194
//
176-
JITDUMP("Jump successor " FMT_BB " of " FMT_BB " reaches, relop must be true\n",
177-
domBlock->bbJumpDest->bbNum, domBlock->bbNum);
178-
relopValue = 1;
195+
JITDUMP("Jump successor " FMT_BB " of " FMT_BB " reaches, relop must be %s\n",
196+
domBlock->bbJumpDest->bbNum, domBlock->bbNum, domIsSameRelop ? "true" : "false");
197+
relopValue = domIsSameRelop ? 1 : 0;
179198
break;
180199
}
181200
else if (falseReaches)
182201
{
183-
// Fall through from dominator reaches, taken jump doesn't; relop must be false.
202+
// Fall through from dominator reaches, taken jump doesn't; relop must be false/true.
184203
//
185-
JITDUMP("Fall through successor " FMT_BB " of " FMT_BB " reaches, relop must be false\n",
186-
domBlock->bbNext->bbNum, domBlock->bbNum);
187-
relopValue = 0;
204+
JITDUMP("Fall through successor " FMT_BB " of " FMT_BB " reaches, relop must be %s\n",
205+
domBlock->bbNext->bbNum, domBlock->bbNum, domIsSameRelop ? "false" : "true");
206+
relopValue = domIsSameRelop ? 0 : 1;
188207
break;
189208
}
190209
else
@@ -259,6 +278,8 @@ bool Compiler::optRedundantBranch(BasicBlock* const block)
259278
// Arguments:
260279
// block - block with branch to optimize
261280
// domBlock - a dominating block that has an equivalent branch
281+
// domIsSameRelop - if true, dominating block does the same compare;
282+
// if false, dominating block does a reverse compare
262283
//
263284
// Returns:
264285
// True if the branch was optimized.
@@ -273,7 +294,7 @@ bool Compiler::optRedundantBranch(BasicBlock* const block)
273294
// / \ | |
274295
// C D C D
275296
//
276-
bool Compiler::optJumpThread(BasicBlock* const block, BasicBlock* const domBlock)
297+
bool Compiler::optJumpThread(BasicBlock* const block, BasicBlock* const domBlock, bool domIsSameRelop)
277298
{
278299
assert(block->bbJumpKind == BBJ_COND);
279300
assert(domBlock->bbJumpKind == BBJ_COND);
@@ -370,8 +391,13 @@ bool Compiler::optJumpThread(BasicBlock* const block, BasicBlock* const domBlock
370391
// are correlated exclusively with a true value for block's relop, and which
371392
// are correlated exclusively with a false value (aka true preds and false preds).
372393
//
373-
// To do this we try and follow the flow from domBlock to block; any block pred
374-
// reachable from domBlock's true edge is a true pred, and vice versa.
394+
// To do this we try and follow the flow from domBlock to block. When domIsSameRelop
395+
// is true, any block pred reachable from domBlock's true edge is a true pred of block,
396+
// and any block pred reachable from domBlock's false edge is a false pred of block.
397+
//
398+
// If domIsSameRelop is false, then the roles of the of the paths from domBlock swap:
399+
// any block pred reachable from domBlock's true edge is a false pred of block,
400+
// and any block pred reachable from domBlock's false edge is a true pred of block.
375401
//
376402
// However, there are some exceptions:
377403
//
@@ -400,8 +426,8 @@ bool Compiler::optJumpThread(BasicBlock* const block, BasicBlock* const domBlock
400426
int numTruePreds = 0;
401427
int numFalsePreds = 0;
402428
BasicBlock* fallThroughPred = nullptr;
403-
BasicBlock* const trueSuccessor = domBlock->bbJumpDest;
404-
BasicBlock* const falseSuccessor = domBlock->bbNext;
429+
BasicBlock* const trueSuccessor = domIsSameRelop ? domBlock->bbJumpDest : domBlock->bbNext;
430+
BasicBlock* const falseSuccessor = domIsSameRelop ? domBlock->bbNext : domBlock->bbJumpDest;
405431
BasicBlock* const trueTarget = block->bbJumpDest;
406432
BasicBlock* const falseTarget = block->bbNext;
407433
BlockSet truePreds = BlockSetOps::MakeEmpty(this);

src/coreclr/jit/valuenum.cpp

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4450,6 +4450,161 @@ bool ValueNumStore::IsVNHandle(ValueNum vn)
44504450
return c->m_attribs == CEA_Handle;
44514451
}
44524452

4453+
//------------------------------------------------------------------------
4454+
// GetRelatedRelop: return value number for reversed/swapped comparison
4455+
//
4456+
// Arguments:
4457+
// vn - vn to base things on
4458+
// vrk - whether the new vn should swap, reverse, or both
4459+
//
4460+
// Returns:
4461+
// vn for reversed/swapped comparsion, or NoVN.
4462+
//
4463+
// Note:
4464+
// If "vn" corresponds to (x > y), the resulting VN corresponds to
4465+
// VRK_Swap (y < x)
4466+
// VRK_Reverse (x <= y)
4467+
// VRK_SwapReverse (y >= x)
4468+
//
4469+
// Will return NoVN for all float comparisons.
4470+
//
4471+
ValueNum ValueNumStore::GetRelatedRelop(ValueNum vn, VN_RELATION_KIND vrk)
4472+
{
4473+
if (vn == NoVN)
4474+
{
4475+
return NoVN;
4476+
}
4477+
4478+
// Pull out any exception set.
4479+
//
4480+
ValueNum valueVN;
4481+
ValueNum excepVN;
4482+
VNUnpackExc(vn, &valueVN, &excepVN);
4483+
4484+
// Verify we have a binary func application
4485+
//
4486+
VNFuncApp funcAttr;
4487+
if (!GetVNFunc(valueVN, &funcAttr))
4488+
{
4489+
return NoVN;
4490+
}
4491+
4492+
if (funcAttr.m_arity != 2)
4493+
{
4494+
return NoVN;
4495+
}
4496+
4497+
// Don't try and model float compares.
4498+
//
4499+
if (varTypeIsFloating(TypeOfVN(funcAttr.m_args[0])))
4500+
{
4501+
return NoVN;
4502+
}
4503+
4504+
const bool reverse = (vrk == VN_RELATION_KIND::VRK_Reverse) || (vrk == VN_RELATION_KIND::VRK_SwapReverse);
4505+
const bool swap = (vrk == VN_RELATION_KIND::VRK_Swap) || (vrk == VN_RELATION_KIND::VRK_SwapReverse);
4506+
4507+
// Set up the new function
4508+
//
4509+
VNFunc newFunc = funcAttr.m_func;
4510+
4511+
// Swap the predicate, if so asked.
4512+
//
4513+
if (swap)
4514+
{
4515+
if (newFunc >= VNF_Boundary)
4516+
{
4517+
switch (newFunc)
4518+
{
4519+
case VNF_LT_UN:
4520+
newFunc = VNF_GT_UN;
4521+
break;
4522+
case VNF_LE_UN:
4523+
newFunc = VNF_GE_UN;
4524+
break;
4525+
case VNF_GE_UN:
4526+
newFunc = VNF_LE_UN;
4527+
break;
4528+
case VNF_GT_UN:
4529+
newFunc = VNF_LT_UN;
4530+
break;
4531+
default:
4532+
return NoVN;
4533+
}
4534+
}
4535+
else
4536+
{
4537+
const genTreeOps op = (genTreeOps)newFunc;
4538+
4539+
if (!GenTree::OperIsRelop(op))
4540+
{
4541+
return NoVN;
4542+
}
4543+
4544+
newFunc = (VNFunc)GenTree::SwapRelop(op);
4545+
}
4546+
}
4547+
4548+
// Reverse the predicate, if so asked.
4549+
//
4550+
if (reverse)
4551+
{
4552+
if (newFunc >= VNF_Boundary)
4553+
{
4554+
switch (newFunc)
4555+
{
4556+
case VNF_LT_UN:
4557+
newFunc = VNF_GE_UN;
4558+
break;
4559+
case VNF_LE_UN:
4560+
newFunc = VNF_GT_UN;
4561+
break;
4562+
case VNF_GE_UN:
4563+
newFunc = VNF_LT_UN;
4564+
break;
4565+
case VNF_GT_UN:
4566+
newFunc = VNF_LE_UN;
4567+
break;
4568+
default:
4569+
return NoVN;
4570+
}
4571+
}
4572+
else
4573+
{
4574+
const genTreeOps op = (genTreeOps)newFunc;
4575+
4576+
if (!GenTree::OperIsRelop(op))
4577+
{
4578+
return NoVN;
4579+
}
4580+
4581+
newFunc = (VNFunc)GenTree::ReverseRelop(op);
4582+
}
4583+
}
4584+
4585+
// Create the resulting VN, swapping arguments if needed.
4586+
//
4587+
ValueNum newVN = VNForFunc(TYP_INT, newFunc, funcAttr.m_args[swap ? 1 : 0], funcAttr.m_args[swap ? 0 : 1]);
4588+
ValueNum result = VNWithExc(newVN, excepVN);
4589+
4590+
#ifdef DEBUG
4591+
if (m_pComp->verbose)
4592+
{
4593+
printf("%s of ", swap ? (reverse ? "swap-reverse" : "swap") : "reverse");
4594+
m_pComp->vnPrint(vn, 1);
4595+
printf(" => ");
4596+
m_pComp->vnPrint(newVN, 1);
4597+
if (result != newVN)
4598+
{
4599+
m_pComp->vnPrint(result, 1);
4600+
}
4601+
printf("\n");
4602+
}
4603+
#endif
4604+
4605+
return result;
4606+
}
4607+
44534608
bool ValueNumStore::IsVNConstantBound(ValueNum vn)
44544609
{
44554610
// Do we have "var < 100"?

src/coreclr/jit/valuenum.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,19 @@ class ValueNumStore
842842
// Returns true iff the VN represents a handle constant.
843843
bool IsVNHandle(ValueNum vn);
844844

845+
// Given VN(x > y), return VN(y > x), VN(x <= y) or VN(y >= x)
846+
//
847+
// If vn is not a relop, return NoVN.
848+
//
849+
enum class VN_RELATION_KIND
850+
{
851+
VRK_Swap, // (y > x)
852+
VRK_Reverse, // (x <= y)
853+
VRK_SwapReverse // (y >= x)
854+
};
855+
856+
ValueNum GetRelatedRelop(ValueNum vn, VN_RELATION_KIND vrk);
857+
845858
// Convert a vartype_t to the value number's storage type for that vartype_t.
846859
// For example, ValueNum of type TYP_LONG are stored in a map of INT64 variables.
847860
// Lang is the language (C++) type for the corresponding vartype_t.

0 commit comments

Comments
 (0)