1 | //===-- LowerGlobalDtors.cpp - Lower @llvm.global_dtors -------------------===// |
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 | /// \file |
10 | /// Lower @llvm.global_dtors. |
11 | /// |
12 | /// Implement @llvm.global_dtors by creating wrapper functions that are |
13 | /// registered in @llvm.global_ctors and which contain a call to |
14 | /// `__cxa_atexit` to register their destructor functions. |
15 | /// |
16 | //===----------------------------------------------------------------------===// |
17 | |
18 | #include "llvm/Transforms/Utils/LowerGlobalDtors.h" |
19 | |
20 | #include "llvm/IR/Constants.h" |
21 | #include "llvm/IR/Instructions.h" |
22 | #include "llvm/IR/Intrinsics.h" |
23 | #include "llvm/InitializePasses.h" |
24 | #include "llvm/Pass.h" |
25 | #include "llvm/Transforms/Utils.h" |
26 | #include "llvm/Transforms/Utils/ModuleUtils.h" |
27 | #include <map> |
28 | |
29 | using namespace llvm; |
30 | |
31 | #define DEBUG_TYPE "lower-global-dtors" |
32 | |
33 | namespace { |
34 | class LowerGlobalDtorsLegacyPass final : public ModulePass { |
35 | StringRef getPassName() const override { |
36 | return "Lower @llvm.global_dtors via `__cxa_atexit`" ; |
37 | } |
38 | |
39 | void getAnalysisUsage(AnalysisUsage &AU) const override { |
40 | AU.setPreservesCFG(); |
41 | ModulePass::getAnalysisUsage(AU); |
42 | } |
43 | |
44 | bool runOnModule(Module &M) override; |
45 | |
46 | public: |
47 | static char ID; |
48 | LowerGlobalDtorsLegacyPass() : ModulePass(ID) { |
49 | initializeLowerGlobalDtorsLegacyPassPass(*PassRegistry::getPassRegistry()); |
50 | } |
51 | }; |
52 | } // End anonymous namespace |
53 | |
54 | char LowerGlobalDtorsLegacyPass::ID = 0; |
55 | INITIALIZE_PASS(LowerGlobalDtorsLegacyPass, DEBUG_TYPE, |
56 | "Lower @llvm.global_dtors via `__cxa_atexit`" , false, false) |
57 | |
58 | ModulePass *llvm::createLowerGlobalDtorsLegacyPass() { |
59 | return new LowerGlobalDtorsLegacyPass(); |
60 | } |
61 | |
62 | static bool runImpl(Module &M); |
63 | bool LowerGlobalDtorsLegacyPass::runOnModule(Module &M) { return runImpl(M); } |
64 | |
65 | PreservedAnalyses LowerGlobalDtorsPass::run(Module &M, |
66 | ModuleAnalysisManager &AM) { |
67 | bool Changed = runImpl(M); |
68 | if (!Changed) |
69 | return PreservedAnalyses::all(); |
70 | |
71 | PreservedAnalyses PA; |
72 | PA.preserveSet<CFGAnalyses>(); |
73 | return PA; |
74 | } |
75 | |
76 | static bool runImpl(Module &M) { |
77 | GlobalVariable *GV = M.getGlobalVariable(Name: "llvm.global_dtors" ); |
78 | if (!GV || !GV->hasInitializer()) |
79 | return false; |
80 | |
81 | const ConstantArray *InitList = dyn_cast<ConstantArray>(Val: GV->getInitializer()); |
82 | if (!InitList) |
83 | return false; |
84 | |
85 | // Validate @llvm.global_dtor's type. |
86 | auto *ETy = dyn_cast<StructType>(Val: InitList->getType()->getElementType()); |
87 | if (!ETy || ETy->getNumElements() != 3 || |
88 | !ETy->getTypeAtIndex(N: 0U)->isIntegerTy() || |
89 | !ETy->getTypeAtIndex(N: 1U)->isPointerTy() || |
90 | !ETy->getTypeAtIndex(N: 2U)->isPointerTy()) |
91 | return false; // Not (int, ptr, ptr). |
92 | |
93 | // Collect the contents of @llvm.global_dtors, ordered by priority. Within a |
94 | // priority, sequences of destructors with the same associated object are |
95 | // recorded so that we can register them as a group. |
96 | std::map< |
97 | uint16_t, |
98 | std::vector<std::pair<Constant *, std::vector<Constant *>>> |
99 | > DtorFuncs; |
100 | for (Value *O : InitList->operands()) { |
101 | auto *CS = dyn_cast<ConstantStruct>(Val: O); |
102 | if (!CS) |
103 | continue; // Malformed. |
104 | |
105 | auto *Priority = dyn_cast<ConstantInt>(Val: CS->getOperand(i_nocapture: 0)); |
106 | if (!Priority) |
107 | continue; // Malformed. |
108 | uint16_t PriorityValue = Priority->getLimitedValue(UINT16_MAX); |
109 | |
110 | Constant *DtorFunc = CS->getOperand(i_nocapture: 1); |
111 | if (DtorFunc->isNullValue()) |
112 | break; // Found a null terminator, skip the rest. |
113 | |
114 | Constant *Associated = CS->getOperand(i_nocapture: 2); |
115 | Associated = cast<Constant>(Val: Associated->stripPointerCasts()); |
116 | |
117 | auto &AtThisPriority = DtorFuncs[PriorityValue]; |
118 | if (AtThisPriority.empty() || AtThisPriority.back().first != Associated) { |
119 | std::vector<Constant *> NewList; |
120 | NewList.push_back(x: DtorFunc); |
121 | AtThisPriority.push_back(x: std::make_pair(x&: Associated, y&: NewList)); |
122 | } else { |
123 | AtThisPriority.back().second.push_back(x: DtorFunc); |
124 | } |
125 | } |
126 | if (DtorFuncs.empty()) |
127 | return false; |
128 | |
129 | // extern "C" int __cxa_atexit(void (*f)(void *), void *p, void *d); |
130 | LLVMContext &C = M.getContext(); |
131 | PointerType *VoidStar = PointerType::getUnqual(C); |
132 | Type *AtExitFuncArgs[] = {VoidStar}; |
133 | FunctionType *AtExitFuncTy = |
134 | FunctionType::get(Result: Type::getVoidTy(C), Params: AtExitFuncArgs, |
135 | /*isVarArg=*/false); |
136 | |
137 | FunctionCallee AtExit = M.getOrInsertFunction( |
138 | Name: "__cxa_atexit" , |
139 | T: FunctionType::get(Result: Type::getInt32Ty(C), |
140 | Params: {PointerType::get(ElementType: AtExitFuncTy, AddressSpace: 0), VoidStar, VoidStar}, |
141 | /*isVarArg=*/false)); |
142 | |
143 | // If __cxa_atexit is defined (e.g. in the case of LTO) and arg0 is not |
144 | // actually used (i.e. it's dummy/stub function as used in emscripten when |
145 | // the program never exits) we can simply return early and clear out |
146 | // @llvm.global_dtors. |
147 | if (auto F = dyn_cast<Function>(Val: AtExit.getCallee())) { |
148 | if (F && F->hasExactDefinition() && F->getArg(i: 0)->getNumUses() == 0) { |
149 | GV->eraseFromParent(); |
150 | return true; |
151 | } |
152 | } |
153 | |
154 | // Declare __dso_local. |
155 | Type *DsoHandleTy = Type::getInt8Ty(C); |
156 | Constant *DsoHandle = M.getOrInsertGlobal(Name: "__dso_handle" , Ty: DsoHandleTy, CreateGlobalCallback: [&] { |
157 | auto *GV = new GlobalVariable(M, DsoHandleTy, /*isConstant=*/true, |
158 | GlobalVariable::ExternalWeakLinkage, nullptr, |
159 | "__dso_handle" ); |
160 | GV->setVisibility(GlobalVariable::HiddenVisibility); |
161 | return GV; |
162 | }); |
163 | |
164 | // For each unique priority level and associated symbol, generate a function |
165 | // to call all the destructors at that level, and a function to register the |
166 | // first function with __cxa_atexit. |
167 | for (auto &PriorityAndMore : DtorFuncs) { |
168 | uint16_t Priority = PriorityAndMore.first; |
169 | uint64_t Id = 0; |
170 | auto &AtThisPriority = PriorityAndMore.second; |
171 | for (auto &AssociatedAndMore : AtThisPriority) { |
172 | Constant *Associated = AssociatedAndMore.first; |
173 | auto ThisId = Id++; |
174 | |
175 | Function *CallDtors = Function::Create( |
176 | Ty: AtExitFuncTy, Linkage: Function::PrivateLinkage, |
177 | N: "call_dtors" + |
178 | (Priority != UINT16_MAX ? (Twine("." ) + Twine(Priority)) |
179 | : Twine()) + |
180 | (AtThisPriority.size() > 1 ? Twine("$" ) + Twine(ThisId) |
181 | : Twine()) + |
182 | (!Associated->isNullValue() ? (Twine("." ) + Associated->getName()) |
183 | : Twine()), |
184 | M: &M); |
185 | BasicBlock *BB = BasicBlock::Create(Context&: C, Name: "body" , Parent: CallDtors); |
186 | FunctionType *VoidVoid = FunctionType::get(Result: Type::getVoidTy(C), |
187 | /*isVarArg=*/false); |
188 | |
189 | for (auto *Dtor : reverse(C&: AssociatedAndMore.second)) |
190 | CallInst::Create(Ty: VoidVoid, F: Dtor, NameStr: "" , InsertAtEnd: BB); |
191 | ReturnInst::Create(C, InsertAtEnd: BB); |
192 | |
193 | Function *RegisterCallDtors = Function::Create( |
194 | Ty: VoidVoid, Linkage: Function::PrivateLinkage, |
195 | N: "register_call_dtors" + |
196 | (Priority != UINT16_MAX ? (Twine("." ) + Twine(Priority)) |
197 | : Twine()) + |
198 | (AtThisPriority.size() > 1 ? Twine("$" ) + Twine(ThisId) |
199 | : Twine()) + |
200 | (!Associated->isNullValue() ? (Twine("." ) + Associated->getName()) |
201 | : Twine()), |
202 | M: &M); |
203 | BasicBlock *EntryBB = BasicBlock::Create(Context&: C, Name: "entry" , Parent: RegisterCallDtors); |
204 | BasicBlock *FailBB = BasicBlock::Create(Context&: C, Name: "fail" , Parent: RegisterCallDtors); |
205 | BasicBlock *RetBB = BasicBlock::Create(Context&: C, Name: "return" , Parent: RegisterCallDtors); |
206 | |
207 | Value *Null = ConstantPointerNull::get(T: VoidStar); |
208 | Value *Args[] = {CallDtors, Null, DsoHandle}; |
209 | Value *Res = CallInst::Create(Func: AtExit, Args, NameStr: "call" , InsertAtEnd: EntryBB); |
210 | Value *Cmp = new ICmpInst(*EntryBB, ICmpInst::ICMP_NE, Res, |
211 | Constant::getNullValue(Ty: Res->getType())); |
212 | BranchInst::Create(IfTrue: FailBB, IfFalse: RetBB, Cond: Cmp, InsertAtEnd: EntryBB); |
213 | |
214 | // If `__cxa_atexit` hits out-of-memory, trap, so that we don't misbehave. |
215 | // This should be very rare, because if the process is running out of |
216 | // memory before main has even started, something is wrong. |
217 | CallInst::Create(Intrinsic::getDeclaration(M: &M, Intrinsic::id: trap), "" , |
218 | FailBB); |
219 | new UnreachableInst(C, FailBB); |
220 | |
221 | ReturnInst::Create(C, InsertAtEnd: RetBB); |
222 | |
223 | // Now register the registration function with @llvm.global_ctors. |
224 | appendToGlobalCtors(M, F: RegisterCallDtors, Priority, Data: Associated); |
225 | } |
226 | } |
227 | |
228 | // Now that we've lowered everything, remove @llvm.global_dtors. |
229 | GV->eraseFromParent(); |
230 | |
231 | return true; |
232 | } |
233 | |