-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Copy prop equivalent phis #104458
Copy prop equivalent phis #104458
Changes from 4 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -161,11 +161,23 @@ bool Compiler::optCopyProp( | |
assert((tree->gtFlags & GTF_VAR_DEF) == 0); | ||
assert(tree->GetLclNum() == lclNum); | ||
|
||
bool madeChanges = false; | ||
LclVarDsc* varDsc = lvaGetDesc(lclNum); | ||
ValueNum lclDefVN = varDsc->GetPerSsaData(tree->GetSsaNum())->m_vnPair.GetConservative(); | ||
bool madeChanges = false; | ||
LclVarDsc* const varDsc = lvaGetDesc(lclNum); | ||
LclSsaVarDsc* const varSsaDsc = varDsc->GetPerSsaData(tree->GetSsaNum()); | ||
GenTree* const varDefTree = varSsaDsc->GetDefNode(); | ||
BasicBlock* const varDefBlock = varSsaDsc->GetBlock(); | ||
ValueNum lclDefVN = varSsaDsc->m_vnPair.GetConservative(); | ||
assert(lclDefVN != ValueNumStore::NoVN); | ||
|
||
bool const varDefTreeIsPhiDef = (varDefTree != nullptr) && varDefTree->IsPhiDefn(); | ||
bool varDefTreeIsPhiDefAtCycleEntry = false; | ||
|
||
if (varDefTreeIsPhiDef) | ||
{ | ||
FlowGraphNaturalLoop* const loop = m_blockToLoop->GetLoop(varDefBlock); | ||
varDefTreeIsPhiDefAtCycleEntry = (loop != nullptr) && (loop->GetHeader() == varDefBlock); | ||
} | ||
|
||
for (LclNumToLiveDefsMap::Node* const iter : LclNumToLiveDefsMap::KeyValueIteration(curSsaName)) | ||
{ | ||
unsigned newLclNum = iter->GetKey(); | ||
|
@@ -190,7 +202,129 @@ bool Compiler::optCopyProp( | |
|
||
if (newLclDefVN != lclDefVN) | ||
{ | ||
continue; | ||
// If the definition is a phi def, and is defined in a block that has a backedge, | ||
// we may be able to prove equivalence with some other phi def in that block, despite | ||
// the fact that the VNs do not match. | ||
// | ||
// This happens because VN is one-pass and doesn't know the VNs for backedge PhiArgs, | ||
// so the VNs for phi defs for loop entry blocks is always a novel opaque VN. But | ||
// we can query those backedge VNs after VN is done, and if all Phi Arg VNs match, then | ||
// the two Phi Defs are equivalent and could have had the same VN. | ||
// | ||
bool foundEquivalentPhi = false; | ||
|
||
// Is the new local def from the same block? | ||
// | ||
if (varDefTreeIsPhiDefAtCycleEntry && (varDefBlock == newLclSsaDef->GetBlock())) | ||
{ | ||
GenTreeLclVarCommon* const otherTree = newLclSsaDef->GetDefNode(); | ||
|
||
// Is it also a phi definition? Do the types match? | ||
// | ||
if ((otherTree != nullptr) && otherTree->IsPhiDefn() && (otherTree->TypeGet() == tree->TypeGet())) | ||
{ | ||
// Are all the Phi Args VN equivalent? | ||
// | ||
GenTreePhi* const treePhi = varDefTree->AsLclVar()->Data()->AsPhi(); | ||
GenTreePhi* const otherPhi = otherTree->AsLclVar()->Data()->AsPhi(); | ||
GenTreePhi::UseIterator treeIter = treePhi->Uses().begin(); | ||
GenTreePhi::UseIterator treeEnd = treePhi->Uses().end(); | ||
GenTreePhi::UseIterator otherIter = otherPhi->Uses().begin(); | ||
GenTreePhi::UseIterator otherEnd = otherPhi->Uses().end(); | ||
|
||
bool phiArgsAreEquivalent = true; | ||
|
||
for (; (treeIter != treeEnd) && (otherIter != otherEnd); ++treeIter, ++otherIter) | ||
{ | ||
GenTreePhiArg* const treePhiArg = treeIter->GetNode()->AsPhiArg(); | ||
GenTreePhiArg* const otherPhiArg = otherIter->GetNode()->AsPhiArg(); | ||
|
||
assert(treePhiArg->gtPredBB == otherPhiArg->gtPredBB); | ||
|
||
ValueNum treePhiArgVN = treePhiArg->gtVNPair.GetConservative(); | ||
ValueNum otherPhiArgVN = otherPhiArg->gtVNPair.GetConservative(); | ||
|
||
const bool phiArgIsFromDfsBackedge = m_dfsTree->IsAncestor(varDefBlock, treePhiArg->gtPredBB); | ||
|
||
if ((treePhiArgVN == ValueNumStore::NoVN) && phiArgIsFromDfsBackedge) | ||
{ | ||
// Consult SSA defs | ||
// | ||
LclSsaVarDsc* const treePhiArgSsaDef = | ||
lvaGetDesc(treePhiArg)->GetPerSsaData(treePhiArg->GetSsaNum()); | ||
ValueNum treePhiArgSsaDefVN = treePhiArgSsaDef->m_vnPair.GetConservative(); | ||
|
||
// Opportunisitcally update the PhiArgVNs, both to short-circuit future checks here | ||
// in copy prop, and help later phases that also scan phi arg lists (eg RBO). | ||
// (consider doing this as a VN post pass?) | ||
// | ||
if (treePhiArgSsaDefVN != treePhiArgVN) | ||
{ | ||
JITDUMP("Updating backedge phi arg [%06u] vn to " FMT_VN "\n", dspTreeID(treePhiArg), | ||
treePhiArgSsaDefVN); | ||
treePhiArg->SetVNs(treePhiArgSsaDef->m_vnPair); | ||
} | ||
|
||
treePhiArgVN = treePhiArgSsaDefVN; | ||
} | ||
|
||
if ((otherPhiArgVN == ValueNumStore::NoVN) && phiArgIsFromDfsBackedge) | ||
{ | ||
LclSsaVarDsc* const otherPhiArgSsaDef = | ||
lvaGetDesc(otherPhiArg)->GetPerSsaData(otherPhiArg->GetSsaNum()); | ||
ValueNum otherPhiArgSsaDefVN = otherPhiArgSsaDef->m_vnPair.GetConservative(); | ||
|
||
// Opportunisitcally update the PhiArgVNs, both to short-circuit future checks here | ||
// in copy prop, and help later phases that also scan phi arg lists (eg RBO). | ||
// (consider doing this as a VN post pass?) | ||
// | ||
if (otherPhiArgSsaDefVN != otherPhiArgVN) | ||
{ | ||
JITDUMP("Updating backedge phi arg [%06u] vn to " FMT_VN "\n", dspTreeID(otherPhiArg), | ||
otherPhiArgSsaDefVN); | ||
otherPhiArg->SetVNs(otherPhiArgSsaDef->m_vnPair); | ||
} | ||
|
||
otherPhiArgVN = otherPhiArgSsaDefVN; | ||
} | ||
|
||
// If the updated VNs differ, the phis are not equivalent | ||
// | ||
if (treePhiArgVN != otherPhiArgVN) | ||
{ | ||
phiArgsAreEquivalent = false; | ||
break; | ||
} | ||
|
||
// If we failed to find meaningful VNs, the phis are not equivalent | ||
// | ||
if (treePhiArgVN == ValueNumStore::NoVN) | ||
{ | ||
phiArgsAreEquivalent = false; | ||
break; | ||
} | ||
} | ||
|
||
// If we didn't verify all phi args we have failed to prove equivalence | ||
// | ||
phiArgsAreEquivalent &= (treeIter == treeEnd); | ||
phiArgsAreEquivalent &= (otherIter == otherEnd); | ||
|
||
if (phiArgsAreEquivalent) | ||
{ | ||
JITDUMP("Found equivalent phi defs: [%06u] and [%06u]\n", dspTreeID(treePhi), | ||
dspTreeID(otherPhi)); | ||
foundEquivalentPhi = true; | ||
} | ||
} | ||
} | ||
|
||
// VNs differed, and we didn't find phis equivalent, move on to the next candidate. | ||
// | ||
if (!foundEquivalentPhi) | ||
{ | ||
continue; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The nesting here is pretty deep... consider introducing a function with some early outs? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I was tempted to try that to see if helped TP any, but haven't yet. |
||
} | ||
|
||
// It may not be profitable to propagate a 'doNotEnregister' lclVar to an existing use of an | ||
|
@@ -257,8 +391,27 @@ bool Compiler::optCopyProp( | |
unsigned newSsaNum = newLclVarDsc->GetSsaNumForSsaDef(newLclSsaDef); | ||
assert(newSsaNum != SsaConfig::RESERVED_SSA_NUM); | ||
|
||
tree->AsLclVarCommon()->SetLclNum(newLclNum); | ||
tree->AsLclVarCommon()->SetSsaNum(newSsaNum); | ||
tree->SetLclNum(newLclNum); | ||
tree->SetSsaNum(newSsaNum); | ||
|
||
// Update VN to match, and propagate up through any enclosing commas. | ||
// (we could in principle try updating through other parents, but | ||
// we lack VN's context for memory, so can't get them all). | ||
// | ||
if (newLclDefVN != lclDefVN) | ||
{ | ||
tree->SetVNs(newLclSsaDef->m_vnPair); | ||
GenTree* parent = tree->gtGetParent(nullptr); | ||
|
||
while ((parent != nullptr) && parent->OperIs(GT_COMMA)) | ||
{ | ||
JITDUMP(" Updating COMMA parent VN [%06u]\n", dspTreeID(parent)); | ||
ValueNumPair op1Xvnp = vnStore->VNPExceptionSet(parent->AsOp()->gtOp1->gtVNPair); | ||
parent->SetVNs(vnStore->VNPWithExc(parent->AsOp()->gtOp2->gtVNPair, op1Xvnp)); | ||
parent = tree->gtGetParent(nullptr); | ||
} | ||
} | ||
|
||
gtUpdateSideEffects(stmt, tree); | ||
newLclSsaDef->AddUse(block); | ||
|
||
|
@@ -337,7 +490,7 @@ void Compiler::optCopyPropPushDef(GenTree* defNode, GenTreeLclVarCommon* lclNode | |
if ((defNode != nullptr) && defNode->IsPhiDefn()) | ||
{ | ||
// TODO-CQ: design better heuristics for propagation and remove this. | ||
ssaNum = SsaConfig::RESERVED_SSA_NUM; | ||
// ssaNum = SsaConfig::RESERVED_SSA_NUM; | ||
} | ||
|
||
pushDef(lclNum, ssaNum); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious what some of these cases look like... the fact that we have a phi in the header should mean we saw a def inside the loop, so in this case that def has to be redefining the local to have the same value as when we come from outside the loop. I would (naively, maybe?) expect that to be rare.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's defining a local to have the same value as some other local at the start of a loop. So yes both have the same value on initial entry and end up with the same value along the backedge. #95645 (comment) has some notes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likely the fallout from of pre-increment or similar, the code modifies a value and then uses the pre-modified value... eg
runtime/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Enumerator.cs
Lines 62 to 65 in 21cde69
This requires a temp in our IR. Morph and loop inversion conspire to make both old and new value live around the loop.