// set this early so we can use it without relying on random memory values
verbose = compIsForInlining() ? impInlineInfo->InlinerCompiler->verbose : false;
+ compNumStatementLinksTraversed = 0;
compPoisoningAnyImplicitByrefs = false;
#endif
fgDomsComputed = false;
optLoopTableValid = false;
+#ifdef DEBUG
+ DoPhase(this, PHASE_STRESS_SPLIT_TREE, &Compiler::StressSplitTree);
+#endif
+
// Insert GC Polls
DoPhase(this, PHASE_INSERT_GC_POLLS, &Compiler::fgInsertGCPolls);
#endif
//------------------------------------------------------------------------
+// StressSplitTree: A phase that stresses the gtSplitTree function.
+//
+// Returns:
+// Suitable phase status
+//
+// Notes:
+// Stress is applied on a function-by-function basis
+//
+PhaseStatus Compiler::StressSplitTree()
+{
+ if (compStressCompile(STRESS_SPLIT_TREES_RANDOMLY, 10))
+ {
+ SplitTreesRandomly();
+ return PhaseStatus::MODIFIED_EVERYTHING;
+ }
+
+ if (compStressCompile(STRESS_SPLIT_TREES_REMOVE_COMMAS, 10))
+ {
+ SplitTreesRemoveCommas();
+ return PhaseStatus::MODIFIED_EVERYTHING;
+ }
+
+ return PhaseStatus::MODIFIED_NOTHING;
+}
+
+//------------------------------------------------------------------------
+// SplitTreesRandomly: Split all statements at a random location.
+//
+void Compiler::SplitTreesRandomly()
+{
+#ifdef DEBUG
+ CLRRandom rng;
+ rng.Init(info.compMethodHash() ^ 0x077cc4d4);
+
+ for (BasicBlock* block : Blocks())
+ {
+ for (Statement* stmt : block->NonPhiStatements())
+ {
+ int numTrees = 0;
+ for (GenTree* tree : stmt->TreeList())
+ {
+ if (tree->OperIs(GT_JTRUE)) // Due to relop invariant
+ {
+ continue;
+ }
+
+ numTrees++;
+ }
+
+ int splitTree = rng.Next(numTrees);
+ for (GenTree* tree : stmt->TreeList())
+ {
+ if (tree->OperIs(GT_JTRUE))
+ continue;
+
+ if (splitTree == 0)
+ {
+ JITDUMP("Splitting " FMT_STMT " at [%06u]\n", stmt->GetID(), dspTreeID(tree));
+ Statement* newStmt;
+ GenTree** use;
+ if (gtSplitTree(block, stmt, tree, &newStmt, &use))
+ {
+ while ((newStmt != nullptr) && (newStmt != stmt))
+ {
+ fgMorphStmtBlockOps(block, newStmt);
+ newStmt = newStmt->GetNextStmt();
+ }
+
+ fgMorphStmtBlockOps(block, stmt);
+ gtUpdateStmtSideEffects(stmt);
+ }
+
+ break;
+ }
+
+ splitTree--;
+ }
+ }
+ }
+#endif
+}
+
+//------------------------------------------------------------------------
+// SplitTreesRemoveCommas: Split trees to remove all commas.
+//
+void Compiler::SplitTreesRemoveCommas()
+{
+ for (BasicBlock* block : Blocks())
+ {
+ Statement* stmt = block->FirstNonPhiDef();
+ while (stmt != nullptr)
+ {
+ Statement* nextStmt = stmt->GetNextStmt();
+ for (GenTree* tree : stmt->TreeList())
+ {
+ if (!tree->OperIs(GT_COMMA))
+ {
+ continue;
+ }
+
+ // Supporting this weird construct would require additional
+ // handling, we need to sort of move the comma into to the
+ // next node in execution order. We don't see this so just
+ // skip it.
+ assert(!tree->IsReverseOp());
+
+ JITDUMP("Removing COMMA [%06u]\n", dspTreeID(tree));
+ Statement* newStmt;
+ GenTree** use;
+ gtSplitTree(block, stmt, tree, &newStmt, &use);
+ GenTree* op1SideEffects = nullptr;
+ gtExtractSideEffList(tree->gtGetOp1(), &op1SideEffects);
+
+ if (op1SideEffects != nullptr)
+ {
+ Statement* op1Stmt = fgNewStmtFromTree(op1SideEffects);
+ fgInsertStmtBefore(block, stmt, op1Stmt);
+ if (newStmt == nullptr)
+ {
+ newStmt = op1Stmt;
+ }
+ }
+
+ *use = tree->gtGetOp2();
+
+ for (Statement* cur = newStmt; (cur != nullptr) && (cur != stmt); cur = cur->GetNextStmt())
+ {
+ fgMorphStmtBlockOps(block, cur);
+ }
+
+ fgMorphStmtBlockOps(block, stmt);
+ gtUpdateStmtSideEffects(stmt);
+
+ // Morphing block ops can introduce commas (and the original
+ // statement can also have more commas left). Proceed from the
+ // earliest newly introduced statement.
+ nextStmt = newStmt != nullptr ? newStmt : stmt;
+ break;
+ }
+
+ stmt = nextStmt;
+ }
+ }
+
+ for (BasicBlock* block : Blocks())
+ {
+ for (Statement* stmt : block->NonPhiStatements())
+ {
+ for (GenTree* tree : stmt->TreeList())
+ {
+ assert(!tree->OperIs(GT_COMMA));
+ }
+ }
+ }
+}
+
+//------------------------------------------------------------------------
// generatePatchpointInfo: allocate and fill in patchpoint info data,
// and report it to the VM
//
GenTreeFlags GenTreeFlags = GTF_SIDE_EFFECT,
bool ignoreRoot = false);
+ bool gtSplitTree(
+ BasicBlock* block, Statement* stmt, GenTree* splitPoint, Statement** firstNewStmt, GenTree*** splitPointUse);
+
// Static fields of struct types (and sometimes the types that those are reduced to) are represented by having the
// static field contain an object pointer to the boxed struct. This simplifies the GC implementation...but
// complicates the JIT somewhat. This predicate returns "true" iff a node with type "fieldNodeType", representing
void fgMergeBlockReturn(BasicBlock* block);
bool fgMorphBlockStmt(BasicBlock* block, Statement* stmt DEBUGARG(const char* msg));
+ void fgMorphStmtBlockOps(BasicBlock* block, Statement* stmt);
//------------------------------------------------------------------------------------------------------------
// MorphMDArrayTempCache: a simple cache of compiler temporaries in the local variable table, used to minimize
// Initialize the per-block variable sets (used for liveness analysis).
void fgInitBlockVarSets();
+ PhaseStatus StressSplitTree();
+ void SplitTreesRandomly();
+ void SplitTreesRemoveCommas();
PhaseStatus fgInsertGCPolls();
BasicBlock* fgCreateGCPoll(GCPollType pollType, BasicBlock* block);
CORINFO_CONTEXT_HANDLE* ExactContextHnd,
methodPointerInfo* ldftnToken);
GenTree* fgMorphLeaf(GenTree* tree);
+public:
GenTree* fgMorphInitBlock(GenTree* tree);
GenTree* fgMorphCopyBlock(GenTree* tree);
GenTree* fgMorphStoreDynBlock(GenTreeStoreDynBlk* tree);
+private:
GenTree* fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optAssertionPropDone = nullptr);
void fgTryReplaceStructLocalWithField(GenTree* tree);
GenTree* fgOptimizeCast(GenTreeCast* cast);
STRESS_MODE(PROMOTE_FEWER_STRUCTS)/* Don't promote some structs that can be promoted */ \
STRESS_MODE(VN_BUDGET)/* Randomize the VN budget */ \
STRESS_MODE(SSA_INFO) /* Select lower thresholds for "complex" SSA num encoding */ \
+ STRESS_MODE(SPLIT_TREES_RANDOMLY) /* Split all statements at a random tree */ \
+ STRESS_MODE(SPLIT_TREES_REMOVE_COMMAS) /* Remove all GT_COMMA nodes */ \
\
/* After COUNT_VARN, stress level 2 does all of these all the time */ \
\
}
//------------------------------------------------------------------------
+// gtSplitTree: Split a statement into multiple statements such that a
+// specified tree is the first executed non-invariant node in the statement.
+//
+// Arguments:
+// block - The block containing the statement.
+// stmt - The statement containing the tree.
+// splitPoint - A tree inside the statement.
+// firstNewStmt - [out] The first new statement that was introduced.
+// [firstNewStmt..stmt) are the statements added by this function.
+// splitNodeUse - The use of the tree to split at.
+//
+// Notes:
+// This method turns all non-invariant nodes that would be executed before
+// the split point into new separate statements. If those nodes were values
+// this involves introducing new locals for those values, such that they can
+// be used in the original statement.
+//
+// This function does not update the flags on the original statement as it is
+// expected that the caller is going to modify the use further. Thus, the
+// caller is responsible for calling gtUpdateStmtSideEffects on the statement,
+// though this is only necessary if the function returned true.
+//
+// The function may introduce new block ops that need to be morphed when used
+// after morph. fgMorphStmtBlockOps can be used on the new statements for
+// this purpose. Note that this will invalidate the use, so it should be done
+// after any further modifications have been made.
+//
+// Returns:
+// True if any changes were made; false if nothing needed to be done to split the tree.
+//
+bool Compiler::gtSplitTree(
+ BasicBlock* block, Statement* stmt, GenTree* splitPoint, Statement** firstNewStmt, GenTree*** splitNodeUse)
+{
+ class Splitter final : public GenTreeVisitor<Splitter>
+ {
+ BasicBlock* m_bb;
+ Statement* m_splitStmt;
+ GenTree* m_splitNode;
+
+ struct UseInfo
+ {
+ GenTree** Use;
+ GenTree* User;
+ };
+ ArrayStack<UseInfo> m_useStack;
+
+ public:
+ enum
+ {
+ DoPreOrder = true,
+ DoPostOrder = true,
+ UseExecutionOrder = true
+ };
+
+ Splitter(Compiler* compiler, BasicBlock* bb, Statement* stmt, GenTree* splitNode)
+ : GenTreeVisitor(compiler)
+ , m_bb(bb)
+ , m_splitStmt(stmt)
+ , m_splitNode(splitNode)
+ , m_useStack(compiler->getAllocator(CMK_ArrayStack))
+ {
+ }
+
+ Statement* FirstStatement = nullptr;
+ GenTree** SplitNodeUse = nullptr;
+ bool MadeChanges = false;
+
+ fgWalkResult PreOrderVisit(GenTree** use, GenTree* user)
+ {
+ assert(!(*use)->OperIs(GT_QMARK));
+ m_useStack.Push(UseInfo{use, user});
+ return WALK_CONTINUE;
+ }
+
+ fgWalkResult PostOrderVisit(GenTree** use, GenTree* user)
+ {
+ if (*use == m_splitNode)
+ {
+ bool userIsLocation = false;
+ bool userIsReturned = false;
+ // Split all siblings and ancestor siblings.
+ int i;
+ for (i = 0; i < m_useStack.Height() - 1; i++)
+ {
+ const UseInfo& useInf = m_useStack.BottomRef(i);
+ if (useInf.Use == use)
+ {
+ break;
+ }
+
+ // If this has the same user as the next node then it is a
+ // sibling of an ancestor -- and thus not on the "path"
+ // that contains the split node.
+ if (m_useStack.BottomRef(i + 1).User == useInf.User)
+ {
+ SplitOutUse(useInf, userIsLocation, userIsReturned);
+ }
+ else
+ {
+ // This is an ancestor of the node we're splitting on.
+ userIsLocation = IsLocation(useInf, userIsLocation);
+ userIsReturned = IsReturned(useInf, userIsReturned);
+ }
+ }
+
+ assert(m_useStack.Bottom(i).Use == use);
+ userIsLocation = IsLocation(m_useStack.BottomRef(i), userIsLocation);
+ userIsReturned = IsReturned(m_useStack.BottomRef(i), userIsReturned);
+
+ // The remaining nodes should be operands of the split node.
+ for (i++; i < m_useStack.Height(); i++)
+ {
+ const UseInfo& useInf = m_useStack.BottomRef(i);
+ assert(useInf.User == *use);
+ SplitOutUse(useInf, userIsLocation, userIsReturned);
+ }
+
+ SplitNodeUse = use;
+
+ return WALK_ABORT;
+ }
+
+ while (m_useStack.Top(0).Use != use)
+ {
+ m_useStack.Pop();
+ }
+
+ return WALK_CONTINUE;
+ }
+
+ private:
+ bool IsLocation(const UseInfo& useInf, bool userIsLocation)
+ {
+ if (useInf.User != nullptr)
+ {
+ if (useInf.User->OperIs(GT_ADDR, GT_ASG) && (useInf.Use == &useInf.User->AsUnOp()->gtOp1))
+ {
+ return true;
+ }
+
+ if (useInf.User->OperIs(GT_STORE_DYN_BLK) && !(*useInf.Use)->OperIs(GT_CNS_INT, GT_INIT_VAL) &&
+ (useInf.Use == &useInf.User->AsStoreDynBlk()->Data()))
+ {
+ return true;
+ }
+
+ if (userIsLocation && useInf.User->OperIs(GT_COMMA) && (useInf.Use == &useInf.User->AsOp()->gtOp2))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ bool IsReturned(const UseInfo& useInf, bool userIsReturned)
+ {
+ if (useInf.User != nullptr)
+ {
+ if (useInf.User->OperIs(GT_RETURN))
+ {
+ return true;
+ }
+
+ if (userIsReturned && useInf.User->OperIs(GT_COMMA) && (useInf.Use == &useInf.User->AsOp()->gtOp2))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ void SplitOutUse(const UseInfo& useInf, bool userIsLocation, bool userIsReturned)
+ {
+ GenTree** use = useInf.Use;
+ GenTree* user = useInf.User;
+
+ if ((*use)->IsInvariant())
+ {
+ return;
+ }
+
+ if ((*use)->OperIs(GT_LCL_VAR) && !m_compiler->lvaGetDesc((*use)->AsLclVarCommon())->IsAddressExposed())
+ {
+ // The splitting we do here should always guarantee that we
+ // only introduce locals for the tree edges that overlap the
+ // split point, so it should be ok to avoid creating statements
+ // for locals that aren't address exposed. Note that this
+ // relies on it being illegal IR to have a tree edge for a
+ // register candidate that overlaps with an interfering node.
+ //
+ // For example, this optimization would be problematic if IR
+ // like the following could occur:
+ //
+ // CALL
+ // LCL_VAR V00
+ // CALL
+ // ASG(V00, ...) (setup)
+ // LCL_VAR V00
+ //
+ return;
+ }
+
+ if (IsLocation(useInf, userIsLocation))
+ {
+ if ((*use)->OperIs(GT_COMMA))
+ {
+ // We have:
+ // User
+ // COMMA
+ // op1
+ // op2
+ // rhs
+ // where the comma is a location, and we want to split it out.
+ //
+ // The first use will be the COMMA --- op1 edge, which we
+ // expect to be handled by simple side effect extraction in
+ // the recursive call.
+ UseInfo use1{&(*use)->AsOp()->gtOp1, *use};
+
+ // For the second use we will update the user to use op2
+ // directly instead of the comma so that we get the proper
+ // location treatment. The edge will then be the User ---
+ // op2 edge.
+ *use = (*use)->gtGetOp2();
+ UseInfo use2{use, user};
+
+ // Locations are never returned.
+ assert(!IsReturned(useInf, userIsReturned));
+ if ((*use)->IsReverseOp())
+ {
+ SplitOutUse(use2, true, false);
+ SplitOutUse(use1, false, false);
+ }
+ else
+ {
+ SplitOutUse(use1, false, false);
+ SplitOutUse(use2, true, false);
+ }
+ return;
+ }
+
+ // Only a handful of nodes can be location, and they are all unary or nullary.
+ assert((*use)->OperIs(GT_IND, GT_OBJ, GT_BLK, GT_LCL_VAR, GT_LCL_FLD));
+ if ((*use)->OperIsUnary())
+ {
+ SplitOutUse(UseInfo{&(*use)->AsUnOp()->gtOp1, user}, false, false);
+ }
+
+ return;
+ }
+
+#ifndef TARGET_64BIT
+ // GT_MUL with GTF_MUL_64RSLT is required to stay with casts on the
+ // operands. Note that one operand may also be a constant, but we
+ // would have exited early above for that case.
+ if ((user != nullptr) && user->OperIs(GT_MUL) && user->Is64RsltMul())
+ {
+ assert((*use)->OperIs(GT_CAST));
+ SplitOutUse(UseInfo{&(*use)->AsCast()->gtOp1, *use}, false, false);
+ return;
+ }
+#endif
+
+ if ((*use)->OperIs(GT_FIELD_LIST, GT_INIT_VAL))
+ {
+ for (GenTree** operandUse : (*use)->UseEdges())
+ {
+ SplitOutUse(UseInfo{operandUse, *use}, false, false);
+ }
+ return;
+ }
+
+ Statement* stmt = nullptr;
+ if (!(*use)->IsValue() || (*use)->OperIs(GT_ASG) || (user == nullptr) ||
+ (user->OperIs(GT_COMMA) && (user->gtGetOp1() == *use)))
+ {
+ GenTree* sideEffects = nullptr;
+ m_compiler->gtExtractSideEffList(*use, &sideEffects);
+ if (sideEffects != nullptr)
+ {
+ stmt = m_compiler->fgNewStmtFromTree(sideEffects, m_splitStmt->GetDebugInfo());
+ }
+ *use = m_compiler->gtNewNothingNode();
+ MadeChanges = true;
+ }
+ else
+ {
+ unsigned lclNum = m_compiler->lvaGrabTemp(true DEBUGARG("Spilling to split statement for tree"));
+
+ if (varTypeIsStruct(*use) &&
+ ((*use)->IsMultiRegNode() ||
+ (IsReturned(useInf, userIsReturned) && m_compiler->compMethodReturnsMultiRegRetType())))
+ {
+ m_compiler->lvaGetDesc(lclNum)->lvIsMultiRegRet = true;
+ }
+
+ GenTree* asg = m_compiler->gtNewTempAssign(lclNum, *use);
+ stmt = m_compiler->fgNewStmtFromTree(asg, m_splitStmt->GetDebugInfo());
+ *use = m_compiler->gtNewLclvNode(lclNum, genActualType(*use));
+ MadeChanges = true;
+ }
+
+ if (stmt != nullptr)
+ {
+ if (FirstStatement == nullptr)
+ {
+ FirstStatement = stmt;
+ }
+
+ m_compiler->fgInsertStmtBefore(m_bb, m_splitStmt, stmt);
+ }
+ }
+ };
+
+ Splitter splitter(this, block, stmt, splitPoint);
+ splitter.WalkTree(stmt->GetRootNodePointer(), nullptr);
+ *firstNewStmt = splitter.FirstStatement;
+ *splitNodeUse = splitter.SplitNodeUse;
+
+ return splitter.MadeChanges;
+}
+
+//------------------------------------------------------------------------
// gtExtractSideEffList: Extracts side effects from the given expression.
//
// Arguments: