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