Skip to content

Commit 5ae9467

Browse files
JIT: add more "simple" complexity helpers (#110486)
In early phases we haven't set tree costs or size estimates, so we fall back to counting statements or nodes. Capture some of this in some helper methods, both pure counting and "exceeds" variants (which are potentially cheaper when comparing against a budget). Update the loop cloning size check to use this. Co-authored-by: Aman Khalid <[email protected]>
1 parent 2d81cbb commit 5ae9467

File tree

6 files changed

+176
-70
lines changed

6 files changed

+176
-70
lines changed

src/coreclr/jit/block.cpp

+131
Original file line numberDiff line numberDiff line change
@@ -1942,3 +1942,134 @@ StackEntry* BasicBlock::bbStackOnEntry() const
19421942
assert(bbEntryState);
19431943
return bbEntryState->esStack;
19441944
}
1945+
1946+
//------------------------------------------------------------------------
1947+
// StatementCount: number of statements in the block.
1948+
//
1949+
// Returns:
1950+
// count of statements
1951+
//
1952+
// Notes:
1953+
// If you are calling this in order to compare the statement count
1954+
// against a limit, use StatementCountExceeds as it may do less work.
1955+
//
1956+
unsigned BasicBlock::StatementCount()
1957+
{
1958+
unsigned count = 0;
1959+
1960+
for (Statement* const stmt : Statements())
1961+
{
1962+
count++;
1963+
}
1964+
1965+
return count;
1966+
}
1967+
1968+
//------------------------------------------------------------------------
1969+
// StatementCountExceeds: check if the number of statements in the block
1970+
// exceeds some limit
1971+
//
1972+
// Arguments:
1973+
// limit - limit on the number of statements
1974+
// count - [out, optional] actual number of statements (if less than or equal to limit)
1975+
//
1976+
// Returns:
1977+
// true if the number of statements is greater than limit
1978+
//
1979+
bool BasicBlock::StatementCountExceeds(unsigned limit, unsigned* count /* = nullptr */)
1980+
{
1981+
unsigned localCount = 0;
1982+
bool overLimit = false;
1983+
1984+
for (Statement* const stmt : Statements())
1985+
{
1986+
if (++localCount > limit)
1987+
{
1988+
overLimit = true;
1989+
break;
1990+
}
1991+
}
1992+
1993+
if (count != nullptr)
1994+
{
1995+
*count = localCount;
1996+
}
1997+
1998+
return overLimit;
1999+
}
2000+
2001+
//------------------------------------------------------------------------
2002+
// ComplexityExceeds: check if the number of nodes in the trees in the block
2003+
// exceeds some limit
2004+
//
2005+
// Arguments:
2006+
// comp - compiler instance
2007+
// limit - limit on the number of nodes
2008+
// count - [out, optional] actual number of nodes (if less than or equal to limit)
2009+
//
2010+
// Returns:
2011+
// true if the number of nodes is greater than limit
2012+
//
2013+
bool BasicBlock::ComplexityExceeds(Compiler* comp, unsigned limit, unsigned* count /* = nullptr */)
2014+
{
2015+
unsigned localCount = 0;
2016+
bool overLimit = false;
2017+
2018+
for (Statement* const stmt : Statements())
2019+
{
2020+
unsigned slack = limit - localCount;
2021+
unsigned actual = 0;
2022+
if (comp->gtComplexityExceeds(stmt->GetRootNode(), slack, &actual))
2023+
{
2024+
overLimit = true;
2025+
break;
2026+
}
2027+
2028+
localCount += actual;
2029+
}
2030+
2031+
if (count != nullptr)
2032+
{
2033+
*count = localCount;
2034+
}
2035+
2036+
return overLimit;
2037+
}
2038+
2039+
//------------------------------------------------------------------------
2040+
// ComplexityExceeds: check if the number of nodes in the trees in the blocks
2041+
// in the range exceeds some limit
2042+
//
2043+
// Arguments:
2044+
// comp - compiler instance
2045+
// limit - limit on the number of nodes
2046+
// count - [out, optional] actual number of nodes (if less than or equal to limit)
2047+
//
2048+
// Returns:
2049+
// true if the number of nodes is greater than limit
2050+
//
2051+
bool BasicBlockRangeList::ComplexityExceeds(Compiler* comp, unsigned limit, unsigned* count /* = nullptr */)
2052+
{
2053+
unsigned localCount = 0;
2054+
bool overLimit = false;
2055+
2056+
for (BasicBlock* const block : *this)
2057+
{
2058+
unsigned slack = limit - localCount;
2059+
unsigned actual = 0;
2060+
if (block->ComplexityExceeds(comp, slack, &actual))
2061+
{
2062+
overLimit = true;
2063+
break;
2064+
}
2065+
2066+
localCount += actual;
2067+
}
2068+
2069+
if (count != nullptr)
2070+
{
2071+
*count = localCount;
2072+
}
2073+
2074+
return overLimit;
2075+
}

src/coreclr/jit/block.h

+8-13
Original file line numberDiff line numberDiff line change
@@ -1667,19 +1667,6 @@ struct BasicBlock : private LIR::Range
16671667

16681668
bool bbFallsThrough() const;
16691669

1670-
// Our slop fraction is 1/50 of the block weight.
1671-
static weight_t GetSlopFraction(weight_t weightBlk)
1672-
{
1673-
return weightBlk / 50.0;
1674-
}
1675-
1676-
// Given an the edge b1 -> b2, calculate the slop fraction by
1677-
// using the higher of the two block weights
1678-
static weight_t GetSlopFraction(BasicBlock* b1, BasicBlock* b2)
1679-
{
1680-
return GetSlopFraction(max(b1->bbWeight, b2->bbWeight));
1681-
}
1682-
16831670
#ifdef DEBUG
16841671
unsigned bbTgtStkDepth; // Native stack depth on entry (for throw-blocks)
16851672
static unsigned s_nMaxTrees; // The max # of tree nodes in any BB
@@ -1727,6 +1714,12 @@ struct BasicBlock : private LIR::Range
17271714
return StatementList(FirstNonPhiDef());
17281715
}
17291716

1717+
// Simple "size" estimates
1718+
//
1719+
unsigned StatementCount();
1720+
bool StatementCountExceeds(unsigned limit, unsigned* count = nullptr);
1721+
bool ComplexityExceeds(Compiler* comp, unsigned limit, unsigned* complexity = nullptr);
1722+
17301723
GenTree* lastNode() const;
17311724

17321725
bool endsWithJmpMethod(Compiler* comp) const;
@@ -2203,6 +2196,8 @@ class BasicBlockRangeList
22032196
{
22042197
return BasicBlockIterator(m_end->Next()); // walk until we see the block *following* the `m_end` block
22052198
}
2199+
2200+
bool ComplexityExceeds(Compiler* comp, unsigned limit, unsigned* count = nullptr);
22062201
};
22072202

22082203
// BBswtDesc -- descriptor for a switch block

src/coreclr/jit/compiler.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -3661,7 +3661,7 @@ class Compiler
36613661
// is #of nodes in subtree) of "tree" is greater than "limit".
36623662
// (This is somewhat redundant with the "GetCostEx()/GetCostSz()" fields, but can be used
36633663
// before they have been set.)
3664-
bool gtComplexityExceeds(GenTree* tree, unsigned limit);
3664+
bool gtComplexityExceeds(GenTree* tree, unsigned limit, unsigned* complexity = nullptr);
36653665

36663666
GenTree* gtReverseCond(GenTree* tree);
36673667

src/coreclr/jit/gentree.cpp

+19-5
Original file line numberDiff line numberDiff line change
@@ -17852,13 +17852,14 @@ ExceptionSetFlags Compiler::gtCollectExceptions(GenTree* tree)
1785217852
// of number of sub nodes.
1785317853
//
1785417854
// Arguments:
17855-
// tree - The tree to check
17856-
// limit - The limit in terms of number of nodes
17855+
// tree - The tree to check
17856+
// limit - The limit in terms of number of nodes
17857+
// complexity - [out, optional] the actual node count (if not greater than limit)
1785717858
//
1785817859
// Return Value:
17859-
// True if there are mode sub nodes in tree; otherwise false.
17860+
// True if there are more than limit nodes in tree; otherwise false.
1786017861
//
17861-
bool Compiler::gtComplexityExceeds(GenTree* tree, unsigned limit)
17862+
bool Compiler::gtComplexityExceeds(GenTree* tree, unsigned limit, unsigned* complexity)
1786217863
{
1786317864
struct ComplexityVisitor : GenTreeVisitor<ComplexityVisitor>
1786417865
{
@@ -17883,13 +17884,26 @@ bool Compiler::gtComplexityExceeds(GenTree* tree, unsigned limit)
1788317884
return WALK_CONTINUE;
1788417885
}
1788517886

17887+
unsigned NumNodes()
17888+
{
17889+
return m_numNodes;
17890+
}
17891+
1788617892
private:
1788717893
unsigned m_limit;
1788817894
unsigned m_numNodes = 0;
1788917895
};
1789017896

1789117897
ComplexityVisitor visitor(this, limit);
17892-
return visitor.WalkTree(&tree, nullptr) == WALK_ABORT;
17898+
17899+
fgWalkResult result = visitor.WalkTree(&tree, nullptr);
17900+
17901+
if (complexity != nullptr)
17902+
{
17903+
*complexity = visitor.NumNodes();
17904+
}
17905+
17906+
return (result == WALK_ABORT);
1789317907
}
1789417908

1789517909
bool GenTree::IsPhiNode()

src/coreclr/jit/jitconfigvalues.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ CONFIG_INTEGER(JitCloneLoops, "JitCloneLoops", 1) // If 0, don't clone.
6262
CONFIG_INTEGER(JitCloneLoopsWithEH, "JitCloneLoopsWithEH", 0) // If 0, don't clone loops containing EH regions
6363
CONFIG_INTEGER(JitCloneLoopsWithGdvTests, "JitCloneLoopsWithGdvTests", 1) // If 0, don't clone loops based on
6464
// invariant type/method address tests
65-
RELEASE_CONFIG_INTEGER(JitCloneLoopsSizeLimit, "JitCloneLoopsSizeLimit", 400) // limit cloning to loops with less
65+
RELEASE_CONFIG_INTEGER(JitCloneLoopsSizeLimit, "JitCloneLoopsSizeLimit", 400) // limit cloning to loops with no more
6666
// than this many tree nodes
6767
CONFIG_INTEGER(JitDebugLogLoopCloning, "JitDebugLogLoopCloning", 0) // In debug builds log places where loop cloning
6868
// optimizations are performed on the fast path.

src/coreclr/jit/loopcloning.cpp

+16-50
Original file line numberDiff line numberDiff line change
@@ -1798,68 +1798,34 @@ void Compiler::optPerformStaticOptimizations(FlowGraphNaturalLoop* loop,
17981798
//
17991799
bool Compiler::optShouldCloneLoop(FlowGraphNaturalLoop* loop, LoopCloneContext* context)
18001800
{
1801-
// Compute loop size
1801+
// See if loop size exceeds the limit.
18021802
//
1803-
unsigned loopSize = 0;
1803+
const int sizeConfig = JitConfig.JitCloneLoopsSizeLimit();
1804+
unsigned const sizeLimit = (sizeConfig >= 0) ? (unsigned)sizeConfig : UINT_MAX;
1805+
unsigned size = 0;
18041806

1805-
// For now we use a very simplistic model where each tree node
1806-
// has the same code size.
1807-
//
1808-
// CostSz is not available until later.
1809-
//
1810-
struct TreeCostWalker : GenTreeVisitor<TreeCostWalker>
1811-
{
1812-
enum
1813-
{
1814-
DoPreOrder = true,
1815-
};
1816-
1817-
unsigned m_nodeCount;
1818-
1819-
TreeCostWalker(Compiler* comp)
1820-
: GenTreeVisitor(comp)
1821-
, m_nodeCount(0)
1807+
BasicBlockVisit result = loop->VisitLoopBlocks([&](BasicBlock* block) {
1808+
assert(sizeLimit >= size);
1809+
unsigned const slack = sizeLimit - size;
1810+
unsigned blockSize = 0;
1811+
if (block->ComplexityExceeds(this, slack, &blockSize))
18221812
{
1813+
return BasicBlockVisit::Abort;
18231814
}
18241815

1825-
fgWalkResult PreOrderVisit(GenTree** use, GenTree* user)
1826-
{
1827-
m_nodeCount++;
1828-
return WALK_CONTINUE;
1829-
}
1830-
1831-
void Reset()
1832-
{
1833-
m_nodeCount = 0;
1834-
}
1835-
unsigned Cost()
1836-
{
1837-
return m_nodeCount;
1838-
}
1839-
};
1840-
1841-
TreeCostWalker costWalker(this);
1842-
1843-
loop->VisitLoopBlocks([&](BasicBlock* block) {
1844-
weight_t normalizedWeight = block->getBBWeight(this);
1845-
for (Statement* const stmt : block->Statements())
1846-
{
1847-
costWalker.Reset();
1848-
costWalker.WalkTree(stmt->GetRootNodePointer(), nullptr);
1849-
loopSize += costWalker.Cost();
1850-
}
1816+
size += blockSize;
18511817
return BasicBlockVisit::Continue;
18521818
});
18531819

1854-
int const sizeLimit = JitConfig.JitCloneLoopsSizeLimit();
1855-
1856-
if ((sizeLimit >= 0) && (loopSize >= (unsigned)sizeLimit))
1820+
if (result == BasicBlockVisit::Abort)
18571821
{
1858-
JITDUMP("Loop cloning: rejecting loop " FMT_LP " of size %u, size limit %d\n", loop->GetIndex(), loopSize,
1859-
sizeLimit);
1822+
JITDUMP("Loop cloning: rejecting loop " FMT_LP ": exceeds size limit %u\n", loop->GetIndex(), sizeLimit);
18601823
return false;
18611824
}
18621825

1826+
JITDUMP("Loop cloning: loop " FMT_LP ": size %u does not exceed size limit %u\n", loop->GetIndex(), size,
1827+
sizeLimit);
1828+
18631829
return true;
18641830
}
18651831

0 commit comments

Comments
 (0)