//==- 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/InitializePasses.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(); 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(); const TargetMachine &TM = TPC.getTM(); bool Changed = false; for (Function &F : M) { const RISCVSubtarget &ST = TM.getSubtarget(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> ConstUsesMap; for (Instruction &I : instructions(F)) { for (Use &U : I.operands()) { auto *C = dyn_cast(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(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 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, Value *> LocalLoads; // Replace all uses with the loaded value. unsigned Idx = 0; for (auto const &Pair : ConstUsesMap) { ConstantFP *Const = Pair.first; const SmallVector &Uses = Pair.second; for (Use *U : Uses) { Instruction *UserInst = cast(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(UserInst)) InsertionBB = PN->getIncomingBlock(*U); else InsertionBB = UserInst->getParent(); if (isa(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, 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; }