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
52

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?

93

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

106

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

108

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?
128

Independent cleanup?

134

nit: deserves brackets.

153

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

314

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

328

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

336

nit: assert VPBB and SuccVPBB have same parent.

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

349

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).

355

SuccVPBB >> ExitVPBB, or OutOfRegionSuccVPBB.

367

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

368

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

374

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.

377

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
52

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!

93

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

106

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.

108

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.

128

This was needed after changing the map, undone now.

134

added, thanks!

153

This code is not needed in the latest version

314

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.

336

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
349

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.

355

adjusted, thanks!

368

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

374

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

377

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
113

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.

117

Comment needs updating.

120

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.

140

CurrentLoop >> LoopOfBB
ParentR >> RegionOfVPBB

142

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});
}
150

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

154

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

319

Simpler to do something like

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

?

329

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().

349

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

372

else {

Already asserted above that numSuccs is 1 or 2.

373

Make sure first successor corresponds to True?

376

nit: count >> contains

379

"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
305

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
113

Reordered assert, added message, thanks!

117

Updated, thanks!

120

Adjusted to check the parent region, thanks!

140

Renamed, thanks!

142

adjusted, thanks!

150

Moved the code to the earlier if

154

That should be handled by setEntry IIUC

305

Added a comment, thanks!

319

Simplified, thanks!

329

Flipped, thanks!

372

Adjusted, thanks!

373

Added assert, thanks!

376

Adjusted, thanks!

379

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
136

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

136–146

nit: BlockIt is not needed below, can simply check

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

nit: can early exit here via

if (!LoopOfBB)
  return VPBB;

and define RegionOfVPBB inside then/else when setting it.

157

nit: assert BB-is-header xor RegionExists?

163

VPBB->setParent(RegionOfVPBB);
here too?

164

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.

166
327

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

383

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);
388

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
136–146

Yes, but that would require 2 lookups I think?

154

Adjusted, thanks!

157

added, thanks!

163

this will be done by setEntry

164

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

166

adjusted, thanks!

327

reordered, thanks!

383

Adjusted, thanks!

388

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
87–102

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;
}

?

118

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});
136–146

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;

?

161–163
383

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
87–102

update, thanks!

118

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

136–146

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.

161–163

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.

383

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
91

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

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

118

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).

136–146

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.)

158

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*.

165

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.

327–329

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
91

Right, adjusted, thanks!

158

Updated, thanks!

165

added, thanks!