{
printf("This hoisted copy placed in PreHeader (" FMT_BB "):\n", preHead->bbNum);
gtDispTree(hoist);
+ printf("\n");
}
#endif
// If we don't have any loops in the method then take an early out now.
if (optLoopCount == 0)
{
+ JITDUMP("\nNo loops; no hoisting\n");
return;
}
unsigned jitNoHoist = JitConfig.JitNoHoist();
if (jitNoHoist > 0)
{
+ JITDUMP("\nJitNoHoist set; no hoisting\n");
return;
}
#endif
{
if (optLoopTable[lnum].lpFlags & LPFLG_REMOVED)
{
+ JITDUMP("\nLoop " FMT_LP " was removed\n", lnum);
continue;
}
void Compiler::optHoistLoopNest(unsigned lnum, LoopHoistContext* hoistCtxt)
{
// Do this loop, then recursively do all nested loops.
- CLANG_FORMAT_COMMENT_ANCHOR;
+ JITDUMP("\n%s " FMT_LP "\n", optLoopTable[lnum].lpParent == BasicBlock::NOT_IN_LOOP ? "Loop Nest" : "Nested Loop",
+ lnum);
#if LOOP_HOIST_STATS
// Record stats
if (pLoopDsc->lpFlags & LPFLG_REMOVED)
{
+ JITDUMP(" ... not hoisting " FMT_LP ": removed\n", lnum);
return;
}
// We must have a do-while loop
if ((pLoopDsc->lpFlags & LPFLG_DO_WHILE) == 0)
{
+ JITDUMP(" ... not hoisting " FMT_LP ": not do-while\n", lnum);
return;
}
// TODO-CQ: Couldn't we make this true if it's not?
if (!fgDominate(head, lbeg))
{
+ JITDUMP(" ... not hoisting " FMT_LP ": head " FMT_BB " does not dominate beg " FMT_BB "\n", lnum, head->bbNum,
+ lbeg->bbNum);
return;
}
// if lbeg is the start of a new try block then we won't be able to hoist
if (!BasicBlock::sameTryRegion(head, lbeg))
{
+ JITDUMP(" ... not hoisting in " FMT_LP ", eh region constraint\n", lnum);
return;
}
// We don't bother hoisting when inside of a catch block
if ((lbeg->bbCatchTyp != BBCT_NONE) && (lbeg->bbCatchTyp != BBCT_FINALLY))
{
+ JITDUMP(" ... not hoisting in " FMT_LP ", within catch\n", lnum);
return;
}
// Find the set of definitely-executed blocks.
// Ideally, the definitely-executed blocks are the ones that post-dominate the entry block.
// Until we have post-dominators, we'll special-case for single-exit blocks.
+ //
+ // Todo: it is not clear if this is a correctness requirement or a profitability heuristic.
+ // It seems like the latter. Ideally have enough safeguards to prevent hoisting exception
+ // or side-effect dependent things.
+ //
+ // We really should consider hoisting from conditionally executed blocks, if they are frequently executed
+ // and it is safe to evaluate the tree early.
+ //
+ // In particular if we have a loop nest, when scanning the outer loop we should consider hoisting from blocks
+ // in enclosed loops. However, this is likely to scale poorly, and we really should instead start
+ // hoisting inner to outer.
+ //
ArrayStack<BasicBlock*> defExec(getAllocatorLoopHoist());
if (pLoopDsc->lpFlags & LPFLG_ONE_EXIT)
{
assert(pLoopDsc->lpExit != nullptr);
+ JITDUMP(" Only considering hoisting in blocks that dominate exit block " FMT_BB "\n", pLoopDsc->lpExit->bbNum);
BasicBlock* cur = pLoopDsc->lpExit;
// Push dominators, until we reach "entry" or exit the loop.
while (cur != nullptr && pLoopDsc->lpContains(cur) && cur != pLoopDsc->lpEntry)
// If we didn't reach the entry block, give up and *just* push the entry block.
if (cur != pLoopDsc->lpEntry)
{
+ JITDUMP(" -- odd, we didn't reach entry from exit via dominators. Only considering hoisting in entry "
+ "block " FMT_BB "\n",
+ pLoopDsc->lpEntry->bbNum);
defExec.Reset();
}
defExec.Push(pLoopDsc->lpEntry);
}
else // More than one exit
{
+ JITDUMP(" only considering hoisting in entry block " FMT_BB "\n", pLoopDsc->lpEntry->bbNum);
// We'll assume that only the entry block is definitely executed.
// We could in the future do better.
defExec.Push(pLoopDsc->lpEntry);
// Don't hoist expressions that are not heavy: tree->GetCostEx() < (2*IND_COST_EX)
if (tree->GetCostEx() < (2 * IND_COST_EX))
{
+ JITDUMP(" tree cost too low: %d < %d (loopVarCount %u >= availableRegCount %u)\n", tree->GetCostEx(),
+ 2 * IND_COST_EX, loopVarCount, availRegCount);
return false;
}
}
// Don't hoist expressions that barely meet CSE cost requirements: tree->GetCostEx() == MIN_CSE_COST
if (tree->GetCostEx() <= MIN_CSE_COST + 1)
{
+ JITDUMP(" tree not good CSE: %d <= %d (varInOutCount %u > availableRegCount %u)\n", tree->GetCostEx(),
+ 2 * MIN_CSE_COST + 1, varInOutCount, availRegCount)
return false;
}
}
bool m_cctorDependent;
bool m_invariant;
+#ifdef DEBUG
+ const char* m_failReason;
+#endif
+
Value(GenTree* node) : m_node(node), m_hoistable(false), m_cctorDependent(false), m_invariant(false)
{
+#ifdef DEBUG
+ m_failReason = "unset";
+#endif
}
GenTree* Node()
for (Statement* const stmt : block->NonPhiStatements())
{
WalkTree(stmt->GetRootNodePointer(), nullptr);
- assert(m_valueStack.TopRef().Node() == stmt->GetRootNode());
+ Value& top = m_valueStack.TopRef();
+ assert(top.Node() == stmt->GetRootNode());
- if (m_valueStack.TopRef().m_hoistable)
+ if (top.m_hoistable)
{
m_compiler->optHoistCandidate(stmt->GetRootNode(), block, m_loopNum, m_hoistContext);
}
+ else
+ {
+ JITDUMP(" [%06u] not %s: %s\n", dspTreeID(top.Node()),
+ top.m_invariant ? "invariant" : "hoistable", top.m_failReason);
+ }
m_valueStack.Reset();
}
// blocked by the SSA invariance check.
isInvariant = isInvariant && IsTreeVNInvariant(tree);
+ Value& top = m_valueStack.TopRef();
+ assert(top.Node() == tree);
+
if (isInvariant)
{
- Value& top = m_valueStack.TopRef();
- assert(top.Node() == tree);
top.m_invariant = true;
// In general it doesn't make sense to hoist a local node but there are exceptions, for example
// LCL_FLD nodes (because then the variable cannot be enregistered and the node always turns
top.m_hoistable = IsNodeHoistable(tree);
}
+#ifdef DEBUG
+ if (!isInvariant)
+ {
+ top.m_failReason = "local, not rvalue / not in SSA / defined within current loop";
+ }
+ else if (!top.m_hoistable)
+ {
+ top.m_failReason = "not handled by cse";
+ }
+#endif
+
return fgWalkResult::WALK_CONTINUE;
}
bool treeHasHoistableChildren = false;
int childCount;
+#ifdef DEBUG
+ const char* failReason = "unknown";
+#endif
+
for (childCount = 0; m_valueStack.TopRef(childCount).Node() != tree; childCount++)
{
Value& child = m_valueStack.TopRef(childCount);
if (!child.m_invariant)
{
treeIsInvariant = false;
+ INDEBUG(failReason = "variant child";)
}
if (child.m_cctorDependent)
// unless it has a static var reference that can't be hoisted past its cctor call.
bool treeIsHoistable = treeIsInvariant && !treeIsCctorDependent;
+#ifdef DEBUG
+ if (treeIsInvariant && !treeIsHoistable)
+ {
+ failReason = "cctor dependent";
+ }
+#endif
+
// But we must see if anything else prevents "tree" from being hoisted.
//
if (treeIsInvariant)
if (treeIsHoistable)
{
treeIsHoistable = IsNodeHoistable(tree);
+ if (!treeIsHoistable)
+ {
+ INDEBUG(failReason = "not handled by cse";)
+ }
}
// If it's a call, it must be a helper call, and be pure.
GenTreeCall* call = tree->AsCall();
if (call->gtCallType != CT_HELPER)
{
+ INDEBUG(failReason = "non-helper call";)
treeIsHoistable = false;
}
else
CorInfoHelpFunc helpFunc = eeGetHelperNum(call->gtCallMethHnd);
if (!s_helperCallProperties.IsPure(helpFunc))
{
+ INDEBUG(failReason = "impure helper call";)
treeIsHoistable = false;
}
else if (s_helperCallProperties.MayRunCctor(helpFunc) &&
((call->gtFlags & GTF_CALL_HOISTABLE) == 0))
{
+ INDEBUG(failReason = "non-hoistable helper call";)
treeIsHoistable = false;
}
}
//
if ((tree->gtFlags & GTF_EXCEPT) != 0)
{
+ INDEBUG(failReason = "side effect ordering constraint";)
treeIsHoistable = false;
}
}
if (!treeIsInvariant)
{
// Here we have a tree that is not loop invariant and we thus cannot hoist
+ INDEBUG(failReason = "tree VN is loop variant";)
treeIsHoistable = false;
}
}
{
// If this node is a MEMORYBARRIER or an Atomic operation
// then don't hoist and stop any further hoisting after this node
+ INDEBUG(failReason = "atomic op or memory barrier";)
treeIsHoistable = false;
m_beforeSideEffect = false;
}
m_compiler->optHoistCandidate(value.Node(), m_currentBlock, m_loopNum, m_hoistContext);
}
+ else if (value.Node() != tree)
+ {
+ JITDUMP(" [%06u] not %s: %s\n", dspTreeID(value.Node()),
+ value.m_invariant ? "invariant" : "hoistable", value.m_failReason);
+ }
}
}
top.m_cctorDependent = treeIsCctorDependent;
top.m_invariant = treeIsInvariant;
+#ifdef DEBUG
+ if (!top.m_invariant || !top.m_hoistable)
+ {
+ top.m_failReason = failReason;
+ }
+#endif
+
return fgWalkResult::WALK_CONTINUE;
}
};
BasicBlock* block = blocks->Pop();
weight_t blockWeight = block->getBBWeight(this);
- JITDUMP(" optHoistLoopBlocks " FMT_BB " (weight=%6s) of loop " FMT_LP " <" FMT_BB ".." FMT_BB
+ JITDUMP("\n optHoistLoopBlocks " FMT_BB " (weight=%6s) of loop " FMT_LP " <" FMT_BB ".." FMT_BB
">, firstBlock is %s\n",
block->bbNum, refCntWtd2str(blockWeight), loopNum, loopDsc->lpFirst->bbNum, loopDsc->lpBottom->bbNum,
dspBool(block == loopDsc->lpEntry));
// It must pass the hoistable profitablity tests for this loop level
if (!optIsProfitableToHoistableTree(tree, lnum))
{
+ JITDUMP(" ... not profitable to hoist\n");
return;
}
bool b;
if (hoistCtxt->m_hoistedInParentLoops.Lookup(tree->gtVNPair.GetLiberal(), &b))
{
+ JITDUMP(" ... already hoisted same VN in parent\n");
// already hoisted in a parent loop, so don't hoist this expression.
return;
}
if (hoistCtxt->GetHoistedInCurLoop(this)->Lookup(tree->gtVNPair.GetLiberal(), &b))
{
+ JITDUMP(" ... already hoisted same VN in current\n");
// already hoisted this expression in the current loop, so don't hoist this expression.
return;
}
// the prolog and this explicit intialization. Therefore, it doesn't
// require zero initialization in the prolog.
lclDsc->lvHasExplicitInit = 1;
- JITDUMP("Marking L%02u as having an explicit init\n", lclNum);
+ JITDUMP("Marking " FMT_LP " as having an explicit init\n", lclNum);
}
}
break;