Skip to content

Commit 0797038

Browse files
authored
JIT: extend redundant relop to handle side effects (#61275)
Handle the case where the side-effecting redundant relop appears just before the jump tree relop. Also revamp how we search for related VNs so it can be done as a search loop.
1 parent be2b009 commit 0797038

File tree

3 files changed

+182
-85
lines changed

3 files changed

+182
-85
lines changed

src/coreclr/jit/redundantbranchopts.cpp

Lines changed: 149 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ bool Compiler::optRedundantBranch(BasicBlock* const block)
116116
return false;
117117
}
118118

119+
const ValueNumStore::VN_RELATION_KIND vnRelations[] = {ValueNumStore::VN_RELATION_KIND::VRK_Same,
120+
ValueNumStore::VN_RELATION_KIND::VRK_Reverse,
121+
ValueNumStore::VN_RELATION_KIND::VRK_Swap,
122+
ValueNumStore::VN_RELATION_KIND::VRK_SwapReverse};
123+
119124
while (domBlock != nullptr)
120125
{
121126
// Check the current dominator
@@ -134,21 +139,21 @@ bool Compiler::optRedundantBranch(BasicBlock* const block)
134139
//
135140
// Look for an exact match and also try the various swapped/reversed forms.
136141
//
137-
const ValueNum treeVN = tree->GetVN(VNK_Liberal);
138-
const ValueNum domCmpVN = domCmpTree->GetVN(VNK_Liberal);
139-
const ValueNum domCmpSwpVN =
140-
vnStore->GetRelatedRelop(domCmpVN, ValueNumStore::VN_RELATION_KIND::VRK_Swap);
141-
const ValueNum domCmpRevVN =
142-
vnStore->GetRelatedRelop(domCmpVN, ValueNumStore::VN_RELATION_KIND::VRK_Reverse);
143-
const ValueNum domCmpSwpRevVN =
144-
vnStore->GetRelatedRelop(domCmpVN, ValueNumStore::VN_RELATION_KIND::VRK_SwapReverse);
145-
146-
const bool matchCmp = ((domCmpVN != ValueNumStore::NoVN) && (domCmpVN == treeVN));
147-
const bool matchSwp = ((domCmpSwpVN != ValueNumStore::NoVN) && (domCmpSwpVN == treeVN));
148-
const bool matchRev = ((domCmpRevVN != ValueNumStore::NoVN) && (domCmpRevVN == treeVN));
149-
const bool matchSwpRev = ((domCmpSwpRevVN != ValueNumStore::NoVN) && (domCmpSwpRevVN == treeVN));
150-
const bool domIsSameRelop = matchCmp || matchSwp;
151-
const bool domIsRevRelop = matchRev || matchSwpRev;
142+
const ValueNum treeVN = tree->GetVN(VNK_Liberal);
143+
const ValueNum domCmpVN = domCmpTree->GetVN(VNK_Liberal);
144+
ValueNumStore::VN_RELATION_KIND vnRelationMatch = ValueNumStore::VN_RELATION_KIND::VRK_Same;
145+
bool matched = false;
146+
147+
for (auto vnRelation : vnRelations)
148+
{
149+
const ValueNum relatedVN = vnStore->GetRelatedRelop(domCmpVN, vnRelation);
150+
if ((relatedVN != ValueNumStore::NoVN) && (relatedVN == treeVN))
151+
{
152+
vnRelationMatch = vnRelation;
153+
matched = true;
154+
break;
155+
}
156+
}
152157

153158
// Note we could also infer the tree relop's value from relops higher in the dom tree
154159
// that involve the same operands but are not swaps or reverses.
@@ -157,18 +162,20 @@ bool Compiler::optRedundantBranch(BasicBlock* const block)
157162
//
158163
// That is left as a future enhancement.
159164
//
160-
if (domIsSameRelop || domIsRevRelop)
165+
if (matched)
161166
{
162167
// The compare in "tree" is redundant.
163168
// Is there a unique path from the dominating compare?
164169
//
165-
JITDUMP("\nDominator " FMT_BB " of " FMT_BB " has %srelop with %s liberal VN\n", domBlock->bbNum,
166-
block->bbNum, matchSwp || matchSwpRev ? "swapped " : "",
167-
domIsSameRelop ? "the same" : "a reverse");
170+
JITDUMP("\nDominator " FMT_BB " of " FMT_BB " has relop with %s liberal VN\n", domBlock->bbNum,
171+
block->bbNum, ValueNumStore::VNRelationString(vnRelationMatch));
168172
DISPTREE(domCmpTree);
169173
JITDUMP(" Redundant compare; current relop:\n");
170174
DISPTREE(tree);
171175

176+
const bool domIsSameRelop = (vnRelationMatch == ValueNumStore::VN_RELATION_KIND::VRK_Same) ||
177+
(vnRelationMatch == ValueNumStore::VN_RELATION_KIND::VRK_Swap);
178+
172179
BasicBlock* const trueSuccessor = domBlock->bbJumpDest;
173180
BasicBlock* const falseSuccessor = domBlock->bbNext;
174181
const bool trueReaches = optReachable(trueSuccessor, block, domBlock);
@@ -787,6 +794,14 @@ bool Compiler::optRedundantRelop(BasicBlock* const block)
787794
return false;
788795
}
789796

797+
// If there's just one statement, bail.
798+
//
799+
if (stmt == block->firstStmt())
800+
{
801+
JITDUMP(" -- no, no prior stmt\n");
802+
return false;
803+
}
804+
790805
GenTree* const jumpTree = stmt->GetRootNode();
791806

792807
if (!jumpTree->OperIs(GT_JTRUE))
@@ -813,21 +828,17 @@ bool Compiler::optRedundantRelop(BasicBlock* const block)
813828

814829
// If relop's value is known, bail.
815830
//
816-
const ValueNum treeVN = tree->GetVN(VNK_Liberal);
831+
const ValueNum treeVN = vnStore->VNNormalValue(tree->GetVN(VNK_Liberal));
817832

818833
if (vnStore->IsVNConstant(treeVN))
819834
{
820835
JITDUMP(" -- no, jump tree cond is constant\n");
821836
return false;
822837
}
823838

824-
// If there's just one statement, bail.
839+
// Save off the jump tree's liberal exceptional VN.
825840
//
826-
if (stmt == block->firstStmt())
827-
{
828-
JITDUMP(" -- no, no prior stmt\n");
829-
return false;
830-
}
841+
const ValueNum treeExcVN = vnStore->VNExceptionSet(tree->GetVN(VNK_Liberal));
831842

832843
JITDUMP("\noptRedundantRelop in " FMT_BB "; jump tree is\n", block->bbNum);
833844
DISPTREE(jumpTree);
@@ -836,9 +847,16 @@ bool Compiler::optRedundantRelop(BasicBlock* const block)
836847
// * makes the current relop redundant;
837848
// * can safely and profitably forward substituted to the jump.
838849
//
839-
Statement* prevStmt = stmt;
840-
GenTree* candidateTree = nullptr;
841-
bool reverse = false;
850+
Statement* prevStmt = stmt;
851+
GenTree* candidateTree = nullptr;
852+
Statement* candidateStmt = nullptr;
853+
ValueNumStore::VN_RELATION_KIND vnRelationMatch = ValueNumStore::VN_RELATION_KIND::VRK_Same;
854+
bool sideEffect = false;
855+
856+
const ValueNumStore::VN_RELATION_KIND vnRelations[] = {ValueNumStore::VN_RELATION_KIND::VRK_Same,
857+
ValueNumStore::VN_RELATION_KIND::VRK_Reverse,
858+
ValueNumStore::VN_RELATION_KIND::VRK_Swap,
859+
ValueNumStore::VN_RELATION_KIND::VRK_SwapReverse};
842860

843861
// We need to keep track of which locals might be killed by
844862
// the trees between the expression we want to forward substitute
@@ -858,6 +876,13 @@ bool Compiler::optRedundantRelop(BasicBlock* const block)
858876

859877
while (true)
860878
{
879+
// If we've run a cross a side effecting pred tree, stop looking.
880+
//
881+
if (sideEffect)
882+
{
883+
break;
884+
}
885+
861886
prevStmt = prevStmt->GetPrevStmt();
862887

863888
// Backwards statement walks wrap around, so if we get
@@ -882,12 +907,22 @@ bool Compiler::optRedundantRelop(BasicBlock* const block)
882907
continue;
883908
}
884909

885-
// If prevTree has side effects other than GTF_EXCEPT or GTF_ASG, bail.
910+
// If prevTree has side effects other than GTF_EXCEPT or GTF_ASG, bail,
911+
// unless it is in the immediately preceeding statement.
912+
//
913+
// (we'll later show that any exception must come from the RHS as the LHS
914+
// will be a simple local).
886915
//
887916
if ((prevTree->gtFlags & GTF_SIDE_EFFECT) != (prevTree->gtFlags & (GTF_EXCEPT | GTF_ASG)))
888917
{
889-
JITDUMP(" -- prev tree has side effects\n");
890-
break;
918+
if (prevStmt->GetNextStmt() != stmt)
919+
{
920+
JITDUMP(" -- prev tree has side effects and is not next to jumpTree\n");
921+
break;
922+
}
923+
924+
JITDUMP(" -- prev tree has side effects, allowing as prev tree is immediately before jumpTree\n");
925+
sideEffect = true;
891926
}
892927

893928
if (!prevTree->OperIs(GT_ASG))
@@ -946,46 +981,43 @@ bool Compiler::optRedundantRelop(BasicBlock* const block)
946981

947982
definedLocals[definedLocalsCount++] = prevTreeLcl;
948983

949-
// If the VN of RHS is the VN of the current tree, or is "related", consider forward sub.
950-
//
951-
// Todo: generalize to allow when normal VN of RHS == normal VN of tree, and RHS except set contains tree except
952-
// set.
953-
// The concern here is whether can we forward sub an excepting (but otherwise non-side effecting tree) and
954-
// guarantee
955-
// the same side effect behavior.
956-
//
957-
// A common case we see is that there's a compare of an array element guarded by a bounds check,
958-
// so prevTree is something like:
984+
// If the normal liberal VN of RHS is the normal liberal VN of the current tree, or is "related",
985+
// consider forward sub.
959986
//
960-
// (ASG lcl, (COMMA (bds-check), (NE (array-indir), ...)))
961-
//
962-
// This may have the same normal VN as our tree, but also has an exception set.
963-
//
964-
// Another common pattern is that the prevTree contains a call.
965-
//
966-
const ValueNum domCmpVN = prevTreeRHS->GetVN(VNK_Liberal);
967-
const ValueNum domCmpSwpVN = vnStore->GetRelatedRelop(domCmpVN, ValueNumStore::VN_RELATION_KIND::VRK_Swap);
968-
const ValueNum domCmpRevVN = vnStore->GetRelatedRelop(domCmpVN, ValueNumStore::VN_RELATION_KIND::VRK_Reverse);
969-
const ValueNum domCmpSwpRevVN =
970-
vnStore->GetRelatedRelop(domCmpVN, ValueNumStore::VN_RELATION_KIND::VRK_SwapReverse);
971-
972-
const bool matchCmp = ((domCmpVN != ValueNumStore::NoVN) && (domCmpVN == treeVN));
973-
const bool matchSwp = ((domCmpSwpVN != ValueNumStore::NoVN) && (domCmpSwpVN == treeVN));
974-
const bool matchRev = ((domCmpRevVN != ValueNumStore::NoVN) && (domCmpRevVN == treeVN));
975-
const bool matchSwpRev = ((domCmpSwpRevVN != ValueNumStore::NoVN) && (domCmpSwpRevVN == treeVN));
976-
const bool domIsSameRelop = matchCmp || matchSwp;
977-
const bool domIsRevRelop = matchRev || matchSwpRev;
978-
979-
// If the tree is some unrelated computation, keep looking farther up.
980-
//
981-
if (!domIsSameRelop && !domIsRevRelop)
987+
const ValueNum domCmpVN = vnStore->VNNormalValue(prevTreeRHS->GetVN(VNK_Liberal));
988+
bool matched = false;
989+
990+
for (auto vnRelation : vnRelations)
991+
{
992+
const ValueNum relatedVN = vnStore->GetRelatedRelop(domCmpVN, vnRelation);
993+
if ((relatedVN != ValueNumStore::NoVN) && (relatedVN == treeVN))
994+
{
995+
vnRelationMatch = vnRelation;
996+
matched = true;
997+
break;
998+
}
999+
}
1000+
1001+
if (!matched)
9821002
{
9831003
JITDUMP(" -- prev tree VN is not related\n");
9841004
continue;
9851005
}
9861006

987-
JITDUMP(" -- prev tree has %srelop with %s liberal VN\n", matchSwp || matchSwpRev ? "swapped " : "",
988-
domIsSameRelop ? "the same" : "a reverse");
1007+
JITDUMP(" -- prev tree has relop with %s liberal VN\n", ValueNumStore::VNRelationString(vnRelationMatch));
1008+
1009+
// If the jump tree VN has exceptions, verify that the RHS tree has a superset.
1010+
//
1011+
if (treeExcVN != vnStore->VNForEmptyExcSet())
1012+
{
1013+
const ValueNum prevTreeExcVN = vnStore->VNExceptionSet(prevTreeRHS->GetVN(VNK_Liberal));
1014+
1015+
if (!vnStore->VNExcIsSubset(prevTreeExcVN, treeExcVN))
1016+
{
1017+
JITDUMP(" -- prev tree does not anticipate all jump tree exceptions\n");
1018+
break;
1019+
}
1020+
}
9891021

9901022
// See if we can safely move a copy of prevTreeRHS later, to replace tree.
9911023
// We can, if none of its lcls are killed.
@@ -1015,9 +1047,9 @@ bool Compiler::optRedundantRelop(BasicBlock* const block)
10151047
continue;
10161048
}
10171049

1018-
// Heuristic: if the lcl defined here is live out, forward sub
1019-
// won't result in the original becoming dead. If the local is not
1020-
// live out that still may happen, but it's less likely.
1050+
// If the lcl defined here is live out, forward sub is problematic.
1051+
// We'll either create a redundant tree (as the original won't be dead)
1052+
// or lose the def (if we actually move the RHS tree).
10211053
//
10221054
if (VarSetOps::IsMember(this, block->bbLiveOut, prevTreeLclDsc->lvVarIndex))
10231055
{
@@ -1027,35 +1059,62 @@ bool Compiler::optRedundantRelop(BasicBlock* const block)
10271059

10281060
JITDUMP(" -- prev tree is viable candidate for relop fwd sub!\n");
10291061
candidateTree = prevTreeRHS;
1030-
reverse = domIsRevRelop;
1062+
candidateStmt = prevStmt;
10311063
}
10321064

10331065
if (candidateTree == nullptr)
10341066
{
10351067
return false;
10361068
}
10371069

1038-
// Looks good -- we are going to forward-sub candidateTree
1039-
//
1040-
GenTree* const substituteTree = gtCloneExpr(candidateTree);
1070+
GenTree* substituteTree = nullptr;
1071+
bool usedCopy = false;
1072+
1073+
if (candidateStmt->GetNextStmt() == stmt)
1074+
{
1075+
// We are going forward-sub candidateTree
1076+
//
1077+
substituteTree = candidateTree;
1078+
}
1079+
else
1080+
{
1081+
// We going to forward-sub a copy of candidateTree
1082+
//
1083+
assert(!sideEffect);
1084+
substituteTree = gtCloneExpr(candidateTree);
1085+
usedCopy = true;
1086+
}
10411087

1042-
// If we need the reverse compare, make it so
1088+
// If we need the reverse compare, make it so.
1089+
// We also need to set a proper VN.
10431090
//
1044-
if (reverse)
1091+
if ((vnRelationMatch == ValueNumStore::VN_RELATION_KIND::VRK_Reverse) ||
1092+
(vnRelationMatch == ValueNumStore::VN_RELATION_KIND::VRK_SwapReverse))
10451093
{
1094+
// Copy the vn info as it will be trashed when we change the oper.
1095+
//
1096+
ValueNumPair origVNP = substituteTree->gtVNPair;
1097+
1098+
// Update the tree. Note we don't actually swap operands...?
1099+
//
10461100
substituteTree->SetOper(GenTree::ReverseRelop(substituteTree->OperGet()));
10471101

1048-
// This substitute tree will have the VN the same as the original.
1049-
// (modulo excep sets...? hmmm)
1050-
substituteTree->SetVNsFromNode(tree);
1102+
// Compute the right set of VNs for this new tree.
1103+
//
1104+
ValueNum origNormConVN = vnStore->VNConservativeNormalValue(origVNP);
1105+
ValueNum origNormLibVN = vnStore->VNLiberalNormalValue(origVNP);
1106+
ValueNum newNormConVN = vnStore->GetRelatedRelop(origNormConVN, ValueNumStore::VN_RELATION_KIND::VRK_Reverse);
1107+
ValueNum newNormLibVN = vnStore->GetRelatedRelop(origNormLibVN, ValueNumStore::VN_RELATION_KIND::VRK_Reverse);
1108+
ValueNumPair newNormalVNP(newNormLibVN, newNormConVN);
1109+
ValueNumPair origExcVNP = vnStore->VNPExceptionSet(origVNP);
1110+
ValueNumPair newVNP = vnStore->VNPWithExc(newNormalVNP, origExcVNP);
1111+
1112+
substituteTree->SetVNs(newVNP);
10511113
}
10521114

1053-
// We just intentionally created a duplicate -- we don't want CSE to undo this.
1054-
// (hopefully, the original is now dead).
1055-
//
1056-
// Also this relop is now a subtree of a jump.
1115+
// This relop is now a subtree of a jump.
10571116
//
1058-
substituteTree->gtFlags |= (GTF_DONT_CSE | GTF_RELOP_JMP_USED);
1117+
substituteTree->gtFlags |= (GTF_RELOP_JMP_USED | GTF_DONT_CSE);
10591118

10601119
// Swap in the new tree.
10611120
//
@@ -1066,6 +1125,13 @@ bool Compiler::optRedundantRelop(BasicBlock* const block)
10661125

10671126
DEBUG_DESTROY_NODE(tree);
10681127

1128+
// If we didn't forward sub a copy, the candidateStmt must be removed.
1129+
//
1130+
if (!usedCopy)
1131+
{
1132+
fgRemoveStmt(block, candidateStmt);
1133+
}
1134+
10691135
JITDUMP(" -- done! new jump tree is\n");
10701136
DISPTREE(jumpTree);
10711137

0 commit comments

Comments
 (0)