@@ -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+ //
18051904Compiler::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 (" \n Replacing 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.
0 commit comments