| //==- RISCVPromoteConstant.cpp - Promote constant fp to global for RISC-V --==// | |
| // | |
| // 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 | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "RISCV.h" | |
| #include "RISCVSubtarget.h" | |
| #include "llvm/ADT/DenseMap.h" | |
| #include "llvm/ADT/SmallVector.h" | |
| #include "llvm/ADT/Statistic.h" | |
| #include "llvm/CodeGen/TargetLowering.h" | |
| #include "llvm/CodeGen/TargetPassConfig.h" | |
| #include "llvm/IR/BasicBlock.h" | |
| #include "llvm/IR/Constant.h" | |
| #include "llvm/IR/Constants.h" | |
| #include "llvm/IR/Function.h" | |
| #include "llvm/IR/GlobalValue.h" | |
| #include "llvm/IR/GlobalVariable.h" | |
| #include "llvm/IR/IRBuilder.h" | |
| #include "llvm/IR/InstIterator.h" | |
| #include "llvm/IR/Instruction.h" | |
| #include "llvm/IR/Instructions.h" | |
| #include "llvm/IR/IntrinsicInst.h" | |
| #include "llvm/IR/Module.h" | |
| #include "llvm/IR/Type.h" | |
| #include "llvm/Pass.h" | |
| #include "llvm/Support/Casting.h" | |
| #include "llvm/Support/Debug.h" | |
| using namespace llvm; | |
| #define DEBUG_TYPE "riscv-promote-const" | |
| #define RISCV_PROMOTE_CONSTANT_NAME "RISC-V Promote Constants" | |
| STATISTIC(NumPromoted, "Number of constant literals promoted to globals"); | |
| STATISTIC(NumPromotedUses, "Number of uses of promoted literal constants"); | |
| namespace { | |
| class RISCVPromoteConstant : public ModulePass { | |
| public: | |
| static char ID; | |
| RISCVPromoteConstant() : ModulePass(ID) {} | |
| StringRef getPassName() const override { return RISCV_PROMOTE_CONSTANT_NAME; } | |
| void getAnalysisUsage(AnalysisUsage &AU) const override { | |
| AU.addRequired<TargetPassConfig>(); | |
| AU.setPreservesCFG(); | |
| } | |
| /// Iterate over the functions and promote the double fp constants that | |
| /// would otherwise go into the constant pool to a constant array. | |
| bool runOnModule(Module &M) override { | |
| if (skipModule(M)) | |
| return false; | |
| // TargetMachine and Subtarget are needed to query isFPImmlegal. | |
| const TargetPassConfig &TPC = getAnalysis<TargetPassConfig>(); | |
| const TargetMachine &TM = TPC.getTM<TargetMachine>(); | |
| bool Changed = false; | |
| for (Function &F : M) { | |
| if (F.isDeclaration()) | |
| continue; | |
| const RISCVSubtarget &ST = TM.getSubtarget<RISCVSubtarget>(F); | |
| const RISCVTargetLowering *TLI = ST.getTargetLowering(); | |
| Changed |= runOnFunction(F, TLI); | |
| } | |
| return Changed; | |
| } | |
| private: | |
| bool runOnFunction(Function &F, const RISCVTargetLowering *TLI); | |
| }; | |
| } // end anonymous namespace | |
| char RISCVPromoteConstant::ID = 0; | |
| INITIALIZE_PASS(RISCVPromoteConstant, DEBUG_TYPE, RISCV_PROMOTE_CONSTANT_NAME, | |
| false, false) | |
| ModulePass *llvm::createRISCVPromoteConstantPass() { | |
| return new RISCVPromoteConstant(); | |
| } | |
| bool RISCVPromoteConstant::runOnFunction(Function &F, | |
| const RISCVTargetLowering *TLI) { | |
| if (F.hasOptNone() || F.hasOptSize()) | |
| return false; | |
| // Bail out and make no transformation if the target doesn't support | |
| // doubles, or if we're not targeting RV64 as we currently see some | |
| // regressions for those targets. | |
| if (!TLI->isTypeLegal(MVT::f64) || !TLI->isTypeLegal(MVT::i64)) | |
| return false; | |
| // Collect all unique double constants and their uses in the function. Use | |
| // MapVector to preserve insertion order. | |
| MapVector<ConstantFP *, SmallVector<Use *, 8>> ConstUsesMap; | |
| for (Instruction &I : instructions(F)) { | |
| for (Use &U : I.operands()) { | |
| auto *C = dyn_cast<ConstantFP>(U.get()); | |
| if (!C || !C->getType()->isDoubleTy()) | |
| continue; | |
| // Do not promote if it wouldn't be loaded from the constant pool. | |
| if (TLI->isFPImmLegal(C->getValueAPF(), MVT::f64, | |
| /*ForCodeSize=*/false)) | |
| continue; | |
| // Do not promote a constant if it is used as an immediate argument | |
| // for an intrinsic. | |
| if (auto *II = dyn_cast<IntrinsicInst>(U.getUser())) { | |
| Function *IntrinsicFunc = II->getFunction(); | |
| unsigned OperandIdx = U.getOperandNo(); | |
| if (IntrinsicFunc && IntrinsicFunc->getAttributes().hasParamAttr( | |
| OperandIdx, Attribute::ImmArg)) { | |
| LLVM_DEBUG(dbgs() << "Skipping promotion of constant in: " << *II | |
| << " because operand " << OperandIdx | |
| << " must be an immediate.\n"); | |
| continue; | |
| } | |
| } | |
| // Note: FP args to inline asm would be problematic if we had a | |
| // constraint that required an immediate floating point operand. At the | |
| // time of writing LLVM doesn't recognise such a constraint. | |
| ConstUsesMap[C].push_back(&U); | |
| } | |
| } | |
| int PromotableConstants = ConstUsesMap.size(); | |
| LLVM_DEBUG(dbgs() << "Found " << PromotableConstants | |
| << " promotable constants in " << F.getName() << "\n"); | |
| // Bail out if no promotable constants found, or if only one is found. | |
| if (PromotableConstants < 2) { | |
| LLVM_DEBUG(dbgs() << "Performing no promotions as insufficient promotable " | |
| "constants found\n"); | |
| return false; | |
| } | |
| NumPromoted += PromotableConstants; | |
| // Create a global array containing the promoted constants. | |
| Module *M = F.getParent(); | |
| Type *DoubleTy = Type::getDoubleTy(M->getContext()); | |
| SmallVector<Constant *, 16> ConstantVector; | |
| for (auto const &Pair : ConstUsesMap) | |
| ConstantVector.push_back(Pair.first); | |
| ArrayType *ArrayTy = ArrayType::get(DoubleTy, ConstantVector.size()); | |
| Constant *GlobalArrayInitializer = | |
| ConstantArray::get(ArrayTy, ConstantVector); | |
| auto *GlobalArray = new GlobalVariable( | |
| *M, ArrayTy, | |
| /*isConstant=*/true, GlobalValue::InternalLinkage, GlobalArrayInitializer, | |
| ".promoted_doubles." + F.getName()); | |
| // A cache to hold the loaded value for a given constant within a basic block. | |
| DenseMap<std::pair<ConstantFP *, BasicBlock *>, Value *> LocalLoads; | |
| // Replace all uses with the loaded value. | |
| unsigned Idx = 0; | |
| for (auto const &Pair : ConstUsesMap) { | |
| ConstantFP *Const = Pair.first; | |
| const SmallVector<Use *, 8> &Uses = Pair.second; | |
| for (Use *U : Uses) { | |
| Instruction *UserInst = cast<Instruction>(U->getUser()); | |
| BasicBlock *InsertionBB; | |
| // If the user is a PHI node, we must insert the load in the | |
| // corresponding predecessor basic block. Otherwise, it's inserted into | |
| // the same block as the use. | |
| if (auto *PN = dyn_cast<PHINode>(UserInst)) | |
| InsertionBB = PN->getIncomingBlock(*U); | |
| else | |
| InsertionBB = UserInst->getParent(); | |
| if (isa<CatchSwitchInst>(InsertionBB->getTerminator())) { | |
| LLVM_DEBUG(dbgs() << "Bailing out: catchswitch means thre is no valid " | |
| "insertion point.\n"); | |
| return false; | |
| } | |
| auto CacheKey = std::make_pair(Const, InsertionBB); | |
| Value *LoadedVal = nullptr; | |
| // Re-use a load if it exists in the insertion block. | |
| if (LocalLoads.count(CacheKey)) { | |
| LoadedVal = LocalLoads.at(CacheKey); | |
| } else { | |
| // Otherwise, create a new GEP and Load at the correct insertion point. | |
| // It is always safe to insert in the first insertion point in the BB, | |
| // so do that and let other passes reorder. | |
| IRBuilder<> Builder(InsertionBB->getFirstInsertionPt()); | |
| Value *ElementPtr = Builder.CreateConstInBoundsGEP2_64( | |
| GlobalArray->getValueType(), GlobalArray, 0, Idx, "double.addr"); | |
| LoadedVal = Builder.CreateLoad(DoubleTy, ElementPtr, "double.val"); | |
| // Cache the newly created load for this block. | |
| LocalLoads[CacheKey] = LoadedVal; | |
| } | |
| U->set(LoadedVal); | |
| ++NumPromotedUses; | |
| } | |
| ++Idx; | |
| } | |
| return true; | |
| } |