1//===- RelLookupTableConverterPass - Rel Table Conv -----------------------===//
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// This file implements relative lookup table converter that converts
10// lookup tables to relative lookup tables to make them PIC-friendly.
11//
12//===----------------------------------------------------------------------===//
13
14#include "llvm/Transforms/Utils/RelLookupTableConverter.h"
15#include "llvm/Analysis/ConstantFolding.h"
16#include "llvm/Analysis/TargetTransformInfo.h"
17#include "llvm/IR/BasicBlock.h"
18#include "llvm/IR/IRBuilder.h"
19#include "llvm/IR/Instructions.h"
20#include "llvm/IR/Module.h"
21
22using namespace llvm;
23
24struct LookupTableInfo {
25 Value *Index;
26 SmallVector<Constant *> Ptrs;
27};
28
29static bool shouldConvertToRelLookupTable(LookupTableInfo &Info, Module &M,
30 GlobalVariable &GV) {
31 // If lookup table has more than one user,
32 // do not generate a relative lookup table.
33 // This is to simplify the analysis that needs to be done for this pass.
34 // TODO: Add support for lookup tables with multiple uses.
35 // For ex, this can happen when a function that uses a lookup table gets
36 // inlined into multiple call sites.
37 //
38 // If the original lookup table does not have local linkage and is
39 // not dso_local, do not generate a relative lookup table.
40 // This optimization creates a relative lookup table that consists of
41 // offsets between the start of the lookup table and its elements.
42 // To be able to generate these offsets, relative lookup table and
43 // its elements should have internal linkage and be dso_local, which means
44 // that they should resolve to symbols within the same linkage unit.
45 if (!GV.hasInitializer() || !GV.isConstant() || !GV.hasOneUse() ||
46 !GV.hasLocalLinkage() || !GV.isDSOLocal() || !GV.isImplicitDSOLocal())
47 return false;
48
49 auto *GEP = dyn_cast<GetElementPtrInst>(Val: GV.use_begin()->getUser());
50 if (!GEP || !GEP->hasOneUse())
51 return false;
52
53 auto *Load = dyn_cast<LoadInst>(Val: GEP->use_begin()->getUser());
54 if (!Load || Load->isVolatile())
55 return false;
56
57 // If values are not 64-bit pointers, do not generate a relative lookup table.
58 const DataLayout &DL = M.getDataLayout();
59 Type *ElemType = Load->getType();
60 if (!ElemType->isPointerTy() || DL.getPointerTypeSizeInBits(ElemType) != 64)
61 return false;
62
63 // Make sure this is a gep of the form GV + scale*var.
64 unsigned IndexWidth = DL.getIndexTypeSizeInBits(Ty: GEP->getType());
65 SmallMapVector<Value *, APInt, 4> VarOffsets;
66 APInt ConstOffset(IndexWidth, 0);
67 if (!GEP->collectOffset(DL, BitWidth: IndexWidth, VariableOffsets&: VarOffsets, ConstantOffset&: ConstOffset) ||
68 !ConstOffset.isZero() || VarOffsets.size() != 1)
69 return false;
70
71 // This can't be a pointer lookup table if the stride is smaller than a
72 // pointer.
73 Info.Index = VarOffsets.front().first;
74 const APInt &Stride = VarOffsets.front().second;
75 if (Stride.ult(RHS: DL.getTypeStoreSize(Ty: ElemType)))
76 return false;
77
78 SmallVector<GlobalVariable *, 4> GVOps;
79 Triple TT = M.getTargetTriple();
80 // FIXME: This should be removed in the future.
81 bool ShouldDropUnnamedAddr =
82 // Drop unnamed_addr to avoid matching pattern in
83 // `handleIndirectSymViaGOTPCRel`, which generates GOTPCREL relocations
84 // not supported by the GNU linker and LLD versions below 18 on aarch64.
85 TT.isAArch64()
86 // Apple's ld64 (and ld-prime on Xcode 15.2) miscompile something on
87 // x86_64-apple-darwin. See
88 // https://github.com/rust-lang/rust/issues/140686 and
89 // https://github.com/rust-lang/rust/issues/141306.
90 || (TT.isX86() && TT.isOSDarwin());
91
92 APInt Offset(IndexWidth, 0);
93 uint64_t GVSize = GV.getGlobalSize(DL);
94 for (; Offset.ult(RHS: GVSize); Offset += Stride) {
95 Constant *C =
96 ConstantFoldLoadFromConst(C: GV.getInitializer(), Ty: ElemType, Offset, DL);
97 if (!C)
98 return false;
99
100 GlobalValue *GVOp;
101 APInt GVOffset;
102
103 // If an operand is not a constant offset from a lookup table,
104 // do not generate a relative lookup table.
105 if (!IsConstantOffsetFromGlobal(C, GV&: GVOp, Offset&: GVOffset, DL))
106 return false;
107
108 // If operand is mutable, do not generate a relative lookup table.
109 auto *GlobalVarOp = dyn_cast<GlobalVariable>(Val: GVOp);
110 if (!GlobalVarOp || !GlobalVarOp->isConstant())
111 return false;
112
113 if (!GlobalVarOp->hasLocalLinkage() || !GlobalVarOp->isDSOLocal() ||
114 !GlobalVarOp->isImplicitDSOLocal())
115 return false;
116
117 // On AArch64 small code model, the text-to-data span can be up to 4GB,
118 // which exceeds 32-bit signed relative offsets. Avoid converting if the
119 // target operand requires dynamic relocations (placing it in .data.rel.ro
120 // in the data segment rather than .rodata in the text segment).
121 if (TT.isAArch64() &&
122 (!GlobalVarOp->hasInitializer() ||
123 GlobalVarOp->getInitializer()->needsDynamicRelocation()))
124 return false;
125
126 if (ShouldDropUnnamedAddr)
127 GVOps.push_back(Elt: GlobalVarOp);
128
129 Info.Ptrs.push_back(Elt: C);
130 }
131
132 if (ShouldDropUnnamedAddr)
133 for (auto *GVOp : GVOps)
134 GVOp->setUnnamedAddr(GlobalValue::UnnamedAddr::None);
135
136 return true;
137}
138
139static GlobalVariable *createRelLookupTable(LookupTableInfo &Info,
140 GlobalVariable &LookupTable) {
141 Module &M = *LookupTable.getParent();
142 ArrayType *IntArrayTy =
143 ArrayType::get(ElementType: Type::getInt32Ty(C&: M.getContext()), NumElements: Info.Ptrs.size());
144
145 GlobalVariable *RelLookupTable = new GlobalVariable(
146 M, IntArrayTy, LookupTable.isConstant(), LookupTable.getLinkage(),
147 nullptr, LookupTable.getName() + ".rel", &LookupTable,
148 LookupTable.getThreadLocalMode(), LookupTable.getAddressSpace(),
149 LookupTable.isExternallyInitialized());
150
151 Type *IntPtrTy = M.getDataLayout().getIntPtrType(C&: M.getContext());
152 Type *Int32Ty = Type::getInt32Ty(C&: M.getContext());
153 Constant *Base = ConstantExpr::getPtrToInt(C: RelLookupTable, Ty: IntPtrTy);
154
155 uint64_t Idx = 0;
156 SmallVector<Constant *, 64> RelLookupTableContents(Info.Ptrs.size());
157
158 for (Constant *Element : Info.Ptrs) {
159 Constant *Target = ConstantExpr::getPtrToInt(C: Element, Ty: IntPtrTy);
160 Constant *Sub = ConstantExpr::getSub(C1: Target, C2: Base);
161 Constant *RelOffset = ConstantExpr::getTrunc(C: Sub, Ty: Int32Ty);
162 RelLookupTableContents[Idx++] = RelOffset;
163 }
164
165 Constant *Initializer =
166 ConstantArray::get(T: IntArrayTy, V: RelLookupTableContents);
167 RelLookupTable->setInitializer(Initializer);
168 RelLookupTable->setUnnamedAddr(GlobalValue::UnnamedAddr::Global);
169 RelLookupTable->setAlignment(llvm::Align(4));
170 return RelLookupTable;
171}
172
173static void convertToRelLookupTable(LookupTableInfo &Info,
174 GlobalVariable &LookupTable) {
175 GetElementPtrInst *GEP =
176 cast<GetElementPtrInst>(Val: LookupTable.use_begin()->getUser());
177 LoadInst *Load = cast<LoadInst>(Val: GEP->use_begin()->getUser());
178
179 Module &M = *LookupTable.getParent();
180 BasicBlock *BB = GEP->getParent();
181 IRBuilder<> Builder(BB);
182
183 // Generate an array that consists of relative offsets.
184 GlobalVariable *RelLookupTable =
185 createRelLookupTable(Info, LookupTable);
186
187 // Place new instruction sequence before GEP.
188 Builder.SetInsertPoint(GEP);
189 IntegerType *IntTy = cast<IntegerType>(Val: Info.Index->getType());
190 Value *Offset = Builder.CreateShl(LHS: Info.Index, RHS: ConstantInt::get(Ty: IntTy, V: 2),
191 Name: "reltable.shift");
192
193 // Insert the call to load.relative intrinsic before LOAD.
194 // GEP might not be immediately followed by a LOAD, like it can be hoisted
195 // outside the loop or another instruction might be inserted them in between.
196 Builder.SetInsertPoint(Load);
197 Function *LoadRelIntrinsic = llvm::Intrinsic::getOrInsertDeclaration(
198 M: &M, id: Intrinsic::load_relative, OverloadTys: {Info.Index->getType()});
199
200 // Create a call to load.relative intrinsic that computes the target address
201 // by adding base address (lookup table address) and relative offset.
202 Value *Result = Builder.CreateCall(Callee: LoadRelIntrinsic, Args: {RelLookupTable, Offset},
203 Name: "reltable.intrinsic");
204
205 // Replace load instruction with the new generated instruction sequence.
206 Load->replaceAllUsesWith(V: Result);
207 // Remove Load and GEP instructions.
208 Load->eraseFromParent();
209 GEP->eraseFromParent();
210}
211
212// Convert lookup tables to relative lookup tables in the module.
213static bool convertToRelativeLookupTables(
214 Module &M, function_ref<TargetTransformInfo &(Function &)> GetTTI) {
215 for (Function &F : M) {
216 if (F.isDeclaration())
217 continue;
218
219 // Check if we have a target that supports relative lookup tables.
220 if (!GetTTI(F).shouldBuildRelLookupTables())
221 return false;
222
223 // We assume that the result is independent of the checked function.
224 break;
225 }
226
227 bool Changed = false;
228
229 for (GlobalVariable &GV : llvm::make_early_inc_range(Range: M.globals())) {
230 LookupTableInfo Info;
231 if (!shouldConvertToRelLookupTable(Info, M, GV))
232 continue;
233
234 convertToRelLookupTable(Info, LookupTable&: GV);
235
236 // Remove the original lookup table.
237 GV.eraseFromParent();
238
239 Changed = true;
240 }
241
242 return Changed;
243}
244
245PreservedAnalyses RelLookupTableConverterPass::run(Module &M,
246 ModuleAnalysisManager &AM) {
247 FunctionAnalysisManager &FAM =
248 AM.getResult<FunctionAnalysisManagerModuleProxy>(IR&: M).getManager();
249
250 auto GetTTI = [&](Function &F) -> TargetTransformInfo & {
251 return FAM.getResult<TargetIRAnalysis>(IR&: F);
252 };
253
254 if (!convertToRelativeLookupTables(M, GetTTI))
255 return PreservedAnalyses::all();
256
257 PreservedAnalyses PA;
258 PA.preserveSet<CFGAnalyses>();
259 return PA;
260}
261