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: split loop headers that are also try entries #110880

Merged
merged 7 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 4 additions & 1 deletion src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -2240,6 +2240,8 @@ class FlowGraphNaturalLoop

bool IsPostDominatedOnLoopIteration(BasicBlock* block, BasicBlock* postDominator);

void SetEntryEdge(FlowEdge* newEdge);

#ifdef DEBUG
static void Dump(FlowGraphNaturalLoop* loop);
#endif // DEBUG
Expand Down Expand Up @@ -2566,7 +2568,7 @@ struct RelopImplicationInfo
//
struct CloneTryInfo
{
CloneTryInfo(Compiler* comp);
CloneTryInfo(BitVecTraits& traits);

// bbID based traits and vector
//
Expand Down Expand Up @@ -7133,6 +7135,7 @@ class Compiler
void optCompactLoop(FlowGraphNaturalLoop* loop);
bool optCreatePreheader(FlowGraphNaturalLoop* loop);
void optSetWeightForPreheaderOrExit(FlowGraphNaturalLoop* loop, BasicBlock* block);
bool optSplitHeaderIfNecessary(FlowGraphNaturalLoop* loop);

bool optCanonicalizeExits(FlowGraphNaturalLoop* loop);
bool optCanonicalizeExit(FlowGraphNaturalLoop* loop, BasicBlock* exit);
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/fgdiagnostic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4704,6 +4704,7 @@ void Compiler::fgDebugCheckLoops()
{
assert(loop->EntryEdges().size() == 1);
assert(loop->EntryEdge(0)->getSourceBlock()->KindIs(BBJ_ALWAYS));
assert(!bbIsTryBeg(loop->GetHeader()));

loop->VisitRegularExitBlocks([=](BasicBlock* exit) {
for (BasicBlock* pred : exit->PredBlocks())
Expand Down
25 changes: 18 additions & 7 deletions src/coreclr/jit/fgehopt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2599,11 +2599,21 @@ BasicBlock* Compiler::fgCloneTryRegion(BasicBlock* tryEntry, CloneTryInfo& info,
#if defined(FEATURE_EH_WINDOWS_X86)

// For non-funclet X86 we must also clone the next block after the callfinallyret.
// (it will contain an END_LFIN)
// (it will contain an END_LFIN). But if this block is also a CALLFINALLY we
// bail out, since we can't clone it in isolation, but we need to clone it.
// (a proper fix would be to split the block, perhaps).
//
if (!UsesFunclets())
{
addBlockToClone(block->GetTarget(), "lfin-continuation");
BasicBlock* const lfin = block->GetTarget();

if (lfin->KindIs(BBJ_CALLFINALLY))
{
JITDUMP("Can't clone, as an END_LFIN is contained in CALLFINALLY block " FMT_BB "\n",
lfin->bbNum);
return nullptr;
}
addBlockToClone(lfin, "lfin-continuation");
}
#endif
}
Expand Down Expand Up @@ -3121,19 +3131,20 @@ bool Compiler::fgCanCloneTryRegion(BasicBlock* tryEntry)
{
assert(bbIsTryBeg(tryEntry));

CloneTryInfo info(this);
BitVecTraits traits(compBasicBlockID, this);
CloneTryInfo info(traits);
BasicBlock* const result = fgCloneTryRegion(tryEntry, info);
return result != nullptr;
}

//------------------------------------------------------------------------
// CloneTryInfo::CloneTryInfo
// CloneTryInfo::CloneTryInfo: construct an object for cloning a try region
//
// Arguments:
// construct an object for cloning a try region
// traits - bbID based traits to use for the Visited set
//
CloneTryInfo::CloneTryInfo(Compiler* comp)
: Traits(comp->compBasicBlockID, comp)
CloneTryInfo::CloneTryInfo(BitVecTraits& traits)
: Traits(traits)
, Visited(BitVecOps::MakeEmpty(&Traits))
{
}
14 changes: 13 additions & 1 deletion src/coreclr/jit/flowgraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4393,6 +4393,18 @@ BasicBlock* FlowGraphNaturalLoop::GetPreheader() const
return preheader;
}

//------------------------------------------------------------------------
// SetEntryEdge: Set the entry edge of a loop
//
// Arguments:
// entryEdge - The new entry edge
//
void FlowGraphNaturalLoop::SetEntryEdge(FlowEdge* entryEdge)
{
m_entryEdges.clear();
m_entryEdges.push_back(entryEdge);
}

//------------------------------------------------------------------------
// GetDepth: Get the depth of the loop.
//
Expand Down Expand Up @@ -6212,7 +6224,7 @@ void FlowGraphNaturalLoop::DuplicateWithEH(BasicBlock** insertAfter, BlockToBloc
//
if (comp->bbIsTryBeg(blk))
{
CloneTryInfo info(comp);
CloneTryInfo info(traits);
info.Map = map;
info.AddEdges = false;
info.ProfileScale = weightScale;
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/ifconversion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ void OptIfConversionDsc::IfConvertDump()
{
assert(m_startBlock != nullptr);
m_comp->fgDumpBlock(m_startBlock);
BasicBlock* dumpBlock = m_startBlock->KindIs(BBJ_COND) ? m_startBlock->GetFalseTarget() : m_startBlock->Next();
BasicBlock* dumpBlock = m_startBlock->KindIs(BBJ_COND) ? m_startBlock->GetFalseTarget() : m_startBlock->GetTarget();
for (; dumpBlock != m_finalBlock; dumpBlock = dumpBlock->GetUniqueSucc())
{
m_comp->fgDumpBlock(dumpBlock);
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/jitconfigvalues.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ CONFIG_INTEGER(JitBreakMorphTree, "JitBreakMorphTree", 0xffffffff)
CONFIG_INTEGER(JitBreakOnBadCode, "JitBreakOnBadCode", 0)
CONFIG_INTEGER(JitBreakOnMinOpts, "JITBreakOnMinOpts", 0) // Halt if jit switches to MinOpts
CONFIG_INTEGER(JitCloneLoops, "JitCloneLoops", 1) // If 0, don't clone. Otherwise clone loops for optimizations.
CONFIG_INTEGER(JitCloneLoopsWithEH, "JitCloneLoopsWithEH", 0) // If 0, don't clone loops containing EH regions
CONFIG_INTEGER(JitCloneLoopsWithEH, "JitCloneLoopsWithEH", 1) // If 0, don't clone loops containing EH regions
CONFIG_INTEGER(JitCloneLoopsWithGdvTests, "JitCloneLoopsWithGdvTests", 1) // If 0, don't clone loops based on
// invariant type/method address tests
RELEASE_CONFIG_INTEGER(JitCloneLoopsSizeLimit, "JitCloneLoopsSizeLimit", 400) // limit cloning to loops with no more
Expand Down
117 changes: 116 additions & 1 deletion src/coreclr/jit/optimizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2762,7 +2762,7 @@ bool Compiler::optCanonicalizeLoops()
// change as a result of creating preheaders. On the other hand the exit
// blocks themselves may have changed (previously it may have been another
// loop's header, now it might be its preheader instead). Exit
// canonicalization stil works even with this.
// canonicalization still works even with this.
//
// The exit canonicalization needs to be done in post order (inner -> outer
// loops) so that inner exits that also exit outer loops have proper exit
Expand All @@ -2772,6 +2772,13 @@ bool Compiler::optCanonicalizeLoops()
changed |= optCanonicalizeExits(loop);
}

// We may have created preheaders in different EH regions than the loop
// header. If so, split the header to put it into the same region as the preheader.
for (FlowGraphNaturalLoop* loop : m_loops->InReversePostOrder())
{
changed |= optSplitHeaderIfNecessary(loop);
}

return changed;
}

Expand Down Expand Up @@ -2938,11 +2945,119 @@ bool Compiler::optCreatePreheader(FlowGraphNaturalLoop* loop)
fgReplaceJumpTarget(enterBlock, header, preheader);
}

loop->SetEntryEdge(newEdge);

optSetWeightForPreheaderOrExit(loop, preheader);

return true;
}

//-----------------------------------------------------------------------------
// optSplitHeaderIfNecessary: If preheader and header are in different try
// regions, split the header to put it into the same try region as the preheader
//
// Parameters:
// loop - loop that may need header splitting
//
// Returns:
// True if the header was split
//
// Notes:
// Ensures that no loop header is also a try entry.
//
bool Compiler::optSplitHeaderIfNecessary(FlowGraphNaturalLoop* loop)
{
BasicBlock* header = loop->GetHeader();
BasicBlock* preheader = loop->GetPreheader();

if (BasicBlock::sameTryRegion(header, preheader))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be sufficient to check bbIsTryBeg directly here? Then we could avoid having to look for the preheader / the entry edge updating.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would but then I'd have to do a bit of work to find the proper EH region for the header (not a big deal, we already looked for it when we created the preheader).

{
assert(!bbIsTryBeg(header));
return false;
}

// If the preheader and header are in different try regions,
// the header should be a try entry.
//
assert(bbIsTryBeg(header));

JITDUMP("Splitting " FMT_LP " header / try entry " FMT_BB "\n", loop->GetIndex(), header->bbNum);

Statement* const firstStmt = header->firstStmt();
BasicBlock* newTryEntry = nullptr;

if (firstStmt == nullptr)
{
// Empty header
//
newTryEntry = fgSplitBlockAtEnd(header);
}
else
{
// Non-empty header.
//
Statement* const lastStmt = header->lastStmt();
bool const hasTerminator = header->HasTerminator();
Statement* const stopStmt = hasTerminator ? lastStmt : nullptr;
Statement* splitBefore = firstStmt;

while ((splitBefore != stopStmt) && (splitBefore->GetRootNode()->gtFlags & (GTF_EXCEPT | GTF_CALL)) == 0)
{
splitBefore = splitBefore->GetNextStmt();
}

// If no statement can throw, split at the end, as long as there's no terminator
//
if (splitBefore == nullptr)
{
assert(!hasTerminator);
newTryEntry = fgSplitBlockAtEnd(header);
}
// If the header has a single statement and needs a terminator, or the first statement
// can throw, split at the beginning
//
else if (splitBefore == firstStmt)
{
newTryEntry = fgSplitBlockAtBeginning(header);
}
// Else split in the middle
//
else
{
newTryEntry = fgSplitBlockAfterStatement(header, splitBefore->GetPrevStmt());
}
}

// update EH table, and keep track of the outermost enclosing try
//
EHblkDsc* outermostHBtab = nullptr;
for (EHblkDsc* const HBtab : EHClauses(this))
{
if (HBtab->ebdTryBeg == header)
{
fgSetTryBeg(HBtab, newTryEntry);
outermostHBtab = HBtab;
}
}
assert(outermostHBtab != nullptr);

// Recompute preheader placement
//
const unsigned enclosingTryIndex = outermostHBtab->ebdEnclosingTryIndex;

if (enclosingTryIndex == EHblkDsc::NO_ENCLOSING_INDEX)
{
header->clearTryIndex();
}
else
{
header->setTryIndex(enclosingTryIndex);
}

assert(!bbIsTryBeg(header));
return true;
}

//-----------------------------------------------------------------------------
// optCanonicalizeExits: Canonicalize all regular exits of the loop so that
// they have only loop predecessors.
Expand Down
Loading