Skip to content

Commit 657865f

Browse files
Always create loop pre headers (#83956)
* Always create loop pre-header As part of finding natural loops and creating the loop table, create a loop pre-header for every loop. This simplifies a lot of downstream phases, as the loop pre-header will be guaranteed to exist, and will already exist in the dominator tree. Introduce code to preserve an empty pre-header block through the optimization phases. Remove now unnecessary code in hoisting and elsewhere. Fixes #77033, #62665 * Fix loop unrolling to work with loop pre-headers * Add `optLoopsRequirePreHeaders` variable * Prevent removing pre-header blocks * Allow removing unreachable pre-headers Disallow creating pre-header after SSA is built * Make optLoopCloningEnabled() static * Teach loop cloning to expect and respect loop pre-headers * Remove special case pre-header handling in hoisting * Remove unused SSA update code in fgCreateLoopPreHeader * Remove unneeded pre-header code from fgDominate * Remove workaround to avoid extraneous LSRA diffs due to bbNum ordering * Update comments * Improve loop table rebuilding with pre-headers When the loop table is built, it looks around for various loop patterns, including looking for a guaranteed-executed, pre-loop constant initializer. This is used in loop cloning and loop unrolling. It needs to look "a little harder" in the case we created loop pre-headers, then rebuild the loop table (currently, only due to loop unrolling of loops that contain nested loops). The new code only allows for empty pre-headers. This works since in our current phase ordering, no hoisting happens by the time the loop table is rebuilt. (Actually, it's currently not necessary to do this at all, since the constant initializer info is only used by cloning and loop unrolling, both of which have finished by the time the loop table is rebuilt. However, we might someday choose to rebuild the loop table after cloning and before unrolling, at which point it would be necessary.) * Update comments
1 parent aefdc63 commit 657865f

File tree

9 files changed

+334
-384
lines changed

9 files changed

+334
-384
lines changed

src/coreclr/jit/block.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ enum BasicBlockFlags : unsigned __int64
541541
// Flags to update when two blocks are compacted
542542

543543
BBF_COMPACT_UPD = BBF_GC_SAFE_POINT | BBF_HAS_JMP | BBF_HAS_IDX_LEN | BBF_HAS_MD_IDX_LEN | BBF_BACKWARD_JUMP | \
544-
BBF_HAS_NEWOBJ | BBF_HAS_NULLCHECK | BBF_HAS_MDARRAYREF,
544+
BBF_HAS_NEWOBJ | BBF_HAS_NULLCHECK | BBF_HAS_MDARRAYREF | BBF_LOOP_PREHEADER,
545545

546546
// Flags a block should not have had before it is split.
547547

src/coreclr/jit/compiler.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5003,8 +5003,9 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl
50035003

50045004
// Dominator and reachability sets are no longer valid.
50055005
// The loop table is no longer valid.
5006-
fgDomsComputed = false;
5007-
optLoopTableValid = false;
5006+
fgDomsComputed = false;
5007+
optLoopTableValid = false;
5008+
optLoopsRequirePreHeaders = false;
50085009

50095010
#ifdef DEBUG
50105011
DoPhase(this, PHASE_STRESS_SPLIT_TREE, &Compiler::StressSplitTree);

src/coreclr/jit/compiler.h

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5232,7 +5232,7 @@ class Compiler
52325232
void vnPrint(ValueNum vn, unsigned level);
52335233
#endif
52345234

5235-
bool fgDominate(BasicBlock* b1, BasicBlock* b2); // Return true if b1 dominates b2
5235+
bool fgDominate(const BasicBlock* b1, const BasicBlock* b2); // Return true if b1 dominates b2
52365236

52375237
// Dominator computation member functions
52385238
// Not exposed outside Compiler
@@ -6193,7 +6193,7 @@ class Compiler
61936193
bool optHoistLoopNest(unsigned lnum, LoopHoistContext* hoistCtxt);
61946194

61956195
// Do hoisting for a particular loop ("lnum" is an index into the optLoopTable.)
6196-
bool optHoistThisLoop(unsigned lnum, LoopHoistContext* hoistCtxt, BasicBlockList* existingPreHeaders);
6196+
bool optHoistThisLoop(unsigned lnum, LoopHoistContext* hoistCtxt);
61976197

61986198
// Hoist all expressions in "blocks" that are invariant in loop "loopNum" (an index into the optLoopTable)
61996199
// outside of that loop.
@@ -6262,7 +6262,6 @@ class Compiler
62626262

62636263
PhaseStatus optCloneLoops();
62646264
void optCloneLoop(unsigned loopInd, LoopCloneContext* context);
6265-
void optEnsureUniqueHead(unsigned loopInd, weight_t ambientWeight);
62666265
PhaseStatus optUnrollLoops(); // Unrolls loops (needs to have cost info)
62676266
void optRemoveRedundantZeroInits();
62686267
PhaseStatus optIfConversion(); // If conversion
@@ -6339,8 +6338,6 @@ class Compiler
63396338
int lpLoopVarFPCount; // The register count for the FP LclVars that are read/written inside this loop
63406339
int lpVarInOutFPCount; // The register count for the FP LclVars that are alive inside or across this loop
63416340

6342-
bool lpHoistAddedPreheader; // The loop preheader was added during hoisting
6343-
63446341
typedef JitHashTable<CORINFO_FIELD_HANDLE, JitPtrKeyFuncs<struct CORINFO_FIELD_STRUCT_>, FieldKindForVN>
63456342
FieldHandleSet;
63466343
FieldHandleSet* lpFieldsModified; // This has entries for all static field and object instance fields modified
@@ -6512,10 +6509,11 @@ class Compiler
65126509
bool fgHasLoops; // True if this method has any loops, set in fgComputeReachability
65136510

65146511
public:
6515-
LoopDsc* optLoopTable; // loop descriptor table
6516-
bool optLoopTableValid; // info in loop table should be valid
6517-
unsigned char optLoopCount; // number of tracked loops
6518-
unsigned char loopAlignCandidates; // number of loops identified for alignment
6512+
LoopDsc* optLoopTable; // loop descriptor table
6513+
bool optLoopTableValid; // info in loop table should be valid
6514+
bool optLoopsRequirePreHeaders; // Do we require that all loops (in the loop table) have pre-headers?
6515+
unsigned char optLoopCount; // number of tracked loops
6516+
unsigned char loopAlignCandidates; // number of loops identified for alignment
65196517

65206518
// Every time we rebuild the loop table, we increase the global "loop epoch". Any loop indices or
65216519
// loop table pointers from the previous epoch are invalid.
@@ -6575,7 +6573,7 @@ class Compiler
65756573
bool optComputeIterInfo(GenTree* incr, BasicBlock* from, BasicBlock* to, unsigned* pIterVar);
65766574
bool optPopulateInitInfo(unsigned loopInd, BasicBlock* initBlock, GenTree* init, unsigned iterVar);
65776575
bool optExtractInitTestIncr(
6578-
BasicBlock* head, BasicBlock* bottom, BasicBlock* exit, GenTree** ppInit, GenTree** ppTest, GenTree** ppIncr);
6576+
BasicBlock** pInitBlock, BasicBlock* bottom, BasicBlock* exit, GenTree** ppInit, GenTree** ppTest, GenTree** ppIncr);
65796577

65806578
void optFindNaturalLoops();
65816579

@@ -6642,7 +6640,7 @@ class Compiler
66426640
bool optIsLoopEntry(BasicBlock* block) const;
66436641

66446642
// The depth of the loop described by "lnum" (an index into the loop table.) (0 == top level)
6645-
unsigned optLoopDepth(unsigned lnum)
6643+
unsigned optLoopDepth(unsigned lnum) const
66466644
{
66476645
assert(lnum < optLoopCount);
66486646
unsigned depth = 0;
@@ -7650,7 +7648,7 @@ class Compiler
76507648
bool optCheckLoopCloningGDVTestProfitable(GenTreeOp* guard, LoopCloneVisitorInfo* info);
76517649
bool optIsHandleOrIndirOfHandle(GenTree* tree, GenTreeFlags handleType);
76527650

7653-
bool optLoopCloningEnabled();
7651+
static bool optLoopCloningEnabled();
76547652

76557653
#ifdef DEBUG
76567654
void optDebugLogLoopCloning(BasicBlock* block, Statement* insertBefore);

src/coreclr/jit/fgbasic.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4971,6 +4971,8 @@ void Compiler::fgRemoveBlock(BasicBlock* block, bool unreachable)
49714971

49724972
JITDUMP("fgRemoveBlock " FMT_BB ", unreachable=%s\n", block->bbNum, dspBool(unreachable));
49734973

4974+
assert(unreachable || !optLoopsRequirePreHeaders || ((block->bbFlags & BBF_LOOP_PREHEADER) == 0));
4975+
49744976
// If we've cached any mappings from switch blocks to SwitchDesc's (which contain only the
49754977
// *unique* successors of the switch block), invalidate that cache, since an entry in one of
49764978
// the SwitchDescs might be removed.

src/coreclr/jit/fgdiagnostic.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4335,6 +4335,7 @@ void Compiler::fgDebugCheckSsa()
43354335
// - All basic blocks with loop numbers set have a corresponding loop in the table
43364336
// - All basic blocks without a loop number are not in a loop
43374337
// - All parents of the loop with the block contain that block
4338+
// - If optLoopsRequirePreHeaders is true, the loop has a pre-header
43384339
// - If the loop has a pre-header, it is valid
43394340
// - The loop flags are valid
43404341
// - no loop shares `top` with any of its children
@@ -4593,6 +4594,11 @@ void Compiler::fgDebugCheckLoopTable()
45934594
}
45944595
}
45954596

4597+
if (optLoopsRequirePreHeaders)
4598+
{
4599+
assert((loop.lpFlags & LPFLG_HAS_PREHEAD) != 0);
4600+
}
4601+
45964602
// If the loop has a pre-header, ensure the pre-header form is correct.
45974603
if ((loop.lpFlags & LPFLG_HAS_PREHEAD) != 0)
45984604
{

src/coreclr/jit/fgflow.cpp

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -361,27 +361,20 @@ void Compiler::fgRemoveBlockAsPred(BasicBlock* block)
361361
fgRemoveRefPred(bNext, bNext->bbPreds->getSourceBlock());
362362
}
363363
}
364+
fgRemoveRefPred(block->bbJumpDest, block);
365+
break;
364366

365-
FALLTHROUGH;
366-
367-
case BBJ_COND:
368367
case BBJ_ALWAYS:
369368
case BBJ_EHCATCHRET:
370-
371-
/* Update the predecessor list for 'block->bbJumpDest' and 'block->bbNext' */
372369
fgRemoveRefPred(block->bbJumpDest, block);
373-
374-
if (block->bbJumpKind != BBJ_COND)
375-
{
376-
break;
377-
}
378-
379-
/* If BBJ_COND fall through */
380-
FALLTHROUGH;
370+
break;
381371

382372
case BBJ_NONE:
373+
fgRemoveRefPred(block->bbNext, block);
374+
break;
383375

384-
/* Update the predecessor list for 'block->bbNext' */
376+
case BBJ_COND:
377+
fgRemoveRefPred(block->bbJumpDest, block);
385378
fgRemoveRefPred(block->bbNext, block);
386379
break;
387380

src/coreclr/jit/fgopt.cpp

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
// Assumptions:
2727
// -- Dominators have been calculated (`fgDomsComputed` is true).
2828
//
29-
bool Compiler::fgDominate(BasicBlock* b1, BasicBlock* b2)
29+
bool Compiler::fgDominate(const BasicBlock* b1, const BasicBlock* b2)
3030
{
3131
noway_assert(fgDomsComputed);
3232

@@ -60,17 +60,6 @@ bool Compiler::fgDominate(BasicBlock* b1, BasicBlock* b2)
6060

6161
if (b1->bbNum > fgDomBBcount)
6262
{
63-
// If b1 is a loop preheader (that was created after the dominators were calculated),
64-
// then it has a single successor that is the loop entry, and it is the only non-loop
65-
// predecessor of the loop entry. Thus, b1 dominates the loop entry and also dominates
66-
// what the loop entry dominates.
67-
if (b1->bbFlags & BBF_LOOP_PREHEADER)
68-
{
69-
BasicBlock* loopEntry = b1->GetUniqueSucc();
70-
assert(loopEntry != nullptr);
71-
return fgDominate(loopEntry, b2);
72-
}
73-
7463
// unknown dominators; err on the safe side and return false
7564
return false;
7665
}
@@ -1947,6 +1936,17 @@ bool Compiler::fgCanCompactBlocks(BasicBlock* block, BasicBlock* bNext)
19471936
return false;
19481937
}
19491938

1939+
// Don't allow removing an empty loop pre-header.
1940+
// We can compact a pre-header `bNext` into an empty `block` since BBF_COMPACT_UPD propagates
1941+
// BBF_LOOP_PREHEADER to `block`.
1942+
if (optLoopsRequirePreHeaders)
1943+
{
1944+
if (((block->bbFlags & BBF_LOOP_PREHEADER) != 0) && (bNext->countOfInEdges() != 1))
1945+
{
1946+
return false;
1947+
}
1948+
}
1949+
19501950
// Don't compact the first block if it was specially created as a scratch block.
19511951
if (fgBBisScratch(block))
19521952
{
@@ -1983,16 +1983,14 @@ bool Compiler::fgCanCompactBlocks(BasicBlock* block, BasicBlock* bNext)
19831983
}
19841984
}
19851985

1986-
// We cannot compact a block that participates in loop
1987-
// alignment.
1986+
// We cannot compact a block that participates in loop alignment.
19881987
//
19891988
if ((bNext->countOfInEdges() > 1) && bNext->isLoopAlign())
19901989
{
19911990
return false;
19921991
}
19931992

1994-
// If we are trying to compact blocks from different loops
1995-
// that don't do it.
1993+
// Don't compact blocks from different loops.
19961994
//
19971995
if ((block->bbNatLoopNum != BasicBlock::NOT_IN_LOOP) && (bNext->bbNatLoopNum != BasicBlock::NOT_IN_LOOP) &&
19981996
(block->bbNatLoopNum != bNext->bbNatLoopNum))
@@ -2058,8 +2056,13 @@ void Compiler::fgCompactBlocks(BasicBlock* block, BasicBlock* bNext)
20582056
JITDUMP("Second block has %u other incoming edges\n", bNext->countOfInEdges());
20592057
assert(block->isEmpty());
20602058

2061-
// `block` can no longer be a loop pre-header (if it was before).
2059+
// When loops require pre-headers, `block` cannot be a pre-header.
2060+
// We should have screened this out in fgCanCompactBlocks().
2061+
//
2062+
// When pre-headers are not required, then if `block` was a pre-header,
2063+
// it no longer is.
20622064
//
2065+
assert(!optLoopsRequirePreHeaders || ((block->bbFlags & BBF_LOOP_PREHEADER) == 0));
20632066
block->bbFlags &= ~BBF_LOOP_PREHEADER;
20642067

20652068
// Retarget all the other edges incident on bNext. Do this
@@ -3053,6 +3056,15 @@ bool Compiler::fgOptimizeEmptyBlock(BasicBlock* block)
30533056
break;
30543057
}
30553058

3059+
// Don't delete empty loop pre-headers.
3060+
if (optLoopsRequirePreHeaders)
3061+
{
3062+
if ((block->bbFlags & BBF_LOOP_PREHEADER) != 0)
3063+
{
3064+
break;
3065+
}
3066+
}
3067+
30563068
/* special case if this is the last BB */
30573069
if (block == fgLastBB)
30583070
{
@@ -6027,8 +6039,9 @@ PhaseStatus Compiler::fgUpdateFlowGraphPhase()
60276039

60286040
// Dominator and reachability sets are no longer valid.
60296041
// The loop table is no longer valid.
6030-
fgDomsComputed = false;
6031-
optLoopTableValid = false;
6042+
fgDomsComputed = false;
6043+
optLoopTableValid = false;
6044+
optLoopsRequirePreHeaders = false;
60326045

60336046
return madeChanges ? PhaseStatus::MODIFIED_EVERYTHING : PhaseStatus::MODIFIED_NOTHING;
60346047
}

0 commit comments

Comments
 (0)