Skip to content

Commit 0ad8b50

Browse files
authored
JIT: Have physical promotion insert read backs before possible implicit control flow (#86706)
Physical promotion relies on being able to read back any promoted field that is fresher in its struct local before control flows to any successor block. This was failing to take implicit control flow into account. Fix #86498
1 parent 33f78c0 commit 0ad8b50

File tree

6 files changed

+237
-63
lines changed

6 files changed

+237
-63
lines changed

src/coreclr/jit/promotion.cpp

Lines changed: 152 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,10 +1150,68 @@ GenTree* Promotion::CreateReadBack(Compiler* compiler, unsigned structLclNum, co
11501150
return store;
11511151
}
11521152

1153+
//------------------------------------------------------------------------
1154+
// EndBlock:
1155+
// Handle reaching the end of the currently started block by preparing
1156+
// internal state for upcoming basic blocks, and inserting any necessary
1157+
// readbacks.
1158+
//
1159+
// Remarks:
1160+
// We currently expect all fields to be most up-to-date in their field locals
1161+
// at the beginning of every basic block. That means all replacements should
1162+
// have Replacement::NeedsReadBack == false and Replacement::NeedsWriteBack
1163+
// == true at the beginning of every block. This function makes it so that is
1164+
// the case.
1165+
//
1166+
void ReplaceVisitor::EndBlock()
1167+
{
1168+
for (AggregateInfo* agg : m_aggregates)
1169+
{
1170+
if (agg == nullptr)
1171+
{
1172+
continue;
1173+
}
1174+
1175+
for (size_t i = 0; i < agg->Replacements.size(); i++)
1176+
{
1177+
Replacement& rep = agg->Replacements[i];
1178+
assert(!rep.NeedsReadBack || !rep.NeedsWriteBack);
1179+
if (rep.NeedsReadBack)
1180+
{
1181+
if (m_liveness->IsReplacementLiveOut(m_currentBlock, agg->LclNum, (unsigned)i))
1182+
{
1183+
JITDUMP("Reading back replacement V%02u.[%03u..%03u) -> V%02u near the end of " FMT_BB ":\n",
1184+
agg->LclNum, rep.Offset, rep.Offset + genTypeSize(rep.AccessType), rep.LclNum,
1185+
m_currentBlock->bbNum);
1186+
1187+
GenTree* readBack = Promotion::CreateReadBack(m_compiler, agg->LclNum, rep);
1188+
Statement* stmt = m_compiler->fgNewStmtFromTree(readBack);
1189+
DISPSTMT(stmt);
1190+
m_compiler->fgInsertStmtNearEnd(m_currentBlock, stmt);
1191+
}
1192+
else
1193+
{
1194+
JITDUMP("Skipping reading back dead replacement V%02u.[%03u..%03u) -> V%02u near the end of " FMT_BB
1195+
"\n",
1196+
agg->LclNum, rep.Offset, rep.Offset + genTypeSize(rep.AccessType), rep.LclNum,
1197+
m_currentBlock->bbNum);
1198+
}
1199+
rep.NeedsReadBack = false;
1200+
}
1201+
1202+
rep.NeedsWriteBack = true;
1203+
}
1204+
}
1205+
1206+
m_hasPendingReadBacks = false;
1207+
}
1208+
11531209
Compiler::fgWalkResult ReplaceVisitor::PostOrderVisit(GenTree** use, GenTree* user)
11541210
{
11551211
GenTree* tree = *use;
11561212

1213+
use = InsertMidTreeReadBacksIfNecessary(use);
1214+
11571215
if (tree->OperIsStore())
11581216
{
11591217
if (tree->OperIsLocalStore())
@@ -1191,6 +1249,80 @@ Compiler::fgWalkResult ReplaceVisitor::PostOrderVisit(GenTree** use, GenTree* us
11911249
return fgWalkResult::WALK_CONTINUE;
11921250
}
11931251

1252+
//------------------------------------------------------------------------
1253+
// InsertMidTreeReadBacksIfNecessary:
1254+
// If necessary, insert IR to read back all replacements before the specified use.
1255+
//
1256+
// Parameters:
1257+
// use - The use
1258+
//
1259+
// Returns:
1260+
// New use pointing to the old tree.
1261+
//
1262+
// Remarks:
1263+
// When a struct field is most up-to-date in its struct local it is marked to
1264+
// need a read back. We then need to decide when to insert IR to read it back
1265+
// to its field local.
1266+
//
1267+
// We normally do this before the first use of the field we find, or before
1268+
// we transfer control to any successor. This method handles the case of
1269+
// implicit control flow related to EH; when this basic block is in a
1270+
// try-region (or filter block) and we find a tree that may throw it eagerly
1271+
// inserts pending readbacks.
1272+
//
1273+
GenTree** ReplaceVisitor::InsertMidTreeReadBacksIfNecessary(GenTree** use)
1274+
{
1275+
if (!m_hasPendingReadBacks || !m_compiler->ehBlockHasExnFlowDsc(m_currentBlock))
1276+
{
1277+
return use;
1278+
}
1279+
1280+
if (((*use)->gtFlags & (GTF_EXCEPT | GTF_CALL)) == 0)
1281+
{
1282+
assert(!(*use)->OperMayThrow(m_compiler));
1283+
return use;
1284+
}
1285+
1286+
if (!(*use)->OperMayThrow(m_compiler))
1287+
{
1288+
return use;
1289+
}
1290+
1291+
JITDUMP("Reading back pending replacements before tree with possible exception side effect inside block in try "
1292+
"region\n");
1293+
1294+
for (AggregateInfo* agg : m_aggregates)
1295+
{
1296+
if (agg == nullptr)
1297+
{
1298+
continue;
1299+
}
1300+
1301+
for (Replacement& rep : agg->Replacements)
1302+
{
1303+
// TODO-CQ: We should ensure we do not mark dead fields as
1304+
// requiring readback. Currently it is handled by querying liveness
1305+
// as part of end-of-block readback insertion, but for these
1306+
// mid-tree readbacks we cannot query liveness information for
1307+
// arbitrary locals.
1308+
if (!rep.NeedsReadBack)
1309+
{
1310+
continue;
1311+
}
1312+
1313+
rep.NeedsReadBack = false;
1314+
GenTree* readBack = Promotion::CreateReadBack(m_compiler, agg->LclNum, rep);
1315+
*use =
1316+
m_compiler->gtNewOperNode(GT_COMMA, (*use)->IsValue() ? (*use)->TypeGet() : TYP_VOID, readBack, *use);
1317+
use = &(*use)->AsOp()->gtOp2;
1318+
m_madeChanges = true;
1319+
}
1320+
}
1321+
1322+
m_hasPendingReadBacks = false;
1323+
return use;
1324+
}
1325+
11941326
//------------------------------------------------------------------------
11951327
// LoadStoreAroundCall:
11961328
// Handle a call that may involve struct local arguments and that may
@@ -1237,7 +1369,10 @@ void ReplaceVisitor::LoadStoreAroundCall(GenTreeCall* call, GenTree* user)
12371369
GenTreeLclVarCommon* retBufLcl = retBufArg->GetNode()->AsLclVarCommon();
12381370
unsigned size = m_compiler->typGetObjLayout(call->gtRetClsHnd)->GetSize();
12391371

1240-
MarkForReadBack(retBufLcl->GetLclNum(), retBufLcl->GetLclOffs(), size);
1372+
if (MarkForReadBack(retBufLcl->GetLclNum(), retBufLcl->GetLclOffs(), size))
1373+
{
1374+
JITDUMP("Retbuf has replacements that were marked for read back\n");
1375+
}
12411376
}
12421377
}
12431378

@@ -1269,10 +1404,9 @@ bool ReplaceVisitor::IsPromotedStructLocalDying(GenTreeLclVarCommon* lcl)
12691404
}
12701405

12711406
AggregateInfo* agg = m_aggregates[lcl->GetLclNum()];
1272-
1273-
for (size_t i = 0; i < agg->Replacements.size(); i++)
1407+
for (Replacement& rep : agg->Replacements)
12741408
{
1275-
if (agg->Replacements[i].NeedsReadBack)
1409+
if (rep.NeedsReadBack)
12761410
{
12771411
return false;
12781412
}
@@ -1487,11 +1621,11 @@ void ReplaceVisitor::WriteBackBefore(GenTree** use, unsigned lcl, unsigned offs,
14871621
// offs - The starting offset of the range in the struct local that needs to be read back from.
14881622
// size - The size of the range
14891623
//
1490-
void ReplaceVisitor::MarkForReadBack(unsigned lcl, unsigned offs, unsigned size)
1624+
bool ReplaceVisitor::MarkForReadBack(unsigned lcl, unsigned offs, unsigned size)
14911625
{
14921626
if (m_aggregates[lcl] == nullptr)
14931627
{
1494-
return;
1628+
return false;
14951629
}
14961630

14971631
jitstd::vector<Replacement>& replacements = m_aggregates[lcl]->Replacements;
@@ -1506,17 +1640,20 @@ void ReplaceVisitor::MarkForReadBack(unsigned lcl, unsigned offs, unsigned size)
15061640
}
15071641
}
15081642

1509-
bool result = false;
1510-
unsigned end = offs + size;
1643+
bool any = false;
1644+
unsigned end = offs + size;
15111645
while ((index < replacements.size()) && (replacements[index].Offset < end))
15121646
{
1513-
result = true;
1647+
any = true;
15141648
Replacement& rep = replacements[index];
15151649
assert(rep.Overlaps(offs, size));
1516-
rep.NeedsReadBack = true;
1517-
rep.NeedsWriteBack = false;
1650+
rep.NeedsReadBack = true;
1651+
rep.NeedsWriteBack = false;
1652+
m_hasPendingReadBacks = true;
15181653
index++;
15191654
}
1655+
1656+
return any;
15201657
}
15211658

15221659
//------------------------------------------------------------------------
@@ -1631,10 +1768,12 @@ PhaseStatus Promotion::Run()
16311768
ReplaceVisitor replacer(this, aggregates, &liveness);
16321769
for (BasicBlock* bb : m_compiler->Blocks())
16331770
{
1771+
replacer.StartBlock(bb);
1772+
16341773
for (Statement* stmt : bb->Statements())
16351774
{
16361775
DISPSTMT(stmt);
1637-
replacer.Reset();
1776+
replacer.StartStatement();
16381777
replacer.WalkTree(stmt->GetRootNodePointer(), nullptr);
16391778

16401779
if (replacer.MadeChanges())
@@ -1646,42 +1785,7 @@ PhaseStatus Promotion::Run()
16461785
}
16471786
}
16481787

1649-
for (unsigned i = 0; i < numLocals; i++)
1650-
{
1651-
if (aggregates[i] == nullptr)
1652-
{
1653-
continue;
1654-
}
1655-
1656-
for (size_t j = 0; j < aggregates[i]->Replacements.size(); j++)
1657-
{
1658-
Replacement& rep = aggregates[i]->Replacements[j];
1659-
assert(!rep.NeedsReadBack || !rep.NeedsWriteBack);
1660-
if (rep.NeedsReadBack)
1661-
{
1662-
if (liveness.IsReplacementLiveOut(bb, i, (unsigned)j))
1663-
{
1664-
JITDUMP("Reading back replacement V%02u.[%03u..%03u) -> V%02u near the end of " FMT_BB ":\n", i,
1665-
rep.Offset, rep.Offset + genTypeSize(rep.AccessType), rep.LclNum, bb->bbNum);
1666-
1667-
GenTree* readBack = CreateReadBack(m_compiler, i, rep);
1668-
Statement* stmt = m_compiler->fgNewStmtFromTree(readBack);
1669-
DISPSTMT(stmt);
1670-
m_compiler->fgInsertStmtNearEnd(bb, stmt);
1671-
}
1672-
else
1673-
{
1674-
JITDUMP(
1675-
"Skipping reading back dead replacement V%02u.[%03u..%03u) -> V%02u near the end of " FMT_BB
1676-
"\n",
1677-
i, rep.Offset, rep.Offset + genTypeSize(rep.AccessType), rep.LclNum, bb->bbNum);
1678-
}
1679-
rep.NeedsReadBack = false;
1680-
}
1681-
1682-
rep.NeedsWriteBack = true;
1683-
}
1684-
}
1788+
replacer.EndBlock();
16851789
}
16861790

16871791
// Insert initial IR to read arguments/OSR locals into replacement locals,

src/coreclr/jit/promotion.h

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -244,10 +244,11 @@ class DecompositionPlan;
244244

245245
class ReplaceVisitor : public GenTreeVisitor<ReplaceVisitor>
246246
{
247-
Promotion* m_prom;
248247
jitstd::vector<AggregateInfo*>& m_aggregates;
249248
PromotionLiveness* m_liveness;
250-
bool m_madeChanges = false;
249+
bool m_madeChanges = false;
250+
bool m_hasPendingReadBacks = false;
251+
BasicBlock* m_currentBlock = nullptr;
251252

252253
public:
253254
enum
@@ -257,7 +258,7 @@ class ReplaceVisitor : public GenTreeVisitor<ReplaceVisitor>
257258
};
258259

259260
ReplaceVisitor(Promotion* prom, jitstd::vector<AggregateInfo*>& aggregates, PromotionLiveness* liveness)
260-
: GenTreeVisitor(prom->m_compiler), m_prom(prom), m_aggregates(aggregates), m_liveness(liveness)
261+
: GenTreeVisitor(prom->m_compiler), m_aggregates(aggregates), m_liveness(liveness)
261262
{
262263
}
263264

@@ -266,20 +267,28 @@ class ReplaceVisitor : public GenTreeVisitor<ReplaceVisitor>
266267
return m_madeChanges;
267268
}
268269

269-
void Reset()
270+
void StartBlock(BasicBlock* block)
271+
{
272+
m_currentBlock = block;
273+
}
274+
275+
void EndBlock();
276+
277+
void StartStatement()
270278
{
271279
m_madeChanges = false;
272280
}
273281

274282
fgWalkResult PostOrderVisit(GenTree** use, GenTree* user);
275283

276284
private:
285+
GenTree** InsertMidTreeReadBacksIfNecessary(GenTree** use);
277286
void LoadStoreAroundCall(GenTreeCall* call, GenTree* user);
278287
bool IsPromotedStructLocalDying(GenTreeLclVarCommon* structLcl);
279288
void ReplaceLocal(GenTree** use, GenTree* user);
280289
void StoreBeforeReturn(GenTreeUnOp* ret);
281290
void WriteBackBefore(GenTree** use, unsigned lcl, unsigned offs, unsigned size);
282-
void MarkForReadBack(unsigned lcl, unsigned offs, unsigned size);
291+
bool MarkForReadBack(unsigned lcl, unsigned offs, unsigned size);
283292

284293
void HandleStore(GenTree** use, GenTree* user);
285294
bool OverlappingReplacements(GenTreeLclVarCommon* lcl,

src/coreclr/jit/promotiondecomposition.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1033,6 +1033,7 @@ void ReplaceVisitor::HandleStore(GenTree** use, GenTree* user)
10331033

10341034
plan.MarkNonRemainderUseOfStructLocal();
10351035
dstFirstRep->NeedsReadBack = true;
1036+
m_hasPendingReadBacks = true;
10361037
dstFirstRep++;
10371038
}
10381039

@@ -1052,6 +1053,7 @@ void ReplaceVisitor::HandleStore(GenTree** use, GenTree* user)
10521053

10531054
plan.MarkNonRemainderUseOfStructLocal();
10541055
dstLastRep->NeedsReadBack = true;
1056+
m_hasPendingReadBacks = true;
10551057
dstEndRep--;
10561058
}
10571059
}
@@ -1122,7 +1124,10 @@ void ReplaceVisitor::HandleStore(GenTree** use, GenTree* user)
11221124
{
11231125
GenTreeLclVarCommon* lclStore = store->AsLclVarCommon();
11241126
unsigned size = lclStore->GetLayout(m_compiler)->GetSize();
1125-
MarkForReadBack(lclStore->GetLclNum(), lclStore->GetLclOffs(), size);
1127+
if (MarkForReadBack(lclStore->GetLclNum(), lclStore->GetLclOffs(), size))
1128+
{
1129+
JITDUMP("Marked store destination replacements to be read back (could not decompose this store)\n");
1130+
}
11261131
}
11271132
}
11281133
}

0 commit comments

Comments
 (0)