xref: /netbsd-src/external/apache2/llvm/dist/llvm/lib/CodeGen/WasmEHPrepare.cpp (revision 82d56013d7b633d116a93943de88e08335357a7c)
1 //===-- WasmEHPrepare - Prepare excepton handling for WebAssembly --------===//
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 transformation is designed for use by code generators which use
10 // WebAssembly exception handling scheme. This currently supports C++
11 // exceptions.
12 //
13 // WebAssembly exception handling uses Windows exception IR for the middle level
14 // representation. This pass does the following transformation for every
15 // catchpad block:
16 // (In C-style pseudocode)
17 //
18 // - Before:
19 //   catchpad ...
20 //   exn = wasm.get.exception();
21 //   selector = wasm.get.selector();
22 //   ...
23 //
24 // - After:
25 //   catchpad ...
26 //   exn = wasm.catch(WebAssembly::CPP_EXCEPTION);
27 //   // Only add below in case it's not a single catch (...)
28 //   wasm.landingpad.index(index);
29 //   __wasm_lpad_context.lpad_index = index;
30 //   __wasm_lpad_context.lsda = wasm.lsda();
31 //   _Unwind_CallPersonality(exn);
32 //   selector = __wasm.landingpad_context.selector;
33 //   ...
34 //
35 //
36 // * Background: Direct personality function call
37 // In WebAssembly EH, the VM is responsible for unwinding the stack once an
38 // exception is thrown. After the stack is unwound, the control flow is
39 // transfered to WebAssembly 'catch' instruction.
40 //
41 // Unwinding the stack is not done by libunwind but the VM, so the personality
42 // function in libcxxabi cannot be called from libunwind during the unwinding
43 // process. So after a catch instruction, we insert a call to a wrapper function
44 // in libunwind that in turn calls the real personality function.
45 //
46 // In Itanium EH, if the personality function decides there is no matching catch
47 // clause in a call frame and no cleanup action to perform, the unwinder doesn't
48 // stop there and continues unwinding. But in Wasm EH, the unwinder stops at
49 // every call frame with a catch intruction, after which the personality
50 // function is called from the compiler-generated user code here.
51 //
52 // In libunwind, we have this struct that serves as a communincation channel
53 // between the compiler-generated user code and the personality function in
54 // libcxxabi.
55 //
56 // struct _Unwind_LandingPadContext {
57 //   uintptr_t lpad_index;
58 //   uintptr_t lsda;
59 //   uintptr_t selector;
60 // };
61 // struct _Unwind_LandingPadContext __wasm_lpad_context = ...;
62 //
63 // And this wrapper in libunwind calls the personality function.
64 //
65 // _Unwind_Reason_Code _Unwind_CallPersonality(void *exception_ptr) {
66 //   struct _Unwind_Exception *exception_obj =
67 //       (struct _Unwind_Exception *)exception_ptr;
68 //   _Unwind_Reason_Code ret = __gxx_personality_v0(
69 //       1, _UA_CLEANUP_PHASE, exception_obj->exception_class, exception_obj,
70 //       (struct _Unwind_Context *)__wasm_lpad_context);
71 //   return ret;
72 // }
73 //
74 // We pass a landing pad index, and the address of LSDA for the current function
75 // to the wrapper function _Unwind_CallPersonality in libunwind, and we retrieve
76 // the selector after it returns.
77 //
78 //===----------------------------------------------------------------------===//
79 
80 #include "llvm/ADT/SetVector.h"
81 #include "llvm/ADT/Statistic.h"
82 #include "llvm/ADT/Triple.h"
83 #include "llvm/Analysis/DomTreeUpdater.h"
84 #include "llvm/CodeGen/Passes.h"
85 #include "llvm/CodeGen/TargetLowering.h"
86 #include "llvm/CodeGen/TargetSubtargetInfo.h"
87 #include "llvm/CodeGen/WasmEHFuncInfo.h"
88 #include "llvm/IR/Dominators.h"
89 #include "llvm/IR/IRBuilder.h"
90 #include "llvm/IR/Intrinsics.h"
91 #include "llvm/IR/IntrinsicsWebAssembly.h"
92 #include "llvm/InitializePasses.h"
93 #include "llvm/Pass.h"
94 #include "llvm/Transforms/Utils/BasicBlockUtils.h"
95 
96 using namespace llvm;
97 
98 #define DEBUG_TYPE "wasmehprepare"
99 
100 namespace {
101 class WasmEHPrepare : public FunctionPass {
102   Type *LPadContextTy = nullptr; // type of 'struct _Unwind_LandingPadContext'
103   GlobalVariable *LPadContextGV = nullptr; // __wasm_lpad_context
104 
105   // Field addresses of struct _Unwind_LandingPadContext
106   Value *LPadIndexField = nullptr; // lpad_index field
107   Value *LSDAField = nullptr;      // lsda field
108   Value *SelectorField = nullptr;  // selector
109 
110   Function *ThrowF = nullptr;       // wasm.throw() intrinsic
111   Function *LPadIndexF = nullptr;   // wasm.landingpad.index() intrinsic
112   Function *LSDAF = nullptr;        // wasm.lsda() intrinsic
113   Function *GetExnF = nullptr;      // wasm.get.exception() intrinsic
114   Function *CatchF = nullptr;       // wasm.catch() intrinsic
115   Function *GetSelectorF = nullptr; // wasm.get.ehselector() intrinsic
116   FunctionCallee CallPersonalityF =
117       nullptr; // _Unwind_CallPersonality() wrapper
118 
119   bool prepareThrows(Function &F);
120   bool prepareEHPads(Function &F);
121   void prepareEHPad(BasicBlock *BB, bool NeedPersonality, unsigned Index = 0);
122 
123 public:
124   static char ID; // Pass identification, replacement for typeid
125 
WasmEHPrepare()126   WasmEHPrepare() : FunctionPass(ID) {}
127   void getAnalysisUsage(AnalysisUsage &AU) const override;
128   bool doInitialization(Module &M) override;
129   bool runOnFunction(Function &F) override;
130 
getPassName() const131   StringRef getPassName() const override {
132     return "WebAssembly Exception handling preparation";
133   }
134 };
135 } // end anonymous namespace
136 
137 char WasmEHPrepare::ID = 0;
138 INITIALIZE_PASS_BEGIN(WasmEHPrepare, DEBUG_TYPE,
139                       "Prepare WebAssembly exceptions", false, false)
INITIALIZE_PASS_DEPENDENCY(DominatorTreeWrapperPass)140 INITIALIZE_PASS_DEPENDENCY(DominatorTreeWrapperPass)
141 INITIALIZE_PASS_END(WasmEHPrepare, DEBUG_TYPE, "Prepare WebAssembly exceptions",
142                     false, false)
143 
144 FunctionPass *llvm::createWasmEHPass() { return new WasmEHPrepare(); }
145 
getAnalysisUsage(AnalysisUsage & AU) const146 void WasmEHPrepare::getAnalysisUsage(AnalysisUsage &AU) const {
147   AU.addRequired<DominatorTreeWrapperPass>();
148 }
149 
doInitialization(Module & M)150 bool WasmEHPrepare::doInitialization(Module &M) {
151   IRBuilder<> IRB(M.getContext());
152   LPadContextTy = StructType::get(IRB.getInt32Ty(),   // lpad_index
153                                   IRB.getInt8PtrTy(), // lsda
154                                   IRB.getInt32Ty()    // selector
155   );
156   return false;
157 }
158 
159 // Erase the specified BBs if the BB does not have any remaining predecessors,
160 // and also all its dead children.
161 template <typename Container>
eraseDeadBBsAndChildren(const Container & BBs,DomTreeUpdater * DTU)162 static void eraseDeadBBsAndChildren(const Container &BBs, DomTreeUpdater *DTU) {
163   SmallVector<BasicBlock *, 8> WL(BBs.begin(), BBs.end());
164   while (!WL.empty()) {
165     auto *BB = WL.pop_back_val();
166     if (!pred_empty(BB))
167       continue;
168     WL.append(succ_begin(BB), succ_end(BB));
169     DeleteDeadBlock(BB, DTU);
170   }
171 }
172 
runOnFunction(Function & F)173 bool WasmEHPrepare::runOnFunction(Function &F) {
174   bool Changed = false;
175   Changed |= prepareThrows(F);
176   Changed |= prepareEHPads(F);
177   return Changed;
178 }
179 
prepareThrows(Function & F)180 bool WasmEHPrepare::prepareThrows(Function &F) {
181   auto &DT = getAnalysis<DominatorTreeWrapperPass>().getDomTree();
182   DomTreeUpdater DTU(&DT, /*PostDominatorTree*/ nullptr,
183                      DomTreeUpdater::UpdateStrategy::Eager);
184   Module &M = *F.getParent();
185   IRBuilder<> IRB(F.getContext());
186   bool Changed = false;
187 
188   // wasm.throw() intinsic, which will be lowered to wasm 'throw' instruction.
189   ThrowF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_throw);
190   // Insert an unreachable instruction after a call to @llvm.wasm.throw and
191   // delete all following instructions within the BB, and delete all the dead
192   // children of the BB as well.
193   for (User *U : ThrowF->users()) {
194     // A call to @llvm.wasm.throw() is only generated from __cxa_throw()
195     // builtin call within libcxxabi, and cannot be an InvokeInst.
196     auto *ThrowI = cast<CallInst>(U);
197     if (ThrowI->getFunction() != &F)
198       continue;
199     Changed = true;
200     auto *BB = ThrowI->getParent();
201     SmallVector<BasicBlock *, 4> Succs(successors(BB));
202     auto &InstList = BB->getInstList();
203     InstList.erase(std::next(BasicBlock::iterator(ThrowI)), InstList.end());
204     IRB.SetInsertPoint(BB);
205     IRB.CreateUnreachable();
206     eraseDeadBBsAndChildren(Succs, &DTU);
207   }
208 
209   return Changed;
210 }
211 
prepareEHPads(Function & F)212 bool WasmEHPrepare::prepareEHPads(Function &F) {
213   Module &M = *F.getParent();
214   IRBuilder<> IRB(F.getContext());
215 
216   SmallVector<BasicBlock *, 16> CatchPads;
217   SmallVector<BasicBlock *, 16> CleanupPads;
218   for (BasicBlock &BB : F) {
219     if (!BB.isEHPad())
220       continue;
221     auto *Pad = BB.getFirstNonPHI();
222     if (isa<CatchPadInst>(Pad))
223       CatchPads.push_back(&BB);
224     else if (isa<CleanupPadInst>(Pad))
225       CleanupPads.push_back(&BB);
226   }
227   if (CatchPads.empty() && CleanupPads.empty())
228     return false;
229 
230   assert(F.hasPersonalityFn() && "Personality function not found");
231 
232   // __wasm_lpad_context global variable
233   LPadContextGV = cast<GlobalVariable>(
234       M.getOrInsertGlobal("__wasm_lpad_context", LPadContextTy));
235   LPadIndexField = IRB.CreateConstGEP2_32(LPadContextTy, LPadContextGV, 0, 0,
236                                           "lpad_index_gep");
237   LSDAField =
238       IRB.CreateConstGEP2_32(LPadContextTy, LPadContextGV, 0, 1, "lsda_gep");
239   SelectorField = IRB.CreateConstGEP2_32(LPadContextTy, LPadContextGV, 0, 2,
240                                          "selector_gep");
241 
242   // wasm.landingpad.index() intrinsic, which is to specify landingpad index
243   LPadIndexF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_landingpad_index);
244   // wasm.lsda() intrinsic. Returns the address of LSDA table for the current
245   // function.
246   LSDAF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_lsda);
247   // wasm.get.exception() and wasm.get.ehselector() intrinsics. Calls to these
248   // are generated in clang.
249   GetExnF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_get_exception);
250   GetSelectorF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_get_ehselector);
251 
252   // wasm.catch() will be lowered down to wasm 'catch' instruction in
253   // instruction selection.
254   CatchF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_catch);
255 
256   // _Unwind_CallPersonality() wrapper function, which calls the personality
257   CallPersonalityF = M.getOrInsertFunction(
258       "_Unwind_CallPersonality", IRB.getInt32Ty(), IRB.getInt8PtrTy());
259   if (Function *F = dyn_cast<Function>(CallPersonalityF.getCallee()))
260     F->setDoesNotThrow();
261 
262   unsigned Index = 0;
263   for (auto *BB : CatchPads) {
264     auto *CPI = cast<CatchPadInst>(BB->getFirstNonPHI());
265     // In case of a single catch (...), we don't need to emit a personalify
266     // function call
267     if (CPI->getNumArgOperands() == 1 &&
268         cast<Constant>(CPI->getArgOperand(0))->isNullValue())
269       prepareEHPad(BB, false);
270     else
271       prepareEHPad(BB, true, Index++);
272   }
273 
274   // Cleanup pads don't need a personality function call.
275   for (auto *BB : CleanupPads)
276     prepareEHPad(BB, false);
277 
278   return true;
279 }
280 
281 // Prepare an EH pad for Wasm EH handling. If NeedPersonality is false, Index is
282 // ignored.
prepareEHPad(BasicBlock * BB,bool NeedPersonality,unsigned Index)283 void WasmEHPrepare::prepareEHPad(BasicBlock *BB, bool NeedPersonality,
284                                  unsigned Index) {
285   assert(BB->isEHPad() && "BB is not an EHPad!");
286   IRBuilder<> IRB(BB->getContext());
287   IRB.SetInsertPoint(&*BB->getFirstInsertionPt());
288 
289   auto *FPI = cast<FuncletPadInst>(BB->getFirstNonPHI());
290   Instruction *GetExnCI = nullptr, *GetSelectorCI = nullptr;
291   for (auto &U : FPI->uses()) {
292     if (auto *CI = dyn_cast<CallInst>(U.getUser())) {
293       if (CI->getCalledOperand() == GetExnF)
294         GetExnCI = CI;
295       if (CI->getCalledOperand() == GetSelectorF)
296         GetSelectorCI = CI;
297     }
298   }
299 
300   // Cleanup pads do not have any of wasm.get.exception() or
301   // wasm.get.ehselector() calls. We need to do nothing.
302   if (!GetExnCI) {
303     assert(!GetSelectorCI &&
304            "wasm.get.ehselector() cannot exist w/o wasm.get.exception()");
305     return;
306   }
307 
308   // Replace wasm.get.exception intrinsic with wasm.catch intrinsic, which will
309   // be lowered to wasm 'catch' instruction. We do this mainly because
310   // instruction selection cannot handle wasm.get.exception intrinsic's token
311   // argument.
312   Instruction *CatchCI =
313       IRB.CreateCall(CatchF, {IRB.getInt32(WebAssembly::CPP_EXCEPTION)}, "exn");
314   GetExnCI->replaceAllUsesWith(CatchCI);
315   GetExnCI->eraseFromParent();
316 
317   // In case it is a catchpad with single catch (...) or a cleanuppad, we don't
318   // need to call personality function because we don't need a selector.
319   if (!NeedPersonality) {
320     if (GetSelectorCI) {
321       assert(GetSelectorCI->use_empty() &&
322              "wasm.get.ehselector() still has uses!");
323       GetSelectorCI->eraseFromParent();
324     }
325     return;
326   }
327   IRB.SetInsertPoint(CatchCI->getNextNode());
328 
329   // This is to create a map of <landingpad EH label, landingpad index> in
330   // SelectionDAGISel, which is to be used in EHStreamer to emit LSDA tables.
331   // Pseudocode: wasm.landingpad.index(Index);
332   IRB.CreateCall(LPadIndexF, {FPI, IRB.getInt32(Index)});
333 
334   // Pseudocode: __wasm_lpad_context.lpad_index = index;
335   IRB.CreateStore(IRB.getInt32(Index), LPadIndexField);
336 
337   auto *CPI = cast<CatchPadInst>(FPI);
338   // TODO Sometimes storing the LSDA address every time is not necessary, in
339   // case it is already set in a dominating EH pad and there is no function call
340   // between from that EH pad to here. Consider optimizing those cases.
341   // Pseudocode: __wasm_lpad_context.lsda = wasm.lsda();
342   IRB.CreateStore(IRB.CreateCall(LSDAF), LSDAField);
343 
344   // Pseudocode: _Unwind_CallPersonality(exn);
345   CallInst *PersCI = IRB.CreateCall(CallPersonalityF, CatchCI,
346                                     OperandBundleDef("funclet", CPI));
347   PersCI->setDoesNotThrow();
348 
349   // Pseudocode: int selector = __wasm.landingpad_context.selector;
350   Instruction *Selector =
351       IRB.CreateLoad(IRB.getInt32Ty(), SelectorField, "selector");
352 
353   // Replace the return value from wasm.get.ehselector() with the selector value
354   // loaded from __wasm_lpad_context.selector.
355   assert(GetSelectorCI && "wasm.get.ehselector() call does not exist");
356   GetSelectorCI->replaceAllUsesWith(Selector);
357   GetSelectorCI->eraseFromParent();
358 }
359 
calculateWasmEHInfo(const Function * F,WasmEHFuncInfo & EHInfo)360 void llvm::calculateWasmEHInfo(const Function *F, WasmEHFuncInfo &EHInfo) {
361   // If an exception is not caught by a catchpad (i.e., it is a foreign
362   // exception), it will unwind to its parent catchswitch's unwind destination.
363   // We don't record an unwind destination for cleanuppads because every
364   // exception should be caught by it.
365   for (const auto &BB : *F) {
366     if (!BB.isEHPad())
367       continue;
368     const Instruction *Pad = BB.getFirstNonPHI();
369 
370     if (const auto *CatchPad = dyn_cast<CatchPadInst>(Pad)) {
371       const auto *UnwindBB = CatchPad->getCatchSwitch()->getUnwindDest();
372       if (!UnwindBB)
373         continue;
374       const Instruction *UnwindPad = UnwindBB->getFirstNonPHI();
375       if (const auto *CatchSwitch = dyn_cast<CatchSwitchInst>(UnwindPad))
376         // Currently there should be only one handler per a catchswitch.
377         EHInfo.setUnwindDest(&BB, *CatchSwitch->handlers().begin());
378       else // cleanuppad
379         EHInfo.setUnwindDest(&BB, UnwindBB);
380     }
381   }
382 }
383