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?

84

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

91

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

105

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

107

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

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.

336

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

359

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

362–363

nit: assert VPBB and SuccVPBB have same parent.

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

362–363

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

362–363

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

362–363

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

367

SuccVPBB >> ExitVPBB, or OutOfRegionSuccVPBB.

389

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.

392

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.

84

Moved out, thanks!

91

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

105

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.

107

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.

147

This was needed after changing the map, undone now.

163

added, thanks!

170

This code is not needed in the latest version

336

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.

362–363

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
362–363

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

362–363

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.

367

adjusted, thanks!

389

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

392

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
109

Comment needs updating.

112

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.

112

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.

161–162

CurrentLoop >> LoopOfBB
ParentR >> RegionOfVPBB

161–162

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?

340

Simpler to do something like

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

?

360

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

362–363

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

366

else {

Already asserted above that numSuccs is 1 or 2.

367

Make sure first successor corresponds to True?

371

nit: count >> contains

374

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

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
109

Updated, thanks!

112

Reordered assert, added message, thanks!

112

Adjusted to check the parent region, thanks!

161–162

Renamed, thanks!

161–162

adjusted, thanks!

166

Moved the code to the earlier if

170

That should be handled by setEntry IIUC

325

Added a comment, thanks!

340

Simplified, thanks!

360

Flipped, thanks!

366

Adjusted, thanks!

367

Added assert, thanks!

371

Adjusted, thanks!

374

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
155

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

161–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
349

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

377

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

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
161–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!

349

reordered, thanks!

377

Adjusted, thanks!

382

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
85–99

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

?

110

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});
161–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
377

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
85–99

update, thanks!

110

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

161–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.

377

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
89

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

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

110

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

161–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.

349–351

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
89

Right, adjusted, thanks!

175

Updated, thanks!

182

added, thanks!