JIT: Add support for induced field accesses in physical promotion (#87410)
authorJakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
Fri, 16 Jun 2023 21:30:03 +0000 (23:30 +0200)
committerGitHub <noreply@github.com>
Fri, 16 Jun 2023 21:30:03 +0000 (23:30 +0200)
Add support for promoting fields based on the fact that store decomposition
induces new field accesses. To do so we store all struct stores that involve
candidates for physical promotion as part of the initial accounting pass. Then
when picking the replacements we do it as a fixpoint computation, iteratively
expanding the set of replacements based on field accesses induced by store
decomposition with the existing set of replacements.

Fix #87371

src/coreclr/jit/promotion.cpp
src/coreclr/jit/promotion.h

index 277f5fb9a29a750bc488b51fccace9fe899b022f..b94feb571ce16ab5da39fcc8ba21635d0f44a516 100644 (file)
@@ -202,13 +202,28 @@ bool AggregateInfo::OverlappingReplacements(unsigned      offset,
     return true;
 }
 
+struct PrimitiveAccess
+{
+    weight_t  CountWtd = 0;
+    unsigned  Offset;
+    var_types AccessType;
+
+    INDEBUG(unsigned Count = 0);
+
+    PrimitiveAccess(unsigned offset, var_types accessType) : Offset(offset), AccessType(accessType)
+    {
+    }
+};
+
 // Tracks all the accesses into one particular struct local.
 class LocalUses
 {
-    jitstd::vector<Access> m_accesses;
+    jitstd::vector<Access>          m_accesses;
+    jitstd::vector<PrimitiveAccess> m_inducedAccesses;
 
 public:
-    LocalUses(Compiler* comp) : m_accesses(comp->getAllocator(CMK_Promotion))
+    LocalUses(Compiler* comp)
+        : m_accesses(comp->getAllocator(CMK_Promotion)), m_inducedAccesses(comp->getAllocator(CMK_Promotion))
     {
     }
 
@@ -307,6 +322,56 @@ public:
 #endif
     }
 
+    //------------------------------------------------------------------------
+    // RecordInducedAccess:
+    //   Record an induced access into this local with the specified offset and access type.
+    //
+    // Parameters:
+    //   offs         - The offset being accessed
+    //   accessType   - The type of the access
+    //   weight       - Weight of the block containing the access
+    //
+    // Remarks:
+    //   Induced accesses are accesses that are induced by physical promotion
+    //   due to assignment decompositon. They are always of primitive type.
+    //
+    void RecordInducedAccess(unsigned offs, var_types accessType, weight_t weight)
+    {
+        PrimitiveAccess* access = nullptr;
+
+        size_t index = 0;
+        if (m_inducedAccesses.size() > 0)
+        {
+            index = Promotion::BinarySearch<PrimitiveAccess, &PrimitiveAccess::Offset>(m_inducedAccesses, offs);
+            if ((ssize_t)index >= 0)
+            {
+                do
+                {
+                    PrimitiveAccess& candidateAccess = m_inducedAccesses[index];
+                    if (candidateAccess.AccessType == accessType)
+                    {
+                        access = &candidateAccess;
+                        break;
+                    }
+
+                    index++;
+                } while (index < m_inducedAccesses.size() && m_inducedAccesses[index].Offset == offs);
+            }
+            else
+            {
+                index = ~index;
+            }
+        }
+
+        if (access == nullptr)
+        {
+            access = &*m_inducedAccesses.insert(m_inducedAccesses.begin() + index, PrimitiveAccess(offs, accessType));
+        }
+
+        access->CountWtd += weight;
+        INDEBUG(access->Count++);
+    }
+
     //------------------------------------------------------------------------
     // PickPromotions:
     //   Pick specific replacements to make for this struct local after a set
@@ -336,33 +401,121 @@ public:
                 continue;
             }
 
-            if (!EvaluateReplacement(comp, lclNum, access))
+            if (!EvaluateReplacement(comp, lclNum, access, 0))
             {
                 continue;
             }
 
-#ifdef DEBUG
-            char buf[32];
-            sprintf_s(buf, sizeof(buf), "V%02u.[%03u..%03u)", lclNum, access.Offset,
-                      access.Offset + genTypeSize(access.AccessType));
-            size_t len  = strlen(buf) + 1;
-            char*  bufp = new (comp, CMK_DebugOnly) char[len];
-            strcpy_s(bufp, len, buf);
-#endif
-            unsigned   newLcl = comp->lvaGrabTemp(false DEBUGARG(bufp));
-            LclVarDsc* dsc    = comp->lvaGetDesc(newLcl);
-            dsc->lvType       = access.AccessType;
+            if (*aggregateInfo == nullptr)
+            {
+                *aggregateInfo = new (comp, CMK_Promotion) AggregateInfo(comp->getAllocator(CMK_Promotion), lclNum);
+            }
+
+            (*aggregateInfo)->Replacements.push_back(Replacement(access.Offset, access.AccessType));
+        }
+
+        JITDUMP("\n");
+    }
+
+    //------------------------------------------------------------------------
+    // PickInducedPromotions:
+    //   Pick additional promotions to make based on the fact that some
+    //   accesses will be induced by assignment decomposition.
+    //
+    // Parameters:
+    //   comp   - Compiler instance
+    //   lclNum - Local num for this struct local
+    //   aggregateInfo - [out] Pointer to aggregate info to create and insert replacements into.
+    //
+    bool PickInducedPromotions(Compiler* comp, unsigned lclNum, AggregateInfo** aggregateInfo)
+    {
+        if (m_inducedAccesses.size() <= 0)
+        {
+            return false;
+        }
+
+        bool any = false;
+        JITDUMP("Picking induced promotions for V%02u\n", lclNum);
+        for (PrimitiveAccess& inducedAccess : m_inducedAccesses)
+        {
+            bool overlapsOtherInducedAccess = false;
+            for (PrimitiveAccess& otherInducedAccess : m_inducedAccesses)
+            {
+                if (&otherInducedAccess == &inducedAccess)
+                {
+                    continue;
+                }
+
+                if (inducedAccess.Offset + genTypeSize(inducedAccess.AccessType) <= otherInducedAccess.Offset)
+                {
+                    break;
+                }
+
+                if (otherInducedAccess.Offset + genTypeSize(otherInducedAccess.AccessType) <= inducedAccess.Offset)
+                {
+                    continue;
+                }
+
+                overlapsOtherInducedAccess = true;
+                break;
+            }
+
+            if (overlapsOtherInducedAccess)
+            {
+                continue;
+            }
+
+            Access* access = FindAccess(inducedAccess.Offset, inducedAccess.AccessType);
+
+            if (access == nullptr)
+            {
+                Access fakeAccess(inducedAccess.Offset, inducedAccess.AccessType, nullptr);
+                if (!EvaluateReplacement(comp, lclNum, fakeAccess, inducedAccess.CountWtd))
+                {
+                    continue;
+                }
+            }
+            else
+            {
+                if (!EvaluateReplacement(comp, lclNum, *access, inducedAccess.CountWtd))
+                {
+                    continue;
+                }
+            }
 
             if (*aggregateInfo == nullptr)
             {
                 *aggregateInfo = new (comp, CMK_Promotion) AggregateInfo(comp->getAllocator(CMK_Promotion), lclNum);
             }
 
+            size_t insertionIndex;
+            if ((*aggregateInfo)->Replacements.size() > 0)
+            {
+#ifdef DEBUG
+                Replacement* overlapRep;
+                assert(!(*aggregateInfo)
+                            ->OverlappingReplacements(inducedAccess.Offset, genTypeSize(inducedAccess.AccessType),
+                                                      &overlapRep, nullptr));
+#endif
+
+                insertionIndex =
+                    Promotion::BinarySearch<Replacement, &Replacement::Offset>((*aggregateInfo)->Replacements,
+                                                                               inducedAccess.Offset);
+                assert((ssize_t)insertionIndex < 0);
+                insertionIndex = ~insertionIndex;
+            }
+            else
+            {
+                insertionIndex = 0;
+            }
+
             (*aggregateInfo)
-                ->Replacements.push_back(Replacement(access.Offset, access.AccessType, newLcl DEBUGARG(bufp)));
+                ->Replacements.insert((*aggregateInfo)->Replacements.begin() + insertionIndex,
+                                      Replacement(inducedAccess.Offset, inducedAccess.AccessType));
+            any = true;
         }
 
-        JITDUMP("\n");
+        return any;
     }
 
     //------------------------------------------------------------------------
@@ -370,14 +523,15 @@ public:
     //   Evaluate legality and profitability of a single replacement candidate.
     //
     // Parameters:
-    //   comp   - Compiler instance
-    //   lclNum - Local num for this struct local
-    //   access - Access information for the candidate.
+    //   comp            - Compiler instance
+    //   lclNum          - Local num for this struct local
+    //   access          - Access information for the candidate.
+    //   inducedCountWtd - Additional weighted count due to induced accesses.
     //
     // Returns:
     //   True if we should promote this access and create a replacement; otherwise false.
     //
-    bool EvaluateReplacement(Compiler* comp, unsigned lclNum, const Access& access)
+    bool EvaluateReplacement(Compiler* comp, unsigned lclNum, const Access& access, weight_t inducedCountWtd)
     {
         weight_t countOverlappedCallArgWtd          = 0;
         weight_t countOverlappedRetbufsWtd          = 0;
@@ -387,7 +541,9 @@ public:
         for (const Access& otherAccess : m_accesses)
         {
             if (&otherAccess == &access)
+            {
                 continue;
+            }
 
             if (!otherAccess.Overlaps(access.Offset, genTypeSize(access.AccessType)))
             {
@@ -407,14 +563,14 @@ public:
         weight_t costWithout = 0;
 
         // We cost any normal access (which is a struct load or store) without promotion at 3 cycles.
-        costWithout += access.CountWtd * 3;
+        costWithout += (access.CountWtd + inducedCountWtd) * 3;
 
         weight_t costWith = 0;
 
         // For promoted accesses we expect these to turn into reg-reg movs (and in many cases be fully contained in the
         // parent).
         // We cost these at 0.5 cycles.
-        costWith += access.CountWtd * 0.5;
+        costWith += (access.CountWtd + inducedCountWtd) * 0.5;
 
         // Now look at the overlapping struct uses that promotion will make more expensive.
 
@@ -501,8 +657,24 @@ public:
         return false;
     }
 
+    //------------------------------------------------------------------------
+    // ClearInducedAccesses:
+    //   Clear the stored induced access metrics.
+    //
+    void ClearInducedAccesses()
+    {
+        m_inducedAccesses.clear();
+    }
+
 #ifdef DEBUG
-    void Dump(unsigned lclNum)
+    //------------------------------------------------------------------------
+    // DumpAccesses:
+    //   Dump the stored access metrics for a specified local.
+    //
+    // Parameters:
+    //   lclNum - The local
+    //
+    void DumpAccesses(unsigned lclNum)
     {
         if (m_accesses.size() <= 0)
         {
@@ -535,15 +707,85 @@ public:
                    access.CountReturnsWtd);
         }
     }
+
+    //------------------------------------------------------------------------
+    // DumpInducedAccesses:
+    //   Dump induced accesses for a specified struct local.
+    //
+    // Parameters:
+    //   lclNum - The local
+    //
+    void DumpInducedAccesses(unsigned lclNum)
+    {
+        if (m_inducedAccesses.size() <= 0)
+        {
+            return;
+        }
+
+        printf("Induced accesses for V%02u\n", lclNum);
+        for (PrimitiveAccess& access : m_inducedAccesses)
+        {
+            printf("  %s @ %03u\n", varTypeName(access.AccessType), access.Offset);
+            printf("    #: (%u, " FMT_WT ")\n", access.Count, access.CountWtd);
+        }
+    }
 #endif
+
+private:
+    //------------------------------------------------------------------------
+    // FindAccess:
+    //   Find access metrics information for the specified offset and access type.
+    //
+    // Parameters:
+    //   offs       - The offset
+    //   accessType - Access type
+    //
+    // Returns:
+    //   Pointer to a matching access, or nullptr if no match was found.
+    //
+    Access* FindAccess(unsigned offs, var_types accessType)
+    {
+        if (m_accesses.size() <= 0)
+        {
+            return nullptr;
+        }
+
+        size_t index = Promotion::BinarySearch<Access, &Access::Offset>(m_accesses, offs);
+        if ((ssize_t)index < 0)
+        {
+            return nullptr;
+        }
+
+        do
+        {
+            Access& candidateAccess = m_accesses[index];
+            if (candidateAccess.AccessType == accessType)
+            {
+                return &candidateAccess;
+            }
+
+            index++;
+        } while ((index < m_inducedAccesses.size()) && (m_inducedAccesses[index].Offset == offs));
+
+        return nullptr;
+    }
+};
+
+// Struct used to save all struct stores involving physical promotion candidates.
+// These stores can induce new field accesses as part of assignment decomposition.
+struct CandidateStore
+{
+    GenTreeLclVarCommon* Store;
+    BasicBlock*          Block;
 };
 
 // Visitor that records information about uses of struct locals.
 class LocalsUseVisitor : public GenTreeVisitor<LocalsUseVisitor>
 {
-    Promotion*  m_prom;
-    LocalUses** m_uses;
-    BasicBlock* m_curBB = nullptr;
+    Promotion*                 m_prom;
+    LocalUses**                m_uses;
+    BasicBlock*                m_curBB = nullptr;
+    ArrayStack<CandidateStore> m_candidateStores;
 
 public:
     enum
@@ -552,7 +794,10 @@ public:
         ComputeStack = true,
     };
 
-    LocalsUseVisitor(Promotion* prom) : GenTreeVisitor(prom->m_compiler), m_prom(prom)
+    LocalsUseVisitor(Promotion* prom)
+        : GenTreeVisitor(prom->m_compiler)
+        , m_prom(prom)
+        , m_candidateStores(prom->m_compiler->getAllocator(CMK_Promotion))
     {
         m_uses = new (prom->m_compiler, CMK_Promotion) LocalUses*[prom->m_compiler->lvaCount]{};
     }
@@ -570,21 +815,16 @@ public:
     }
 
     //------------------------------------------------------------------------
-    // GetUsesByLocal:
-    //   Get the uses information for a specified local.
+    // PreOrderVisit:
+    //   Visit a node in preorder and add its use information to the metrics.
     //
     // Parameters:
-    //   bb - The current basic block.
+    //   use  - The use edge
+    //   user - The user
     //
     // Returns:
-    //   Information about uses, or null if this local has no uses information
-    //   associated with it.
+    //   Visitor result
     //
-    LocalUses* GetUsesByLocal(unsigned lcl)
-    {
-        return m_uses[lcl];
-    }
-
     fgWalkResult PreOrderVisit(GenTree** use, GenTree* user)
     {
         GenTree* tree = *use;
@@ -629,6 +869,16 @@ public:
                     accessType   = lcl->TypeGet();
                     accessLayout = accessType == TYP_STRUCT ? lcl->GetLayout(m_compiler) : nullptr;
                     accessFlags  = ClassifyLocalAccess(lcl, effectiveUser);
+
+                    if (lcl->TypeIs(TYP_STRUCT) &&
+                        ((user != nullptr) && user->OperIsLocalStore() && user->Data()->OperIsLocalRead()))
+                    {
+                        // Make sure we add it only once if both the destination and source are candidates.
+                        if ((m_candidateStores.Height() <= 0) || (m_candidateStores.Top().Store != user))
+                        {
+                            m_candidateStores.Push(CandidateStore{user->AsLclVarCommon(), m_curBB});
+                        }
+                    }
                 }
 
                 LocalUses* uses = GetOrCreateUses(lcl->GetLclNum());
@@ -640,6 +890,200 @@ public:
         return fgWalkResult::WALK_CONTINUE;
     }
 
+    //------------------------------------------------------------------------
+    // PickPromotions:
+    //   Pick promotions and create aggregate information for each promoted
+    //   struct with promotions.
+    //
+    // Parameters:
+    //   aggregates - Appropriately sized vector to create aggregate information in.
+    //
+    // Returns:
+    //   True if any struct was physically promoted with at least one replacement;
+    //   otherwise false.
+    //
+    bool PickPromotions(jitstd::vector<AggregateInfo*>& aggregates)
+    {
+        unsigned numLocals = (unsigned)aggregates.size();
+        JITDUMP("Picking promotions\n");
+
+        bool any = false;
+
+        for (unsigned lclNum = 0; lclNum < numLocals; lclNum++)
+        {
+            LocalUses* uses = m_uses[lclNum];
+            if (uses == nullptr)
+            {
+                continue;
+            }
+
+#ifdef DEBUG
+            if (m_compiler->verbose)
+            {
+                uses->DumpAccesses(lclNum);
+            }
+#endif
+
+            uses->PickPromotions(m_compiler, lclNum, &aggregates[lclNum]);
+
+            any |= aggregates[lclNum] != nullptr;
+        }
+
+        if (!any)
+        {
+            return false;
+        }
+
+        if (m_candidateStores.Height() > 0)
+        {
+            // Now look for induced accesses due to assignment decomposition.
+
+            JITDUMP("Looking for induced accesses with %d stores between candidates\n", m_candidateStores.Height());
+            // Expand the set of fields iteratively based on the current picked
+            // set. We put a limit on this fixpoint computation to avoid
+            // pathological cases. From measurements no methods in our own
+            // collections need more than 10 iterations and 99.5% of methods
+            // need fewer than 5 iterations.
+            for (int iters = 0; iters < 10; iters++)
+            {
+                for (int i = 0; i < m_candidateStores.Height(); i++)
+                {
+                    const CandidateStore& candidateStore = m_candidateStores.BottomRef(i);
+                    GenTreeLclVarCommon*  store          = candidateStore.Store;
+
+                    assert(store->TypeIs(TYP_STRUCT));
+                    assert(store->Data()->OperIsLocalRead());
+
+                    GenTreeLclVarCommon* src = store->Data()->AsLclVarCommon();
+
+                    LclVarDsc* dstDsc = m_compiler->lvaGetDesc(store);
+                    LclVarDsc* srcDsc = m_compiler->lvaGetDesc(src);
+
+                    assert(Promotion::IsCandidateForPhysicalPromotion(dstDsc) ||
+                           Promotion::IsCandidateForPhysicalPromotion(srcDsc));
+
+                    if (dstDsc->lvPromoted)
+                    {
+                        InduceAccessesFromRegularlyPromotedStruct(aggregates, src, store, candidateStore.Block);
+                    }
+                    else if (srcDsc->lvPromoted)
+                    {
+                        InduceAccessesFromRegularlyPromotedStruct(aggregates, store, src, candidateStore.Block);
+                    }
+                    else
+                    {
+                        if (Promotion::IsCandidateForPhysicalPromotion(dstDsc))
+                        {
+                            InduceAccessesInCandidate(aggregates, store, src, candidateStore.Block);
+                        }
+
+                        if (Promotion::IsCandidateForPhysicalPromotion(srcDsc))
+                        {
+                            InduceAccessesInCandidate(aggregates, src, store, candidateStore.Block);
+                        }
+                    }
+                }
+
+                bool any = false;
+                for (unsigned lclNum = 0; lclNum < numLocals; lclNum++)
+                {
+                    LocalUses* uses = m_uses[lclNum];
+                    if (uses == nullptr)
+                    {
+                        continue;
+                    }
+#ifdef DEBUG
+                    if (m_compiler->verbose)
+                    {
+                        uses->DumpInducedAccesses(lclNum);
+                    }
+#endif
+
+                    any |= uses->PickInducedPromotions(m_compiler, lclNum, &aggregates[lclNum]);
+                }
+
+                if (!any)
+                {
+                    break;
+                }
+
+                for (unsigned lclNum = 0; lclNum < numLocals; lclNum++)
+                {
+                    if (m_uses[lclNum] != nullptr)
+                    {
+                        m_uses[lclNum]->ClearInducedAccesses();
+                    }
+                }
+            }
+        }
+
+        for (AggregateInfo* agg : aggregates)
+        {
+            if (agg == nullptr)
+            {
+                continue;
+            }
+
+            jitstd::vector<Replacement>& reps = agg->Replacements;
+
+            assert(reps.size() > 0);
+            // Create locals
+            for (Replacement& rep : reps)
+            {
+#ifdef DEBUG
+                char buf[32];
+                sprintf_s(buf, sizeof(buf), "V%02u.[%03u..%03u)", agg->LclNum, rep.Offset,
+                          rep.Offset + genTypeSize(rep.AccessType));
+                size_t len  = strlen(buf) + 1;
+                char*  bufp = new (m_compiler, CMK_DebugOnly) char[len];
+                strcpy_s(bufp, len, buf);
+                rep.Description = bufp;
+#endif
+
+                rep.LclNum     = m_compiler->lvaGrabTemp(false DEBUGARG(rep.Description));
+                LclVarDsc* dsc = m_compiler->lvaGetDesc(rep.LclNum);
+                dsc->lvType    = rep.AccessType;
+            }
+
+#ifdef DEBUG
+            JITDUMP("V%02u promoted with %d replacements\n", agg->LclNum, (int)reps.size());
+            for (const Replacement& rep : reps)
+            {
+                JITDUMP("  [%03u..%03u) promoted as %s V%02u\n", rep.Offset, rep.Offset + genTypeSize(rep.AccessType),
+                        varTypeName(rep.AccessType), rep.LclNum);
+            }
+#endif
+
+            JITDUMP("Computing unpromoted remainder for V%02u\n", agg->LclNum);
+            StructSegments unpromotedParts =
+                Promotion::SignificantSegments(m_compiler, m_compiler->lvaGetDesc(agg->LclNum)->GetLayout());
+            for (Replacement& rep : reps)
+            {
+                unpromotedParts.Subtract(StructSegments::Segment(rep.Offset, rep.Offset + genTypeSize(rep.AccessType)));
+            }
+
+            JITDUMP("  Remainder: ");
+            DBEXEC(m_compiler->verbose, unpromotedParts.Dump());
+            JITDUMP("\n\n");
+
+            StructSegments::Segment unpromotedSegment;
+            if (unpromotedParts.CoveringSegment(&unpromotedSegment))
+            {
+                agg->UnpromotedMin = unpromotedSegment.Start;
+                agg->UnpromotedMax = unpromotedSegment.End;
+                assert(unpromotedSegment.Start < unpromotedSegment.End);
+            }
+            else
+            {
+                // Aggregate is fully promoted, leave UnpromotedMin == UnpromotedMax to indicate this.
+            }
+
+            any = true;
+        }
+
+        return any;
+    }
+
 private:
     //------------------------------------------------------------------------
     // GetOrCreateUses:
@@ -661,6 +1105,112 @@ private:
         return m_uses[lclNum];
     }
 
+    //------------------------------------------------------------------------
+    // InduceAccessesFromRegularlyPromotedStruct:
+    //   Create induced accesses based on the fact that there is an assignment
+    //   between a physical promotion candidate and regularly promoted struct.
+    //
+    // Parameters:
+    //   aggregates   - Aggregate information with current set of replacements
+    //                  for each struct local.
+    //   candidateLcl - The local node for a physical promotion candidate.
+    //   regPromLcl   - The local node for the regularly promoted struct that
+    //                  may induce new LCL_FLD nodes in the candidate.
+    //   block        - The block that the assignment appears in.
+    //
+    void InduceAccessesFromRegularlyPromotedStruct(jitstd::vector<AggregateInfo*>& aggregates,
+                                                   GenTreeLclVarCommon*            candidateLcl,
+                                                   GenTreeLclVarCommon*            regPromLcl,
+                                                   BasicBlock*                     block)
+    {
+        unsigned regPromOffs   = regPromLcl->GetLclOffs();
+        unsigned candidateOffs = candidateLcl->GetLclOffs();
+        unsigned size          = regPromLcl->GetLayout(m_compiler)->GetSize();
+
+        LclVarDsc* regPromDsc = m_compiler->lvaGetDesc(regPromLcl);
+        for (unsigned fieldLcl = regPromDsc->lvFieldLclStart, i = 0; i < regPromDsc->lvFieldCnt; fieldLcl++, i++)
+        {
+            LclVarDsc* fieldDsc = m_compiler->lvaGetDesc(fieldLcl);
+            if ((fieldDsc->lvFldOffset >= regPromOffs) &&
+                (fieldDsc->lvFldOffset + genTypeSize(fieldDsc->lvType) <= (regPromOffs + size)))
+            {
+                InduceAccess(aggregates, candidateLcl->GetLclNum(),
+                             candidateLcl->GetLclOffs() + (fieldDsc->lvFldOffset - regPromOffs), fieldDsc->lvType,
+                             block);
+            }
+        }
+    }
+
+    //------------------------------------------------------------------------
+    // InduceAccessesInCandidate:
+    //   Create induced accesses based on the fact that a specified candidate
+    //   is being assigned from another struct local (the inducer).
+    //
+    // Parameters:
+    //   aggregates - Aggregate information with current set of replacements
+    //                for each struct local.
+    //   candidate  - The local node for the physical promotion candidate.
+    //   inducer    - The local node that may induce new LCL_FLD nodes in the candidate.
+    //   block      - The block that the assignment appears in.
+    //
+    void InduceAccessesInCandidate(jitstd::vector<AggregateInfo*>& aggregates,
+                                   GenTreeLclVarCommon*            candidate,
+                                   GenTreeLclVarCommon*            inducer,
+                                   BasicBlock*                     block)
+    {
+        unsigned candOffs    = candidate->GetLclOffs();
+        unsigned inducerOffs = inducer->GetLclOffs();
+        unsigned size        = candidate->GetLayout(m_compiler)->GetSize();
+
+        AggregateInfo* inducerAgg = aggregates[inducer->GetLclNum()];
+        if (inducerAgg != nullptr)
+        {
+            Replacement* firstRep;
+            Replacement* endRep;
+            if (inducerAgg->OverlappingReplacements(inducerOffs, size, &firstRep, &endRep))
+            {
+                for (Replacement* rep = firstRep; rep < endRep; rep++)
+                {
+                    if ((rep->Offset >= inducerOffs) &&
+                        (rep->Offset + genTypeSize(rep->AccessType) <= (inducerOffs + size)))
+                    {
+                        InduceAccess(aggregates, candidate->GetLclNum(), candOffs + (rep->Offset - inducerOffs),
+                                     rep->AccessType, block);
+                    }
+                }
+            }
+        }
+    }
+
+    //------------------------------------------------------------------------
+    // InduceAccessesInCandidate:
+    //   Record an induced access in a candidate for physical promotion.
+    //
+    // Parameters:
+    //   aggregates - Aggregate information with current set of replacements
+    //                for each struct local.
+    //   lclNum     - Local that has the induced access.
+    //   offset     - Offset at which the induced access starts.
+    //   type       - Type of the induced access.
+    //   block      - The block with the induced access.
+    //
+    void InduceAccess(
+        jitstd::vector<AggregateInfo*>& aggregates, unsigned lclNum, unsigned offset, var_types type, BasicBlock* block)
+    {
+        AggregateInfo* agg = aggregates[lclNum];
+        if (agg != nullptr)
+        {
+            Replacement* overlapRep;
+            if (agg->OverlappingReplacements(offset, genTypeSize(type), &overlapRep, nullptr))
+            {
+                return;
+            }
+        }
+
+        LocalUses* uses = GetOrCreateUses(lclNum);
+        uses->RecordInducedAccess(offset, type, block->getBBWeight(m_compiler));
+    }
+
     //------------------------------------------------------------------------
     // ClassifyLocalAccess:
     //   Given a local node and its user, classify information about it.
@@ -1139,8 +1689,6 @@ StructSegments Promotion::SignificantSegments(Compiler*    compiler,
     }
 #endif
 
-    // TODO-TP: Cache this per class layout, we call this for every struct
-    // operation on a promoted local.
     return segments;
 }
 
@@ -1163,6 +1711,11 @@ GenTree* Promotion::CreateWriteBack(Compiler* compiler, unsigned structLclNum, c
 {
     GenTree* value = compiler->gtNewLclVarNode(replacement.LclNum);
     GenTree* store = compiler->gtNewStoreLclFldNode(structLclNum, replacement.AccessType, replacement.Offset, value);
+
+    if (!compiler->lvaGetDesc(structLclNum)->lvDoNotEnregister)
+    {
+        compiler->lvaSetVarDoNotEnregister(structLclNum DEBUGARG(DoNotEnregisterReason::LocalField));
+    }
     return store;
 }
 
@@ -1185,6 +1738,11 @@ GenTree* Promotion::CreateReadBack(Compiler* compiler, unsigned structLclNum, co
 {
     GenTree* value = compiler->gtNewLclFldNode(structLclNum, replacement.AccessType, replacement.Offset);
     GenTree* store = compiler->gtNewStoreLclVarNode(replacement.LclNum, value);
+
+    if (!compiler->lvaGetDesc(structLclNum)->lvDoNotEnregister)
+    {
+        compiler->lvaSetVarDoNotEnregister(structLclNum DEBUGARG(DoNotEnregisterReason::LocalField));
+    }
     return store;
 }
 
@@ -1786,80 +2344,11 @@ PhaseStatus Promotion::Run()
         }
     }
 
-    unsigned numLocals = m_compiler->lvaCount;
-
-#ifdef DEBUG
-    if (m_compiler->verbose)
-    {
-        for (unsigned lcl = 0; lcl < m_compiler->lvaCount; lcl++)
-        {
-            LocalUses* uses = localsUse.GetUsesByLocal(lcl);
-            if (uses != nullptr)
-            {
-                uses->Dump(lcl);
-            }
-        }
-    }
-#endif
-
     // Pick promotions based on the use information we just collected.
-    bool                           anyReplacements = false;
     jitstd::vector<AggregateInfo*> aggregates(m_compiler->lvaCount, nullptr, m_compiler->getAllocator(CMK_Promotion));
-    for (unsigned i = 0; i < numLocals; i++)
-    {
-        LocalUses* uses = localsUse.GetUsesByLocal(i);
-        if (uses == nullptr)
-        {
-            continue;
-        }
-
-        uses->PickPromotions(m_compiler, i, &aggregates[i]);
-
-        if (aggregates[i] == nullptr)
-        {
-            continue;
-        }
-
-        jitstd::vector<Replacement>& reps = aggregates[i]->Replacements;
-
-        assert(reps.size() > 0);
-        anyReplacements = true;
-#ifdef DEBUG
-        JITDUMP("V%02u promoted with %d replacements\n", i, (int)reps.size());
-        for (const Replacement& rep : reps)
-        {
-            JITDUMP("  [%03u..%03u) promoted as %s V%02u\n", rep.Offset, rep.Offset + genTypeSize(rep.AccessType),
-                    varTypeName(rep.AccessType), rep.LclNum);
-        }
-#endif
-
-        JITDUMP("Computing unpromoted remainder for V%02u\n", i);
-        StructSegments unpromotedParts = SignificantSegments(m_compiler, m_compiler->lvaGetDesc(i)->GetLayout());
-        for (size_t i = 0; i < reps.size(); i++)
-        {
-            unpromotedParts.Subtract(
-                StructSegments::Segment(reps[i].Offset, reps[i].Offset + genTypeSize(reps[i].AccessType)));
-        }
-
-        JITDUMP("  Remainder: ");
-        DBEXEC(m_compiler->verbose, unpromotedParts.Dump());
-        JITDUMP("\n\n");
-
-        StructSegments::Segment unpromotedSegment;
-        if (unpromotedParts.CoveringSegment(&unpromotedSegment))
-        {
-            aggregates[i]->UnpromotedMin = unpromotedSegment.Start;
-            aggregates[i]->UnpromotedMax = unpromotedSegment.End;
-            assert(unpromotedSegment.Start < unpromotedSegment.End);
-        }
-        else
-        {
-            // Aggregate is fully promoted, leave UnpromotedMin == UnpromotedMax to indicate this.
-        }
-    }
-
-    if (!anyReplacements)
+    if (!localsUse.PickPromotions(aggregates))
     {
+        // No promotions picked.
         return PhaseStatus::MODIFIED_NOTHING;
     }
 
@@ -1904,17 +2393,17 @@ PhaseStatus Promotion::Run()
     // Insert initial IR to read arguments/OSR locals into replacement locals,
     // and add necessary explicit zeroing.
     Statement* prevStmt = nullptr;
-    for (unsigned lclNum = 0; lclNum < numLocals; lclNum++)
+    for (AggregateInfo* agg : aggregates)
     {
-        if (aggregates[lclNum] == nullptr)
+        if (agg == nullptr)
         {
             continue;
         }
 
-        LclVarDsc* dsc = m_compiler->lvaGetDesc(lclNum);
+        LclVarDsc* dsc = m_compiler->lvaGetDesc(agg->LclNum);
         if (dsc->lvIsParam || dsc->lvIsOSRLocal)
         {
-            InsertInitialReadBack(lclNum, aggregates[lclNum]->Replacements, &prevStmt);
+            InsertInitialReadBack(agg->LclNum, agg->Replacements, &prevStmt);
         }
         else if (dsc->lvSuppressedZeroInit)
         {
@@ -1923,7 +2412,7 @@ PhaseStatus Promotion::Run()
             // Now that we are promoting some fields that assumption may be
             // invalidated for those fields, and we may need to insert explicit
             // zero inits again.
-            ExplicitlyZeroInitReplacementLocals(lclNum, aggregates[lclNum]->Replacements, &prevStmt);
+            ExplicitlyZeroInitReplacementLocals(agg->LclNum, agg->Replacements, &prevStmt);
         }
     }
 
index 2eb279c17a3087e828ab3b2bacf1af907e219639..98c7f7a6b500f44295398585687db54c42494432 100644 (file)
@@ -12,7 +12,7 @@ struct Replacement
 {
     unsigned  Offset;
     var_types AccessType;
-    unsigned  LclNum;
+    unsigned  LclNum = BAD_VAR_NUM;
     // Is the replacement local (given by LclNum) fresher than the value in the struct local?
     bool NeedsWriteBack = true;
     // Is the value in the struct local fresher than the replacement local?
@@ -21,16 +21,10 @@ struct Replacement
     // back before transferring control if necessary.
     bool NeedsReadBack = false;
 #ifdef DEBUG
-    const char* Description;
+    const char* Description = "";
 #endif
 
-    Replacement(unsigned offset, var_types accessType, unsigned lclNum DEBUGARG(const char* description))
-        : Offset(offset)
-        , AccessType(accessType)
-        , LclNum(lclNum)
-#ifdef DEBUG
-        , Description(description)
-#endif
+    Replacement(unsigned offset, var_types accessType) : Offset(offset), AccessType(accessType)
     {
     }