| //===------- SVEShuffleOpts - SVE Shuffle Optimization --------------------===// | |
| // | |
| // 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 | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // Tries to pattern match and combine scalable vector shuffles that could | |
| // be more efficiently performed by tbl instructions. | |
| // | |
| // An example would be a loop with 4 multiply-accumulate reductions, where the | |
| // new data in each vector iterations comes from a 4-way deinterleaving of | |
| // smaller datatypes loaded from memory which are then zero extended. | |
| // | |
| // Something like the following: | |
| // %bgra = call ... @llvm.masked.load | |
| // %deinterleave = call ... @llvm.vector.deinterleave4(%bgra) | |
| // If the load was of a <vscale x 8 x i16>, we now have 4 deinterleaved | |
| // <vscale x 2 x i16> values. | |
| // %b.i16 = extractvalue %deinterleave, 0 | |
| // %b.i64 = zext <vscale x 2 x i16> %b.i16 to <vscale x 2 x i64> | |
| // %acc.b.next = add <vscale x 2 x i64> %acc.b, %b.i64 | |
| // <repeat for the other 3 subvectors> | |
| // | |
| // If the initial load is a legal vector rather than 4x the size (generating a | |
| // structured ld4 instead), we would see multiple uunpkhi/lo instructions for | |
| // the extensions, followed by uzp1/2 instructions for the deinterleave. | |
| // Instead, we can replace all of those with 4 tbl instructions. The tradeoff, | |
| // of course, is that we now have 4 mask values to maintain which may increase | |
| // register pressure. | |
| // | |
| // This basic transform could be performed in CodeGenPrepare (as the equivalent | |
| // for NEON is), or in a DAG Combine. However, we hope to extend it to detect | |
| // other shuffles that we can fold into the tbl. Extending the above example, | |
| // if instead of directly adding to the accumulator we multiplied it by a | |
| // common term for all 4 components that had been reversed: | |
| // %common.load = call @llvm.masked.load | |
| // %common.reverse = call @llvm.vector.reverse | |
| // These would be loaded at the extended size, <vscale x 2 x i64> in our | |
| // example. | |
| // %b.mul = mul <vscale x 2 x i64> %b.i64, %common.reverse | |
| // %acc.b.next = add <vscale x 2 x i64> %acc.b, %b.mul | |
| // <repeat for the other 3 subvectors, using %common.reverse for each) | |
| // | |
| // In this case, the reverse isn't applied to the deinterleaved data in the | |
| // original IR, but to the common term multiplied by the individual bgra | |
| // elements. If the order of the elements in the accumulator is important, we | |
| // cannot change that. If, however, we know that the accumulator is reduced to | |
| // a single scalar after the loop and the data is either integers or floating | |
| // point with reassociation allowed, we could instead choose a different mask | |
| // for the tbls to reverse the individual bgra elements instead, removing an | |
| // additional instruction from the loop. This does require looking beyond the | |
| // blocks in the loop, so DAGCombine won't help. | |
| // | |
| // We should also be able to introduce new shuffles in order to balance out | |
| // SVE's bottom/top instruction pairs, which act on even/odd lanes instead of | |
| // the high or low half of a register. | |
| // | |
| // This pass may end up being a temporary solution that is removed if we can | |
| // create a generic vector shuffle intrinsic and move this feature to | |
| // LoopVectorize itself, as that would allow for better cost modelling. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "AArch64.h" | |
| #include "AArch64Subtarget.h" | |
| #include "AArch64TargetMachine.h" | |
| #include "llvm/Analysis/AssumptionCache.h" | |
| #include "llvm/Analysis/LoopInfo.h" | |
| #include "llvm/Analysis/LoopPass.h" | |
| #include "llvm/Analysis/MemorySSA.h" | |
| #include "llvm/Analysis/TargetTransformInfo.h" | |
| #include "llvm/Analysis/ValueTracking.h" | |
| #include "llvm/CodeGen/TargetLowering.h" | |
| #include "llvm/CodeGen/TargetPassConfig.h" | |
| #include "llvm/CodeGen/TargetSubtargetInfo.h" | |
| #include "llvm/IR/Constants.h" | |
| #include "llvm/IR/IRBuilder.h" | |
| #include "llvm/IR/Instructions.h" | |
| #include "llvm/IR/IntrinsicInst.h" | |
| #include "llvm/IR/IntrinsicsAArch64.h" | |
| #include "llvm/IR/LLVMContext.h" | |
| #include "llvm/IR/Module.h" | |
| #include "llvm/IR/PatternMatch.h" | |
| #include "llvm/InitializePasses.h" | |
| #include <array> | |
| using namespace llvm; | |
| using namespace llvm::PatternMatch; | |
| #define DEBUG_TYPE "aarch64-sve-shuffle-opts" | |
| /// A mapping between a vector_deinterleaveN intrinsic and extending cast | |
| /// instructions used on the resulting subvectors. | |
| using DeinterleaveMap = SmallDenseMap<CallInst *, std::array<CastInst *, 4>>; | |
| /// Evaluate a deinterleave and see what the uses are. If we find other | |
| /// operations that we can combine into a tbl shuffle, add the deinterleave and | |
| /// the operations (currently only zext or uitofp) to the candidates map. | |
| static void evaluateDeinterleave(IntrinsicInst *I, DeinterleaveMap &Candidates, | |
| Loop &L, const AArch64TargetLowering &TL, | |
| const DataLayout DL) { | |
| assert(I->getIntrinsicID() == Intrinsic::vector_deinterleave4 && | |
| "Only deinterleave4 supported currently"); | |
| ConstantRange VScaleRange = getVScaleRange(I->getFunction(), 64); | |
| // TBL zeroes elements with an out-of-bounds index, but for the largest | |
| // possible SVE vector (2048b) the maximum value for i8 elements (255) is not | |
| // large enough to encode an 'out of bounds' value. So we can only perform | |
| // this optimization for i8 elements if we know vscale is < 16. | |
| EVT InputVT = TL.getValueType(DL, I->getOperand(0)->getType()); | |
| if (!InputVT.isScalableVector() || | |
| (InputVT.getScalarSizeInBits() < 16 && | |
| (!VScaleRange.getUpper().ult(16) || VScaleRange.isUpperWrapped())) || | |
| TL.getTypeConversion(I->getContext(), InputVT).first != | |
| TargetLoweringBase::TypeLegal) | |
| return; | |
| std::array<CastInst *, 4> Extends = {}; | |
| unsigned Opcode = 0; | |
| Type *DestTy = nullptr; | |
| for (User *U : I->users()) { | |
| auto *Extract = dyn_cast<ExtractValueInst>(U); | |
| if (!Extract || !Extract->hasOneUse()) | |
| return; | |
| // We expect only a single cast instruction as a user for the extract. | |
| auto *Extend = dyn_cast_if_present<CastInst>(*Extract->users().begin()); | |
| if (!Extend || (!isa<ZExtInst>(Extend) && !isa<UIToFPInst>(Extend))) | |
| return; | |
| // We're only interested if the uses are in the loop. This is almost | |
| // certainly the case. | |
| if (!L.contains(Extend)) | |
| return; | |
| Opcode = Extend->getOpcode(); | |
| DestTy = Extend->getDestTy(); | |
| // Make sure DestTy matches the input size. | |
| if (DestTy->getPrimitiveSizeInBits() != InputVT.getSizeInBits()) | |
| return; | |
| Extends[Extract->getIndices().front()] = Extend; | |
| } | |
| // Check that all extracted values are being extended the same way, and that | |
| // we have the expected number of extensions. | |
| if (!all_of(Extends, [DestTy, Opcode](CastInst *CI) { | |
| return !CI || (CI->getDestTy() == DestTy && CI->getOpcode() == Opcode); | |
| })) | |
| return; | |
| Candidates.try_emplace(I, Extends); | |
| } | |
| /// Given a map of deinterleaves to zext or uitofp casts, remove the operations | |
| /// and replace them with tbl shuffles. | |
| static void optimizeSVEDeinterleavedExtends(DeinterleaveMap Deinterleaves) { | |
| for (auto &[Deinterleave, Extends] : Deinterleaves) { | |
| VectorType *DestTy = cast<VectorType>(Extends[0]->getDestTy()); | |
| VectorType *SrcTy = cast<VectorType>(Extends[0]->getSrcTy()); | |
| unsigned DstBits = DestTy->getScalarSizeInBits(); | |
| unsigned SrcBits = SrcTy->getScalarSizeInBits(); | |
| bool IsUIToFP = isa<UIToFPInst>(Extends[0]); | |
| VectorType *StepVecTy = VectorType::getInteger(DestTy); | |
| Value *Input = Deinterleave->getOperand(0); | |
| Type *InputTy = Input->getType(); | |
| APInt Invalid = APInt::getAllOnes(DstBits); | |
| for (auto [Idx, Extend] : enumerate(Extends)) { | |
| // If not all lanes were extracted, we can have gaps. Skip over them. | |
| if (!Extend) | |
| continue; | |
| // Build the mask using stepvectors and casting. | |
| // We want to select the Idx'th element, and every 4 elements after that. | |
| // Each element needs to be zero extended; we can do that by providing | |
| // tbl index values that are out of range. We can't do that nicely with | |
| // a stepvector of the same element type as the input type, but we can | |
| // do it with elements the size of the output type. | |
| // E.g. for element 0 of a 16b -> 64b zext, we would start with a mask of | |
| // 0xFFFF_FFFF_FFFF_0000 + Idx for the start of the stepvector, and use a | |
| // step of 4. We then cast that back to an element size of 16b, yielding | |
| // <0x0000 + Idx, 0xFFFF, 0xFFFF, 0xFFFF, 0x0004 + Idx, 0xFFFF...>. | |
| APInt StartIdx = Invalid << SrcBits; | |
| StartIdx += Idx; | |
| IRBuilder<> Builder(Extend); | |
| Value *StepVector = Builder.CreateStepVector(StepVecTy); | |
| Value *ScaledSteps = | |
| Builder.CreateNUWMul(StepVector, ConstantInt::get(StepVecTy, 4)); | |
| Value *ZextTbl = Builder.CreateNUWAdd( | |
| ScaledSteps, ConstantInt::get(StepVecTy, StartIdx)); | |
| Value *FinalMask = Builder.CreateBitCast(ZextTbl, InputTy); | |
| // Replace the deinterleave, extractvalue, and extension chain with | |
| // a tbl directly on the input value. | |
| Value *Tbl = Builder.CreateIntrinsic(Intrinsic::aarch64_sve_tbl, | |
| {InputTy}, {Input, FinalMask}); | |
| Value *Widen = Builder.CreateBitCast(Tbl, StepVecTy); | |
| if (IsUIToFP) | |
| Widen = Builder.CreateUIToFP(Widen, DestTy); | |
| LLVM_DEBUG(dbgs() << "SVETBLOPT: Replaced " << *Extend << " with " | |
| << *Widen << "\n"); | |
| Extend->replaceAllUsesWith(Widen); | |
| Extend->eraseFromParent(); | |
| } | |
| // Delete the unused extracts and deinterleave. | |
| for (User *U : make_early_inc_range(Deinterleave->users())) | |
| cast<Instruction>(U)->eraseFromParent(); | |
| Deinterleave->eraseFromParent(); | |
| } | |
| } | |
| static bool processLoop(Loop &L, const AArch64Subtarget &ST, DataLayout DL) { | |
| // At present, we only want to do this for innermost loops when SVE | |
| // is available. | |
| if (!L.isInnermost() || !ST.isSVEorStreamingSVEAvailable()) | |
| return false; | |
| // TODO: Pull other shuffles into the tbl where possible. | |
| // TODO: Add more advanced cases, such as introducing shuffles so that | |
| // the SVE odd/even BT narrowing instructions can be used. | |
| // TODO: Support other deinterleaves. | |
| const AArch64TargetLowering &TL = *ST.getTargetLowering(); | |
| assert(DL.isLittleEndian() && | |
| "Shuffle optimizations unsupported for big endian targets."); | |
| DeinterleaveMap Candidates; | |
| for (auto *BB : L.blocks()) | |
| for (auto &I : *BB) | |
| if (match(&I, m_Intrinsic<Intrinsic::vector_deinterleave4>(m_Value()))) | |
| evaluateDeinterleave(cast<IntrinsicInst>(&I), Candidates, L, TL, DL); | |
| if (Candidates.empty()) | |
| return false; | |
| optimizeSVEDeinterleavedExtends(Candidates); | |
| return true; | |
| } | |
| namespace { | |
| struct SVEShuffleOpts : public LoopPass { | |
| static char ID; // Pass identification, replacement for typeid | |
| SVEShuffleOpts() : LoopPass(ID) {} | |
| bool runOnLoop(Loop *L, LPPassManager &PM) override { | |
| if (skipLoop(L)) | |
| return false; | |
| TargetPassConfig &TPC = getAnalysis<TargetPassConfig>(); | |
| const AArch64TargetMachine &TM = TPC.getTM<AArch64TargetMachine>(); | |
| const Function *F = L->getHeader()->getParent(); | |
| const AArch64Subtarget &ST = *TM.getSubtargetImpl(*F); | |
| return processLoop(*L, ST, F->getParent()->getDataLayout()); | |
| } | |
| void getAnalysisUsage(AnalysisUsage &AU) const override { | |
| AU.addRequired<TargetPassConfig>(); | |
| AU.setPreservesCFG(); | |
| } | |
| StringRef getPassName() const override { return "SVE Shuffle Optimizations"; } | |
| }; | |
| } // end anonymous namespace | |
| char SVEShuffleOpts::ID = 0; | |
| static const char *name = "SVE Shuffle Optimizations"; | |
| INITIALIZE_PASS_BEGIN(SVEShuffleOpts, DEBUG_TYPE, name, false, false) | |
| INITIALIZE_PASS_DEPENDENCY(TargetPassConfig) | |
| INITIALIZE_PASS_END(SVEShuffleOpts, DEBUG_TYPE, name, false, false) | |
| Pass *llvm::createSVEShuffleOptsPass() { return new SVEShuffleOpts(); } | |
| PreservedAnalyses | |
| AArch64SVEShuffleOptsPass::run(Loop &L, LoopAnalysisManager &AM, | |
| LoopStandardAnalysisResults &AR, LPMUpdater &U) { | |
| const Function *F = L.getHeader()->getParent(); | |
| const AArch64Subtarget &ST = *TM.getSubtargetImpl(*F); | |
| if (processLoop(L, ST, F->getParent()->getDataLayout())) { | |
| PreservedAnalyses PA; | |
| PA.preserveSet<CFGAnalyses>(); | |
| PA.preserve<TargetIRAnalysis>(); | |
| PA.preserve<AssumptionAnalysis>(); | |
| PA.preserve<MemorySSAAnalysis>(); | |
| return PA; | |
| } | |
| return PreservedAnalyses::all(); | |
| } |