Refactor fgComputeLife.
authorPat Gavlin <pagavlin@microsoft.com>
Sun, 31 Jul 2016 21:41:09 +0000 (14:41 -0700)
committerPat Gavlin <pagavlin@microsoft.com>
Mon, 1 Aug 2016 16:47:38 +0000 (09:47 -0700)
- Extract the liveness computations for call and local var nodes out into
  fgComputeLifeCall and fgComputeLifeLocal, respectively.
- Split fgComputeLife into two versions: one for the RyuJIT backend and
  one for the legacy backend. The former leaves out the QMARK/COLON
  processing that is necessary in the latter.

Commit migrated from https://github.com/dotnet/coreclr/commit/02ab8f9fbb223c14eb7faed209618f54a0ba7bfd

src/coreclr/src/jit/compiler.h
src/coreclr/src/jit/liveness.cpp

index 276a0f1..b0125d9 100644 (file)
@@ -3572,6 +3572,11 @@ public :
 
     void                fgUpdateRefCntForExtract(GenTreePtr  wholeTree,
                                                  GenTreePtr  keptTree);
+
+    void                fgComputeLifeCall(VARSET_TP& life, GenTreeCall* call);
+
+    bool                fgComputeLifeLocal(VARSET_TP& life, VARSET_TP& keepAliveVars, GenTree* lclVarNode, GenTree* node);
+
     VARSET_VALRET_TP    fgComputeLife     (VARSET_VALARG_TP life,
                                            GenTreePtr   startNode,
                                            GenTreePtr   endNode,
index 18ea49b..25c4085 100644 (file)
@@ -1518,16 +1518,371 @@ VARSET_VALRET_TP    Compiler::fgUpdateLiveSet(VARSET_VALARG_TP  liveSet,
     return newLiveSet;
 }
 
+void Compiler::fgComputeLifeCall(VARSET_TP& life, GenTreeCall* call)
+{
+    assert(call != nullptr);
+
+    // If this is a tail-call and we have any unmanaged p/invoke calls in
+    // the method then we're going to run the p/invoke epilog
+    // So we mark the FrameRoot as used by this instruction.
+    // This ensure that this variable is kept alive at the tail-call
+    if (call->IsTailCall() && info.compCallUnmanaged)
+    {
+        assert((!opts.ShouldUsePInvokeHelpers()) || (info.compLvFrameListRoot == BAD_VAR_NUM));
+        if (!opts.ShouldUsePInvokeHelpers())
+        {
+            /* Get the TCB local and make it live */
+
+            noway_assert(info.compLvFrameListRoot < lvaCount);
+
+            LclVarDsc* frameVarDsc = &lvaTable[info.compLvFrameListRoot];
+
+            if (frameVarDsc->lvTracked)
+            {
+                VARSET_TP VARSET_INIT_NOCOPY(varBit, VarSetOps::MakeSingleton(this, frameVarDsc->lvVarIndex));
+
+                VarSetOps::AddElemD(this, life, frameVarDsc->lvVarIndex);
+
+                /* Record interference with other live variables */
+
+                fgMarkIntf(life, varBit);
+            }
+        }
+    }
+
+    /* GC refs cannot be enregistered accross an unmanaged call */
+
+    // TODO: we should generate the code for saving to/restoring
+    //       from the inlined N/Direct frame instead.
+
+    /* Is this call to unmanaged code? */
+    if (call->IsUnmanaged())
+    {
+        /* Get the TCB local and make it live */
+        assert((!opts.ShouldUsePInvokeHelpers()) || (info.compLvFrameListRoot == BAD_VAR_NUM));
+        if (!opts.ShouldUsePInvokeHelpers())
+        {
+            noway_assert(info.compLvFrameListRoot < lvaCount);
+
+            LclVarDsc* frameVarDsc = &lvaTable[info.compLvFrameListRoot];
+
+            if (frameVarDsc->lvTracked)
+            {
+                unsigned varIndex  = frameVarDsc->lvVarIndex;
+                noway_assert(varIndex < lvaTrackedCount);
+
+                // Is the variable already known to be alive?
+                //
+                if  (VarSetOps::IsMember(this, life, varIndex))
+                {
+                    // Since we may call this multiple times, clear the GTF_CALL_M_FRAME_VAR_DEATH if set.
+                    //
+                    call->gtCallMoreFlags &= ~GTF_CALL_M_FRAME_VAR_DEATH; 
+                }
+                else
+                {
+                    // The variable is just coming to life
+                    // Since this is a backwards walk of the trees 
+                    // that makes this change in liveness a 'last-use'
+                    //
+                    VarSetOps::AddElemD(this, life, varIndex);
+                    call->gtCallMoreFlags |= GTF_CALL_M_FRAME_VAR_DEATH;
+                }
+
+                // Record an interference with the other live variables
+                //
+                VARSET_TP VARSET_INIT_NOCOPY(varBit, VarSetOps::MakeSingleton(this, varIndex));
+                fgMarkIntf(life, varBit);
+            }
+        }
+
+        /* Do we have any live variables? */
+
+        if (!VarSetOps::IsEmpty(this, life))
+        {
+            // For each live variable if it is a GC-ref type, we
+            // mark it volatile to prevent if from being enregistered
+            // across the unmanaged call.
+
+            unsigned lclNum;
+            LclVarDsc* varDsc;
+            for (lclNum = 0, varDsc = lvaTable;
+                 lclNum < lvaCount;
+                 lclNum++  , varDsc++)
+            {
+                /* Ignore the variable if it's not tracked */
+
+                if  (!varDsc->lvTracked)
+                    continue;
+
+                unsigned  varNum = varDsc->lvVarIndex;
+
+                /* Ignore the variable if it's not live here */
+
+                if  (!VarSetOps::IsMember(this, life, varDsc->lvVarIndex))
+                    continue;
+
+                // If it is a GC-ref type then mark it DoNotEnregister.
+                if (varTypeIsGC(varDsc->TypeGet()))
+                {
+                    lvaSetVarDoNotEnregister(lclNum DEBUGARG(DNER_LiveAcrossUnmanagedCall));
+                }
+            }
+        }
+    }
+}
+
+bool Compiler::fgComputeLifeLocal(VARSET_TP& life, VARSET_TP& keepAliveVars, GenTree* lclVarNode, GenTree* node)
+{
+    unsigned lclNum = lclVarNode->gtLclVarCommon.gtLclNum;
+
+    noway_assert(lclNum < lvaCount);
+    LclVarDsc* varDsc = &lvaTable[lclNum];
+
+    unsigned        varIndex;
+    VARSET_TP       varBit;
+
+    // Is this a tracked variable?
+    if  (varDsc->lvTracked)
+    {
+        varIndex = varDsc->lvVarIndex;
+        noway_assert(varIndex < lvaTrackedCount);
+
+        /* Is this a definition or use? */
+
+        if  (lclVarNode->gtFlags & GTF_VAR_DEF)
+        {
+            /*
+                The variable is being defined here. The variable
+                should be marked dead from here until its closest
+                previous use.
+
+                IMPORTANT OBSERVATION:
+
+                    For GTF_VAR_USEASG (i.e. x <op>= a) we cannot
+                    consider it a "pure" definition because it would
+                    kill x (which would be wrong because x is
+                    "used" in such a construct) -> see below the case when x is live
+             */
+
+            if  (VarSetOps::IsMember(this, life, varIndex))
+            {
+                /* The variable is live */
+
+                if ((lclVarNode->gtFlags & GTF_VAR_USEASG) == 0)
+                {
+                    /* Mark variable as dead from here to its closest use */
+
+                    if (!VarSetOps::IsMember(this, keepAliveVars, varIndex))
+                    {
+                        VarSetOps::RemoveElemD(this, life, varIndex);
+                    }
+#ifdef DEBUG
+                    if (verbose&&0)
+                    {
+                        printf("Def V%02u,T%02u at ", lclNum, varIndex);
+                        printTreeID(lclVarNode);
+                        printf(" life %s -> %s\n",
+                               VarSetOps::ToString(this, VarSetOps::Union(this, life, VarSetOps::MakeSingleton(this, varIndex))),
+                               VarSetOps::ToString(this, life));
+                    }
+#endif // DEBUG
+                }
+            }
+            else
+            {
+                /* Dead assignment to the variable */
+                lclVarNode->gtFlags |= GTF_VAR_DEATH;
+
+                if (!opts.MinOpts())
+                {
+                    // keepAliveVars always stay alive
+                    noway_assert(!VarSetOps::IsMember(this, keepAliveVars, varIndex));
+
+                    /* This is a dead store unless the variable is marked
+                       GTF_VAR_USEASG and we are in an interior statement
+                       that will be used (e.g. while (i++) or a GT_COMMA) */
+
+                    return true;
+                }
+            }
+
+            return false;
+        }
+        else // it is a use
+        {
+            // Is the variable already known to be alive?
+            if (VarSetOps::IsMember(this, life, varIndex))
+            {
+                // Since we may do liveness analysis multiple times, clear the GTF_VAR_DEATH if set.
+                lclVarNode->gtFlags &= ~GTF_VAR_DEATH;
+                return false;
+            }
+
+#ifdef DEBUG
+            if (verbose && 0)
+            {
+                printf("Ref V%02u,T%02u] at ", lclNum, varIndex);
+                printTreeID(node);
+                printf(" life %s -> %s\n",
+                       VarSetOps::ToString(this, life),
+                       VarSetOps::ToString(this, VarSetOps::Union(this, life, varBit)));
+            }
+#endif // DEBUG
+
+
+            // The variable is being used, and it is not currently live.
+            // So the variable is just coming to life
+            lclVarNode->gtFlags |= GTF_VAR_DEATH;
+            VarSetOps::AddElemD(this, life, varIndex);
+
+            // Record interference with other live variables
+            fgMarkIntf(life, VarSetOps::MakeSingleton(this, varIndex));
+        }
+    }
+    // Note that promoted implies not tracked (i.e. only the fields are tracked).
+    else if (varTypeIsStruct(varDsc->lvType))
+    {
+        noway_assert(!varDsc->lvTracked);
+
+        lvaPromotionType promotionType = lvaGetPromotionType(varDsc);
+
+        if (promotionType != PROMOTION_TYPE_NONE)
+        {
+            VarSetOps::AssignNoCopy(this, varBit, VarSetOps::MakeEmpty(this));
+
+            for (unsigned i = varDsc->lvFieldLclStart;
+                 i < varDsc->lvFieldLclStart + varDsc->lvFieldCnt;
+                 ++i)
+            {
+#if !defined(_TARGET_64BIT_) && !defined(LEGACY_BACKEND)
+                if (!varTypeIsLong(lvaTable[i].lvType) || !lvaTable[i].lvPromoted)
+#endif // !defined(_TARGET_64BIT_) && !defined(LEGACY_BACKEND)
+                {
+                    noway_assert(lvaTable[i].lvIsStructField);
+                }
+                if  (lvaTable[i].lvTracked)
+                {
+                    varIndex = lvaTable[i].lvVarIndex;
+                    noway_assert(varIndex < lvaTrackedCount);
+                    VarSetOps::AddElemD(this, varBit, varIndex);
+                }
+            }
+            if  (node->gtFlags & GTF_VAR_DEF)
+            {
+                VarSetOps::DiffD(this, varBit, keepAliveVars);
+                VarSetOps::DiffD(this, life, varBit);
+                return false;
+            }
+            // This is a use.
+
+            // Are the variables already known to be alive?
+            if (VarSetOps::IsSubset(this, varBit, life))
+            {
+                node->gtFlags &= ~GTF_VAR_DEATH;  // Since we may now call this multiple times, reset if live.
+                return false;
+            }
+
+            // Some variables are being used, and they are not currently live.
+            // So they are just coming to life, in the backwards traversal; in a forwards
+            // traversal, one or more are dying.  Mark this.
+
+            node->gtFlags |= GTF_VAR_DEATH;
+
+            // Are all the variables becoming alive (in the backwards traversal), or just a subset?
+            if (!VarSetOps::IsEmptyIntersection(this, varBit, life))
+            {
+                // Only a subset of the variables are become live; we must record that subset.
+                // (Lack of an entry for "lclVarNode" will be considered to imply all become dead in the
+                // forward traversal.)
+                VARSET_TP* deadVarSet = new (this, CMK_bitset) VARSET_TP;
+                VarSetOps::AssignNoCopy(this, *deadVarSet, VarSetOps::Diff(this, varBit, life));
+                GetPromotedStructDeathVars()->Set(lclVarNode, deadVarSet);
+            }
+
+            // In any case, all the field vars are now live (in the backwards traversal).
+            VarSetOps::UnionD(this, life, varBit);
+
+            // Record interference with other live variables
+            fgMarkIntf(life, varBit);
+        }
+    }
+
+    return false;
+}
+
 /*****************************************************************************
  *
  * Compute the set of live variables at each node in a given statement
  * or subtree of a statement moving backward from startNode to endNode
  */
 
+#ifndef LEGACY_BACKEND
+VARSET_VALRET_TP    Compiler::fgComputeLife(VARSET_VALARG_TP lifeArg,
+                                            GenTreePtr       startNode,
+                                            GenTreePtr       endNode,
+                                            VARSET_VALARG_TP volatileVars,
+                                            bool*            pStmtInfoDirty
+                                   DEBUGARG(bool*            treeModf))
+{
+    GenTreePtr      tree;
+    unsigned        lclNum;
+
+    VARSET_TP VARSET_INIT(this, life, lifeArg); // lifeArg is const ref; copy to allow modification.
+
+    VARSET_TP       VARSET_INIT(this, keepAliveVars, volatileVars);
+#ifdef DEBUGGING_SUPPORT
+    VarSetOps::UnionD(this, keepAliveVars, compCurBB->bbScope); // Don't kill vars in scope
+#endif
+
+    noway_assert(VarSetOps::Equal(this, VarSetOps::Intersection(this, keepAliveVars, life), keepAliveVars));
+    noway_assert(compCurStmt->gtOper == GT_STMT);
+    noway_assert(endNode || (startNode == compCurStmt->gtStmt.gtStmtExpr));
+
+    // NOTE: Live variable analysis will not work if you try
+    // to use the result of an assignment node directly!
+    for (tree = startNode; tree != endNode; tree = tree->gtPrev)
+    {
+AGAIN:
+        assert(tree->OperGet() != GT_QMARK);
+
+        if (tree->gtOper == GT_CALL)
+        {
+            fgComputeLifeCall(life, tree->AsCall());
+        }
+        else if (tree->OperIsNonPhiLocal() || tree->OperIsLocalAddr())
+        {
+            bool isDeadStore = fgComputeLifeLocal(life, keepAliveVars, tree, tree);
+            if (isDeadStore)
+            {
+                LclVarDsc* varDsc = &lvaTable[tree->gtLclVarCommon.gtLclNum];
+
+                bool doAgain;
+                if (fgRemoveDeadStore(&tree, varDsc, life, &doAgain, pStmtInfoDirty DEBUGARG(treeModf)))
+                {
+                    assert(!doAgain);
+                    break;
+                }
+
+                if (doAgain)
+                {
+                    goto AGAIN;
+                }
+            }
+        }
+    }
+
+    // Return the set of live variables out of this statement
+    return life;
+}
+
+#else // LEGACY_BACKEND
+
 #ifdef _PREFAST_
 #pragma warning(push)
 #pragma warning(disable:21000) // Suppress PREFast warning about overly large function
 #endif
+
 VARSET_VALRET_TP    Compiler::fgComputeLife(VARSET_VALARG_TP lifeArg,
                                             GenTreePtr       startNode,
                                             GenTreePtr       endNode,
@@ -1730,122 +2085,11 @@ SKIP_QMARK:
 
         if (tree->gtOper == GT_CALL)
         {
-            // if this is a tail-call and we have any unmanaged p/invoke calls in
-            // the method then we're going to run the p/invoke epilog
-            // So we mark the FrameRoot as used by this instruction.
-            // This ensure that this variable is kept alive at the tail-call
-
-            if (tree->gtCall.IsTailCall() && info.compCallUnmanaged)
-            {
-                assert((!opts.ShouldUsePInvokeHelpers()) || (info.compLvFrameListRoot == BAD_VAR_NUM));
-                if (!opts.ShouldUsePInvokeHelpers())
-                {
-                    /* Get the TCB local and make it live */
-
-                    noway_assert(info.compLvFrameListRoot < lvaCount);
-
-                    LclVarDsc* frameVarDsc = &lvaTable[info.compLvFrameListRoot];
-
-                    if (frameVarDsc->lvTracked)
-                    {
-                        VARSET_TP VARSET_INIT_NOCOPY(varBit, VarSetOps::MakeSingleton(this, frameVarDsc->lvVarIndex));
-
-                        VarSetOps::AddElemD(this, life, frameVarDsc->lvVarIndex);
-
-                        /* Record interference with other live variables */
-
-                        fgMarkIntf(life, varBit);
-                    }
-                }
-            }
-
-            /* GC refs cannot be enregistered accross an unmanaged call */
-
-            // TODO: we should generate the code for saving to/restoring
-            //       from the inlined N/Direct frame instead.
-
-            /* Is this call to unmanaged code? */
-
-            if (tree->gtCall.IsUnmanaged())
-            {
-                /* Get the TCB local and make it live */
-                assert((!opts.ShouldUsePInvokeHelpers()) || (info.compLvFrameListRoot == BAD_VAR_NUM));
-                if (!opts.ShouldUsePInvokeHelpers())
-                {
-                    noway_assert(info.compLvFrameListRoot < lvaCount);
-
-                    LclVarDsc* frameVarDsc = &lvaTable[info.compLvFrameListRoot];
-
-                    if (frameVarDsc->lvTracked)
-                    {
-                        unsigned varIndex  = frameVarDsc->lvVarIndex;
-                        noway_assert(varIndex < lvaTrackedCount);
-
-                        // Is the variable already known to be alive?
-                        //
-                        if  (VarSetOps::IsMember(this, life, varIndex))
-                        {
-                            // Since we may call this multiple times, clear the GTF_CALL_M_FRAME_VAR_DEATH if set.
-                            //
-                            tree->gtCall.gtCallMoreFlags &= ~GTF_CALL_M_FRAME_VAR_DEATH; 
-                        }
-                        else
-                        {
-                            // The variable is just coming to life
-                            // Since this is a backwards walk of the trees 
-                            // that makes this change in liveness a 'last-use'
-                            //
-                            VarSetOps::AddElemD(this, life, varIndex);
-                            tree->gtCall.gtCallMoreFlags |= GTF_CALL_M_FRAME_VAR_DEATH;
-                        }
-
-                        // Record an interference with the other live variables
-                        //
-                        VARSET_TP VARSET_INIT_NOCOPY(varBit, VarSetOps::MakeSingleton(this, varIndex));
-                        fgMarkIntf(life, varBit);
-                    }
-                }
-
-                /* Do we have any live variables? */
-
-                if (!VarSetOps::IsEmpty(this, life))
-                {
-                    // For each live variable if it is a GC-ref type, we
-                    // mark it volatile to prevent if from being enregistered
-                    // across the unmanaged call.
-
-                    LclVarDsc* varDsc;
-
-                    for (lclNum = 0, varDsc = lvaTable;
-                         lclNum < lvaCount;
-                         lclNum++  , varDsc++)
-                    {
-                        /* Ignore the variable if it's not tracked */
-
-                        if  (!varDsc->lvTracked)
-                            continue;
-
-                        unsigned  varNum = varDsc->lvVarIndex;
-
-                        /* Ignore the variable if it's not live here */
-
-                        if  (!VarSetOps::IsMember(this, life, varDsc->lvVarIndex))
-                            continue;
-
-                        // If it is a GC-ref type then mark it DoNotEnregister.
-                        if (varTypeIsGC(varDsc->TypeGet()))
-                        {
-                            lvaSetVarDoNotEnregister(lclNum DEBUGARG(DNER_LiveAcrossUnmanagedCall));
-                        }
-                    }
-                }
-            }
+            fgComputeLifeCall(life, tree->AsCall());
+            continue;
         }
 
         // Is this a use/def of a local variable?
-        CLANG_FORMAT_COMMENT_ANCHOR;
-
-#ifdef LEGACY_BACKEND
         // Generally, the last use information is associated with the lclVar node.
         // However, for LEGACY_BACKEND, the information must be associated
         // with the OBJ itself for promoted structs.
@@ -1870,188 +2114,24 @@ SKIP_QMARK:
         {
             lclVarTree = tree;
         }
-#else // !LEGACY_BACKEND
-        GenTreePtr lclVarTree = tree;
-#endif // !LEGACY_BACKEND
+
         if (lclVarTree->OperIsNonPhiLocal() || lclVarTree->OperIsLocalAddr())
         {
-            lclNum = lclVarTree->gtLclVarCommon.gtLclNum;
-
-            noway_assert(lclNum < lvaCount);
-            LclVarDsc * varDsc = lvaTable + lclNum;
-
-            unsigned        varIndex;
-            VARSET_TP       varBit;
-
-            // Is this a tracked variable?
-            if  (varDsc->lvTracked)
+            bool isDeadStore = fgComputeLifeLocal(life, keepAliveVars, lclVarTree, tree);
+            if (isDeadStore)
             {
-                varIndex = varDsc->lvVarIndex;
-                noway_assert(varIndex < lvaTrackedCount);
-
-                /* Is this a definition or use? */
+                LclVarDsc* varDsc = &lvaTable[lclVarTree->gtLclVarCommon.gtLclNum];
 
-                if  (lclVarTree->gtFlags & GTF_VAR_DEF)
+                bool doAgain;
+                if (fgRemoveDeadStore(&tree, varDsc, life, &doAgain, pStmtInfoDirty DEBUGARG(treeModf)))
                 {
-                    /*
-                        The variable is being defined here. The variable
-                        should be marked dead from here until its closest
-                        previous use.
-
-                        IMPORTANT OBSERVATION:
-
-                            For GTF_VAR_USEASG (i.e. x <op>= a) we cannot
-                            consider it a "pure" definition because it would
-                            kill x (which would be wrong because x is
-                            "used" in such a construct) -> see below the case when x is live
-                     */
-
-                    if  (VarSetOps::IsMember(this, life, varIndex))
-                    {
-                        /* The variable is live */
-
-                        if ((lclVarTree->gtFlags & GTF_VAR_USEASG) == 0)
-                        {
-                            /* Mark variable as dead from here to its closest use */
-
-                            if (!VarSetOps::IsMember(this, keepAliveVars, varIndex))
-                            {
-                                VarSetOps::RemoveElemD(this, life, varIndex);
-                            }
-#ifdef DEBUG
-                            if (verbose&&0)
-                            {
-                                printf("Def V%02u,T%02u at ", lclNum, varIndex);
-                                printTreeID(lclVarTree);
-                                printf(" life %s -> %s\n",
-                                       VarSetOps::ToString(this, VarSetOps::Union(this, life, VarSetOps::MakeSingleton(this, varIndex))),
-                                       VarSetOps::ToString(this, life));
-                            }
-#endif // DEBUG
-                        }
-                    }
-                    else
-                    {
-                        /* Dead assignment to the variable */
-                        lclVarTree->gtFlags |= GTF_VAR_DEATH;
-
-                        if (opts.MinOpts())
-                            continue;
-
-                        // keepAliveVars always stay alive
-                        noway_assert(!VarSetOps::IsMember(this, keepAliveVars, varIndex));
-
-                        /* This is a dead store unless the variable is marked
-                           GTF_VAR_USEASG and we are in an interior statement
-                           that will be used (e.g. while (i++) or a GT_COMMA) */
-
-                        bool doAgain = false;
-                        if (fgRemoveDeadStore(&tree, varDsc, life, &doAgain, pStmtInfoDirty DEBUGARG(treeModf)))
-                            break;
-
-                        if (doAgain)
-                            goto AGAIN;
-                    }
-
-                    continue;
+                    assert(!doAgain);
+                    break;
                 }
-                else // it is a use
-                {
-                    // Is the variable already known to be alive?
-                    if (VarSetOps::IsMember(this, life, varIndex))
-                    {
-                        // Since we may do liveness analysis multiple times, clear the GTF_VAR_DEATH if set.
-                        lclVarTree->gtFlags &= ~GTF_VAR_DEATH;
-                        continue;
-                    }
-
-#ifdef DEBUG
-                    if (verbose && 0)
-                    {
-                        printf("Ref V%02u,T%02u] at ", lclNum, varIndex);
-                        printTreeID(tree);
-                        printf(" life %s -> %s\n",
-                               VarSetOps::ToString(this, life),
-                               VarSetOps::ToString(this, VarSetOps::Union(this, life, varBit)));
-                    }
-#endif // DEBUG
-
-
-                    // The variable is being used, and it is not currently live.
-                    // So the variable is just coming to life
-                    lclVarTree->gtFlags |= GTF_VAR_DEATH;
-                    VarSetOps::AddElemD(this, life, varIndex);
 
-                    // Record interference with other live variables
-                    fgMarkIntf(life, VarSetOps::MakeSingleton(this, varIndex));
-                }
-
-            }
-            // Note that promoted implies not tracked (i.e. only the fields are tracked).
-            else if (varTypeIsStruct(varDsc->lvType))
-            {
-                noway_assert(!varDsc->lvTracked);
-
-                lvaPromotionType promotionType = lvaGetPromotionType(varDsc);
-
-                if (promotionType != PROMOTION_TYPE_NONE)
+                if (doAgain)
                 {
-                    VarSetOps::AssignNoCopy(this, varBit, VarSetOps::MakeEmpty(this));
-
-                    for (unsigned i = varDsc->lvFieldLclStart;
-                         i < varDsc->lvFieldLclStart + varDsc->lvFieldCnt;
-                         ++i)
-                    {
-#if !defined(_TARGET_64BIT_) && !defined(LEGACY_BACKEND)
-                        if (!varTypeIsLong(lvaTable[i].lvType) || !lvaTable[i].lvPromoted)
-#endif // !defined(_TARGET_64BIT_) && !defined(LEGACY_BACKEND)
-                        {
-                            noway_assert(lvaTable[i].lvIsStructField);
-                        }
-                        if  (lvaTable[i].lvTracked)
-                        {
-                            varIndex = lvaTable[i].lvVarIndex;
-                            noway_assert(varIndex < lvaTrackedCount);
-                            VarSetOps::AddElemD(this, varBit, varIndex);
-                        }
-                    }
-                    if  (tree->gtFlags & GTF_VAR_DEF)
-                    {
-                        VarSetOps::DiffD(this, varBit, keepAliveVars);
-                        VarSetOps::DiffD(this, life, varBit);
-                        continue;
-                    }
-                    // This is a use.
-
-                    // Are the variables already known to be alive?
-                    if (VarSetOps::IsSubset(this, varBit, life))
-                    {
-                        tree->gtFlags &= ~GTF_VAR_DEATH;  // Since we may now call this multiple times, reset if live.
-                        continue;
-                    }
-
-                    // Some variables are being used, and they are not currently live.
-                    // So they are just coming to life, in the backwards traversal; in a forwards
-                    // traversal, one or more are dying.  Mark this.
-
-                    tree->gtFlags |= GTF_VAR_DEATH;
-
-                    // Are all the variables becoming alive (in the backwards traversal), or just a subset?
-                    if (!VarSetOps::IsEmptyIntersection(this, varBit, life))
-                    {
-                        // Only a subset of the variables are become live; we must record that subset.
-                        // (Lack of an entry for "lclVarTree" will be considered to imply all become dead in the
-                        // forward traversal.)
-                        VARSET_TP* deadVarSet = new (this, CMK_bitset) VARSET_TP;
-                        VarSetOps::AssignNoCopy(this, *deadVarSet, VarSetOps::Diff(this, varBit, life));
-                        GetPromotedStructDeathVars()->Set(lclVarTree, deadVarSet);
-                    }
-
-                    // In any case, all the field vars are now live (in the backwards traversal).
-                    VarSetOps::UnionD(this, life, varBit);
-
-                    // Record interference with other live variables
-                    fgMarkIntf(life, varBit);
+                    goto AGAIN;
                 }
             }
         }
@@ -2145,6 +2225,8 @@ SKIP_QMARK:
 #pragma warning(pop)
 #endif
 
+#endif // !LEGACY_BACKEND
+
 // fgRemoveDeadStore - remove a store to a local which has no exposed uses.
 //
 //   pTree          - GenTree** to local, including store-form local or local addr (post-rationalize)