1//===-- WebAssemblyLowerGlobalDtors.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/// WebAssembly doesn't have a builtin way to invoke static destructors.
13/// Implement @llvm.global_dtors by creating wrapper functions that are
14/// registered in @llvm.global_ctors and which contain a call to
15/// `__cxa_atexit` to register their destructor functions.
16///
17//===----------------------------------------------------------------------===//
18
19#include "WebAssembly.h"
20#include "llvm/ADT/MapVector.h"
21#include "llvm/IR/Constants.h"
22#include "llvm/IR/Instructions.h"
23#include "llvm/IR/Intrinsics.h"
24#include "llvm/IR/Module.h"
25#include "llvm/Pass.h"
26#include "llvm/Support/Debug.h"
27#include "llvm/Support/raw_ostream.h"
28#include "llvm/Transforms/Utils/ModuleUtils.h"
29using namespace llvm;
30
31#define DEBUG_TYPE "wasm-lower-global-dtors"
32
33namespace {
34class LowerGlobalDtors final : public ModulePass {
35  StringRef getPassName() const override {
36    return "WebAssembly Lower @llvm.global_dtors";
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
46public:
47  static char ID;
48  LowerGlobalDtors() : ModulePass(ID) {}
49};
50} // End anonymous namespace
51
52char LowerGlobalDtors::ID = 0;
53INITIALIZE_PASS(LowerGlobalDtors, DEBUG_TYPE,
54                "Lower @llvm.global_dtors for WebAssembly", false, false)
55
56ModulePass *llvm::createWebAssemblyLowerGlobalDtors() {
57  return new LowerGlobalDtors();
58}
59
60bool LowerGlobalDtors::runOnModule(Module &M) {
61  LLVM_DEBUG(dbgs() << "********** Lower Global Destructors **********\n");
62
63  GlobalVariable *GV = M.getGlobalVariable("llvm.global_dtors");
64  if (!GV || !GV->hasInitializer())
65    return false;
66
67  const ConstantArray *InitList = dyn_cast<ConstantArray>(GV->getInitializer());
68  if (!InitList)
69    return false;
70
71  // Sanity-check @llvm.global_dtor's type.
72  auto *ETy = dyn_cast<StructType>(InitList->getType()->getElementType());
73  if (!ETy || ETy->getNumElements() != 3 ||
74      !ETy->getTypeAtIndex(0U)->isIntegerTy() ||
75      !ETy->getTypeAtIndex(1U)->isPointerTy() ||
76      !ETy->getTypeAtIndex(2U)->isPointerTy())
77    return false; // Not (int, ptr, ptr).
78
79  // Collect the contents of @llvm.global_dtors, ordered by priority. Within a
80  // priority, sequences of destructors with the same associated object are
81  // recorded so that we can register them as a group.
82  std::map<
83      uint16_t,
84      std::vector<std::pair<Constant *, std::vector<Constant *>>>
85  > DtorFuncs;
86  for (Value *O : InitList->operands()) {
87    auto *CS = dyn_cast<ConstantStruct>(O);
88    if (!CS)
89      continue; // Malformed.
90
91    auto *Priority = dyn_cast<ConstantInt>(CS->getOperand(0));
92    if (!Priority)
93      continue; // Malformed.
94    uint16_t PriorityValue = Priority->getLimitedValue(UINT16_MAX);
95
96    Constant *DtorFunc = CS->getOperand(1);
97    if (DtorFunc->isNullValue())
98      break; // Found a null terminator, skip the rest.
99
100    Constant *Associated = CS->getOperand(2);
101    Associated = cast<Constant>(Associated->stripPointerCasts());
102
103    auto &AtThisPriority = DtorFuncs[PriorityValue];
104    if (AtThisPriority.empty() || AtThisPriority.back().first != Associated) {
105        std::vector<Constant *> NewList;
106        NewList.push_back(DtorFunc);
107        AtThisPriority.push_back(std::make_pair(Associated, NewList));
108    } else {
109        AtThisPriority.back().second.push_back(DtorFunc);
110    }
111  }
112  if (DtorFuncs.empty())
113    return false;
114
115  // extern "C" int __cxa_atexit(void (*f)(void *), void *p, void *d);
116  LLVMContext &C = M.getContext();
117  PointerType *VoidStar = Type::getInt8PtrTy(C);
118  Type *AtExitFuncArgs[] = {VoidStar};
119  FunctionType *AtExitFuncTy =
120      FunctionType::get(Type::getVoidTy(C), AtExitFuncArgs,
121                        /*isVarArg=*/false);
122
123  FunctionCallee AtExit = M.getOrInsertFunction(
124      "__cxa_atexit",
125      FunctionType::get(Type::getInt32Ty(C),
126                        {PointerType::get(AtExitFuncTy, 0), VoidStar, VoidStar},
127                        /*isVarArg=*/false));
128
129  // Declare __dso_local.
130  Constant *DsoHandle = M.getNamedValue("__dso_handle");
131  if (!DsoHandle) {
132    Type *DsoHandleTy = Type::getInt8Ty(C);
133    GlobalVariable *Handle = new GlobalVariable(
134        M, DsoHandleTy, /*isConstant=*/true,
135        GlobalVariable::ExternalWeakLinkage, nullptr, "__dso_handle");
136    Handle->setVisibility(GlobalVariable::HiddenVisibility);
137    DsoHandle = Handle;
138  }
139
140  // For each unique priority level and associated symbol, generate a function
141  // to call all the destructors at that level, and a function to register the
142  // first function with __cxa_atexit.
143  for (auto &PriorityAndMore : DtorFuncs) {
144    uint16_t Priority = PriorityAndMore.first;
145    uint64_t Id = 0;
146    auto &AtThisPriority = PriorityAndMore.second;
147    for (auto &AssociatedAndMore : AtThisPriority) {
148      Constant *Associated = AssociatedAndMore.first;
149      auto ThisId = Id++;
150
151      Function *CallDtors = Function::Create(
152          AtExitFuncTy, Function::PrivateLinkage,
153          "call_dtors" +
154              (Priority != UINT16_MAX ? (Twine(".") + Twine(Priority))
155                                      : Twine()) +
156              (AtThisPriority.size() > 1 ? Twine("$") + Twine(ThisId)
157                                         : Twine()) +
158              (!Associated->isNullValue() ? (Twine(".") + Associated->getName())
159                                          : Twine()),
160          &M);
161      BasicBlock *BB = BasicBlock::Create(C, "body", CallDtors);
162      FunctionType *VoidVoid = FunctionType::get(Type::getVoidTy(C),
163                                                 /*isVarArg=*/false);
164
165      for (auto Dtor : reverse(AssociatedAndMore.second))
166        CallInst::Create(VoidVoid, Dtor, "", BB);
167      ReturnInst::Create(C, BB);
168
169      Function *RegisterCallDtors = Function::Create(
170          VoidVoid, Function::PrivateLinkage,
171          "register_call_dtors" +
172              (Priority != UINT16_MAX ? (Twine(".") + Twine(Priority))
173                                      : Twine()) +
174              (AtThisPriority.size() > 1 ? Twine("$") + Twine(ThisId)
175                                         : Twine()) +
176              (!Associated->isNullValue() ? (Twine(".") + Associated->getName())
177                                          : Twine()),
178          &M);
179      BasicBlock *EntryBB = BasicBlock::Create(C, "entry", RegisterCallDtors);
180      BasicBlock *FailBB = BasicBlock::Create(C, "fail", RegisterCallDtors);
181      BasicBlock *RetBB = BasicBlock::Create(C, "return", RegisterCallDtors);
182
183      Value *Null = ConstantPointerNull::get(VoidStar);
184      Value *Args[] = {CallDtors, Null, DsoHandle};
185      Value *Res = CallInst::Create(AtExit, Args, "call", EntryBB);
186      Value *Cmp = new ICmpInst(*EntryBB, ICmpInst::ICMP_NE, Res,
187                                Constant::getNullValue(Res->getType()));
188      BranchInst::Create(FailBB, RetBB, Cmp, EntryBB);
189
190      // If `__cxa_atexit` hits out-of-memory, trap, so that we don't misbehave.
191      // This should be very rare, because if the process is running out of
192      // memory before main has even started, something is wrong.
193      CallInst::Create(Intrinsic::getDeclaration(&M, Intrinsic::trap), "",
194                       FailBB);
195      new UnreachableInst(C, FailBB);
196
197      ReturnInst::Create(C, RetBB);
198
199      // Now register the registration function with @llvm.global_ctors.
200      appendToGlobalCtors(M, RegisterCallDtors, Priority, Associated);
201    }
202  }
203
204  // Now that we've lowered everything, remove @llvm.global_dtors.
205  GV->eraseFromParent();
206
207  return true;
208}
209