This is an archive of the discontinued LLVM Phabricator instance.

[VPlan] Simplify HCFG construction of region blocks (NFC).
ClosedPublic

Authored by fhahn on Aug 29 2023, 1:39 PM.

Details

Summary

Update the logic to update the successors and predecessors of region
blocks directly. This adds special handling for header and latch blocks
in place, and removes the separate loop to fix up the region blocks.

Helps to simplify D158333.

Diff Detail

Event Timeline

fhahn created this revision.Aug 29 2023, 1:39 PM
Herald added a project: Restricted Project. · View Herald TranscriptAug 29 2023, 1:39 PM
fhahn requested review of this revision.Aug 29 2023, 1:39 PM
Herald added a project: Restricted Project. · View Herald TranscriptAug 29 2023, 1:39 PM
Herald added subscribers: wangpc, vkmr. · View Herald Transcript
Ayal added a comment.Aug 31 2023, 5:25 AM

Title should indicate this is an HCFG simplification patch.

Worth indicating that this refactoring assists D158333.

llvm/lib/Transforms/Vectorize/VPlanHCFGBuilder.cpp
53

Mapping BB's to their VPBB's except for header BB's that are mapped to the enclosing VPRegion may be confusing (exacerbated by reusing VPBB or using VPB for VPBlockBase), and should be documented.

Would it be better to keep BB2VPBB as is, mapping BB's to their VPBasicBlocks, and augment it with a getRegionOfHeader() function that checks if a VPBB is the entry of its enclosing region and if so returns the region, otherwise returns the given VPBasicBlock?

85

Would two methods be clearer, one taking a VPBasicBlock and the other a VPRegionBlock?

90

Should avoid connecting VPBBPred-VPBB that are latch-header, handled above?

92

Two cases:

  1. VPBBPred's parent == VPBB's grandparent: VPBBPred-VPBB are preheader-header, handled above?
  2. VPBBPred's grandparent == VPBB's parent: VPBBPred-VPBB are latch-exit, handled here?
92

Can be simplified - trying to select one of two expected predecessors?

135

Independent cleanup?

163

nit: deserves brackets.

170

This check if BB is a header should probably be generalized (e.g., into isHeader(BasicBlock*)) and used below.

335

Why/Is the above change needed? HeaderVPBB sounds like a VPBasicBlock, rather than its region.

357

nit: "during the RPO traversal" - Recipes will be created for each successor when it is reached later during this RPO traversal.

357–358

nit: assert VPBB and SuccVPBB have same parent.

nit: set predecessors (checking if header?) and early continue;

357–358

Worth dealing here with four cases explicitly: BB is header or not, BB is latch or not?

Setting predecessors: easier to check if BB is header of its loop, and if so hook the region of its VPBB (setting its entry) to a single predecessor - the VPBB of preheader - having a distinct Region? Otherwise setVPBBPredsFromBB.

Setting successors: easier to check if BB is latch of its loop, and if so hook the region of its VPBB (setting its exit) to a single successor - the VPBB of exit - having a distinct Region? Otherwise set VPBB's successor(s).

357–358

Better to check if BB is a header before calling setVPBBPredsFromBB?

357–358

nit: consider asserting at the outset that NumSuccs is 1 or 2, to save indentation.

362

SuccVPBB >> ExitVPBB, or OutOfRegionSuccVPBB.

395

An empty VPBB was created, and set as successor, but not hooked up to its predecessor, which is done here. Setting predecessors separately from setting successors (rather than together as bidirectional edges), presumably to keep predecessors in order.
Instructions of loop exit weren't visited, nor are they visited here.

398

nit: independent cleanup?

fhahn edited the summary of this revision. (Show Details)Sep 1 2023, 4:39 AM
fhahn updated this revision to Diff 555331.Sep 1 2023, 4:40 AM
fhahn marked 14 inline comments as done.

Address latest comments and update description, thanks!

fhahn marked 2 inline comments as done.Sep 1 2023, 4:42 AM
fhahn added inline comments.
llvm/lib/Transforms/Vectorize/VPlanHCFGBuilder.cpp
53

Undone changes here, getOrCreateVPB checks if the created VPBB is the entry for a region. I still retained getOrCreateVPB, because it is convenient to get the block to set the successors, e.g. when connecting the pre-header to the inner loop header.

85

Moved out, thanks!

90

Yes, the check here is to catch setting the predecessor for the exit block, as it needs to connect to the predecessor region. Added a comment.

92

In the else case, VPBB Is the exit block of PredVPBB's region, which isn't handled elsewhere (checking elsewhere would be more complicated I think.

92

Retained loop to add an assert that we only have. single predecessors.

135

This was needed after changing the map, undone now.

163

added, thanks!

170

This code is not needed in the latest version

335

We need to connect the pre-header to the top region and set the name of the header block (to avoid unnecessary test changes). Update to clarify that.

357–358

Added assert to setOneSuccessor and fixed an issue uncovered where the parent for regions wasn't set properly.

fhahn added inline comments.Sep 1 2023, 4:42 AM
llvm/lib/Transforms/Vectorize/VPlanHCFGBuilder.cpp
357–358

Updated and moved before setting successors. Checking if it's a header can be done by checking if the VPBB is the entry to its parent region

357–358

Updated to handle header and latch separately while setting successors. To simplify the latter, I updated the code to collect the successors in a vector, as this is used to check for the latch.

362

adjusted, thanks!

395

Yes, predecessors are only set once the block has been visited in RPO for that reason, so it needs to be done explicitly here.

398

Was only needed after changing the map, undone, thanks!

fhahn updated this revision to Diff 555332.Sep 1 2023, 4:43 AM
fhahn marked 2 inline comments as done.

Undo uneeded change

Ayal added inline comments.Sep 13 2023, 1:48 AM
llvm/lib/Transforms/Vectorize/VPlanHCFGBuilder.cpp
97

This assumes/asserts BB is not a header block, with a preheader PredVPBB having VPBB's region as a single successor.

This assumes successors have already been set, before setting predecessors.

nit: assert first; add error message.

108

Comment needs updating.

111

Better to distinguish between latch and preheader by checking if enclosing regions are the same, as in setVPBBPredsFromBB(), rather than relying on having already set the successor(s) of preheader but not yet of the latch? The latch should have no successors even after processing it.

162–163

CurrentLoop >> LoopOfBB
ParentR >> RegionOfVPBB

162–163

May be clearer to do something like

bool RegionExists = Loop2Region.contains(CurrentLoop);
if (RegionExists)
  ParentR = Loop2Region[CurrentLoop];
else {
  ParentR = new VPRegionBlock(...);
  ParentR->setEntry(VPBB); // Or feed it in the constructor.
  Loop2Region.insert({CurrentLoop, ParentR});
}
166

nit: worth separating the two if's with an empty line.

170

Should VPBB's parent be set to ParentR also for header blocks which create the region when visited?

339

Simpler to do something like

VPBlockBase *HeaderVPBB = getOrCreateVPBB(TheLoop->getHeader());
HeaderVPBB->setName("vector.body");
ThePreheaderVPBB->setOneSuccessor(HeaderVPBB->getParent());

?

357–358

Proposed above to check for latch using parental regions than number of successors.

358

Check if BB is header of its loop according to LI, and assert that Region's entry is VPBB, rather than the other way around? I.e., have isHeaderBB() rather than isHeaderVPBB().

361

else {

Already asserted above that numSuccs is 1 or 2.

362

Make sure first successor corresponds to True?

366

nit: count >> contains

369

"one of its predecessors is the region" >> "one of its successors is the header, which is set to its region by getOrCreateVPB(), in constrast to getOrCreateVPBB()".

This is confusing.
Can we keep using getOrCreateVPBB() only, checking if BB is pre/header/latch using LI, and processing its (predecessors and) successors accordingly?

Ayal added inline comments.Sep 19 2023, 2:26 AM
llvm/lib/Transforms/Vectorize/VPlanHCFGBuilder.cpp
324

buildPlain/HierarchicalCFG() is called following VPlan::createInitialVPlan(), which creates VPBB's (and will create more, plus Region, in D158333) directly rather than via getOrCreateVPBB(); so need to revisit them and update BB2VPBB, Loop2Region, setParent. Worth a comment.

fhahn updated this revision to Diff 557059.Sep 19 2023, 10:43 AM
fhahn marked 14 inline comments as done.

Address latest comments, thanks!

llvm/lib/Transforms/Vectorize/VPlanHCFGBuilder.cpp
97

Reordered assert, added message, thanks!

108

Updated, thanks!

111

Adjusted to check the parent region, thanks!

162–163

Renamed, thanks!

162–163

adjusted, thanks!

166

Moved the code to the earlier if

170

That should be handled by setEntry IIUC

324

Added a comment, thanks!

339

Simplified, thanks!

358

Flipped, thanks!

361

Adjusted, thanks!

362

Added assert, thanks!

366

Adjusted, thanks!

369

Updated to us LI to check for latch and use early continue.

Ayal added inline comments.Sep 19 2023, 2:19 PM
llvm/lib/Transforms/Vectorize/VPlanHCFGBuilder.cpp
143

isHeaderVPBB used only in assert, define under !NDEBUG?
Suggest below to reuse it outside of asserts.

162

nit: BlockIt is not needed below, can simply check

// Retrieve VPBB if exists.
if (BB2VPBB.contains(BB))
  return BB2VPBB[BB];
171

nit: can early exit here via

if (!LoopOfBB)
  return VPBB;

and define RegionOfVPBB inside then/else when setting it.

174

nit: assert BB-is-header xor RegionExists?

180

VPBB->setParent(RegionOfVPBB);
here too?

181

When called with BB being the header of TheLoop, its parentLoop may be null or not; in any case, will Loop2Region[TheLoop->getParentLoop()] surely return null?
Can set the parent of RegionOfVPBB only if LoopOfBB != TheLoop.

183
348

nit: can handle the simpler non-header case first.

372

Trying to sketch how this could be done using only getOrCreateVPBB(), i.e., w/o getOrCreateVPB(), ordered according to simplicity:

unsigned NumSuccs = succ_size(BB);
if (NumSuccs == 1) {
  auto *Successor = getOrCreateVPBB(BB->getSingleSuccessor());
  VPBB->setOneSuccessor(isHeaderVPBB(Successor) ? Successor->getParent()
                                                : Successor);
  continue;
}

assert(NumSuccs == 2 && "block must have one or two successors");
// Look up the branch condition to get the corresponding VPValue
// representing the condition bit in VPlan (which may be in another VPBB).
assert(IRDef2VPValue.contains(BI->getCondition()) &&
       "Missing condition bit in IRDef2VPValue!");
VPBasicBlock *Successor0 = getOrCreateVPBB(BI->getSuccessor(0));
VPBasicBlock *Successor1 = getOrCreateVPBB(BI->getSuccessor(1));
if (!isLatchBB(BB, LoopForBB)) {
  VPBB->setTwoSuccessors(Successor0, Successor1);
  continue;
}
// For a latch we need to set the successor of the region rather than that
// of VPBB and it should be set to the exit, i.e., non-header successor.
Region->setOneSuccessor(isHeaderVPBB(Successor0) ? Successor1 : Successor0);
Region->setExiting(VPBB);
377

Should the parents of Region and ExitVPBB already be set, presumably to the same parent?
(As will be asserted when setting one to be the successor of the other.)

Ayal added a comment.Sep 19 2023, 3:56 PM

Title should indicate this is an HCFG simplification patch.

fhahn updated this revision to Diff 557181.Sep 21 2023, 8:14 AM
fhahn marked 9 inline comments as done.

Address latest comments, thanks!

fhahn added inline comments.Sep 21 2023, 8:17 AM
llvm/lib/Transforms/Vectorize/VPlanHCFGBuilder.cpp
162

Yes, but that would require 2 lookups I think?

171

Adjusted, thanks!

174

added, thanks!

180

this will be done by setEntry

181

It should be guaranteed to be null for TheLoop->getParentLoop(), as no entries for it will be added.

183

adjusted, thanks!

348

reordered, thanks!

372

Adjusted, thanks!

377

Yep, parent of the region is already set, removed, thanks!

fhahn retitled this revision from [VPlan] Update successors/predecessors of region blocks directly (NFC). to [VPlan] Simplify HCFG construction of region blocks (NFC)..Sep 21 2023, 8:18 AM
Ayal added inline comments.Sep 22 2023, 3:41 PM
llvm/lib/Transforms/Vectorize/VPlanHCFGBuilder.cpp
86

Perhaps the following would more clearly update the current code by handling the exit block case separately, than during traversal over all predecessors:

if (auto *LatchBB = getLatchOfExit(BB)) {
  auto *PredRegion = getOrCreateVPBB(LatchBB)->getParent();
  assert(VPBB == cast<VPBasicBlock>(PredRegion->getSingleSuccessor()) &&
         "successor must already be set for PredRegion; it must have VPBB "
         "as single successor");
  VPBB->setPredecessors({PredRegion});
  return;
}
// Collect VPBB predecessors.
SmallVector<VPBlockBase *, 2> VPBBPreds;
for (BasicBlock *Pred : predecessors(BB))
  VPBBPreds.push_back(getOrCreateVPBB(Pred));
VPBB->setPredecessors(VPBBPreds);

where a lambda or function would check isExitBB(), analogous to isHeaderBB(), possibly along with providing the Latch:

BasicBlock* getLatchOfExit(BasicBlock *BB) {
  auto *SinglePred = BB->getSinglePredecessor();
  if (!SinglePred || LI->getLoopFor(SinglePred) == LI->getLoopFor(BB))
    return nullptr;
  // can assert SinglePred is the latch of its loop.
  return SinglePred;
}

?

109

Worth augmenting the comment with a note that this loop not only searches for the preheader (for which it could early-break once found or employ find_if()) but also (gets or) creates VPBB's for (all) its latch(es).

This loop over predecessors has a trip count of 2; would unrolling it be clearer?

assert(pred_size(BB) == 2 && "A preheader and single latch are expected");
auto PredBBI = pred_begin(BB);
auto *PredVPBB0 = getOrCreateVPBB(*PredBBI);
auto *PredVPBB1 = getOrCreateVPBB(*++PredBBI);
int Pred0InRegion = PredVPBB0->getParent() == Region;
assert(Pred0InRegion ^ PredVPBB1->getParent() == Region &&
       "Expecting latch to be in region but not preheader");
Region->setPredecessors({Pred0InRegion ? PredVPBB1 : PredVPBB0});
162

Yes, but such 2 lookups should be folded by the compiler rather than the programmer...
We do use contains()/[] below for Loop2Region.
Would this work better:

// Retrieve VPBB if exists.
if (auto BlockIt = BB2VPBB.find(BB) != BB2VPBB.end())
  return BlockIt->second;

?

178–180
372

Note that checking if BB == LoopForBB->getLoopLatch() implies a single latch, compared to checking if LoopForBB->isLoopLatch(BB). This is fine, and independent of handling loops with multiple exiting blocks.

fhahn updated this revision to Diff 557272.Sep 23 2023, 1:18 PM
fhahn marked 5 inline comments as done.

Address latest comments, thanks!

fhahn added inline comments.Sep 23 2023, 1:30 PM
llvm/lib/Transforms/Vectorize/VPlanHCFGBuilder.cpp
86

update, thanks!

109

Now that we liberally use loop info, we can just use that here as well to identify the loop predecessor.

162

Yes, but such 2 lookups should be folded by the compiler rather than the programmer...

Agreed, but I think in this case the current compilers won't be able to do so :(

Would this work better:

I don't think this will work, as BlockIt would be a bool I think (result of the compare).

Probably better to use lookup, which returns nullptr if the key doesn't exist.

178–180

I think the current coding standard recommends using branches for both if and else when either requires them (https://llvm.org/docs/CodingStandards.html#don-t-use-braces-on-simple-single-statement-bodies-of-if-else-loop-statements). Left the code as is for now.

372

Ack!

Ayal accepted this revision.Sep 23 2023, 5:16 PM

This looks good to me, thanks for accommodating! Adding a couple of last minor comments.

llvm/lib/Transforms/Vectorize/VPlanHCFGBuilder.cpp
90

This comment should be replaced by an assert, or dropped.

Note (somewhere) that exit blocks are assumed to have a single predecessor.

109

Using LoopOfBB->getLoopPredecessor() is indeed much simpler!
Note that the latch VPBB will now be created (potentially) later when visiting its predecessor rather than here when visiting its successor (the header).

162

Yes, lookup looks fine!
The default value constructed in case of absence would be a nullptr, given that value type is VPBasicBlock*.
(A parenthesis might make BlockIt the desired iterator.)

175

Use lookup here as well instead of contains and []?
The default value constructed in case of absence would be a nullptr, given that value type is VPRegionBlock*.

182

Worth a comment, something like:
// If BB's loop is nested inside another loop within VPlan's scope, the header of that enclosing loop was already visited and its region constructed and recorded in Loop2Region. That region is now set as the parent of VPBB's region. Otherwise it is set to null.

348–350

Very well, let's be consistent.

This revision is now accepted and ready to land.Sep 23 2023, 5:16 PM
This revision was automatically updated to reflect the committed changes.
fhahn marked 5 inline comments as done.
fhahn added a comment.Sep 24 2023, 1:55 PM

Thanks!

llvm/lib/Transforms/Vectorize/VPlanHCFGBuilder.cpp
90

Right, adjusted, thanks!

175

Updated, thanks!

182

added, thanks!