| //===-- VPlanPredicator.cpp - VPlan predicator ----------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| /// | |
| /// \file | |
| /// This file implements predication for VPlans. | |
| /// | |
| //===----------------------------------------------------------------------===// | |
| #include "VPRecipeBuilder.h" | |
| #include "VPlan.h" | |
| #include "VPlanCFG.h" | |
| #include "VPlanDominatorTree.h" | |
| #include "VPlanPatternMatch.h" | |
| #include "VPlanTransforms.h" | |
| #include "VPlanUtils.h" | |
| #include "llvm/ADT/PostOrderIterator.h" | |
| using namespace llvm; | |
| using namespace VPlanPatternMatch; | |
| namespace { | |
| class VPPredicator { | |
| VPlan &Plan; | |
| /// Builder to construct recipes to compute masks. | |
| VPBuilder Builder; | |
| /// Dominator tree for the VPlan. | |
| VPDominatorTree VPDT; | |
| /// Post-dominator tree for the VPlan. | |
| VPPostDominatorTree VPPDT; | |
| /// Post-dominator frontier for the VPlan. | |
| VPPostDominanceFrontier VPPDF; | |
| /// When we if-convert we need to create edge masks. We have to cache values | |
| /// so that we don't end up with exponential recursion/IR. | |
| using EdgeMaskCacheTy = | |
| DenseMap<std::pair<const VPBasicBlock *, const VPBasicBlock *>, | |
| VPValue *>; | |
| using BlockMaskCacheTy = DenseMap<const VPBasicBlock *, VPValue *>; | |
| EdgeMaskCacheTy EdgeMaskCache; | |
| BlockMaskCacheTy BlockMaskCache; | |
| /// Create an edge mask for every destination of cases and/or default. | |
| void createSwitchEdgeMasks(const VPInstruction *SI); | |
| /// Computes and return the predicate of the edge between \p Src and \p Dst, | |
| /// possibly inserting new recipes at \p Dst (using Builder's insertion point) | |
| VPValue *createEdgeMask(const VPBasicBlock *Src, const VPBasicBlock *Dst); | |
| /// Record \p Mask as the *entry* mask of \p VPBB, which is expected to not | |
| /// already have a mask. | |
| void setBlockInMask(const VPBasicBlock *VPBB, VPValue *Mask) { | |
| // TODO: Include the masks as operands in the predicated VPlan directly to | |
| // avoid keeping the map of masks beyond the predication transform. | |
| assert(!getBlockInMask(VPBB) && "Mask already set"); | |
| BlockMaskCache[VPBB] = Mask; | |
| } | |
| /// Record \p Mask as the mask of the edge from \p Src to \p Dst. The edge is | |
| /// expected to not have a mask already. | |
| VPValue *setEdgeMask(const VPBasicBlock *Src, const VPBasicBlock *Dst, | |
| VPValue *Mask) { | |
| assert(Src != Dst && "Src and Dst must be different"); | |
| assert(!getEdgeMask(Src, Dst) && "Mask already set"); | |
| return EdgeMaskCache[{Src, Dst}] = Mask; | |
| } | |
| /// Returns where to insert new masks in \p VPBB. | |
| VPBasicBlock::iterator getMaskInsertPoint(VPBasicBlock *VPBB) { | |
| if (VPValue *Mask = getBlockInMask(VPBB)) | |
| if (VPRecipeBase *MaskR = Mask->getDefiningRecipe()) | |
| if (MaskR->getParent() == VPBB) // In-mask may be the IDom's. | |
| return std::next(MaskR->getIterator()); | |
| return VPBB->getFirstNonPhi(); | |
| } | |
| using EdgeTy = std::pair<const VPBasicBlock *, const VPBasicBlock *>; | |
| /// Compute the set of edges that are "furthest up" in the CFG for each | |
| /// incoming value of \p Phi. | |
| MapVector<EdgeTy, VPValue *> computeBlendEdges(VPPhi *Phi); | |
| /// Given a set of \p Edges that each can reach \p VPBB, return the OR of all | |
| /// edges, or an equivalent block in-mask. | |
| VPValue *createBlendMaskForEdges(ArrayRef<EdgeTy> Edges, VPBasicBlock *VPBB); | |
| public: | |
| VPPredicator(VPlan &Plan) | |
| : Plan(Plan), VPDT(Plan), VPPDT(Plan), VPPDF(VPPDT) {} | |
| /// Returns the *entry* mask for \p VPBB. | |
| VPValue *getBlockInMask(const VPBasicBlock *VPBB) const { | |
| return BlockMaskCache.lookup(VPBB); | |
| } | |
| /// Returns the precomputed predicate of the edge from \p Src to \p Dst. | |
| VPValue *getEdgeMask(const VPBasicBlock *Src, const VPBasicBlock *Dst) const { | |
| return EdgeMaskCache.lookup({Src, Dst}); | |
| } | |
| /// Compute the predicate of \p VPBB. | |
| void createBlockInMask(VPBasicBlock *VPBB); | |
| /// Convert phi recipes in \p VPBB to VPBlendRecipes. | |
| void convertPhisToBlends(VPBasicBlock *VPBB); | |
| /// Predicate and linearize the plan. | |
| void run(); | |
| }; | |
| } // namespace | |
| VPValue *VPPredicator::createEdgeMask(const VPBasicBlock *Src, | |
| const VPBasicBlock *Dst) { | |
| assert(is_contained(Dst->getPredecessors(), Src) && "Invalid edge"); | |
| // Look for cached value. | |
| VPValue *EdgeMask = getEdgeMask(Src, Dst); | |
| if (EdgeMask) | |
| return EdgeMask; | |
| VPValue *SrcMask = getBlockInMask(Src); | |
| // If there's a single successor, there's no terminator recipe. | |
| if (Src->getNumSuccessors() == 1) | |
| return setEdgeMask(Src, Dst, SrcMask); | |
| auto *Term = cast<VPInstruction>(Src->getTerminator()); | |
| if (Term->getOpcode() == Instruction::Switch) { | |
| createSwitchEdgeMasks(Term); | |
| return getEdgeMask(Src, Dst); | |
| } | |
| assert(Term->getOpcode() == VPInstruction::BranchOnCond && | |
| "Unsupported terminator"); | |
| if (Src->getSuccessors()[0] == Src->getSuccessors()[1]) | |
| return setEdgeMask(Src, Dst, SrcMask); | |
| EdgeMask = Term->getOperand(0); | |
| assert(EdgeMask && "No Edge Mask found for condition"); | |
| if (Src->getSuccessors()[0] != Dst) | |
| EdgeMask = Builder.createNot(EdgeMask, Term->getDebugLoc()); | |
| if (SrcMask) { // Otherwise block in-mask is all-one, no need to AND. | |
| // The bitwise 'And' of SrcMask and EdgeMask introduces new UB if SrcMask | |
| // is false and EdgeMask is poison. Avoid that by using 'LogicalAnd' | |
| // instead which generates 'select i1 SrcMask, i1 EdgeMask, i1 false'. | |
| EdgeMask = Builder.createLogicalAnd(SrcMask, EdgeMask, Term->getDebugLoc()); | |
| } | |
| return setEdgeMask(Src, Dst, EdgeMask); | |
| } | |
| void VPPredicator::createBlockInMask(VPBasicBlock *VPBB) { | |
| // Start inserting after the block's phis, which be replaced by blends later. | |
| Builder.setInsertPoint(VPBB, VPBB->getFirstNonPhi()); | |
| // Reuse the mask of the immediate dominator if the VPBB post-dominates the | |
| // immediate dominator. | |
| auto *IDom = VPDT.getNode(VPBB)->getIDom(); | |
| assert(IDom && "Block in loop must have immediate dominator"); | |
| auto *IDomBB = cast<VPBasicBlock>(IDom->getBlock()); | |
| if (VPPDT.properlyDominates(VPBB, IDomBB)) { | |
| setBlockInMask(VPBB, getBlockInMask(IDomBB)); | |
| return; | |
| } | |
| // All-one mask is modelled as no-mask following the convention for masked | |
| // load/store/gather/scatter. Initialize BlockMask to no-mask. | |
| VPValue *BlockMask = nullptr; | |
| // This is the block mask. We OR all unique incoming edges. | |
| for (auto *Predecessor : SetVector<VPBlockBase *>( | |
| VPBB->getPredecessors().begin(), VPBB->getPredecessors().end())) { | |
| VPValue *EdgeMask = createEdgeMask(cast<VPBasicBlock>(Predecessor), VPBB); | |
| if (!EdgeMask) { // Mask of predecessor is all-one so mask of block is | |
| // too. | |
| setBlockInMask(VPBB, EdgeMask); | |
| return; | |
| } | |
| if (!BlockMask) { // BlockMask has its initial nullptr value. | |
| BlockMask = EdgeMask; | |
| continue; | |
| } | |
| BlockMask = Builder.createOr(BlockMask, EdgeMask, {}); | |
| } | |
| setBlockInMask(VPBB, BlockMask); | |
| } | |
| void VPPredicator::createSwitchEdgeMasks(const VPInstruction *SI) { | |
| const VPBasicBlock *Src = SI->getParent(); | |
| // Create masks where SI is a switch. We create masks for all edges from SI's | |
| // parent block at the same time. This is more efficient, as we can create and | |
| // collect compares for all cases once. | |
| VPValue *Cond = SI->getOperand(0); | |
| VPBasicBlock *DefaultDst = cast<VPBasicBlock>(Src->getSuccessors()[0]); | |
| MapVector<VPBasicBlock *, SmallVector<VPValue *>> Dst2Compares; | |
| for (const auto &[Idx, Succ] : enumerate(drop_begin(Src->getSuccessors()))) { | |
| VPBasicBlock *Dst = cast<VPBasicBlock>(Succ); | |
| assert(!getEdgeMask(Src, Dst) && "Edge masks already created"); | |
| // Cases whose destination is the same as default are redundant and can | |
| // be ignored - they will get there anyhow. | |
| if (Dst == DefaultDst) | |
| continue; | |
| auto &Compares = Dst2Compares[Dst]; | |
| VPValue *V = SI->getOperand(Idx + 1); | |
| Compares.push_back(Builder.createICmp(CmpInst::ICMP_EQ, Cond, V)); | |
| } | |
| // We need to handle 2 separate cases below for all entries in Dst2Compares, | |
| // which excludes destinations matching the default destination. | |
| VPValue *SrcMask = getBlockInMask(Src); | |
| VPValue *DefaultMask = nullptr; | |
| for (const auto &[Dst, Conds] : Dst2Compares) { | |
| // 1. Dst is not the default destination. Dst is reached if any of the | |
| // cases with destination == Dst are taken. Join the conditions for each | |
| // case whose destination == Dst using an OR. | |
| VPValue *Mask = Conds[0]; | |
| for (VPValue *V : drop_begin(Conds)) | |
| Mask = Builder.createOr(Mask, V); | |
| if (SrcMask) | |
| Mask = Builder.createLogicalAnd(SrcMask, Mask); | |
| setEdgeMask(Src, Dst, Mask); | |
| // 2. Create the mask for the default destination, which is reached if | |
| // none of the cases with destination != default destination are taken. | |
| // Join the conditions for each case where the destination is != Dst using | |
| // an OR and negate it. | |
| DefaultMask = DefaultMask ? Builder.createOr(DefaultMask, Mask) : Mask; | |
| } | |
| if (DefaultMask) { | |
| DefaultMask = Builder.createNot(DefaultMask); | |
| if (SrcMask) | |
| DefaultMask = Builder.createLogicalAnd(SrcMask, DefaultMask); | |
| } else { | |
| // There are no destinations other than the default destination, so this is | |
| // an unconditional branch. | |
| DefaultMask = SrcMask; | |
| } | |
| setEdgeMask(Src, DefaultDst, DefaultMask); | |
| } | |
| // Start by keeping track of what edges lead to which value. Then see if any | |
| // node has the same value for all outgoing edges. If so then propagate that | |
| // value up to every node it postdominates. E.g: | |
| // | |
| // Entry Edges = {C->ɸ : %x, D->ɸ : %x, F->ɸ : %y} | |
| // / \ [C,D,F all outgoing edges equal: go up postdom frontier] | |
| // A B ~> {A->C : %x, A->D : %x, Entry->B : %y} | |
| // / \ |\ [A all outgoing edges equal: go up postdom frontier] | |
| // C D | E ~> {Entry->A : %x, Entry->B : %y} | |
| // \ \ |/ | |
| // \ | F | |
| // \ | / | |
| // ɸ = phi [%x, C], [%x, D], [%y, F] | |
| MapVector<VPPredicator::EdgeTy, VPValue *> | |
| VPPredicator::computeBlendEdges(VPPhi *Phi) { | |
| MapVector<EdgeTy, VPValue *> Edges; | |
| // Mark the given edge as providing the value \p V. | |
| auto AddEdge = [&Edges](const VPBlockBase *From, const VPBlockBase *To, | |
| VPValue *V) { | |
| EdgeTy Edge = {cast<VPBasicBlock>(From), cast<VPBasicBlock>(To)}; | |
| assert((!Edges.contains(Edge) || Edges.lookup(Edge) == V) && | |
| "Clobbering an edge?"); | |
| Edges[Edge] = V; | |
| }; | |
| for (auto [InVal, InVPBB] : Phi->incoming_values_and_blocks()) | |
| AddEdge(InVPBB, Phi->getParent(), InVal); | |
| SetVector<const VPBlockBase *> Worklist(from_range, Phi->incoming_blocks()); | |
| while (!Worklist.empty()) { | |
| auto *VPBB = cast<VPBasicBlock>(Worklist.pop_back_val()); | |
| // Check that all outgoing edges from VPBB have the same value. | |
| SmallVector<EdgeTy> OutEdges; | |
| for (const VPBlockBase *Succ : VPBB->getSuccessors()) | |
| OutEdges.emplace_back(VPBB, cast<VPBasicBlock>(Succ)); | |
| auto OutVals = | |
| map_range(OutEdges, [&Edges](EdgeTy E) { return Edges.lookup(E); }); | |
| VPValue *Common = *OutVals.begin(); | |
| if (!Common || !all_equal(OutVals)) | |
| continue; | |
| // They have the same value: we can move the edges up. | |
| for (EdgeTy Edge : OutEdges) | |
| Edges.erase(Edge); | |
| // Iterate up through the post dominance frontier. | |
| assert(VPPDF.find(VPBB) != VPPDF.end() && | |
| "VPBB must have a post-dominance frontier entry"); | |
| for (const VPBlockBase *Frontier : VPPDF.find(VPBB)->second) { | |
| for (const VPBlockBase *FrontierSucc : Frontier->getSuccessors()) | |
| if (VPPDT.dominates(VPBB, FrontierSucc)) | |
| AddEdge(Frontier, FrontierSucc, Common); | |
| Worklist.insert(cast<VPBasicBlock>(Frontier)); | |
| } | |
| } | |
| return Edges; | |
| } | |
| VPValue *VPPredicator::createBlendMaskForEdges(ArrayRef<EdgeTy> Edges, | |
| VPBasicBlock *VPBB) { | |
| // If the nearest common postdominator to all of Edges destinations isn't VPBB | |
| // then we can use its block in-mask. E.g: | |
| // | |
| // A ... B | |
| // \ \ / | |
| // \ C | |
| // \ / | |
| // ... D ... | |
| // \ | / | |
| // VPBB | |
| // | |
| // If the edges are A->D and B->C, PostDom will be D. We can reuse Ds block | |
| // in-mask. | |
| const VPBasicBlock *PostDom = Edges[0].second; | |
| for (auto [_, DstVPBB] : drop_begin(Edges)) | |
| PostDom = | |
| cast<VPBasicBlock>(VPPDT.findNearestCommonDominator(PostDom, DstVPBB)); | |
| assert(VPPDT.dominates(VPBB, PostDom) && "VPBB doesn't postdominate edges"); | |
| if (PostDom != VPBB) | |
| return getBlockInMask(PostDom); | |
| // Otherwise, compute the disjunction of edges. | |
| VPValue *Mask = nullptr; | |
| for (auto [Src, ConstDst] : Edges) { | |
| auto *Dst = const_cast<VPBasicBlock *>(ConstDst); | |
| VPValue *EdgeMask; | |
| { | |
| VPBuilder::InsertPointGuard Guard(Builder); | |
| Builder.setInsertPoint(Dst, getMaskInsertPoint(Dst)); | |
| EdgeMask = createEdgeMask(Src, Dst); | |
| } | |
| Mask = Mask ? Builder.createOr(Mask, EdgeMask) : EdgeMask; | |
| } | |
| return Mask; | |
| } | |
| void VPPredicator::convertPhisToBlends(VPBasicBlock *VPBB) { | |
| Builder.setInsertPoint(VPBB, getMaskInsertPoint(VPBB)); | |
| SmallVector<VPPhi *> Phis; | |
| for (VPRecipeBase &R : VPBB->phis()) | |
| Phis.push_back(cast<VPPhi>(&R)); | |
| for (VPPhi *PhiR : Phis) { | |
| // The non-header Phi is converted into a Blend recipe below, | |
| // so we don't have to worry about the insertion order and we can just use | |
| // the builder. At this point we generate the predication tree. There may | |
| // be duplications since this is a simple recursive scan, but future | |
| // optimizations will clean it up. | |
| auto NotPoison = make_filter_range(PhiR->incoming_values(), [](VPValue *V) { | |
| return !match(V, m_Poison()); | |
| }); | |
| if (all_equal(NotPoison)) { | |
| PhiR->replaceAllUsesWith(NotPoison.empty() ? PhiR->getIncomingValue(0) | |
| : *NotPoison.begin()); | |
| PhiR->eraseFromParent(); | |
| continue; | |
| } | |
| MapVector<VPValue *, SmallVector<EdgeTy>> InValEdgesMap; | |
| for (auto [Edge, Val] : computeBlendEdges(PhiR)) | |
| InValEdgesMap[Val].push_back(Edge); | |
| auto InValEdges = InValEdgesMap.takeVector(); | |
| // Sort the incoming value order to match PhiR as much as possible. | |
| llvm::stable_sort(InValEdges, [&PhiR](auto &L, auto &R) { | |
| auto InVs = PhiR->incoming_values(); | |
| return std::distance(InVs.begin(), find(InVs, L.first)) < | |
| std::distance(InVs.begin(), find(InVs, R.first)); | |
| }); | |
| SmallVector<VPValue *, 2> OperandsWithMask; | |
| for (const auto &[InVPV, Edges] : InValEdges) { | |
| OperandsWithMask.push_back(InVPV); | |
| OperandsWithMask.push_back(createBlendMaskForEdges(Edges, VPBB)); | |
| } | |
| PHINode *IRPhi = cast_or_null<PHINode>(PhiR->getUnderlyingValue()); | |
| auto *Blend = | |
| new VPBlendRecipe(IRPhi, OperandsWithMask, *PhiR, PhiR->getDebugLoc()); | |
| Builder.insert(Blend); | |
| PhiR->replaceAllUsesWith(Blend); | |
| PhiR->eraseFromParent(); | |
| } | |
| } | |
| void VPPredicator::run() { | |
| VPBasicBlock *Header = Plan.getVectorLoopRegion()->getEntryBasicBlock(); | |
| // Scan the body of the loop in a topological order to visit each basic | |
| // block after having visited its predecessor basic blocks. | |
| ReversePostOrderTraversal<VPBlockShallowTraversalWrapper<VPBlockBase *>> RPOT( | |
| Header); | |
| // Non-outer regions with VPBBs only are supported at the moment. | |
| auto Blocks = to_vector(VPBlockUtils::blocksAs<VPBasicBlock>(RPOT)); | |
| for (VPBasicBlock *VPBB : Blocks) { | |
| // Introduce the mask for VPBB, which may introduce needed edge masks, and | |
| // convert all phi recipes of VPBB to blend recipes unless VPBB is the | |
| // header. | |
| if (VPBB != Header) | |
| createBlockInMask(VPBB); | |
| VPValue *BlockMask = getBlockInMask(VPBB); | |
| // Mask all VPInstructions in the block. | |
| for (VPInstruction &VPI : make_isa_range<VPInstruction>(*VPBB)) { | |
| if (BlockMask) | |
| VPI.addMask(BlockMask); | |
| // Drop the execution frequency of unmasked VPInstructions, as they | |
| // always execute. | |
| if (!VPI.isMasked()) | |
| VPI.clearExecutionFrequency(); | |
| } | |
| } | |
| for (VPBasicBlock *VPBB : reverse(Blocks)) | |
| if (VPBB != Header) | |
| convertPhisToBlends(VPBB); | |
| // Linearize the blocks of the loop into one serial chain. | |
| VPBlockBase *PrevVPBB = nullptr; | |
| for (VPBasicBlock *VPBB : Blocks) { | |
| auto Successors = to_vector(VPBB->getSuccessors()); | |
| if (Successors.size() > 1) | |
| VPBB->getTerminator()->eraseFromParent(); | |
| // Flatten the CFG in the loop. To do so, first disconnect VPBB from its | |
| // successors. Then connect VPBB to the previously visited VPBB. | |
| for (auto *Succ : Successors) | |
| VPBlockUtils::disconnectBlocks(VPBB, Succ); | |
| if (PrevVPBB) | |
| VPBlockUtils::connectBlocks(PrevVPBB, VPBB); | |
| PrevVPBB = VPBB; | |
| } | |
| } | |
| void VPlanTransforms::introduceMasksAndLinearize(VPlan &Plan) { | |
| // Nested loop regions (outer-loop vectorization) are not supported yet. | |
| if (Plan.isOuterLoop()) | |
| return; | |
| VPPredicator(Plan).run(); | |
| } |