-
Notifications
You must be signed in to change notification settings - Fork 16k
[LV] Vectorize early exit loops with multiple exits. #174864
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
base: main
Are you sure you want to change the base?
Changes from 1 commit
56d823e
901e533
cc05088
dba117a
4cea6d9
ac29adb
d6d1c61
ce012e9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -873,33 +873,28 @@ void VPlanTransforms::handleEarlyExits(VPlan &Plan, | |
| auto *LatchVPBB = cast<VPBasicBlock>(MiddleVPBB->getSinglePredecessor()); | ||
| VPBlockBase *HeaderVPB = cast<VPBasicBlock>(LatchVPBB->getSuccessors()[1]); | ||
|
|
||
| // Disconnect all early exits from the loop leaving it with a single exit from | ||
| // the latch. Early exits that are countable are left for a scalar epilog. The | ||
| // condition of uncountable early exits (currently at most one is supported) | ||
| // is fused into the latch exit, and used to branch from middle block to the | ||
| // early exit destination. | ||
| [[maybe_unused]] bool HandledUncountableEarlyExit = false; | ||
| // Disconnect countable early exits from the loop, leaving it with a single | ||
| // exit from the latch. Countable early exits are left for a scalar epilog. | ||
| // When there are uncountable early exits, skip this loop entirely - they are | ||
| // handled separately in handleUncountableEarlyExits. | ||
| for (VPIRBasicBlock *EB : Plan.getExitBlocks()) { | ||
| for (VPBlockBase *Pred : to_vector(EB->getPredecessors())) { | ||
| if (Pred == MiddleVPBB) | ||
| if (Pred == MiddleVPBB || HasUncountableEarlyExit) | ||
| continue; | ||
| if (HasUncountableEarlyExit) { | ||
| assert(!HandledUncountableEarlyExit && | ||
| "can handle exactly one uncountable early exit"); | ||
| handleUncountableEarlyExit(cast<VPBasicBlock>(Pred), EB, Plan, | ||
| cast<VPBasicBlock>(HeaderVPB), LatchVPBB); | ||
| HandledUncountableEarlyExit = true; | ||
| } else { | ||
| for (VPRecipeBase &R : EB->phis()) | ||
| cast<VPIRPhi>(&R)->removeIncomingValueFor(Pred); | ||
| } | ||
| cast<VPBasicBlock>(Pred)->getTerminator()->eraseFromParent(); | ||
|
|
||
| // Remove phi operands for the early exiting block. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you follow my suggestion above you can just add an assert here, i.e.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did not add an assertion yet for now, as we have the early exit above now |
||
| for (VPRecipeBase &R : EB->phis()) | ||
| cast<VPIRPhi>(&R)->removeIncomingValueFor(Pred); | ||
| auto *EarlyExitingVPBB = cast<VPBasicBlock>(Pred); | ||
| EarlyExitingVPBB->getTerminator()->eraseFromParent(); | ||
| VPBlockUtils::disconnectBlocks(Pred, EB); | ||
| } | ||
| } | ||
|
|
||
| assert((!HasUncountableEarlyExit || HandledUncountableEarlyExit) && | ||
| "missed an uncountable exit that must be handled"); | ||
| if (HasUncountableEarlyExit) { | ||
| handleUncountableEarlyExits(Plan, cast<VPBasicBlock>(HeaderVPB), LatchVPBB, | ||
| MiddleVPBB); | ||
| } | ||
| } | ||
|
|
||
| void VPlanTransforms::addMiddleCheck(VPlan &Plan, | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -3944,75 +3944,147 @@ void VPlanTransforms::convertToConcreteRecipes(VPlan &Plan) { | |||||
| R->eraseFromParent(); | ||||||
| } | ||||||
|
|
||||||
| void VPlanTransforms::handleUncountableEarlyExit(VPBasicBlock *EarlyExitingVPBB, | ||||||
| VPBasicBlock *EarlyExitVPBB, | ||||||
| VPlan &Plan, | ||||||
| VPBasicBlock *HeaderVPBB, | ||||||
| VPBasicBlock *LatchVPBB) { | ||||||
| auto *MiddleVPBB = cast<VPBasicBlock>(LatchVPBB->getSuccessors()[0]); | ||||||
| if (!EarlyExitVPBB->getSinglePredecessor() && | ||||||
| EarlyExitVPBB->getPredecessors()[1] == MiddleVPBB) { | ||||||
| assert(EarlyExitVPBB->getNumPredecessors() == 2 && | ||||||
| EarlyExitVPBB->getPredecessors()[0] == EarlyExitingVPBB && | ||||||
| "unsupported early exit VPBB"); | ||||||
| // Early exit operand should always be last phi operand. If EarlyExitVPBB | ||||||
| // has two predecessors and EarlyExitingVPBB is the first, swap the operands | ||||||
| // of the phis. | ||||||
| for (VPRecipeBase &R : EarlyExitVPBB->phis()) | ||||||
| cast<VPIRPhi>(&R)->swapOperands(); | ||||||
| } | ||||||
| void VPlanTransforms::handleUncountableEarlyExits(VPlan &Plan, | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I haven't looked at this yet, but I'll try to review this code later today! |
||||||
| VPBasicBlock *HeaderVPBB, | ||||||
| VPBasicBlock *LatchVPBB, | ||||||
| VPBasicBlock *MiddleVPBB) { | ||||||
| struct EarlyExitInfo { | ||||||
| VPBasicBlock *EarlyExitingVPBB; | ||||||
| VPIRBasicBlock *EarlyExitVPBB; | ||||||
| VPValue *CondToExit; | ||||||
| }; | ||||||
|
|
||||||
| VPBuilder Builder(LatchVPBB->getTerminator()); | ||||||
| VPBlockBase *TrueSucc = EarlyExitingVPBB->getSuccessors()[0]; | ||||||
| assert(match(EarlyExitingVPBB->getTerminator(), m_BranchOnCond()) && | ||||||
| "Terminator must be be BranchOnCond"); | ||||||
| VPValue *CondOfEarlyExitingVPBB = | ||||||
| EarlyExitingVPBB->getTerminator()->getOperand(0); | ||||||
| auto *CondToEarlyExit = TrueSucc == EarlyExitVPBB | ||||||
| ? CondOfEarlyExitingVPBB | ||||||
| : Builder.createNot(CondOfEarlyExitingVPBB); | ||||||
|
|
||||||
| // Create a BranchOnTwoConds in the latch that branches to: | ||||||
| // [0] vector.early.exit, [1] middle block, [2] header (continue looping). | ||||||
| VPValue *IsEarlyExitTaken = | ||||||
| Builder.createNaryOp(VPInstruction::AnyOf, {CondToEarlyExit}); | ||||||
| VPBasicBlock *VectorEarlyExitVPBB = | ||||||
| Plan.createVPBasicBlock("vector.early.exit"); | ||||||
| VectorEarlyExitVPBB->setParent(EarlyExitVPBB->getParent()); | ||||||
|
|
||||||
| VPBlockUtils::connectBlocks(VectorEarlyExitVPBB, EarlyExitVPBB); | ||||||
|
|
||||||
| // Update the exit phis in the early exit block. | ||||||
| VPBuilder MiddleBuilder(MiddleVPBB); | ||||||
| VPBuilder EarlyExitB(VectorEarlyExitVPBB); | ||||||
| for (VPRecipeBase &R : EarlyExitVPBB->phis()) { | ||||||
| auto *ExitIRI = cast<VPIRPhi>(&R); | ||||||
| // Early exit operand should always be last, i.e., 0 if EarlyExitVPBB has | ||||||
| // a single predecessor and 1 if it has two. | ||||||
| unsigned EarlyExitIdx = ExitIRI->getNumOperands() - 1; | ||||||
| if (ExitIRI->getNumOperands() != 1) { | ||||||
| // The first of two operands corresponds to the latch exit, via MiddleVPBB | ||||||
| // predecessor. Extract its final lane. | ||||||
| ExitIRI->extractLastLaneOfLastPartOfFirstOperand(MiddleBuilder); | ||||||
| SmallVector<EarlyExitInfo> Exits; | ||||||
| for (VPIRBasicBlock *EB : Plan.getExitBlocks()) { | ||||||
| for (VPBlockBase *Pred : to_vector(EB->getPredecessors())) { | ||||||
| if (Pred == MiddleVPBB) | ||||||
| continue; | ||||||
| // Collect condition for this early exit. | ||||||
| auto *EarlyExitingVPBB = cast<VPBasicBlock>(Pred); | ||||||
| VPBlockBase *TrueSucc = EarlyExitingVPBB->getSuccessors()[0]; | ||||||
| assert(match(EarlyExitingVPBB->getTerminator(), m_BranchOnCond()) && | ||||||
| "Terminator must be BranchOnCond"); | ||||||
| VPValue *CondOfEarlyExitingVPBB = | ||||||
| EarlyExitingVPBB->getTerminator()->getOperand(0); | ||||||
| auto *CondToEarlyExit = TrueSucc == EB | ||||||
| ? CondOfEarlyExitingVPBB | ||||||
| : Builder.createNot(CondOfEarlyExitingVPBB); | ||||||
| Exits.push_back({ | ||||||
| EarlyExitingVPBB, | ||||||
| EB, | ||||||
| CondToEarlyExit, | ||||||
| }); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // Sort exits by dominance to get the correct program order. | ||||||
| VPDominatorTree VPDT(Plan); | ||||||
| llvm::sort(Exits, [&VPDT](const EarlyExitInfo &A, const EarlyExitInfo &B) { | ||||||
| return VPDT.dominates(A.EarlyExitingVPBB, B.EarlyExitingVPBB); | ||||||
| }); | ||||||
|
|
||||||
| VPValue *IncomingFromEarlyExit = ExitIRI->getOperand(EarlyExitIdx); | ||||||
| if (!isa<VPIRValue>(IncomingFromEarlyExit)) { | ||||||
| // Update the incoming value from the early exit. | ||||||
| VPValue *FirstActiveLane = EarlyExitB.createNaryOp( | ||||||
| VPInstruction::FirstActiveLane, {CondToEarlyExit}, | ||||||
| DebugLoc::getUnknown(), "first.active.lane"); | ||||||
| IncomingFromEarlyExit = EarlyExitB.createNaryOp( | ||||||
| VPInstruction::ExtractLane, {FirstActiveLane, IncomingFromEarlyExit}, | ||||||
| DebugLoc::getUnknown(), "early.exit.value"); | ||||||
| ExitIRI->setOperand(EarlyExitIdx, IncomingFromEarlyExit); | ||||||
| // Build the AnyOf condition for the latch terminator. For multiple exits, | ||||||
| // also create an exit dispatch block to determine which exit to take. | ||||||
| VPValue *Combined = Exits[0].CondToExit; | ||||||
| for (const auto &Exit : drop_begin(Exits)) | ||||||
| Combined = Builder.createOr(Combined, Exit.CondToExit); | ||||||
| VPValue *IsAnyExitTaken = | ||||||
| Builder.createNaryOp(VPInstruction::AnyOf, {Combined}); | ||||||
|
|
||||||
| VPSymbolicValue FirstActiveLane; | ||||||
| // Process exits in reverse order so phi operands are added in the order | ||||||
| // matching the original program order (last exit's operand added first | ||||||
| // becomes last). The vector is reversed afterwards to restore forward order | ||||||
| // for the dispatch logic. | ||||||
| SmallVector<VPBasicBlock *> VectorEarlyExitVPBBs; | ||||||
| for (const auto &[EarlyExitingVPBB, EarlyExitVPBB, CondToExit] : | ||||||
| reverse(Exits)) { | ||||||
| VPBasicBlock *VectorEarlyExitVPBB = | ||||||
| Plan.createVPBasicBlock("vector.early.exit"); | ||||||
| VectorEarlyExitVPBB->setParent(EarlyExitVPBB->getParent()); | ||||||
| VectorEarlyExitVPBBs.push_back(VectorEarlyExitVPBB); | ||||||
|
|
||||||
| for (VPRecipeBase &R : EarlyExitVPBB->phis()) { | ||||||
| auto *ExitIRI = cast<VPIRPhi>(&R); | ||||||
| VPValue *IncomingVal = | ||||||
| ExitIRI->getIncomingValueForBlock(EarlyExitingVPBB); | ||||||
|
|
||||||
| // Compute the incoming value for this early exit. | ||||||
| VPValue *NewIncoming = IncomingVal; | ||||||
| if (!isa<VPIRValue>(IncomingVal)) { | ||||||
| VPBuilder EarlyExitB(VectorEarlyExitVPBB); | ||||||
| NewIncoming = EarlyExitB.createNaryOp( | ||||||
| VPInstruction::ExtractLane, {&FirstActiveLane, IncomingVal}, | ||||||
| DebugLoc::getUnknown(), "early.exit.value"); | ||||||
| } | ||||||
| ExitIRI->removeIncomingValueFor(EarlyExitingVPBB); | ||||||
| // Add the new incoming value for this early exit. | ||||||
| ExitIRI->addOperand(NewIncoming); | ||||||
| } | ||||||
|
|
||||||
| EarlyExitingVPBB->getTerminator()->eraseFromParent(); | ||||||
| VPBlockUtils::disconnectBlocks(EarlyExitingVPBB, EarlyExitVPBB); | ||||||
| VPBlockUtils::connectBlocks(VectorEarlyExitVPBB, EarlyExitVPBB); | ||||||
| } | ||||||
| VectorEarlyExitVPBBs = to_vector(llvm::reverse(VectorEarlyExitVPBBs)); | ||||||
|
|
||||||
| // Replace the conditional branch controlling the latch exit from the vector | ||||||
| // loop with a multi-conditional branch exiting to vector early exit if the | ||||||
| // early exit has been taken, exiting to middle block if the original | ||||||
| // condition of the vector latch is true, otherwise continuing back to header. | ||||||
| // For exit blocks that also have the middle block as predecessor (latch | ||||||
| // exit to the same block as an early exit), extract the last lane of the | ||||||
|
||||||
| // exit to the same block as an early exit), extract the last lane of the | |
| // exits to the same block as an early exit), extract the last lane of the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated, thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When
HasUncountableEarlyExit=true, rather than constructing lists of all the blocks and their predecessors only to ignore them, why not just bail out early? For example,There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, updated, thanks