1//==- RISCVPromoteConstant.cpp - Promote constant fp to global for RISC-V --==//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "RISCV.h"
10#include "RISCVSubtarget.h"
11#include "llvm/ADT/DenseMap.h"
12#include "llvm/ADT/SmallVector.h"
13#include "llvm/ADT/Statistic.h"
14#include "llvm/CodeGen/TargetLowering.h"
15#include "llvm/CodeGen/TargetPassConfig.h"
16#include "llvm/IR/BasicBlock.h"
17#include "llvm/IR/Constant.h"
18#include "llvm/IR/Constants.h"
19#include "llvm/IR/Function.h"
20#include "llvm/IR/GlobalValue.h"
21#include "llvm/IR/GlobalVariable.h"
22#include "llvm/IR/IRBuilder.h"
23#include "llvm/IR/InstIterator.h"
24#include "llvm/IR/Instruction.h"
25#include "llvm/IR/Instructions.h"
26#include "llvm/IR/IntrinsicInst.h"
27#include "llvm/IR/Module.h"
28#include "llvm/IR/Type.h"
29#include "llvm/Pass.h"
30#include "llvm/Support/Casting.h"
31#include "llvm/Support/Debug.h"
32
33using namespace llvm;
34
35#define DEBUG_TYPE "riscv-promote-const"
36#define RISCV_PROMOTE_CONSTANT_NAME "RISC-V Promote Constants"
37
38STATISTIC(NumPromoted, "Number of constant literals promoted to globals");
39STATISTIC(NumPromotedUses, "Number of uses of promoted literal constants");
40
41namespace {
42
43class RISCVPromoteConstant : public ModulePass {
44public:
45 static char ID;
46 RISCVPromoteConstant() : ModulePass(ID) {}
47
48 StringRef getPassName() const override { return RISCV_PROMOTE_CONSTANT_NAME; }
49
50 void getAnalysisUsage(AnalysisUsage &AU) const override {
51 AU.addRequired<TargetPassConfig>();
52 AU.setPreservesCFG();
53 }
54
55 /// Iterate over the functions and promote the double fp constants that
56 /// would otherwise go into the constant pool to a constant array.
57 bool runOnModule(Module &M) override {
58 if (skipModule(M))
59 return false;
60 // TargetMachine and Subtarget are needed to query isFPImmlegal.
61 const TargetPassConfig &TPC = getAnalysis<TargetPassConfig>();
62 const TargetMachine &TM = TPC.getTM<TargetMachine>();
63 bool Changed = false;
64 for (Function &F : M) {
65 if (F.isDeclaration())
66 continue;
67 const RISCVSubtarget &ST = TM.getSubtarget<RISCVSubtarget>(F);
68 const RISCVTargetLowering *TLI = ST.getTargetLowering();
69 Changed |= runOnFunction(F, TLI);
70 }
71 return Changed;
72 }
73
74private:
75 bool runOnFunction(Function &F, const RISCVTargetLowering *TLI);
76};
77} // end anonymous namespace
78
79char RISCVPromoteConstant::ID = 0;
80
81INITIALIZE_PASS(RISCVPromoteConstant, DEBUG_TYPE, RISCV_PROMOTE_CONSTANT_NAME,
82 false, false)
83
84ModulePass *llvm::createRISCVPromoteConstantPass() {
85 return new RISCVPromoteConstant();
86}
87
88bool RISCVPromoteConstant::runOnFunction(Function &F,
89 const RISCVTargetLowering *TLI) {
90 if (F.hasOptNone() || F.hasOptSize())
91 return false;
92
93 // Bail out and make no transformation if the target doesn't support
94 // doubles, or if we're not targeting RV64 as we currently see some
95 // regressions for those targets.
96 if (!TLI->isTypeLegal(VT: MVT::f64) || !TLI->isTypeLegal(VT: MVT::i64))
97 return false;
98
99 // Collect all unique double constants and their uses in the function. Use
100 // MapVector to preserve insertion order.
101 MapVector<ConstantFP *, SmallVector<Use *, 8>> ConstUsesMap;
102
103 for (Instruction &I : instructions(F)) {
104 for (Use &U : I.operands()) {
105 auto *C = dyn_cast<ConstantFP>(Val: U.get());
106 if (!C || !C->getType()->isDoubleTy())
107 continue;
108 // Do not promote if it wouldn't be loaded from the constant pool.
109 if (TLI->isFPImmLegal(Imm: C->getValueAPF(), VT: MVT::f64,
110 /*ForCodeSize=*/false))
111 continue;
112 // Do not promote a constant if it is used as an immediate argument
113 // for an intrinsic.
114 if (auto *II = dyn_cast<IntrinsicInst>(Val: U.getUser())) {
115 Function *IntrinsicFunc = II->getFunction();
116 unsigned OperandIdx = U.getOperandNo();
117 if (IntrinsicFunc && IntrinsicFunc->getAttributes().hasParamAttr(
118 ArgNo: OperandIdx, Kind: Attribute::ImmArg)) {
119 LLVM_DEBUG(dbgs() << "Skipping promotion of constant in: " << *II
120 << " because operand " << OperandIdx
121 << " must be an immediate.\n");
122 continue;
123 }
124 }
125 // Note: FP args to inline asm would be problematic if we had a
126 // constraint that required an immediate floating point operand. At the
127 // time of writing LLVM doesn't recognise such a constraint.
128 ConstUsesMap[C].push_back(Elt: &U);
129 }
130 }
131
132 int PromotableConstants = ConstUsesMap.size();
133 LLVM_DEBUG(dbgs() << "Found " << PromotableConstants
134 << " promotable constants in " << F.getName() << "\n");
135 // Bail out if no promotable constants found, or if only one is found.
136 if (PromotableConstants < 2) {
137 LLVM_DEBUG(dbgs() << "Performing no promotions as insufficient promotable "
138 "constants found\n");
139 return false;
140 }
141
142 NumPromoted += PromotableConstants;
143
144 // Create a global array containing the promoted constants.
145 Module *M = F.getParent();
146 Type *DoubleTy = Type::getDoubleTy(C&: M->getContext());
147
148 SmallVector<Constant *, 16> ConstantVector;
149 for (auto const &Pair : ConstUsesMap)
150 ConstantVector.push_back(Elt: Pair.first);
151
152 ArrayType *ArrayTy = ArrayType::get(ElementType: DoubleTy, NumElements: ConstantVector.size());
153 Constant *GlobalArrayInitializer =
154 ConstantArray::get(T: ArrayTy, V: ConstantVector);
155
156 auto *GlobalArray = new GlobalVariable(
157 *M, ArrayTy,
158 /*isConstant=*/true, GlobalValue::InternalLinkage, GlobalArrayInitializer,
159 ".promoted_doubles." + F.getName());
160
161 // A cache to hold the loaded value for a given constant within a basic block.
162 DenseMap<std::pair<ConstantFP *, BasicBlock *>, Value *> LocalLoads;
163
164 // Replace all uses with the loaded value.
165 unsigned Idx = 0;
166 for (auto const &Pair : ConstUsesMap) {
167 ConstantFP *Const = Pair.first;
168 const SmallVector<Use *, 8> &Uses = Pair.second;
169
170 for (Use *U : Uses) {
171 Instruction *UserInst = cast<Instruction>(Val: U->getUser());
172 BasicBlock *InsertionBB;
173
174 // If the user is a PHI node, we must insert the load in the
175 // corresponding predecessor basic block. Otherwise, it's inserted into
176 // the same block as the use.
177 if (auto *PN = dyn_cast<PHINode>(Val: UserInst))
178 InsertionBB = PN->getIncomingBlock(U: *U);
179 else
180 InsertionBB = UserInst->getParent();
181
182 if (isa<CatchSwitchInst>(Val: InsertionBB->getTerminator())) {
183 LLVM_DEBUG(dbgs() << "Bailing out: catchswitch means thre is no valid "
184 "insertion point.\n");
185 return false;
186 }
187
188 auto CacheKey = std::make_pair(x&: Const, y&: InsertionBB);
189 Value *LoadedVal = nullptr;
190
191 // Re-use a load if it exists in the insertion block.
192 if (LocalLoads.count(Val: CacheKey)) {
193 LoadedVal = LocalLoads.at(Val: CacheKey);
194 } else {
195 // Otherwise, create a new GEP and Load at the correct insertion point.
196 // It is always safe to insert in the first insertion point in the BB,
197 // so do that and let other passes reorder.
198 IRBuilder<> Builder(InsertionBB, InsertionBB->getFirstInsertionPt());
199 Value *ElementPtr = Builder.CreateConstInBoundsGEP2_64(
200 Ty: GlobalArray->getValueType(), Ptr: GlobalArray, Idx0: 0, Idx1: Idx, Name: "double.addr");
201 LoadedVal = Builder.CreateLoad(Ty: DoubleTy, Ptr: ElementPtr, Name: "double.val");
202
203 // Cache the newly created load for this block.
204 LocalLoads[CacheKey] = LoadedVal;
205 }
206
207 U->set(LoadedVal);
208 ++NumPromotedUses;
209 }
210 ++Idx;
211 }
212
213 return true;
214}
215