-
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
JIT: Have physical promotion insert read backs before possible implicit control flow #86706
Changes from all 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 |
---|---|---|
|
@@ -1112,10 +1112,68 @@ GenTree* Promotion::CreateReadBack(Compiler* compiler, unsigned structLclNum, co | |
return store; | ||
} | ||
|
||
//------------------------------------------------------------------------ | ||
// EndBlock: | ||
// Handle reaching the end of the currently started block by preparing | ||
// internal state for upcoming basic blocks, and inserting any necessary | ||
// readbacks. | ||
// | ||
// Remarks: | ||
// We currently expect all fields to be most up-to-date in their field locals | ||
// at the beginning of every basic block. That means all replacements should | ||
// have Replacement::NeedsReadBack == false and Replacement::NeedsWriteBack | ||
// == true at the beginning of every block. This function makes it so that is | ||
// the case. | ||
// | ||
void ReplaceVisitor::EndBlock() | ||
{ | ||
for (AggregateInfo* agg : m_aggregates) | ||
{ | ||
if (agg == nullptr) | ||
{ | ||
continue; | ||
} | ||
|
||
for (size_t i = 0; i < agg->Replacements.size(); i++) | ||
{ | ||
Replacement& rep = agg->Replacements[i]; | ||
assert(!rep.NeedsReadBack || !rep.NeedsWriteBack); | ||
if (rep.NeedsReadBack) | ||
{ | ||
if (m_liveness->IsReplacementLiveOut(m_currentBlock, agg->LclNum, (unsigned)i)) | ||
{ | ||
JITDUMP("Reading back replacement V%02u.[%03u..%03u) -> V%02u near the end of " FMT_BB ":\n", | ||
agg->LclNum, rep.Offset, rep.Offset + genTypeSize(rep.AccessType), rep.LclNum, | ||
m_currentBlock->bbNum); | ||
|
||
GenTree* readBack = Promotion::CreateReadBack(m_compiler, agg->LclNum, rep); | ||
Statement* stmt = m_compiler->fgNewStmtFromTree(readBack); | ||
DISPSTMT(stmt); | ||
m_compiler->fgInsertStmtNearEnd(m_currentBlock, stmt); | ||
} | ||
else | ||
{ | ||
JITDUMP("Skipping reading back dead replacement V%02u.[%03u..%03u) -> V%02u near the end of " FMT_BB | ||
"\n", | ||
agg->LclNum, rep.Offset, rep.Offset + genTypeSize(rep.AccessType), rep.LclNum, | ||
m_currentBlock->bbNum); | ||
} | ||
rep.NeedsReadBack = false; | ||
} | ||
|
||
rep.NeedsWriteBack = true; | ||
} | ||
} | ||
|
||
m_hasPendingReadBacks = false; | ||
} | ||
|
||
Compiler::fgWalkResult ReplaceVisitor::PostOrderVisit(GenTree** use, GenTree* user) | ||
{ | ||
GenTree* tree = *use; | ||
|
||
use = InsertMidTreeReadBacksIfNecessary(use); | ||
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. Add a header comment for PostOrderVisit. 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. Will add it as part of my follow-up. |
||
|
||
if (tree->OperIsStore()) | ||
{ | ||
if (tree->OperIsLocalStore()) | ||
|
@@ -1153,6 +1211,80 @@ Compiler::fgWalkResult ReplaceVisitor::PostOrderVisit(GenTree** use, GenTree* us | |
return fgWalkResult::WALK_CONTINUE; | ||
} | ||
|
||
//------------------------------------------------------------------------ | ||
// InsertMidTreeReadBacksIfNecessary: | ||
// If necessary, insert IR to read back all replacements before the specified use. | ||
// | ||
// Parameters: | ||
// use - The use | ||
// | ||
// Returns: | ||
// New use pointing to the old tree. | ||
// | ||
// Remarks: | ||
// When a struct field is most up-to-date in its struct local it is marked to | ||
// need a read back. We then need to decide when to insert IR to read it back | ||
// to its field local. | ||
// | ||
// We normally do this before the first use of the field we find, or before | ||
// we transfer control to any successor. This method handles the case of | ||
// implicit control flow related to EH; when this basic block is in a | ||
// try-region (or filter block) and we find a tree that may throw it eagerly | ||
// inserts pending readbacks. | ||
// | ||
GenTree** ReplaceVisitor::InsertMidTreeReadBacksIfNecessary(GenTree** use) | ||
{ | ||
if (!m_hasPendingReadBacks || !m_compiler->ehBlockHasExnFlowDsc(m_currentBlock)) | ||
{ | ||
return use; | ||
} | ||
|
||
if (((*use)->gtFlags & (GTF_EXCEPT | GTF_CALL)) == 0) | ||
{ | ||
assert(!(*use)->OperMayThrow(m_compiler)); | ||
return use; | ||
} | ||
|
||
if (!(*use)->OperMayThrow(m_compiler)) | ||
{ | ||
return use; | ||
} | ||
|
||
JITDUMP("Reading back pending replacements before tree with possible exception side effect inside block in try " | ||
"region\n"); | ||
|
||
for (AggregateInfo* agg : m_aggregates) | ||
{ | ||
if (agg == nullptr) | ||
{ | ||
continue; | ||
} | ||
|
||
for (Replacement& rep : agg->Replacements) | ||
{ | ||
// TODO-CQ: We should ensure we do not mark dead fields as | ||
// requiring readback. Currently it is handled by querying liveness | ||
// as part of end-of-block readback insertion, but for these | ||
// mid-tree readbacks we cannot query liveness information for | ||
// arbitrary locals. | ||
if (!rep.NeedsReadBack) | ||
{ | ||
continue; | ||
} | ||
|
||
rep.NeedsReadBack = false; | ||
GenTree* readBack = Promotion::CreateReadBack(m_compiler, agg->LclNum, rep); | ||
*use = | ||
m_compiler->gtNewOperNode(GT_COMMA, (*use)->IsValue() ? (*use)->TypeGet() : TYP_VOID, readBack, *use); | ||
use = &(*use)->AsOp()->gtOp2; | ||
m_madeChanges = true; | ||
} | ||
} | ||
|
||
m_hasPendingReadBacks = false; | ||
return use; | ||
} | ||
|
||
//------------------------------------------------------------------------ | ||
// LoadStoreAroundCall: | ||
// Handle a call that may involve struct local arguments and that may | ||
|
@@ -1199,7 +1331,10 @@ void ReplaceVisitor::LoadStoreAroundCall(GenTreeCall* call, GenTree* user) | |
GenTreeLclVarCommon* retBufLcl = retBufArg->GetNode()->AsLclVarCommon(); | ||
unsigned size = m_compiler->typGetObjLayout(call->gtRetClsHnd)->GetSize(); | ||
|
||
MarkForReadBack(retBufLcl->GetLclNum(), retBufLcl->GetLclOffs(), size); | ||
if (MarkForReadBack(retBufLcl->GetLclNum(), retBufLcl->GetLclOffs(), size)) | ||
{ | ||
JITDUMP("Retbuf has replacements that were marked for read back\n"); | ||
} | ||
} | ||
} | ||
|
||
|
@@ -1231,10 +1366,9 @@ bool ReplaceVisitor::IsPromotedStructLocalDying(GenTreeLclVarCommon* lcl) | |
} | ||
|
||
AggregateInfo* agg = m_aggregates[lcl->GetLclNum()]; | ||
|
||
for (size_t i = 0; i < agg->Replacements.size(); i++) | ||
for (Replacement& rep : agg->Replacements) | ||
{ | ||
if (agg->Replacements[i].NeedsReadBack) | ||
if (rep.NeedsReadBack) | ||
{ | ||
return false; | ||
} | ||
|
@@ -1449,11 +1583,11 @@ void ReplaceVisitor::WriteBackBefore(GenTree** use, unsigned lcl, unsigned offs, | |
// offs - The starting offset of the range in the struct local that needs to be read back from. | ||
// size - The size of the range | ||
// | ||
void ReplaceVisitor::MarkForReadBack(unsigned lcl, unsigned offs, unsigned size) | ||
bool ReplaceVisitor::MarkForReadBack(unsigned lcl, unsigned offs, unsigned size) | ||
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. Document the return value? 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. In my next change I remove this return value in favor of moving the logging into this function, so will address this with that PR. |
||
{ | ||
if (m_aggregates[lcl] == nullptr) | ||
{ | ||
return; | ||
return false; | ||
} | ||
|
||
jitstd::vector<Replacement>& replacements = m_aggregates[lcl]->Replacements; | ||
|
@@ -1468,17 +1602,20 @@ void ReplaceVisitor::MarkForReadBack(unsigned lcl, unsigned offs, unsigned size) | |
} | ||
} | ||
|
||
bool result = false; | ||
unsigned end = offs + size; | ||
bool any = false; | ||
unsigned end = offs + size; | ||
while ((index < replacements.size()) && (replacements[index].Offset < end)) | ||
{ | ||
result = true; | ||
any = true; | ||
Replacement& rep = replacements[index]; | ||
assert(rep.Overlaps(offs, size)); | ||
rep.NeedsReadBack = true; | ||
rep.NeedsWriteBack = false; | ||
rep.NeedsReadBack = true; | ||
rep.NeedsWriteBack = false; | ||
m_hasPendingReadBacks = true; | ||
index++; | ||
} | ||
|
||
return any; | ||
} | ||
|
||
//------------------------------------------------------------------------ | ||
|
@@ -1593,10 +1730,12 @@ PhaseStatus Promotion::Run() | |
ReplaceVisitor replacer(this, aggregates, &liveness); | ||
for (BasicBlock* bb : m_compiler->Blocks()) | ||
{ | ||
replacer.StartBlock(bb); | ||
|
||
for (Statement* stmt : bb->Statements()) | ||
{ | ||
DISPSTMT(stmt); | ||
replacer.Reset(); | ||
replacer.StartStatement(); | ||
replacer.WalkTree(stmt->GetRootNodePointer(), nullptr); | ||
|
||
if (replacer.MadeChanges()) | ||
|
@@ -1608,42 +1747,7 @@ PhaseStatus Promotion::Run() | |
} | ||
} | ||
|
||
for (unsigned i = 0; i < numLocals; i++) | ||
{ | ||
if (aggregates[i] == nullptr) | ||
{ | ||
continue; | ||
} | ||
|
||
for (size_t j = 0; j < aggregates[i]->Replacements.size(); j++) | ||
{ | ||
Replacement& rep = aggregates[i]->Replacements[j]; | ||
assert(!rep.NeedsReadBack || !rep.NeedsWriteBack); | ||
if (rep.NeedsReadBack) | ||
{ | ||
if (liveness.IsReplacementLiveOut(bb, i, (unsigned)j)) | ||
{ | ||
JITDUMP("Reading back replacement V%02u.[%03u..%03u) -> V%02u near the end of " FMT_BB ":\n", i, | ||
rep.Offset, rep.Offset + genTypeSize(rep.AccessType), rep.LclNum, bb->bbNum); | ||
|
||
GenTree* readBack = CreateReadBack(m_compiler, i, rep); | ||
Statement* stmt = m_compiler->fgNewStmtFromTree(readBack); | ||
DISPSTMT(stmt); | ||
m_compiler->fgInsertStmtNearEnd(bb, stmt); | ||
} | ||
else | ||
{ | ||
JITDUMP( | ||
"Skipping reading back dead replacement V%02u.[%03u..%03u) -> V%02u near the end of " FMT_BB | ||
"\n", | ||
i, rep.Offset, rep.Offset + genTypeSize(rep.AccessType), rep.LclNum, bb->bbNum); | ||
} | ||
rep.NeedsReadBack = false; | ||
} | ||
|
||
rep.NeedsWriteBack = true; | ||
} | ||
} | ||
replacer.EndBlock(); | ||
} | ||
|
||
// Insert initial IR to read arguments/OSR locals into replacement locals, | ||
|
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 suppose this is where you get into the resolution strategies... if the field is partially dead (only live in some successors) you might be tempted to defer the read back until the start of the live successors (if join free, or if all other preds of those successors will do read backs too).
We might be able to get at some of this already via tail merging.
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.
Yeah, I think there's a few opportunities like that around. I liked your idea of focusing on a linear trace when making these decisions.