Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JIT: very simple cloning heuristic #108771

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -7091,6 +7091,7 @@ class Compiler
bool optCanonicalizeExit(FlowGraphNaturalLoop* loop, BasicBlock* exit);

PhaseStatus optCloneLoops();
bool optShouldCloneLoop(FlowGraphNaturalLoop* loop, LoopCloneContext* context);
void optCloneLoop(FlowGraphNaturalLoop* loop, LoopCloneContext* context);
PhaseStatus optUnrollLoops(); // Unrolls loops (needs to have cost info)
bool optTryUnrollLoop(FlowGraphNaturalLoop* loop, bool* changedIR);
Expand Down
6 changes: 4 additions & 2 deletions src/coreclr/jit/jitconfigvalues.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,10 @@ CONFIG_INTEGER(JitBreakMorphTree, W("JitBreakMorphTree"), 0xffffffff)
CONFIG_INTEGER(JitBreakOnBadCode, W("JitBreakOnBadCode"), 0)
CONFIG_INTEGER(JitBreakOnMinOpts, W("JITBreakOnMinOpts"), 0) // Halt if jit switches to MinOpts
CONFIG_INTEGER(JitCloneLoops, W("JitCloneLoops"), 1) // If 0, don't clone. Otherwise clone loops for optimizations.
CONFIG_INTEGER(JitCloneLoopsWithGdvTests, W("JitCloneLoopsWithGdvTests"), 1) // If 0, don't clone loops based on
// invariant type/method address tests
CONFIG_INTEGER(JitCloneLoopsWithGdvTests, W("JitCloneLoopsWithGdvTests"), 1) // If 0, don't clone loops based on
// invariant type/method address tests
RELEASE_CONFIG_INTEGER(JitCloneLoopsSizeLimit, W("JitCloneLoopsSizeLimit"), 400) // llimit cloning to loops with less
AndyAyersMS marked this conversation as resolved.
Show resolved Hide resolved
// than this many tree nodes
CONFIG_INTEGER(JitDebugLogLoopCloning, W("JitDebugLogLoopCloning"), 0) // In debug builds log places where loop cloning
// optimizations are performed on the fast path.
CONFIG_INTEGER(JitDefaultFill, W("JitDefaultFill"), 0xdd) // In debug builds, initialize the memory allocated by the nra
Expand Down
96 changes: 94 additions & 2 deletions src/coreclr/jit/loopcloning.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1773,6 +1773,94 @@ void Compiler::optPerformStaticOptimizations(FlowGraphNaturalLoop* loop,
}
}

//------------------------------------------------------------------------
// optShouldCloneLoop: Decide if a loop that can be cloned should be cloned.
//
// Arguments:
// loop - the current loop for which the optimizations are performed.
// context - data structure where all loop cloning info is kept.
//
// Returns:
// true if expected performance gain from cloning is worth the potential
// size increase.
//
// Remarks:
// This is a simple-minded heuristic meant to avoid "runaway" cloning
// where large loops are cloned.
//
// We estimate the size cost of cloning by summing up the number of
// tree nodes in all statements in all blocks in the loop.
//
// This value is compared to a hard-coded threshold, and if bigger,
// then the method returns false.
//
bool Compiler::optShouldCloneLoop(FlowGraphNaturalLoop* loop, LoopCloneContext* context)
{
// Compute loop size
//
unsigned loopSize = 0;

// For now we use a very simplicstic model where each tree node
AndyAyersMS marked this conversation as resolved.
Show resolved Hide resolved
// has the same code size.
//
// CostSz is not available until later.
//
struct TreeCostWalker : GenTreeVisitor<TreeCostWalker>
{
enum
{
DoPreOrder = true,
};

unsigned m_nodeCount;

TreeCostWalker(Compiler* comp)
: GenTreeVisitor(comp)
, m_nodeCount(0)
{
}

fgWalkResult PreOrderVisit(GenTree** use, GenTree* user)
{
m_nodeCount++;
return WALK_CONTINUE;
}

void Reset()
{
m_nodeCount = 0;
}
unsigned Cost()
{
return m_nodeCount;
}
};

TreeCostWalker costWalker(this);

loop->VisitLoopBlocks([&](BasicBlock* block) {
weight_t normalizedWeight = block->getBBWeight(this);
for (Statement* const stmt : block->Statements())
{
costWalker.Reset();
costWalker.WalkTree(stmt->GetRootNodePointer(), nullptr);
loopSize += costWalker.Cost();
}
return BasicBlockVisit::Continue;
});

int const sizeLimit = JitConfig.JitCloneLoopsSizeLimit();

if ((sizeLimit >= 0) && (loopSize >= (unsigned)sizeLimit))
{
JITDUMP("Loop cloning: rejecting loop " FMT_LP " of size %u, size limit %d\n", loop->GetIndex(), loopSize,
sizeLimit);
return false;
}

return true;
}

//----------------------------------------------------------------------------
// optIsLoopClonable: Determine whether this loop can be cloned.
//
Expand Down Expand Up @@ -2563,7 +2651,7 @@ Compiler::fgWalkResult Compiler::optCanOptimizeByLoopCloning(GenTree* tree, Loop

assert(compCurBB->lastStmt() == info->stmt);
info->context->EnsureLoopOptInfo(info->loop->GetIndex())
->Push(new (this, CMK_LoopOpt) LcTypeTestOptInfo(info->stmt, indir, lclNum, clsHnd));
->Push(new (this, CMK_LoopOpt) LcTypeTestOptInfo(compCurBB, info->stmt, indir, lclNum, clsHnd));
}
}
else if (optIsHandleOrIndirOfHandle(relopOp2, GTF_ICON_FTN_ADDR))
Expand Down Expand Up @@ -2644,7 +2732,7 @@ Compiler::fgWalkResult Compiler::optCanOptimizeByLoopCloning(GenTree* tree, Loop
assert(iconHandle->IsIconHandle(GTF_ICON_FTN_ADDR));
assert(compCurBB->lastStmt() == info->stmt);
LcMethodAddrTestOptInfo* optInfo = new (this, CMK_LoopOpt)
LcMethodAddrTestOptInfo(info->stmt, indir, lclNum, (void*)iconHandle->IconValue(),
LcMethodAddrTestOptInfo(compCurBB, info->stmt, indir, lclNum, (void*)iconHandle->IconValue(),
relopOp2 != iconHandle DEBUG_ARG(
(CORINFO_METHOD_HANDLE)iconHandle->gtTargetHandle));
info->context->EnsureLoopOptInfo(info->loop->GetIndex())->Push(optInfo);
Expand Down Expand Up @@ -2944,6 +3032,10 @@ PhaseStatus Compiler::optCloneLoops()
// No need to clone.
context.CancelLoopOptInfo(loop->GetIndex());
}
else if (!optShouldCloneLoop(loop, &context))
{
context.CancelLoopOptInfo(loop->GetIndex());
}
}
}

Expand Down
15 changes: 13 additions & 2 deletions src/coreclr/jit/loopcloning.h
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,8 @@ struct LcJaggedArrayOptInfo : public LcOptInfo
//
struct LcTypeTestOptInfo : public LcOptInfo
{
// block where statement occurs
BasicBlock* block;
// statement where the opportunity occurs
Statement* stmt;
// indir for the method table
Expand All @@ -330,8 +332,13 @@ struct LcTypeTestOptInfo : public LcOptInfo
// handle being tested for
CORINFO_CLASS_HANDLE clsHnd;

LcTypeTestOptInfo(Statement* stmt, GenTreeIndir* methodTableIndir, unsigned lclNum, CORINFO_CLASS_HANDLE clsHnd)
LcTypeTestOptInfo(BasicBlock* block,
Statement* stmt,
GenTreeIndir* methodTableIndir,
unsigned lclNum,
CORINFO_CLASS_HANDLE clsHnd)
: LcOptInfo(LcTypeTest)
, block(block)
, stmt(stmt)
, methodTableIndir(methodTableIndir)
, lclNum(lclNum)
Expand All @@ -342,6 +349,8 @@ struct LcTypeTestOptInfo : public LcOptInfo

struct LcMethodAddrTestOptInfo : public LcOptInfo
{
// block where statement occurs
BasicBlock* block;
// statement where the opportunity occurs
Statement* stmt;
// indir on the delegate
Expand All @@ -355,12 +364,14 @@ struct LcMethodAddrTestOptInfo : public LcOptInfo
CORINFO_METHOD_HANDLE targetMethHnd;
#endif

LcMethodAddrTestOptInfo(Statement* stmt,
LcMethodAddrTestOptInfo(BasicBlock* block,
Statement* stmt,
GenTreeIndir* delegateAddressIndir,
unsigned delegateLclNum,
void* methAddr,
bool isSlot DEBUG_ARG(CORINFO_METHOD_HANDLE targetMethHnd))
: LcOptInfo(LcMethodAddrTest)
, block(block)
, stmt(stmt)
, delegateAddressIndir(delegateAddressIndir)
, delegateLclNum(delegateLclNum)
Expand Down
Loading