summaryrefslogtreecommitdiff
path: root/src/jit/liveness.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/jit/liveness.cpp')
-rw-r--r--src/jit/liveness.cpp684
1 files changed, 409 insertions, 275 deletions
diff --git a/src/jit/liveness.cpp b/src/jit/liveness.cpp
index 9b07f8cd94..c12301c97a 100644
--- a/src/jit/liveness.cpp
+++ b/src/jit/liveness.cpp
@@ -52,8 +52,15 @@ void Compiler::fgMarkUseDef(GenTreeLclVarCommon* tree, GenTree* asgdLclVar)
varDsc->lvRefCnt = 1;
}
- if (asgdLclVar)
+ // NOTE: the analysis done below is neither necessary nor correct for LIR: it depends on
+ // the nodes that precede `asgdLclVar` in execution order to factor into the dataflow for the
+ // value being assigned to the local var, which is not necessarily the case without tree
+ // order. Furthermore, LIR is always traversed in an order that reflects the dataflow for the
+ // block.
+ if (asgdLclVar != nullptr)
{
+ assert(!compCurBB->IsLIR());
+
/* we have an assignment to a local var : asgdLclVar = ... tree ...
* check for x = f(x) case */
@@ -274,195 +281,203 @@ void Compiler::fgLocalVarLivenessInit()
//
#ifndef LEGACY_BACKEND
//------------------------------------------------------------------------
-// fgPerStatementLocalVarLiveness:
+// fgPerNodeLocalVarLiveness:
// Set fgCurHeapUse and fgCurHeapDef when the global heap is read or updated
// Call fgMarkUseDef for any Local variables encountered
//
// Arguments:
-// startNode must be the first node in the statement
-// asgdLclVar is either nullptr or the assignement's left-hand-side GT_LCL_VAR
-// it is used as an argument to fgMarkUseDef()
+// tree - The current node.
+// asgdLclVar - Either nullptr or the assignement's left-hand-side GT_LCL_VAR.
+// Used as an argument to fgMarkUseDef(); only valid for HIR blocks.
//
-void Compiler::fgPerStatementLocalVarLiveness(GenTreePtr startNode, GenTreePtr asgdLclVar)
+void Compiler::fgPerNodeLocalVarLiveness(GenTree* tree, GenTree* asgdLclVar)
{
- // The startNode must be the 1st node of the statement.
- assert(startNode == compCurStmt->gtStmt.gtStmtList);
+ assert(tree != nullptr);
+ assert(asgdLclVar == nullptr || !compCurBB->IsLIR());
- // The asgdLclVar node must be either nullptr or a GT_LCL_VAR or GT_STORE_LCL_VAR
- assert((asgdLclVar == nullptr) || (asgdLclVar->gtOper == GT_LCL_VAR || asgdLclVar->gtOper == GT_STORE_LCL_VAR));
-
- // We always walk every node in statement list
- for (GenTreePtr tree = startNode; (tree != nullptr); tree = tree->gtNext)
+ switch (tree->gtOper)
{
- switch (tree->gtOper)
- {
- case GT_QMARK:
- case GT_COLON:
- // We never should encounter a GT_QMARK or GT_COLON node
- noway_assert(!"unexpected GT_QMARK/GT_COLON");
- break;
+ case GT_QMARK:
+ case GT_COLON:
+ // We never should encounter a GT_QMARK or GT_COLON node
+ noway_assert(!"unexpected GT_QMARK/GT_COLON");
+ break;
- case GT_LCL_VAR:
- case GT_LCL_FLD:
- case GT_LCL_VAR_ADDR:
- case GT_LCL_FLD_ADDR:
- case GT_STORE_LCL_VAR:
- case GT_STORE_LCL_FLD:
- fgMarkUseDef(tree->AsLclVarCommon(), asgdLclVar);
- break;
+ case GT_LCL_VAR:
+ case GT_LCL_FLD:
+ case GT_LCL_VAR_ADDR:
+ case GT_LCL_FLD_ADDR:
+ case GT_STORE_LCL_VAR:
+ case GT_STORE_LCL_FLD:
+ fgMarkUseDef(tree->AsLclVarCommon(), asgdLclVar);
+ break;
- case GT_CLS_VAR:
- // For Volatile indirection, first mutate the global heap
- // see comments in ValueNum.cpp (under case GT_CLS_VAR)
- // This models Volatile reads as def-then-use of the heap.
- // and allows for a CSE of a subsequent non-volatile read
- if ((tree->gtFlags & GTF_FLD_VOLATILE) != 0)
- {
- // For any Volatile indirection, we must handle it as a
- // definition of the global heap
- fgCurHeapDef = true;
- }
- // If the GT_CLS_VAR is the lhs of an assignment, we'll handle it as a heap def, when we get to
- // assignment.
- // Otherwise, we treat it as a use here.
- if (!fgCurHeapDef && (tree->gtFlags & GTF_CLS_VAR_ASG_LHS) == 0)
- {
- fgCurHeapUse = true;
- }
- break;
+ case GT_CLS_VAR:
+ // For Volatile indirection, first mutate the global heap
+ // see comments in ValueNum.cpp (under case GT_CLS_VAR)
+ // This models Volatile reads as def-then-use of the heap.
+ // and allows for a CSE of a subsequent non-volatile read
+ if ((tree->gtFlags & GTF_FLD_VOLATILE) != 0)
+ {
+ // For any Volatile indirection, we must handle it as a
+ // definition of the global heap
+ fgCurHeapDef = true;
+ }
+ // If the GT_CLS_VAR is the lhs of an assignment, we'll handle it as a heap def, when we get to assignment.
+ // Otherwise, we treat it as a use here.
+ if (!fgCurHeapDef && (tree->gtFlags & GTF_CLS_VAR_ASG_LHS) == 0)
+ {
+ fgCurHeapUse = true;
+ }
+ break;
- case GT_IND:
- // For Volatile indirection, first mutate the global heap
- // see comments in ValueNum.cpp (under case GT_CLS_VAR)
- // This models Volatile reads as def-then-use of the heap.
- // and allows for a CSE of a subsequent non-volatile read
- if ((tree->gtFlags & GTF_IND_VOLATILE) != 0)
- {
- // For any Volatile indirection, we must handle it as a
- // definition of the global heap
- fgCurHeapDef = true;
- }
+ case GT_IND:
+ // For Volatile indirection, first mutate the global heap
+ // see comments in ValueNum.cpp (under case GT_CLS_VAR)
+ // This models Volatile reads as def-then-use of the heap.
+ // and allows for a CSE of a subsequent non-volatile read
+ if ((tree->gtFlags & GTF_IND_VOLATILE) != 0)
+ {
+ // For any Volatile indirection, we must handle it as a
+ // definition of the global heap
+ fgCurHeapDef = true;
+ }
- // If the GT_IND is the lhs of an assignment, we'll handle it
- // as a heap def, when we get to assignment.
- // Otherwise, we treat it as a use here.
- if ((tree->gtFlags & GTF_IND_ASG_LHS) == 0)
+ // If the GT_IND is the lhs of an assignment, we'll handle it
+ // as a heap def, when we get to assignment.
+ // Otherwise, we treat it as a use here.
+ if ((tree->gtFlags & GTF_IND_ASG_LHS) == 0)
+ {
+ GenTreeLclVarCommon* dummyLclVarTree = nullptr;
+ bool dummyIsEntire = false;
+ GenTreePtr addrArg = tree->gtOp.gtOp1->gtEffectiveVal(/*commaOnly*/ true);
+ if (!addrArg->DefinesLocalAddr(this, /*width doesn't matter*/ 0, &dummyLclVarTree, &dummyIsEntire))
{
- GenTreeLclVarCommon* dummyLclVarTree = nullptr;
- bool dummyIsEntire = false;
- GenTreePtr addrArg = tree->gtOp.gtOp1->gtEffectiveVal(/*commaOnly*/ true);
- if (!addrArg->DefinesLocalAddr(this, /*width doesn't matter*/ 0, &dummyLclVarTree, &dummyIsEntire))
- {
- if (!fgCurHeapDef)
- {
- fgCurHeapUse = true;
- }
- }
- else
+ if (!fgCurHeapDef)
{
- // Defines a local addr
- assert(dummyLclVarTree != nullptr);
- fgMarkUseDef(dummyLclVarTree->AsLclVarCommon(), asgdLclVar);
+ fgCurHeapUse = true;
}
}
- break;
-
- // These should have been morphed away to become GT_INDs:
- case GT_FIELD:
- case GT_INDEX:
- unreached();
- break;
-
- // We'll assume these are use-then-defs of the heap.
- case GT_LOCKADD:
- case GT_XADD:
- case GT_XCHG:
- case GT_CMPXCHG:
- if (!fgCurHeapDef)
+ else
{
- fgCurHeapUse = true;
+ // Defines a local addr
+ assert(dummyLclVarTree != nullptr);
+ fgMarkUseDef(dummyLclVarTree->AsLclVarCommon(), asgdLclVar);
}
- fgCurHeapDef = true;
- fgCurHeapHavoc = true;
- break;
+ }
+ break;
- case GT_MEMORYBARRIER:
- // Simliar to any Volatile indirection, we must handle this as a definition of the global heap
- fgCurHeapDef = true;
- break;
+ // These should have been morphed away to become GT_INDs:
+ case GT_FIELD:
+ case GT_INDEX:
+ unreached();
+ break;
- // For now, all calls read/write the heap, the latter in its entirety. Might tighten this case later.
- case GT_CALL:
+ // We'll assume these are use-then-defs of the heap.
+ case GT_LOCKADD:
+ case GT_XADD:
+ case GT_XCHG:
+ case GT_CMPXCHG:
+ if (!fgCurHeapDef)
{
- GenTreeCall* call = tree->AsCall();
- bool modHeap = true;
- if (call->gtCallType == CT_HELPER)
- {
- CorInfoHelpFunc helpFunc = eeGetHelperNum(call->gtCallMethHnd);
+ fgCurHeapUse = true;
+ }
+ fgCurHeapDef = true;
+ fgCurHeapHavoc = true;
+ break;
- if (!s_helperCallProperties.MutatesHeap(helpFunc) && !s_helperCallProperties.MayRunCctor(helpFunc))
- {
- modHeap = false;
- }
+ case GT_MEMORYBARRIER:
+ // Simliar to any Volatile indirection, we must handle this as a definition of the global heap
+ fgCurHeapDef = true;
+ break;
+
+ // For now, all calls read/write the heap, the latter in its entirety. Might tighten this case later.
+ case GT_CALL:
+ {
+ GenTreeCall* call = tree->AsCall();
+ bool modHeap = true;
+ if (call->gtCallType == CT_HELPER)
+ {
+ CorInfoHelpFunc helpFunc = eeGetHelperNum(call->gtCallMethHnd);
+
+ if (!s_helperCallProperties.MutatesHeap(helpFunc) && !s_helperCallProperties.MayRunCctor(helpFunc))
+ {
+ modHeap = false;
}
- if (modHeap)
+ }
+ if (modHeap)
+ {
+ if (!fgCurHeapDef)
{
- if (!fgCurHeapDef)
- {
- fgCurHeapUse = true;
- }
- fgCurHeapDef = true;
- fgCurHeapHavoc = true;
+ fgCurHeapUse = true;
}
+ fgCurHeapDef = true;
+ fgCurHeapHavoc = true;
}
+ }
- // If this is a p/invoke unmanaged call or if this is a tail-call
- // and we have an unmanaged p/invoke call in the method,
- // then we're going to run the p/invoke epilog.
- // So we mark the FrameRoot as used by this instruction.
- // This ensures that the block->bbVarUse will contain
- // the FrameRoot local var if is it a tracked variable.
+ // If this is a p/invoke unmanaged call or if this is a tail-call
+ // and we have an unmanaged p/invoke call in the method,
+ // then we're going to run the p/invoke epilog.
+ // So we mark the FrameRoot as used by this instruction.
+ // This ensures that the block->bbVarUse will contain
+ // the FrameRoot local var if is it a tracked variable.
- if ((tree->gtCall.IsUnmanaged() || (tree->gtCall.IsTailCall() && info.compCallUnmanaged)))
+ if ((tree->gtCall.IsUnmanaged() || (tree->gtCall.IsTailCall() && info.compCallUnmanaged)))
+ {
+ assert((!opts.ShouldUsePInvokeHelpers()) || (info.compLvFrameListRoot == BAD_VAR_NUM));
+ if (!opts.ShouldUsePInvokeHelpers())
{
- assert((!opts.ShouldUsePInvokeHelpers()) || (info.compLvFrameListRoot == BAD_VAR_NUM));
- if (!opts.ShouldUsePInvokeHelpers())
- {
- /* Get the TCB local and mark it as used */
+ /* Get the TCB local and mark it as used */
- noway_assert(info.compLvFrameListRoot < lvaCount);
+ noway_assert(info.compLvFrameListRoot < lvaCount);
- LclVarDsc* varDsc = &lvaTable[info.compLvFrameListRoot];
+ LclVarDsc* varDsc = &lvaTable[info.compLvFrameListRoot];
- if (varDsc->lvTracked)
+ if (varDsc->lvTracked)
+ {
+ if (!VarSetOps::IsMember(this, fgCurDefSet, varDsc->lvVarIndex))
{
- if (!VarSetOps::IsMember(this, fgCurDefSet, varDsc->lvVarIndex))
- {
- VarSetOps::AddElemD(this, fgCurUseSet, varDsc->lvVarIndex);
- }
+ VarSetOps::AddElemD(this, fgCurUseSet, varDsc->lvVarIndex);
}
}
}
+ }
- break;
+ break;
- default:
+ default:
- // Determine whether it defines a heap location.
- if (tree->OperIsAssignment() || tree->OperIsBlkOp())
+ // Determine whether it defines a heap location.
+ if (tree->OperIsAssignment() || tree->OperIsBlkOp())
+ {
+ GenTreeLclVarCommon* dummyLclVarTree = nullptr;
+ if (!tree->DefinesLocal(this, &dummyLclVarTree))
{
- GenTreeLclVarCommon* dummyLclVarTree = nullptr;
- if (!tree->DefinesLocal(this, &dummyLclVarTree))
- {
- // If it doesn't define a local, then it might update the heap.
- fgCurHeapDef = true;
- }
+ // If it doesn't define a local, then it might update the heap.
+ fgCurHeapDef = true;
}
- break;
- }
+ }
+ break;
}
}
-#endif // LEGACY_BACKEND
+
+void Compiler::fgPerStatementLocalVarLiveness(GenTree* startNode, GenTree* asgdLclVar)
+{
+ // The startNode must be the 1st node of the statement.
+ assert(startNode == compCurStmt->gtStmt.gtStmtList);
+
+ // The asgdLclVar node must be either nullptr or a GT_LCL_VAR or GT_STORE_LCL_VAR
+ assert((asgdLclVar == nullptr) || (asgdLclVar->gtOper == GT_LCL_VAR || asgdLclVar->gtOper == GT_STORE_LCL_VAR));
+
+ // We always walk every node in statement list
+ for (GenTreePtr node = startNode; node != nullptr; node = node->gtNext)
+ {
+ fgPerNodeLocalVarLiveness(node, asgdLclVar);
+ }
+}
+
+#endif // !LEGACY_BACKEND
/*****************************************************************************/
void Compiler::fgPerBlockLocalVarLiveness()
@@ -546,65 +561,79 @@ void Compiler::fgPerBlockLocalVarLiveness()
compCurBB = block;
- for (stmt = block->FirstNonPhiDef(); stmt; stmt = stmt->gtNext)
+ if (!block->IsLIR())
{
- noway_assert(stmt->gtOper == GT_STMT);
-
- if (!stmt->gtStmt.gtStmtIsTopLevel())
+ for (stmt = block->FirstNonPhiDef(); stmt; stmt = stmt->gtNext)
{
- continue;
- }
+ noway_assert(stmt->gtOper == GT_STMT);
- compCurStmt = stmt;
+ compCurStmt = stmt;
- asgdLclVar = nullptr;
- tree = stmt->gtStmt.gtStmtExpr;
- noway_assert(tree);
+ asgdLclVar = nullptr;
+ tree = stmt->gtStmt.gtStmtExpr;
+ noway_assert(tree);
- // The following code checks if we have an assignment expression
- // which may become a GTF_VAR_USEDEF - x=f(x).
- // consider if LHS is local var - ignore if RHS contains SIDE_EFFECTS
+ // The following code checks if we have an assignment expression
+ // which may become a GTF_VAR_USEDEF - x=f(x).
+ // consider if LHS is local var - ignore if RHS contains SIDE_EFFECTS
- if ((tree->gtOper == GT_ASG && tree->gtOp.gtOp1->gtOper == GT_LCL_VAR) || tree->gtOper == GT_STORE_LCL_VAR)
- {
- noway_assert(tree->gtOp.gtOp1);
- GenTreePtr rhsNode;
- if (tree->gtOper == GT_ASG)
- {
- noway_assert(tree->gtOp.gtOp2);
- asgdLclVar = tree->gtOp.gtOp1;
- rhsNode = tree->gtOp.gtOp2;
- }
- else
+ if ((tree->gtOper == GT_ASG && tree->gtOp.gtOp1->gtOper == GT_LCL_VAR) ||
+ tree->gtOper == GT_STORE_LCL_VAR)
{
- asgdLclVar = tree;
- rhsNode = tree->gtOp.gtOp1;
- }
+ noway_assert(tree->gtOp.gtOp1);
+ GenTreePtr rhsNode;
+ if (tree->gtOper == GT_ASG)
+ {
+ noway_assert(tree->gtOp.gtOp2);
+ asgdLclVar = tree->gtOp.gtOp1;
+ rhsNode = tree->gtOp.gtOp2;
+ }
+ else
+ {
+ asgdLclVar = tree;
+ rhsNode = tree->gtOp.gtOp1;
+ }
- // If this is an assignment to local var with no SIDE EFFECTS,
- // set asgdLclVar so that genMarkUseDef will flag potential
- // x=f(x) expressions as GTF_VAR_USEDEF.
- // Reset the flag before recomputing it - it may have been set before,
- // but subsequent optimizations could have removed the rhs reference.
- asgdLclVar->gtFlags &= ~GTF_VAR_USEDEF;
- if ((rhsNode->gtFlags & GTF_SIDE_EFFECT) == 0)
- {
- noway_assert(asgdLclVar->gtFlags & GTF_VAR_DEF);
- }
- else
- {
- asgdLclVar = nullptr;
+ // If this is an assignment to local var with no SIDE EFFECTS,
+ // set asgdLclVar so that genMarkUseDef will flag potential
+ // x=f(x) expressions as GTF_VAR_USEDEF.
+ // Reset the flag before recomputing it - it may have been set before,
+ // but subsequent optimizations could have removed the rhs reference.
+ asgdLclVar->gtFlags &= ~GTF_VAR_USEDEF;
+ if ((rhsNode->gtFlags & GTF_SIDE_EFFECT) == 0)
+ {
+ noway_assert(asgdLclVar->gtFlags & GTF_VAR_DEF);
+ }
+ else
+ {
+ asgdLclVar = nullptr;
+ }
}
- }
#ifdef LEGACY_BACKEND
- tree = fgLegacyPerStatementLocalVarLiveness(stmt->gtStmt.gtStmtList, NULL, asgdLclVar);
+ tree = fgLegacyPerStatementLocalVarLiveness(stmt->gtStmt.gtStmtList, NULL, asgdLclVar);
- // We must have walked to the end of this statement.
- noway_assert(!tree);
-#else
- fgPerStatementLocalVarLiveness(stmt->gtStmt.gtStmtList, asgdLclVar);
-#endif // LEGACY_BACKEND
+ // We must have walked to the end of this statement.
+ noway_assert(!tree);
+#else // !LEGACY_BACKEND
+ fgPerStatementLocalVarLiveness(stmt->gtStmt.gtStmtList, asgdLclVar);
+#endif // !LEGACY_BACKEND
+ }
+ }
+ else
+ {
+#ifdef LEGACY_BACKEND
+ unreached();
+#else // !LEGACY_BACKEND
+ // NOTE: the `asgdLclVar` analysis done above is not correct for LIR: it depends
+ // on all of the nodes that precede `asgdLclVar` in execution order to factor into the
+ // dataflow for the value being assigned to the local var, which is not necessarily the
+ // case without tree order. As a result, we simply pass `nullptr` for `asgdLclVar`.
+ for (GenTree* node : LIR::AsRange(block).NonPhiNodes())
+ {
+ fgPerNodeLocalVarLiveness(node, nullptr);
+ }
+#endif // !LEGACY_BACKEND
}
/* Get the TCB local and mark it as used */
@@ -1069,9 +1098,15 @@ void Compiler::fgExtendDbgLifetimes()
continue;
}
+ // TODO-LIR: the code below does not work for blocks that contain LIR. As a result,
+ // we must run liveness at least once before any LIR is created in order
+ // to ensure that this code doesn't attempt to insert HIR into LIR blocks.
+
// If we haven't already done this ...
if (!fgLocalVarLivenessDone)
{
+ assert(!block->IsLIR());
+
// Create a "zero" node
GenTreePtr zero = gtNewZeroConNode(genActualType(type));
@@ -1726,7 +1761,13 @@ bool Compiler::fgComputeLifeLocal(VARSET_TP& life, VARSET_TP& keepAliveVars, Gen
GTF_VAR_USEASG and we are in an interior statement
that will be used (e.g. while (i++) or a GT_COMMA) */
- return true;
+ // Do not consider this store dead if the target local variable represents
+ // a promoted struct field of an address exposed local or if the address
+ // of the variable has been exposed. Improved alias analysis could allow
+ // stores to these sorts of variables to be removed at the cost of compile
+ // time.
+ return !varDsc->lvAddrExposed &&
+ !(varDsc->lvIsStructField && lvaTable[varDsc->lvParentLcl].lvAddrExposed);
}
}
@@ -1894,6 +1935,46 @@ VARSET_VALRET_TP Compiler::fgComputeLife(VARSET_VALARG_TP lifeArg,
return life;
}
+VARSET_VALRET_TP Compiler::fgComputeLifeLIR(VARSET_VALARG_TP lifeArg, BasicBlock* block, VARSET_VALARG_TP volatileVars)
+{
+ 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, block->bbScope); // Don't kill vars in scope
+#endif
+
+ noway_assert(VarSetOps::Equal(this, VarSetOps::Intersection(this, keepAliveVars, life), keepAliveVars));
+
+ LIR::Range& blockRange = LIR::AsRange(block);
+ GenTree* firstNonPhiNode = blockRange.FirstNonPhiNode();
+ if (firstNonPhiNode == nullptr)
+ {
+ return life;
+ }
+
+ for (GenTree *node = blockRange.LastNode(), *next = nullptr, *end = firstNonPhiNode->gtPrev; node != end;
+ node = next)
+ {
+ next = node->gtPrev;
+
+ if (node->OperGet() == GT_CALL)
+ {
+ fgComputeLifeCall(life, node->AsCall());
+ }
+ else if (node->OperIsNonPhiLocal() || node->OperIsLocalAddr())
+ {
+ bool isDeadStore = fgComputeLifeLocal(life, keepAliveVars, node, node);
+ if (isDeadStore)
+ {
+ fgTryRemoveDeadLIRStore(blockRange, node, &next);
+ }
+ }
+ }
+
+ return life;
+}
+
#else // LEGACY_BACKEND
#ifdef _PREFAST_
@@ -2245,6 +2326,84 @@ VARSET_VALRET_TP Compiler::fgComputeLife(VARSET_VALARG_TP lifeArg,
#endif // !LEGACY_BACKEND
+bool Compiler::fgTryRemoveDeadLIRStore(LIR::Range& blockRange, GenTree* node, GenTree** next)
+{
+ assert(node != nullptr);
+ assert(next != nullptr);
+
+ assert(node->OperIsLocalStore() || node->OperIsLocalAddr());
+
+ GenTree* store = nullptr;
+ GenTree* value = nullptr;
+ if (node->OperIsLocalStore())
+ {
+ store = node;
+ value = store->gtGetOp1();
+ }
+ else if (node->OperIsLocalAddr())
+ {
+ LIR::Use addrUse;
+ if (!blockRange.TryGetUse(node, &addrUse) || (addrUse.User()->OperGet() != GT_STOREIND))
+ {
+ *next = node->gtPrev;
+ return false;
+ }
+
+ store = addrUse.User();
+ value = store->gtGetOp2();
+ }
+
+ bool isClosed = false;
+ unsigned sideEffects = 0;
+ LIR::ReadOnlyRange operandsRange = blockRange.GetRangeOfOperandTrees(store, &isClosed, &sideEffects);
+ if (!isClosed || ((sideEffects & GTF_SIDE_EFFECT) != 0) ||
+ (((sideEffects & GTF_ORDER_SIDEEFF) != 0) && (value->OperGet() == GT_CATCH_ARG)))
+ {
+ // If the range of the operands contains unrelated code or if it contains any side effects,
+ // do not remove it. Instead, just remove the store.
+
+ *next = node->gtPrev;
+ }
+ else
+ {
+ // Okay, the operands to the store form a contiguous range that has no side effects. Remove the
+ // range containing the operands and decrement the local var ref counts appropriately.
+
+ // Compute the next node to process. Note that we must be careful not to set the next node to
+ // process to a node that we are about to remove.
+ if (node->OperIsLocalStore())
+ {
+ assert(node == store);
+ *next = (operandsRange.LastNode()->gtNext == store) ? operandsRange.FirstNode()->gtPrev : node->gtPrev;
+ }
+ else
+ {
+ assert(operandsRange.Contains(node));
+ *next = operandsRange.FirstNode()->gtPrev;
+ }
+
+ blockRange.Delete(this, compCurBB, std::move(operandsRange));
+ }
+
+ // If the store is marked as a late argument, it is referenced by a call. Instead of removing it,
+ // bash it to a NOP.
+ if ((store->gtFlags & GTF_LATE_ARG) != 0)
+ {
+ if (store->IsLocal())
+ {
+ lvaDecRefCnts(compCurBB, store);
+ }
+
+ store->gtBashToNOP();
+ }
+ else
+ {
+ blockRange.Delete(this, compCurBB, store);
+ }
+
+ return true;
+}
+
// 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)
@@ -2258,6 +2417,12 @@ VARSET_VALRET_TP Compiler::fgComputeLife(VARSET_VALARG_TP lifeArg,
bool Compiler::fgRemoveDeadStore(
GenTree** pTree, LclVarDsc* varDsc, VARSET_TP life, bool* doAgain, bool* pStmtInfoDirty DEBUGARG(bool* treeModf))
{
+ assert(!compRationalIRForm);
+
+ // Vars should have already been checked for address exposure by this point.
+ assert(!varDsc->lvIsStructField || !lvaTable[varDsc->lvParentLcl].lvAddrExposed);
+ assert(!varDsc->lvAddrExposed);
+
GenTree* asgNode = nullptr;
GenTree* rhsNode = nullptr;
GenTree* addrNode = nullptr;
@@ -2442,11 +2607,6 @@ bool Compiler::fgRemoveDeadStore(
if (rhsNode->gtFlags & GTF_SIDE_EFFECT)
{
- if (compRationalIRForm)
- {
- return false;
- }
-
EXTRACT_SIDE_EFFECTS:
/* Extract the side effects */
@@ -2517,14 +2677,6 @@ bool Compiler::fgRemoveDeadStore(
}
}
- // If there is an embedded statement this could be tricky because we need to
- // walk them next, and we have already skipped over them because they were
- // not top level (but will be if we delete the top level statement)
- if (compCurStmt->gtStmt.gtNextStmt && !compCurStmt->gtStmt.gtNextStmt->gtStmtIsTopLevel())
- {
- return false;
- }
-
/* No side effects - remove the whole statement from the block->bbTreeList */
fgRemoveStmt(compCurBB, compCurStmt);
@@ -2540,18 +2692,8 @@ bool Compiler::fgRemoveDeadStore(
{
/* This is an INTERIOR STATEMENT with a dead assignment - remove it */
- // don't want to deal with this
- if (compRationalIRForm)
- {
- return false;
- }
-
noway_assert(!VarSetOps::IsMember(this, life, varDsc->lvVarIndex));
- JITDUMP("interior assign\n");
- DISPTREE(asgNode);
- JITDUMP("\n");
-
if (rhsNode->gtFlags & GTF_SIDE_EFFECT)
{
/* :-( we have side effects */
@@ -2633,17 +2775,6 @@ bool Compiler::fgRemoveDeadStore(
/* Change the assignment to a GT_NOP node */
- if (compRationalIRForm)
- {
- JITDUMP("deleting tree:\n");
- DISPTREE(rhsNode);
- fgDeleteTreeFromList(compCurStmt->AsStmt(), rhsNode);
- if (tree->gtOper == GT_STOREIND)
- {
- fgDeleteTreeFromList(compCurStmt->AsStmt(), asgNode->gtOp.gtOp1);
- }
- }
-
asgNode->gtBashToNOP();
#ifdef DEBUG
@@ -2653,17 +2784,14 @@ bool Compiler::fgRemoveDeadStore(
/* Re-link the nodes for this statement - Do not update ordering! */
- if (!compRationalIRForm)
- {
- // Do not update costs by calling gtSetStmtInfo. fgSetStmtSeq modifies
- // the tree threading based on the new costs. Removing nodes could
- // cause a subtree to get evaluated first (earlier second) during the
- // liveness walk. Instead just set a flag that costs are dirty and
- // caller has to call gtSetStmtInfo.
- *pStmtInfoDirty = true;
+ // Do not update costs by calling gtSetStmtInfo. fgSetStmtSeq modifies
+ // the tree threading based on the new costs. Removing nodes could
+ // cause a subtree to get evaluated first (earlier second) during the
+ // liveness walk. Instead just set a flag that costs are dirty and
+ // caller has to call gtSetStmtInfo.
+ *pStmtInfoDirty = true;
- fgSetStmtSeq(compCurStmt);
- }
+ fgSetStmtSeq(compCurStmt);
/* Continue analysis from this node */
@@ -2857,56 +2985,62 @@ void Compiler::fgInterBlockLocalVarLiveness()
fgMarkIntf(life);
- /* Get the first statement in the block */
+ if (!block->IsLIR())
+ {
+ /* Get the first statement in the block */
- GenTreePtr firstStmt = block->FirstNonPhiDef();
+ GenTreePtr firstStmt = block->FirstNonPhiDef();
- if (!firstStmt)
- {
- continue;
- }
+ if (!firstStmt)
+ {
+ continue;
+ }
- /* Walk all the statements of the block backwards - Get the LAST stmt */
+ /* Walk all the statements of the block backwards - Get the LAST stmt */
- GenTreePtr nextStmt = block->bbTreeList->gtPrev;
+ GenTreePtr nextStmt = block->bbTreeList->gtPrev;
- do
- {
+ do
+ {
#ifdef DEBUG
- bool treeModf = false;
+ bool treeModf = false;
#endif // DEBUG
- noway_assert(nextStmt);
- noway_assert(nextStmt->gtOper == GT_STMT);
+ noway_assert(nextStmt);
+ noway_assert(nextStmt->gtOper == GT_STMT);
- compCurStmt = nextStmt;
- nextStmt = nextStmt->gtPrev;
+ compCurStmt = nextStmt;
+ nextStmt = nextStmt->gtPrev;
- if (!compCurStmt->gtStmt.gtStmtIsTopLevel())
- {
- continue;
- }
-
- /* Compute the liveness for each tree node in the statement */
- bool stmtInfoDirty = false;
+ /* Compute the liveness for each tree node in the statement */
+ bool stmtInfoDirty = false;
- VarSetOps::AssignNoCopy(this, life, fgComputeLife(life, compCurStmt->gtStmt.gtStmtExpr, nullptr,
- volatileVars, &stmtInfoDirty DEBUGARG(&treeModf)));
+ VarSetOps::AssignNoCopy(this, life, fgComputeLife(life, compCurStmt->gtStmt.gtStmtExpr, nullptr,
+ volatileVars, &stmtInfoDirty DEBUGARG(&treeModf)));
- if (stmtInfoDirty)
- {
- gtSetStmtInfo(compCurStmt);
- fgSetStmtSeq(compCurStmt);
- }
+ if (stmtInfoDirty)
+ {
+ gtSetStmtInfo(compCurStmt);
+ fgSetStmtSeq(compCurStmt);
+ }
#ifdef DEBUG
- if (verbose && treeModf)
- {
- printf("\nfgComputeLife modified tree:\n");
- gtDispTree(compCurStmt->gtStmt.gtStmtExpr);
- printf("\n");
- }
+ if (verbose && treeModf)
+ {
+ printf("\nfgComputeLife modified tree:\n");
+ gtDispTree(compCurStmt->gtStmt.gtStmtExpr);
+ printf("\n");
+ }
#endif // DEBUG
- } while (compCurStmt != firstStmt);
+ } while (compCurStmt != firstStmt);
+ }
+ else
+ {
+#ifdef LEGACY_BACKEND
+ unreached();
+#else // !LEGACY_BACKEND
+ VarSetOps::AssignNoCopy(this, life, fgComputeLifeLIR(life, block, volatileVars));
+#endif // !LEGACY_BACKEND
+ }
/* Done with the current block - if we removed any statements, some
* variables may have become dead at the beginning of the block