//===- ARMParallelDSP.cpp - Parallel DSP Pass -----------------------------===// // // 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 /// Armv6 introduced instructions to perform 32-bit SIMD operations. The /// purpose of this pass is do some IR pattern matching to create ACLE /// DSP intrinsics, which map on these 32-bit SIMD operations. /// This pass runs only when unaligned accesses is supported/enabled. // //===----------------------------------------------------------------------===// #include "ARM.h" #include "ARMSubtarget.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/Statistic.h" #include "llvm/Analysis/AliasAnalysis.h" #include "llvm/Analysis/LoopAccessAnalysis.h" #include "llvm/Analysis/OrderedBasicBlock.h" #include "llvm/CodeGen/TargetPassConfig.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/IntrinsicsARM.h" #include "llvm/IR/NoFolder.h" #include "llvm/IR/PatternMatch.h" #include "llvm/Pass.h" #include "llvm/PassRegistry.h" #include "llvm/PassSupport.h" #include "llvm/Support/Debug.h" #include "llvm/Transforms/Scalar.h" #include "llvm/Transforms/Utils/BasicBlockUtils.h" using namespace llvm; using namespace PatternMatch; #define DEBUG_TYPE "arm-parallel-dsp" STATISTIC(NumSMLAD , "Number of smlad instructions generated"); static cl::opt DisableParallelDSP("disable-arm-parallel-dsp", cl::Hidden, cl::init(false), cl::desc("Disable the ARM Parallel DSP pass")); static cl::opt NumLoadLimit("arm-parallel-dsp-load-limit", cl::Hidden, cl::init(16), cl::desc("Limit the number of loads analysed")); namespace { struct MulCandidate; class Reduction; using MulCandList = SmallVector, 8>; using MemInstList = SmallVectorImpl; using MulPairList = SmallVector, 8>; // 'MulCandidate' holds the multiplication instructions that are candidates // for parallel execution. struct MulCandidate { Instruction *Root; Value* LHS; Value* RHS; bool Exchange = false; bool ReadOnly = true; bool Paired = false; SmallVector VecLd; // Container for loads to widen. MulCandidate(Instruction *I, Value *lhs, Value *rhs) : Root(I), LHS(lhs), RHS(rhs) { } bool HasTwoLoadInputs() const { return isa(LHS) && isa(RHS); } LoadInst *getBaseLoad() const { return VecLd.front(); } }; /// Represent a sequence of multiply-accumulate operations with the aim to /// perform the multiplications in parallel. class Reduction { Instruction *Root = nullptr; Value *Acc = nullptr; MulCandList Muls; MulPairList MulPairs; SetVector Adds; public: Reduction() = delete; Reduction (Instruction *Add) : Root(Add) { } /// Record an Add instruction that is a part of the this reduction. void InsertAdd(Instruction *I) { Adds.insert(I); } /// Create MulCandidates, each rooted at a Mul instruction, that is a part /// of this reduction. void InsertMuls() { auto GetMulOperand = [](Value *V) -> Instruction* { if (auto *SExt = dyn_cast(V)) { if (auto *I = dyn_cast(SExt->getOperand(0))) if (I->getOpcode() == Instruction::Mul) return I; } else if (auto *I = dyn_cast(V)) { if (I->getOpcode() == Instruction::Mul) return I; } return nullptr; }; auto InsertMul = [this](Instruction *I) { Value *LHS = cast(I->getOperand(0))->getOperand(0); Value *RHS = cast(I->getOperand(1))->getOperand(0); Muls.push_back(std::make_unique(I, LHS, RHS)); }; for (auto *Add : Adds) { if (Add == Acc) continue; if (auto *Mul = GetMulOperand(Add->getOperand(0))) InsertMul(Mul); if (auto *Mul = GetMulOperand(Add->getOperand(1))) InsertMul(Mul); } } /// Add the incoming accumulator value, returns true if a value had not /// already been added. Returning false signals to the user that this /// reduction already has a value to initialise the accumulator. bool InsertAcc(Value *V) { if (Acc) return false; Acc = V; return true; } /// Set two MulCandidates, rooted at muls, that can be executed as a single /// parallel operation. void AddMulPair(MulCandidate *Mul0, MulCandidate *Mul1, bool Exchange = false) { LLVM_DEBUG(dbgs() << "Pairing:\n" << *Mul0->Root << "\n" << *Mul1->Root << "\n"); Mul0->Paired = true; Mul1->Paired = true; if (Exchange) Mul1->Exchange = true; MulPairs.push_back(std::make_pair(Mul0, Mul1)); } /// Return true if enough mul operations are found that can be executed in /// parallel. bool CreateParallelPairs(); /// Return the add instruction which is the root of the reduction. Instruction *getRoot() { return Root; } bool is64Bit() const { return Root->getType()->isIntegerTy(64); } Type *getType() const { return Root->getType(); } /// Return the incoming value to be accumulated. This maybe null. Value *getAccumulator() { return Acc; } /// Return the set of adds that comprise the reduction. SetVector &getAdds() { return Adds; } /// Return the MulCandidate, rooted at mul instruction, that comprise the /// the reduction. MulCandList &getMuls() { return Muls; } /// Return the MulCandidate, rooted at mul instructions, that have been /// paired for parallel execution. MulPairList &getMulPairs() { return MulPairs; } /// To finalise, replace the uses of the root with the intrinsic call. void UpdateRoot(Instruction *SMLAD) { Root->replaceAllUsesWith(SMLAD); } void dump() { LLVM_DEBUG(dbgs() << "Reduction:\n"; for (auto *Add : Adds) LLVM_DEBUG(dbgs() << *Add << "\n"); for (auto &Mul : Muls) LLVM_DEBUG(dbgs() << *Mul->Root << "\n" << " " << *Mul->LHS << "\n" << " " << *Mul->RHS << "\n"); LLVM_DEBUG(if (Acc) dbgs() << "Acc in: " << *Acc << "\n") ); } }; class WidenedLoad { LoadInst *NewLd = nullptr; SmallVector Loads; public: WidenedLoad(SmallVectorImpl &Lds, LoadInst *Wide) : NewLd(Wide) { for (auto *I : Lds) Loads.push_back(I); } LoadInst *getLoad() { return NewLd; } }; class ARMParallelDSP : public FunctionPass { ScalarEvolution *SE; AliasAnalysis *AA; TargetLibraryInfo *TLI; DominatorTree *DT; const DataLayout *DL; Module *M; std::map LoadPairs; SmallPtrSet OffsetLoads; std::map> WideLoads; template bool IsNarrowSequence(Value *V); bool Search(Value *V, BasicBlock *BB, Reduction &R); bool RecordMemoryOps(BasicBlock *BB); void InsertParallelMACs(Reduction &Reduction); bool AreSequentialLoads(LoadInst *Ld0, LoadInst *Ld1, MemInstList &VecMem); LoadInst* CreateWideLoad(MemInstList &Loads, IntegerType *LoadTy); bool CreateParallelPairs(Reduction &R); /// Try to match and generate: SMLAD, SMLADX - Signed Multiply Accumulate /// Dual performs two signed 16x16-bit multiplications. It adds the /// products to a 32-bit accumulate operand. Optionally, the instruction can /// exchange the halfwords of the second operand before performing the /// arithmetic. bool MatchSMLAD(Function &F); public: static char ID; ARMParallelDSP() : FunctionPass(ID) { } void getAnalysisUsage(AnalysisUsage &AU) const override { FunctionPass::getAnalysisUsage(AU); AU.addRequired(); AU.addRequired(); AU.addRequired(); AU.addRequired(); AU.addRequired(); AU.addRequired(); AU.addPreserved(); AU.addPreserved(); AU.setPreservesCFG(); } bool runOnFunction(Function &F) override { if (DisableParallelDSP) return false; if (skipFunction(F)) return false; SE = &getAnalysis().getSE(); AA = &getAnalysis().getAAResults(); TLI = &getAnalysis().getTLI(F); DT = &getAnalysis().getDomTree(); auto &TPC = getAnalysis(); M = F.getParent(); DL = &M->getDataLayout(); auto &TM = TPC.getTM(); auto *ST = &TM.getSubtarget(F); if (!ST->allowsUnalignedMem()) { LLVM_DEBUG(dbgs() << "Unaligned memory access not supported: not " "running pass ARMParallelDSP\n"); return false; } if (!ST->hasDSP()) { LLVM_DEBUG(dbgs() << "DSP extension not enabled: not running pass " "ARMParallelDSP\n"); return false; } if (!ST->isLittle()) { LLVM_DEBUG(dbgs() << "Only supporting little endian: not running pass " << "ARMParallelDSP\n"); return false; } LLVM_DEBUG(dbgs() << "\n== Parallel DSP pass ==\n"); LLVM_DEBUG(dbgs() << " - " << F.getName() << "\n\n"); bool Changes = MatchSMLAD(F); return Changes; } }; } template static bool AreSequentialAccesses(MemInst *MemOp0, MemInst *MemOp1, const DataLayout &DL, ScalarEvolution &SE) { if (isConsecutiveAccess(MemOp0, MemOp1, DL, SE)) return true; return false; } bool ARMParallelDSP::AreSequentialLoads(LoadInst *Ld0, LoadInst *Ld1, MemInstList &VecMem) { if (!Ld0 || !Ld1) return false; if (!LoadPairs.count(Ld0) || LoadPairs[Ld0] != Ld1) return false; LLVM_DEBUG(dbgs() << "Loads are sequential and valid:\n"; dbgs() << "Ld0:"; Ld0->dump(); dbgs() << "Ld1:"; Ld1->dump(); ); VecMem.clear(); VecMem.push_back(Ld0); VecMem.push_back(Ld1); return true; } // MaxBitwidth: the maximum supported bitwidth of the elements in the DSP // instructions, which is set to 16. So here we should collect all i8 and i16 // narrow operations. // TODO: we currently only collect i16, and will support i8 later, so that's // why we check that types are equal to MaxBitWidth, and not <= MaxBitWidth. template bool ARMParallelDSP::IsNarrowSequence(Value *V) { if (auto *SExt = dyn_cast(V)) { if (SExt->getSrcTy()->getIntegerBitWidth() != MaxBitWidth) return false; if (auto *Ld = dyn_cast(SExt->getOperand(0))) { // Check that this load could be paired. return LoadPairs.count(Ld) || OffsetLoads.count(Ld); } } return false; } /// Iterate through the block and record base, offset pairs of loads which can /// be widened into a single load. bool ARMParallelDSP::RecordMemoryOps(BasicBlock *BB) { SmallVector Loads; SmallVector Writes; LoadPairs.clear(); WideLoads.clear(); OrderedBasicBlock OrderedBB(BB); // Collect loads and instruction that may write to memory. For now we only // record loads which are simple, sign-extended and have a single user. // TODO: Allow zero-extended loads. for (auto &I : *BB) { if (I.mayWriteToMemory()) Writes.push_back(&I); auto *Ld = dyn_cast(&I); if (!Ld || !Ld->isSimple() || !Ld->hasOneUse() || !isa(Ld->user_back())) continue; Loads.push_back(Ld); } if (Loads.empty() || Loads.size() > NumLoadLimit) return false; using InstSet = std::set; using DepMap = std::map; DepMap RAWDeps; // Record any writes that may alias a load. const auto Size = LocationSize::unknown(); for (auto Write : Writes) { for (auto Read : Loads) { MemoryLocation ReadLoc = MemoryLocation(Read->getPointerOperand(), Size); if (!isModOrRefSet(intersectModRef(AA->getModRefInfo(Write, ReadLoc), ModRefInfo::ModRef))) continue; if (OrderedBB.dominates(Write, Read)) RAWDeps[Read].insert(Write); } } // Check whether there's not a write between the two loads which would // prevent them from being safely merged. auto SafeToPair = [&](LoadInst *Base, LoadInst *Offset) { LoadInst *Dominator = OrderedBB.dominates(Base, Offset) ? Base : Offset; LoadInst *Dominated = OrderedBB.dominates(Base, Offset) ? Offset : Base; if (RAWDeps.count(Dominated)) { InstSet &WritesBefore = RAWDeps[Dominated]; for (auto Before : WritesBefore) { // We can't move the second load backward, past a write, to merge // with the first load. if (OrderedBB.dominates(Dominator, Before)) return false; } } return true; }; // Record base, offset load pairs. for (auto *Base : Loads) { for (auto *Offset : Loads) { if (Base == Offset || OffsetLoads.count(Offset)) continue; if (AreSequentialAccesses(Base, Offset, *DL, *SE) && SafeToPair(Base, Offset)) { LoadPairs[Base] = Offset; OffsetLoads.insert(Offset); break; } } } LLVM_DEBUG(if (!LoadPairs.empty()) { dbgs() << "Consecutive load pairs:\n"; for (auto &MapIt : LoadPairs) { LLVM_DEBUG(dbgs() << *MapIt.first << ", " << *MapIt.second << "\n"); } }); return LoadPairs.size() > 1; } // Search recursively back through the operands to find a tree of values that // form a multiply-accumulate chain. The search records the Add and Mul // instructions that form the reduction and allows us to find a single value // to be used as the initial input to the accumlator. bool ARMParallelDSP::Search(Value *V, BasicBlock *BB, Reduction &R) { // If we find a non-instruction, try to use it as the initial accumulator // value. This may have already been found during the search in which case // this function will return false, signaling a search fail. auto *I = dyn_cast(V); if (!I) return R.InsertAcc(V); if (I->getParent() != BB) return false; switch (I->getOpcode()) { default: break; case Instruction::PHI: // Could be the accumulator value. return R.InsertAcc(V); case Instruction::Add: { // Adds should be adding together two muls, or another add and a mul to // be within the mac chain. One of the operands may also be the // accumulator value at which point we should stop searching. R.InsertAdd(I); Value *LHS = I->getOperand(0); Value *RHS = I->getOperand(1); bool ValidLHS = Search(LHS, BB, R); bool ValidRHS = Search(RHS, BB, R); if (ValidLHS && ValidRHS) return true; return R.InsertAcc(I); } case Instruction::Mul: { Value *MulOp0 = I->getOperand(0); Value *MulOp1 = I->getOperand(1); return IsNarrowSequence<16>(MulOp0) && IsNarrowSequence<16>(MulOp1); } case Instruction::SExt: return Search(I->getOperand(0), BB, R); } return false; } // The pass needs to identify integer add/sub reductions of 16-bit vector // multiplications. // To use SMLAD: // 1) we first need to find integer add then look for this pattern: // // acc0 = ... // ld0 = load i16 // sext0 = sext i16 %ld0 to i32 // ld1 = load i16 // sext1 = sext i16 %ld1 to i32 // mul0 = mul %sext0, %sext1 // ld2 = load i16 // sext2 = sext i16 %ld2 to i32 // ld3 = load i16 // sext3 = sext i16 %ld3 to i32 // mul1 = mul i32 %sext2, %sext3 // add0 = add i32 %mul0, %acc0 // acc1 = add i32 %add0, %mul1 // // Which can be selected to: // // ldr r0 // ldr r1 // smlad r2, r0, r1, r2 // // If constants are used instead of loads, these will need to be hoisted // out and into a register. // // If loop invariants are used instead of loads, these need to be packed // before the loop begins. // bool ARMParallelDSP::MatchSMLAD(Function &F) { bool Changed = false; for (auto &BB : F) { SmallPtrSet AllAdds; if (!RecordMemoryOps(&BB)) continue; for (Instruction &I : reverse(BB)) { if (I.getOpcode() != Instruction::Add) continue; if (AllAdds.count(&I)) continue; const auto *Ty = I.getType(); if (!Ty->isIntegerTy(32) && !Ty->isIntegerTy(64)) continue; Reduction R(&I); if (!Search(&I, &BB, R)) continue; R.InsertMuls(); LLVM_DEBUG(dbgs() << "After search, Reduction:\n"; R.dump()); if (!CreateParallelPairs(R)) continue; InsertParallelMACs(R); Changed = true; AllAdds.insert(R.getAdds().begin(), R.getAdds().end()); } } return Changed; } bool ARMParallelDSP::CreateParallelPairs(Reduction &R) { // Not enough mul operations to make a pair. if (R.getMuls().size() < 2) return false; // Check that the muls operate directly upon sign extended loads. for (auto &MulCand : R.getMuls()) { if (!MulCand->HasTwoLoadInputs()) return false; } auto CanPair = [&](Reduction &R, MulCandidate *PMul0, MulCandidate *PMul1) { // The first elements of each vector should be loads with sexts. If we // find that its two pairs of consecutive loads, then these can be // transformed into two wider loads and the users can be replaced with // DSP intrinsics. auto Ld0 = static_cast(PMul0->LHS); auto Ld1 = static_cast(PMul1->LHS); auto Ld2 = static_cast(PMul0->RHS); auto Ld3 = static_cast(PMul1->RHS); if (AreSequentialLoads(Ld0, Ld1, PMul0->VecLd)) { if (AreSequentialLoads(Ld2, Ld3, PMul1->VecLd)) { LLVM_DEBUG(dbgs() << "OK: found two pairs of parallel loads!\n"); R.AddMulPair(PMul0, PMul1); return true; } else if (AreSequentialLoads(Ld3, Ld2, PMul1->VecLd)) { LLVM_DEBUG(dbgs() << "OK: found two pairs of parallel loads!\n"); LLVM_DEBUG(dbgs() << " exchanging Ld2 and Ld3\n"); R.AddMulPair(PMul0, PMul1, true); return true; } } else if (AreSequentialLoads(Ld1, Ld0, PMul0->VecLd) && AreSequentialLoads(Ld2, Ld3, PMul1->VecLd)) { LLVM_DEBUG(dbgs() << "OK: found two pairs of parallel loads!\n"); LLVM_DEBUG(dbgs() << " exchanging Ld0 and Ld1\n"); LLVM_DEBUG(dbgs() << " and swapping muls\n"); // Only the second operand can be exchanged, so swap the muls. R.AddMulPair(PMul1, PMul0, true); return true; } return false; }; MulCandList &Muls = R.getMuls(); const unsigned Elems = Muls.size(); for (unsigned i = 0; i < Elems; ++i) { MulCandidate *PMul0 = static_cast(Muls[i].get()); if (PMul0->Paired) continue; for (unsigned j = 0; j < Elems; ++j) { if (i == j) continue; MulCandidate *PMul1 = static_cast(Muls[j].get()); if (PMul1->Paired) continue; const Instruction *Mul0 = PMul0->Root; const Instruction *Mul1 = PMul1->Root; if (Mul0 == Mul1) continue; assert(PMul0 != PMul1 && "expected different chains"); if (CanPair(R, PMul0, PMul1)) break; } } return !R.getMulPairs().empty(); } void ARMParallelDSP::InsertParallelMACs(Reduction &R) { auto CreateSMLAD = [&](LoadInst* WideLd0, LoadInst *WideLd1, Value *Acc, bool Exchange, Instruction *InsertAfter) { // Replace the reduction chain with an intrinsic call Value* Args[] = { WideLd0, WideLd1, Acc }; Function *SMLAD = nullptr; if (Exchange) SMLAD = Acc->getType()->isIntegerTy(32) ? Intrinsic::getDeclaration(M, Intrinsic::arm_smladx) : Intrinsic::getDeclaration(M, Intrinsic::arm_smlaldx); else SMLAD = Acc->getType()->isIntegerTy(32) ? Intrinsic::getDeclaration(M, Intrinsic::arm_smlad) : Intrinsic::getDeclaration(M, Intrinsic::arm_smlald); IRBuilder Builder(InsertAfter->getParent(), BasicBlock::iterator(InsertAfter)); Instruction *Call = Builder.CreateCall(SMLAD, Args); NumSMLAD++; return Call; }; // Return the instruction after the dominated instruction. auto GetInsertPoint = [this](Value *A, Value *B) { assert((isa(A) || isa(B)) && "expected at least one instruction"); Value *V = nullptr; if (!isa(A)) V = B; else if (!isa(B)) V = A; else V = DT->dominates(cast(A), cast(B)) ? B : A; return &*++BasicBlock::iterator(cast(V)); }; Value *Acc = R.getAccumulator(); // For any muls that were discovered but not paired, accumulate their values // as before. IRBuilder Builder(R.getRoot()->getParent()); MulCandList &MulCands = R.getMuls(); for (auto &MulCand : MulCands) { if (MulCand->Paired) continue; Instruction *Mul = cast(MulCand->Root); LLVM_DEBUG(dbgs() << "Accumulating unpaired mul: " << *Mul << "\n"); if (R.getType() != Mul->getType()) { assert(R.is64Bit() && "expected 64-bit result"); Builder.SetInsertPoint(&*++BasicBlock::iterator(Mul)); Mul = cast(Builder.CreateSExt(Mul, R.getRoot()->getType())); } if (!Acc) { Acc = Mul; continue; } // If Acc is the original incoming value to the reduction, it could be a // phi. But the phi will dominate Mul, meaning that Mul will be the // insertion point. Builder.SetInsertPoint(GetInsertPoint(Mul, Acc)); Acc = Builder.CreateAdd(Mul, Acc); } if (!Acc) { Acc = R.is64Bit() ? ConstantInt::get(IntegerType::get(M->getContext(), 64), 0) : ConstantInt::get(IntegerType::get(M->getContext(), 32), 0); } else if (Acc->getType() != R.getType()) { Builder.SetInsertPoint(R.getRoot()); Acc = Builder.CreateSExt(Acc, R.getType()); } // Roughly sort the mul pairs in their program order. OrderedBasicBlock OrderedBB(R.getRoot()->getParent()); llvm::sort(R.getMulPairs(), [&OrderedBB](auto &PairA, auto &PairB) { const Instruction *A = PairA.first->Root; const Instruction *B = PairB.first->Root; return OrderedBB.dominates(A, B); }); IntegerType *Ty = IntegerType::get(M->getContext(), 32); for (auto &Pair : R.getMulPairs()) { MulCandidate *LHSMul = Pair.first; MulCandidate *RHSMul = Pair.second; LoadInst *BaseLHS = LHSMul->getBaseLoad(); LoadInst *BaseRHS = RHSMul->getBaseLoad(); LoadInst *WideLHS = WideLoads.count(BaseLHS) ? WideLoads[BaseLHS]->getLoad() : CreateWideLoad(LHSMul->VecLd, Ty); LoadInst *WideRHS = WideLoads.count(BaseRHS) ? WideLoads[BaseRHS]->getLoad() : CreateWideLoad(RHSMul->VecLd, Ty); Instruction *InsertAfter = GetInsertPoint(WideLHS, WideRHS); InsertAfter = GetInsertPoint(InsertAfter, Acc); Acc = CreateSMLAD(WideLHS, WideRHS, Acc, RHSMul->Exchange, InsertAfter); } R.UpdateRoot(cast(Acc)); } LoadInst* ARMParallelDSP::CreateWideLoad(MemInstList &Loads, IntegerType *LoadTy) { assert(Loads.size() == 2 && "currently only support widening two loads"); LoadInst *Base = Loads[0]; LoadInst *Offset = Loads[1]; Instruction *BaseSExt = dyn_cast(Base->user_back()); Instruction *OffsetSExt = dyn_cast(Offset->user_back()); assert((BaseSExt && OffsetSExt) && "Loads should have a single, extending, user"); std::function MoveBefore = [&](Value *A, Value *B) -> void { if (!isa(A) || !isa(B)) return; auto *Source = cast(A); auto *Sink = cast(B); if (DT->dominates(Source, Sink) || Source->getParent() != Sink->getParent() || isa(Source) || isa(Sink)) return; Source->moveBefore(Sink); for (auto &Op : Source->operands()) MoveBefore(Op, Source); }; // Insert the load at the point of the original dominating load. LoadInst *DomLoad = DT->dominates(Base, Offset) ? Base : Offset; IRBuilder IRB(DomLoad->getParent(), ++BasicBlock::iterator(DomLoad)); // Bitcast the pointer to a wider type and create the wide load, while making // sure to maintain the original alignment as this prevents ldrd from being // generated when it could be illegal due to memory alignment. const unsigned AddrSpace = DomLoad->getPointerAddressSpace(); Value *VecPtr = IRB.CreateBitCast(Base->getPointerOperand(), LoadTy->getPointerTo(AddrSpace)); LoadInst *WideLoad = IRB.CreateAlignedLoad(LoadTy, VecPtr, Base->getAlignment()); // Make sure everything is in the correct order in the basic block. MoveBefore(Base->getPointerOperand(), VecPtr); MoveBefore(VecPtr, WideLoad); // From the wide load, create two values that equal the original two loads. // Loads[0] needs trunc while Loads[1] needs a lshr and trunc. // TODO: Support big-endian as well. Value *Bottom = IRB.CreateTrunc(WideLoad, Base->getType()); Value *NewBaseSExt = IRB.CreateSExt(Bottom, BaseSExt->getType()); BaseSExt->replaceAllUsesWith(NewBaseSExt); IntegerType *OffsetTy = cast(Offset->getType()); Value *ShiftVal = ConstantInt::get(LoadTy, OffsetTy->getBitWidth()); Value *Top = IRB.CreateLShr(WideLoad, ShiftVal); Value *Trunc = IRB.CreateTrunc(Top, OffsetTy); Value *NewOffsetSExt = IRB.CreateSExt(Trunc, OffsetSExt->getType()); OffsetSExt->replaceAllUsesWith(NewOffsetSExt); LLVM_DEBUG(dbgs() << "From Base and Offset:\n" << *Base << "\n" << *Offset << "\n" << "Created Wide Load:\n" << *WideLoad << "\n" << *Bottom << "\n" << *NewBaseSExt << "\n" << *Top << "\n" << *Trunc << "\n" << *NewOffsetSExt << "\n"); WideLoads.emplace(std::make_pair(Base, std::make_unique(Loads, WideLoad))); return WideLoad; } Pass *llvm::createARMParallelDSPPass() { return new ARMParallelDSP(); } char ARMParallelDSP::ID = 0; INITIALIZE_PASS_BEGIN(ARMParallelDSP, "arm-parallel-dsp", "Transform functions to use DSP intrinsics", false, false) INITIALIZE_PASS_END(ARMParallelDSP, "arm-parallel-dsp", "Transform functions to use DSP intrinsics", false, false)