Skip to content

Commit e831ce8

Browse files
JIT: Rework *ComplexityExceeds helpers to take caller-defined complexity computation (#118046)
Allow Compiler::gtComplexityExceeds et al to compute the complexity of a tree as something other than the count of its nodes by relying on a callback function. We don't diverge from this definition of complexity anywhere at the moment, though this means callers can compute additional information, like the total node count for a block or loop, without needing out parameters by simply including such computations in the callback. I was motivated to do this by a want for additional insight into the optimization potential of loop inversion candidates, like whether a loop might also benefit from cloning because it contains bounds checks. Such information can be included in the callback to the complexity check. In the near future, we might want to diverge from the number of nodes in a code segment as our definition of complexity and employ something more sophisticated. These changes should make that easy to try out.
1 parent 1e7433e commit e831ce8

File tree

11 files changed

+218
-197
lines changed

11 files changed

+218
-197
lines changed

src/coreclr/jit/block.cpp

Lines changed: 0 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1735,79 +1735,3 @@ bool BasicBlock::StatementCountExceeds(unsigned limit, unsigned* count /* = null
17351735

17361736
return overLimit;
17371737
}
1738-
1739-
//------------------------------------------------------------------------
1740-
// ComplexityExceeds: check if the number of nodes in the trees in the block
1741-
// exceeds some limit
1742-
//
1743-
// Arguments:
1744-
// comp - compiler instance
1745-
// limit - limit on the number of nodes
1746-
// count - [out, optional] actual number of nodes (if less than or equal to limit)
1747-
//
1748-
// Returns:
1749-
// true if the number of nodes is greater than limit
1750-
//
1751-
bool BasicBlock::ComplexityExceeds(Compiler* comp, unsigned limit, unsigned* count /* = nullptr */)
1752-
{
1753-
unsigned localCount = 0;
1754-
bool overLimit = false;
1755-
1756-
for (Statement* const stmt : Statements())
1757-
{
1758-
unsigned slack = limit - localCount;
1759-
unsigned actual = 0;
1760-
if (comp->gtComplexityExceeds(stmt->GetRootNode(), slack, &actual))
1761-
{
1762-
overLimit = true;
1763-
break;
1764-
}
1765-
1766-
localCount += actual;
1767-
}
1768-
1769-
if (count != nullptr)
1770-
{
1771-
*count = localCount;
1772-
}
1773-
1774-
return overLimit;
1775-
}
1776-
1777-
//------------------------------------------------------------------------
1778-
// ComplexityExceeds: check if the number of nodes in the trees in the blocks
1779-
// in the range exceeds some limit
1780-
//
1781-
// Arguments:
1782-
// comp - compiler instance
1783-
// limit - limit on the number of nodes
1784-
// count - [out, optional] actual number of nodes (if less than or equal to limit)
1785-
//
1786-
// Returns:
1787-
// true if the number of nodes is greater than limit
1788-
//
1789-
bool BasicBlockRangeList::ComplexityExceeds(Compiler* comp, unsigned limit, unsigned* count /* = nullptr */)
1790-
{
1791-
unsigned localCount = 0;
1792-
bool overLimit = false;
1793-
1794-
for (BasicBlock* const block : *this)
1795-
{
1796-
unsigned slack = limit - localCount;
1797-
unsigned actual = 0;
1798-
if (block->ComplexityExceeds(comp, slack, &actual))
1799-
{
1800-
overLimit = true;
1801-
break;
1802-
}
1803-
1804-
localCount += actual;
1805-
}
1806-
1807-
if (count != nullptr)
1808-
{
1809-
*count = localCount;
1810-
}
1811-
1812-
return overLimit;
1813-
}

src/coreclr/jit/block.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1779,7 +1779,9 @@ struct BasicBlock : private LIR::Range
17791779
//
17801780
unsigned StatementCount();
17811781
bool StatementCountExceeds(unsigned limit, unsigned* count = nullptr);
1782-
bool ComplexityExceeds(Compiler* comp, unsigned limit, unsigned* complexity = nullptr);
1782+
1783+
template <typename TFunc>
1784+
bool ComplexityExceeds(Compiler* comp, unsigned limit, TFunc getTreeComplexity);
17831785

17841786
GenTree* lastNode() const;
17851787

@@ -2086,7 +2088,8 @@ class BasicBlockRangeList
20862088
return BasicBlockIterator(m_end->Next()); // walk until we see the block *following* the `m_end` block
20872089
}
20882090

2089-
bool ComplexityExceeds(Compiler* comp, unsigned limit, unsigned* count = nullptr);
2091+
template <typename TFunc>
2092+
bool ComplexityExceeds(Compiler* comp, unsigned limit, TFunc getTreeComplexity);
20902093
};
20912094

20922095
// BBJumpTable -- descriptor blocks with N successors

src/coreclr/jit/compiler.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3602,11 +3602,11 @@ class Compiler
36023602

36033603
void gtUpdateNodeOperSideEffects(GenTree* tree);
36043604

3605-
// Returns "true" iff the complexity (not formally defined, but first interpretation
3606-
// is #of nodes in subtree) of "tree" is greater than "limit".
3605+
// Returns "true" iff the complexity (defined by 'getComplexity') of "tree" is greater than "limit".
36073606
// (This is somewhat redundant with the "GetCostEx()/GetCostSz()" fields, but can be used
3608-
// before they have been set.)
3609-
bool gtComplexityExceeds(GenTree* tree, unsigned limit, unsigned* complexity = nullptr);
3607+
// before they have been set, if 'getComplexity' is independent of them.)
3608+
template <typename TFunc>
3609+
bool gtComplexityExceeds(GenTree* tree, unsigned limit, TFunc getComplexity);
36103610

36113611
GenTree* gtReverseCond(GenTree* tree);
36123612

@@ -7007,7 +7007,8 @@ class Compiler
70077007
bool optCanonicalizeExits(FlowGraphNaturalLoop* loop);
70087008
bool optCanonicalizeExit(FlowGraphNaturalLoop* loop, BasicBlock* exit);
70097009

7010-
bool optLoopComplexityExceeds(FlowGraphNaturalLoop* loop, unsigned limit);
7010+
template <typename TFunc>
7011+
bool optLoopComplexityExceeds(FlowGraphNaturalLoop* loop, unsigned limit, TFunc getTreeComplexity);
70117012

70127013
PhaseStatus optCloneLoops();
70137014
PhaseStatus optRangeCheckCloning();

src/coreclr/jit/compiler.hpp

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5411,6 +5411,178 @@ BasicBlockVisit FlowGraphNaturalLoop::VisitRegularExitBlocks(TFunc func)
54115411
return BasicBlockVisit::Continue;
54125412
}
54135413

5414+
//-----------------------------------------------------------
5415+
// gtComplexityExceeds: Check if a tree exceeds a specified complexity limit.
5416+
//
5417+
// Type parameters:
5418+
// TFunc - Callback functor type
5419+
5420+
// Arguments:
5421+
// tree - The tree to check
5422+
// limit - complexity limit
5423+
// getTreeComplexity - Callback functor that takes a GenTree* and returns its complexity
5424+
//
5425+
// Return Value:
5426+
// True if 'tree' exceeds the complexity limit, otherwise false.
5427+
//
5428+
template <typename TFunc>
5429+
bool Compiler::gtComplexityExceeds(GenTree* tree, unsigned limit, TFunc getComplexity)
5430+
{
5431+
struct ComplexityVisitor : GenTreeVisitor<ComplexityVisitor>
5432+
{
5433+
enum
5434+
{
5435+
DoPreOrder = true,
5436+
};
5437+
5438+
ComplexityVisitor(Compiler* comp, unsigned limit, TFunc getComplexity)
5439+
: GenTreeVisitor<ComplexityVisitor>(comp)
5440+
, m_complexity(0)
5441+
, m_limit(limit)
5442+
, m_getComplexity(getComplexity)
5443+
{
5444+
}
5445+
5446+
fgWalkResult PreOrderVisit(GenTree** use, GenTree* user)
5447+
{
5448+
m_complexity += m_getComplexity(*use);
5449+
return (m_complexity > m_limit) ? WALK_ABORT : WALK_CONTINUE;
5450+
}
5451+
5452+
private:
5453+
unsigned m_complexity;
5454+
const unsigned m_limit;
5455+
TFunc m_getComplexity;
5456+
};
5457+
5458+
assert(tree != nullptr);
5459+
5460+
ComplexityVisitor visitor(this, limit, getComplexity);
5461+
5462+
fgWalkResult result = visitor.WalkTree(&tree, nullptr);
5463+
5464+
return (result == WALK_ABORT);
5465+
}
5466+
5467+
//------------------------------------------------------------------------
5468+
// ComplexityExceeds: Check if the trees in a block exceed a specified complexity limit.
5469+
//
5470+
// Type parameters:
5471+
// TFunc - Callback functor type
5472+
//
5473+
// Arguments:
5474+
// comp - compiler instance
5475+
// limit - complexity limit
5476+
// getTreeComplexity - Callback functor that takes a GenTree* and returns its complexity
5477+
//
5478+
// Returns:
5479+
// True if the trees in the block exceed the complexity limit, otherwise false.
5480+
//
5481+
template <typename TFunc>
5482+
bool BasicBlock::ComplexityExceeds(Compiler* comp, unsigned limit, TFunc getTreeComplexity)
5483+
{
5484+
assert(comp != nullptr);
5485+
5486+
unsigned localCount = 0;
5487+
auto getComplexity = [&](GenTree* tree) -> unsigned {
5488+
const unsigned treeComplexity = getTreeComplexity(tree);
5489+
localCount += treeComplexity;
5490+
return treeComplexity;
5491+
};
5492+
5493+
for (Statement* const stmt : Statements())
5494+
{
5495+
const unsigned slack = limit - localCount;
5496+
if (comp->gtComplexityExceeds(stmt->GetRootNode(), slack, getComplexity))
5497+
{
5498+
return true;
5499+
}
5500+
}
5501+
5502+
return false;
5503+
}
5504+
5505+
//------------------------------------------------------------------------
5506+
// ComplexityExceeds: Check if the trees in a range of blocks exceed a specified complexity limit.
5507+
//
5508+
// Type parameters:
5509+
// TFunc - Callback functor type
5510+
//
5511+
// Arguments:
5512+
// comp - compiler instance
5513+
// limit - complexity limit
5514+
// getTreeComplexity - Callback functor that takes a GenTree* and returns its complexity
5515+
//
5516+
// Returns:
5517+
// True if the trees in the block range exceed the complexity limit, otherwise false.
5518+
//
5519+
template <typename TFunc>
5520+
bool BasicBlockRangeList::ComplexityExceeds(Compiler* comp, unsigned limit, TFunc getTreeComplexity)
5521+
{
5522+
assert(comp != nullptr);
5523+
5524+
unsigned localCount = 0;
5525+
auto getComplexity = [&](GenTree* tree) -> unsigned {
5526+
const unsigned treeComplexity = getTreeComplexity(tree);
5527+
localCount += treeComplexity;
5528+
return treeComplexity;
5529+
};
5530+
5531+
for (BasicBlock* const block : *this)
5532+
{
5533+
const unsigned slack = limit - localCount;
5534+
if (block->ComplexityExceeds(comp, slack, getComplexity))
5535+
{
5536+
return true;
5537+
}
5538+
}
5539+
5540+
return false;
5541+
}
5542+
5543+
//------------------------------------------------------------------------
5544+
// optLoopComplexityExceeds: Check if the trees in a loop exceed a specified complexity limit.
5545+
//
5546+
// Type parameters:
5547+
// TFunc - Callback functor type
5548+
//
5549+
// Arguments:
5550+
// comp - compiler instance
5551+
// limit - complexity limit
5552+
// getTreeComplexity - Callback functor that takes a GenTree* and returns its complexity
5553+
//
5554+
// Returns:
5555+
// True if the trees in 'loop' exceed the complexity limit, otherwise false.
5556+
//
5557+
template <typename TFunc>
5558+
bool Compiler::optLoopComplexityExceeds(FlowGraphNaturalLoop* loop, unsigned limit, TFunc getTreeComplexity)
5559+
{
5560+
assert(loop != nullptr);
5561+
5562+
unsigned loopComplexity = 0;
5563+
auto getComplexity = [&](GenTree* tree) -> unsigned {
5564+
const unsigned treeComplexity = getTreeComplexity(tree);
5565+
loopComplexity += treeComplexity;
5566+
return treeComplexity;
5567+
};
5568+
5569+
BasicBlockVisit const result = loop->VisitLoopBlocks([&](BasicBlock* block) {
5570+
assert(limit >= loopComplexity);
5571+
const unsigned slack = limit - loopComplexity;
5572+
return block->ComplexityExceeds(this, slack, getComplexity) ? BasicBlockVisit::Abort
5573+
: BasicBlockVisit::Continue;
5574+
});
5575+
5576+
if (result == BasicBlockVisit::Abort)
5577+
{
5578+
JITDUMP("Loop " FMT_LP ": exceeds complexity limit %u\n", loop->GetIndex(), limit);
5579+
return true;
5580+
}
5581+
5582+
JITDUMP("Loop " FMT_LP ": complexity %u does not exceed limit %u\n", loop->GetIndex(), loopComplexity, limit);
5583+
return false;
5584+
}
5585+
54145586
/*****************************************************************************/
54155587
#endif //_COMPILER_HPP_
54165588
/*****************************************************************************/

src/coreclr/jit/forwardsub.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -569,8 +569,11 @@ bool Compiler::fgForwardSubStatement(Statement* stmt)
569569
// Consider instead using the height of the fwdSubNode.
570570
//
571571
unsigned const nodeLimit = 16;
572+
auto countNode = [](GenTree* tree) -> unsigned {
573+
return 1;
574+
};
572575

573-
if (gtComplexityExceeds(fwdSubNode, nodeLimit))
576+
if (gtComplexityExceeds(fwdSubNode, nodeLimit, countNode))
574577
{
575578
JITDUMP(" tree to sub has more than %u nodes\n", nodeLimit);
576579
return false;
@@ -633,7 +636,7 @@ bool Compiler::fgForwardSubStatement(Statement* stmt)
633636
// height of the fwdSubNode.
634637
//
635638
unsigned const nextTreeLimit = 200;
636-
if ((fsv.GetComplexity() > nextTreeLimit) && gtComplexityExceeds(fwdSubNode, 1))
639+
if ((fsv.GetComplexity() > nextTreeLimit) && gtComplexityExceeds(fwdSubNode, 1, countNode))
637640
{
638641
JITDUMP(" next stmt tree is too large (%u)\n", fsv.GetComplexity());
639642
return false;

src/coreclr/jit/gentree.cpp

Lines changed: 0 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -17867,65 +17867,6 @@ ExceptionSetFlags Compiler::gtCollectExceptions(GenTree* tree)
1786717867
return walker.GetFlags();
1786817868
}
1786917869

17870-
//-----------------------------------------------------------
17871-
// gtComplexityExceeds: Check if a tree exceeds a specified complexity in terms
17872-
// of number of sub nodes.
17873-
//
17874-
// Arguments:
17875-
// tree - The tree to check
17876-
// limit - The limit in terms of number of nodes
17877-
// complexity - [out, optional] the actual node count (if not greater than limit)
17878-
//
17879-
// Return Value:
17880-
// True if there are more than limit nodes in tree; otherwise false.
17881-
//
17882-
bool Compiler::gtComplexityExceeds(GenTree* tree, unsigned limit, unsigned* complexity)
17883-
{
17884-
struct ComplexityVisitor : GenTreeVisitor<ComplexityVisitor>
17885-
{
17886-
enum
17887-
{
17888-
DoPreOrder = true,
17889-
};
17890-
17891-
ComplexityVisitor(Compiler* comp, unsigned limit)
17892-
: GenTreeVisitor(comp)
17893-
, m_limit(limit)
17894-
{
17895-
}
17896-
17897-
fgWalkResult PreOrderVisit(GenTree** use, GenTree* user)
17898-
{
17899-
if (++m_numNodes > m_limit)
17900-
{
17901-
return WALK_ABORT;
17902-
}
17903-
17904-
return WALK_CONTINUE;
17905-
}
17906-
17907-
unsigned NumNodes()
17908-
{
17909-
return m_numNodes;
17910-
}
17911-
17912-
private:
17913-
unsigned m_limit;
17914-
unsigned m_numNodes = 0;
17915-
};
17916-
17917-
ComplexityVisitor visitor(this, limit);
17918-
17919-
fgWalkResult result = visitor.WalkTree(&tree, nullptr);
17920-
17921-
if (complexity != nullptr)
17922-
{
17923-
*complexity = visitor.NumNodes();
17924-
}
17925-
17926-
return (result == WALK_ABORT);
17927-
}
17928-
1792917870
bool GenTree::IsPhiNode()
1793017871
{
1793117872
return (OperGet() == GT_PHI_ARG) || (OperGet() == GT_PHI) || IsPhiDefn();

0 commit comments

Comments
 (0)