Skip to content

Commit 2c62994

Browse files
authored
JIT: Regularize readbacks for parameters/OSR-locals in physical promotion (#87165)
Handle readbacks for parameters/OSR-locals like any other readback is handled. Previously they were handled by creating the scratch BB and then inserting IR after the main replacement had already been done; now, we instead create the scratch BB eagerly and mark these as requiring a read back at the beginning of the scratch BB, and leave normal replacement logic up to handle it. The main benefit is that this unification makes it easier to ensure that future smarter handling of readbacks/writebacks (e.g. "resolution") automatically kicks in for the common case of parameters. Introduce another invariant, which is that we only ever mark a field as requiring readback if it is live. Previously we would always mark them as requiring read backs, but would then check liveness before inserting the actual IR to do the read back. But we don't always have the liveness information at the point where we insert IR for readbacks after #86706. Also expand some debug logging, and address some feedback from #86706.
1 parent 8219f78 commit 2c62994

File tree

5 files changed

+207
-70
lines changed

5 files changed

+207
-70
lines changed

src/coreclr/jit/block.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -846,7 +846,7 @@ BasicBlock* BasicBlock::GetUniquePred(Compiler* compiler) const
846846
//
847847
// Return Value:
848848
// The unique successor of a block, or nullptr if there is no unique successor.
849-
849+
//
850850
BasicBlock* BasicBlock::GetUniqueSucc() const
851851
{
852852
if (bbJumpKind == BBJ_ALWAYS)

src/coreclr/jit/promotion.cpp

Lines changed: 168 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1746,6 +1746,77 @@ GenTree* Promotion::CreateReadBack(Compiler* compiler, unsigned structLclNum, co
17461746
return store;
17471747
}
17481748

1749+
//------------------------------------------------------------------------
1750+
// StartBlock:
1751+
// Handle reaching the end of the currently started block by preparing
1752+
// internal state for upcoming basic blocks, and inserting any necessary
1753+
// readbacks.
1754+
//
1755+
// Parameters:
1756+
// block - The block
1757+
//
1758+
void ReplaceVisitor::StartBlock(BasicBlock* block)
1759+
{
1760+
m_currentBlock = block;
1761+
1762+
#ifdef DEBUG
1763+
// At the start of every block we expect all replacements to be in their
1764+
// local home.
1765+
for (AggregateInfo* agg : m_aggregates)
1766+
{
1767+
if (agg == nullptr)
1768+
{
1769+
continue;
1770+
}
1771+
1772+
for (Replacement& rep : agg->Replacements)
1773+
{
1774+
assert(!rep.NeedsReadBack);
1775+
assert(rep.NeedsWriteBack);
1776+
}
1777+
}
1778+
#endif
1779+
1780+
// OSR locals and parameters may need an initial read back, which we mark
1781+
// when we start the scratch BB.
1782+
if (!m_compiler->fgBBisScratch(block))
1783+
{
1784+
return;
1785+
}
1786+
1787+
for (AggregateInfo* agg : m_aggregates)
1788+
{
1789+
if (agg == nullptr)
1790+
{
1791+
continue;
1792+
}
1793+
1794+
LclVarDsc* dsc = m_compiler->lvaGetDesc(agg->LclNum);
1795+
if (!dsc->lvIsParam && !dsc->lvIsOSRLocal)
1796+
{
1797+
continue;
1798+
}
1799+
1800+
JITDUMP("Marking fields of %s V%02u as needing read-back in scratch " FMT_BB "\n",
1801+
dsc->lvIsParam ? "parameter" : "OSR-local", agg->LclNum, block->bbNum);
1802+
1803+
for (size_t i = 0; i < agg->Replacements.size(); i++)
1804+
{
1805+
Replacement& rep = agg->Replacements[i];
1806+
rep.NeedsWriteBack = false;
1807+
if (m_liveness->IsReplacementLiveIn(block, agg->LclNum, (unsigned)i))
1808+
{
1809+
rep.NeedsReadBack = true;
1810+
JITDUMP(" V%02u (%s) marked\n", rep.LclNum, rep.Description);
1811+
}
1812+
else
1813+
{
1814+
JITDUMP(" V%02u (%s) not marked (not live-in to scratch BB)\n", rep.LclNum, rep.Description);
1815+
}
1816+
}
1817+
}
1818+
}
1819+
17491820
//------------------------------------------------------------------------
17501821
// EndBlock:
17511822
// Handle reaching the end of the currently started block by preparing
@@ -1787,11 +1858,27 @@ void ReplaceVisitor::EndBlock()
17871858
}
17881859
else
17891860
{
1861+
// We only mark fields as requiring read-back if they are
1862+
// live at the point where the stack local was written, so
1863+
// at first glance we would not expect this case to ever
1864+
// happen. However, it is possible that the field is live
1865+
// because it has a future struct use, in which case we may
1866+
// not need to insert any readbacks anywhere. For example,
1867+
// consider:
1868+
//
1869+
// V03 = CALL() // V03 is a struct with promoted V03.[000..008)
1870+
// CALL(struct V03) // V03.[000.008) marked as live here
1871+
//
1872+
// While V03.[000.008) gets marked for readback at the
1873+
// assignment, no readback is necessary at the location of
1874+
// the call argument, and it may die after that.
1875+
17901876
JITDUMP("Skipping reading back dead replacement V%02u.[%03u..%03u) -> V%02u near the end of " FMT_BB
17911877
"\n",
17921878
agg->LclNum, rep.Offset, rep.Offset + genTypeSize(rep.AccessType), rep.LclNum,
17931879
m_currentBlock->bbNum);
17941880
}
1881+
17951882
rep.NeedsReadBack = false;
17961883
}
17971884

@@ -1802,6 +1889,18 @@ void ReplaceVisitor::EndBlock()
18021889
m_hasPendingReadBacks = false;
18031890
}
18041891

1892+
//------------------------------------------------------------------------
1893+
// PostOrderVisit:
1894+
// Visit a node in post-order and make necessary changes for promoted field
1895+
// uses.
1896+
//
1897+
// Parameters:
1898+
// use - The use edge
1899+
// user - The user
1900+
//
1901+
// Returns:
1902+
// Visitor result.
1903+
//
18051904
Compiler::fgWalkResult ReplaceVisitor::PostOrderVisit(GenTree** use, GenTree* user)
18061905
{
18071906
GenTree* tree = *use;
@@ -1896,16 +1995,13 @@ GenTree** ReplaceVisitor::InsertMidTreeReadBacksIfNecessary(GenTree** use)
18961995

18971996
for (Replacement& rep : agg->Replacements)
18981997
{
1899-
// TODO-CQ: We should ensure we do not mark dead fields as
1900-
// requiring readback. Currently it is handled by querying liveness
1901-
// as part of end-of-block readback insertion, but for these
1902-
// mid-tree readbacks we cannot query liveness information for
1903-
// arbitrary locals.
19041998
if (!rep.NeedsReadBack)
19051999
{
19062000
continue;
19072001
}
19082002

2003+
JITDUMP(" V%02.[%03u..%03u) -> V%02u\n", agg->LclNum, rep.Offset, genTypeSize(rep.AccessType), rep.LclNum);
2004+
19092005
rep.NeedsReadBack = false;
19102006
GenTree* readBack = Promotion::CreateReadBack(m_compiler, agg->LclNum, rep);
19112007
*use =
@@ -1969,10 +2065,7 @@ void ReplaceVisitor::LoadStoreAroundCall(GenTreeCall* call, GenTree* user)
19692065
GenTreeLclVarCommon* retBufLcl = retBufArg->GetNode()->AsLclVarCommon();
19702066
unsigned size = m_compiler->typGetObjLayout(call->gtRetClsHnd)->GetSize();
19712067

1972-
if (MarkForReadBack(retBufLcl->GetLclNum(), retBufLcl->GetLclOffs(), size))
1973-
{
1974-
JITDUMP("Retbuf has replacements that were marked for read back\n");
1975-
}
2068+
MarkForReadBack(retBufLcl, size DEBUGARG("used as retbuf"));
19762069
}
19772070
}
19782071

@@ -2106,9 +2199,9 @@ void ReplaceVisitor::ReplaceLocal(GenTree** use, GenTree* user)
21062199

21072200
Replacement& rep = replacements[index];
21082201
assert(accessType == rep.AccessType);
2109-
JITDUMP(" ..replaced with promoted lcl V%02u\n", rep.LclNum);
21102202

21112203
bool isDef = lcl->OperIsLocalStore();
2204+
21122205
if (isDef)
21132206
{
21142207
*use = m_compiler->gtNewStoreLclVarNode(rep.LclNum, lcl->Data());
@@ -2131,6 +2224,7 @@ void ReplaceVisitor::ReplaceLocal(GenTree** use, GenTree* user)
21312224
}
21322225
else if (rep.NeedsReadBack)
21332226
{
2227+
JITDUMP(" ..needs a read back\n");
21342228
*use = m_compiler->gtNewOperNode(GT_COMMA, (*use)->TypeGet(),
21352229
Promotion::CreateReadBack(m_compiler, lclNum, rep), *use);
21362230
rep.NeedsReadBack = false;
@@ -2161,6 +2255,8 @@ void ReplaceVisitor::ReplaceLocal(GenTree** use, GenTree* user)
21612255
m_compiler->lvaGetDesc(rep.LclNum)->lvRedefinedInEmbeddedStatement = true;
21622256
}
21632257

2258+
JITDUMP(" ..replaced with V%02u\n", rep.LclNum);
2259+
21642260
m_madeChanges = true;
21652261
}
21662262

@@ -2272,18 +2368,19 @@ void ReplaceVisitor::WriteBackBefore(GenTree** use, unsigned lcl, unsigned offs,
22722368
// back before their next use.
22732369
//
22742370
// Parameters:
2275-
// lcl - The struct local
2276-
// offs - The starting offset of the range in the struct local that needs to be read back from.
2277-
// size - The size of the range
2371+
// lcl - Local node. Its offset is the start of the range.
2372+
// size - The size of the range
2373+
// reason - The reason the readback is required
22782374
//
2279-
bool ReplaceVisitor::MarkForReadBack(unsigned lcl, unsigned offs, unsigned size)
2375+
void ReplaceVisitor::MarkForReadBack(GenTreeLclVarCommon* lcl, unsigned size DEBUGARG(const char* reason))
22802376
{
2281-
if (m_aggregates[lcl] == nullptr)
2377+
if (m_aggregates[lcl->GetLclNum()] == nullptr)
22822378
{
2283-
return false;
2379+
return;
22842380
}
22852381

2286-
jitstd::vector<Replacement>& replacements = m_aggregates[lcl]->Replacements;
2382+
unsigned offs = lcl->GetLclOffs();
2383+
jitstd::vector<Replacement>& replacements = m_aggregates[lcl->GetLclNum()]->Replacements;
22872384
size_t index = Promotion::BinarySearch<Replacement, &Replacement::Offset>(replacements, offs);
22882385

22892386
if ((ssize_t)index < 0)
@@ -2295,20 +2392,37 @@ bool ReplaceVisitor::MarkForReadBack(unsigned lcl, unsigned offs, unsigned size)
22952392
}
22962393
}
22972394

2298-
bool any = false;
22992395
unsigned end = offs + size;
2300-
while ((index < replacements.size()) && (replacements[index].Offset < end))
2396+
if ((index >= replacements.size()) || (replacements[index].Offset >= end))
2397+
{
2398+
// No overlap with any field.
2399+
return;
2400+
}
2401+
2402+
StructDeaths deaths = m_liveness->GetDeathsForStructLocal(lcl);
2403+
JITDUMP("Fields of [%06u] in range [%03u..%03u) need to be read back: %s\n", Compiler::dspTreeID(lcl), offs,
2404+
offs + size, reason);
2405+
2406+
do
23012407
{
2302-
any = true;
23032408
Replacement& rep = replacements[index];
23042409
assert(rep.Overlaps(offs, size));
2305-
rep.NeedsReadBack = true;
2306-
rep.NeedsWriteBack = false;
2307-
m_hasPendingReadBacks = true;
2308-
index++;
2309-
}
23102410

2311-
return any;
2411+
if (deaths.IsReplacementDying((unsigned)index))
2412+
{
2413+
JITDUMP(" V%02u (%s) not marked (is dying)\n", rep.LclNum, rep.Description);
2414+
}
2415+
else
2416+
{
2417+
rep.NeedsReadBack = true;
2418+
m_hasPendingReadBacks = true;
2419+
JITDUMP(" V%02u (%s) marked\n", rep.LclNum, rep.Description);
2420+
}
2421+
2422+
rep.NeedsWriteBack = false;
2423+
2424+
index++;
2425+
} while ((index < replacements.size()) && (replacements[index].Offset < end));
23122426
}
23132427

23142428
//------------------------------------------------------------------------
@@ -2352,17 +2466,43 @@ PhaseStatus Promotion::Run()
23522466
return PhaseStatus::MODIFIED_NOTHING;
23532467
}
23542468

2469+
// Check for parameters and OSR locals that need to be read back on entry
2470+
// to the function.
2471+
for (AggregateInfo* agg : aggregates)
2472+
{
2473+
if (agg == nullptr)
2474+
{
2475+
continue;
2476+
}
2477+
2478+
LclVarDsc* dsc = m_compiler->lvaGetDesc(agg->LclNum);
2479+
if (dsc->lvIsParam || dsc->lvIsOSRLocal)
2480+
{
2481+
// We will need an initial readback. We create the scratch BB ahead
2482+
// of time so that we get correct liveness and mark the
2483+
// parameters/OSR-locals as requiring read-back as part of
2484+
// ReplaceVisitor::StartBlock when we get to the scratch block.
2485+
m_compiler->fgEnsureFirstBBisScratch();
2486+
break;
2487+
}
2488+
}
2489+
23552490
// Compute liveness for the fields and remainders.
23562491
PromotionLiveness liveness(m_compiler, aggregates);
23572492
liveness.Run();
23582493

23592494
JITDUMP("Making replacements\n\n");
2495+
23602496
// Make all replacements we decided on.
23612497
ReplaceVisitor replacer(this, aggregates, &liveness);
23622498
for (BasicBlock* bb : m_compiler->Blocks())
23632499
{
23642500
replacer.StartBlock(bb);
23652501

2502+
JITDUMP("\nReplacing in ");
2503+
DBEXEC(m_compiler->verbose, bb->dspBlockHeader(m_compiler));
2504+
JITDUMP("\n");
2505+
23662506
for (Statement* stmt : bb->Statements())
23672507
{
23682508
DISPSTMT(stmt);
@@ -2390,8 +2530,7 @@ PhaseStatus Promotion::Run()
23902530
replacer.EndBlock();
23912531
}
23922532

2393-
// Insert initial IR to read arguments/OSR locals into replacement locals,
2394-
// and add necessary explicit zeroing.
2533+
// Add necessary explicit zeroing for some locals.
23952534
Statement* prevStmt = nullptr;
23962535
for (AggregateInfo* agg : aggregates)
23972536
{
@@ -2401,11 +2540,7 @@ PhaseStatus Promotion::Run()
24012540
}
24022541

24032542
LclVarDsc* dsc = m_compiler->lvaGetDesc(agg->LclNum);
2404-
if (dsc->lvIsParam || dsc->lvIsOSRLocal)
2405-
{
2406-
InsertInitialReadBack(agg->LclNum, agg->Replacements, &prevStmt);
2407-
}
2408-
else if (dsc->lvSuppressedZeroInit)
2543+
if (dsc->lvSuppressedZeroInit)
24092544
{
24102545
// We may have suppressed inserting an explicit zero init based on the
24112546
// assumption that the entire local will be zero inited in the prolog.
@@ -2451,27 +2586,6 @@ bool Promotion::IsCandidateForPhysicalPromotion(LclVarDsc* dsc)
24512586
return (dsc->TypeGet() == TYP_STRUCT) && !dsc->lvPromoted && !dsc->IsAddressExposed();
24522587
}
24532588

2454-
//------------------------------------------------------------------------
2455-
// Promotion::InsertInitialReadBack:
2456-
// Insert IR to initially read a struct local's value into its promoted field locals.
2457-
//
2458-
// Parameters:
2459-
// lclNum - The struct local
2460-
// replacements - Replacements for the struct local
2461-
// prevStmt - [in, out] Previous statement to insert after
2462-
//
2463-
void Promotion::InsertInitialReadBack(unsigned lclNum,
2464-
const jitstd::vector<Replacement>& replacements,
2465-
Statement** prevStmt)
2466-
{
2467-
for (unsigned i = 0; i < replacements.size(); i++)
2468-
{
2469-
const Replacement& rep = replacements[i];
2470-
GenTree* readBack = CreateReadBack(m_compiler, lclNum, rep);
2471-
InsertInitStatement(prevStmt, readBack);
2472-
}
2473-
}
2474-
24752589
//------------------------------------------------------------------------
24762590
// Promotion::ExplicitlyZeroInitReplacementLocals:
24772591
// Insert IR to zero out replacement locals if necessary.

src/coreclr/jit/promotion.h

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@ class Promotion
112112
static StructSegments SignificantSegments(Compiler* compiler,
113113
ClassLayout* layout DEBUGARG(FixedBitVect** bitVectRepr = nullptr));
114114

115-
void InsertInitialReadBack(unsigned lclNum, const jitstd::vector<Replacement>& replacements, Statement** prevStmt);
116115
void ExplicitlyZeroInitReplacementLocals(unsigned lclNum,
117116
const jitstd::vector<Replacement>& replacements,
118117
Statement** prevStmt);
@@ -220,6 +219,7 @@ class PromotionLiveness
220219
}
221220

222221
void Run();
222+
bool IsReplacementLiveIn(BasicBlock* bb, unsigned structLcl, unsigned replacement);
223223
bool IsReplacementLiveOut(BasicBlock* bb, unsigned structLcl, unsigned replacement);
224224
StructDeaths GetDeathsForStructLocal(GenTreeLclVarCommon* use);
225225

@@ -274,11 +274,7 @@ class ReplaceVisitor : public GenTreeVisitor<ReplaceVisitor>
274274
return m_mayHaveForwardSub;
275275
}
276276

277-
void StartBlock(BasicBlock* block)
278-
{
279-
m_currentBlock = block;
280-
}
281-
277+
void StartBlock(BasicBlock* block);
282278
void EndBlock();
283279

284280
void StartStatement(Statement* stmt)
@@ -299,7 +295,7 @@ class ReplaceVisitor : public GenTreeVisitor<ReplaceVisitor>
299295
void CheckForwardSubForLastUse(unsigned lclNum);
300296
void StoreBeforeReturn(GenTreeUnOp* ret);
301297
void WriteBackBefore(GenTree** use, unsigned lcl, unsigned offs, unsigned size);
302-
bool MarkForReadBack(unsigned lcl, unsigned offs, unsigned size);
298+
void MarkForReadBack(GenTreeLclVarCommon* lcl, unsigned size DEBUGARG(const char* reason));
303299

304300
void HandleStore(GenTree** use, GenTree* user);
305301
bool OverlappingReplacements(GenTreeLclVarCommon* lcl,

0 commit comments

Comments
 (0)