409 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8//9// This transformation is designed for use by code generators which use10// WebAssembly exception handling scheme. This currently supports C++11// exceptions.12//13// WebAssembly exception handling uses Windows exception IR for the middle level14// representation. This pass does the following transformation for every15// 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_lpad_context.selector;33// ...34//35//36// * Background: Direct personality function call37// In WebAssembly EH, the VM is responsible for unwinding the stack once an38// exception is thrown. After the stack is unwound, the control flow is39// transfered to WebAssembly 'catch' instruction.40//41// Unwinding the stack is not done by libunwind but the VM, so the personality42// function in libcxxabi cannot be called from libunwind during the unwinding43// process. So after a catch instruction, we insert a call to a wrapper function44// in libunwind that in turn calls the real personality function.45//46// In Itanium EH, if the personality function decides there is no matching catch47// clause in a call frame and no cleanup action to perform, the unwinder doesn't48// stop there and continues unwinding. But in Wasm EH, the unwinder stops at49// every call frame with a catch intruction, after which the personality50// function is called from the compiler-generated user code here.51//52// In libunwind, we have this struct that serves as a communincation channel53// between the compiler-generated user code and the personality function in54// 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 function75// to the wrapper function _Unwind_CallPersonality in libunwind, and we retrieve76// the selector after it returns.77//78//===----------------------------------------------------------------------===//79 80#include "llvm/CodeGen/WasmEHPrepare.h"81#include "llvm/CodeGen/MachineBasicBlock.h"82#include "llvm/CodeGen/Passes.h"83#include "llvm/CodeGen/WasmEHFuncInfo.h"84#include "llvm/IR/EHPersonalities.h"85#include "llvm/IR/IRBuilder.h"86#include "llvm/IR/IntrinsicsWebAssembly.h"87#include "llvm/IR/Module.h"88#include "llvm/IR/RuntimeLibcalls.h"89#include "llvm/InitializePasses.h"90#include "llvm/Transforms/Utils/BasicBlockUtils.h"91 92using namespace llvm;93 94#define DEBUG_TYPE "wasm-eh-prepare"95 96namespace {97class WasmEHPrepareImpl {98 friend class WasmEHPrepare;99 100 Type *LPadContextTy = nullptr; // type of 'struct _Unwind_LandingPadContext'101 GlobalVariable *LPadContextGV = nullptr; // __wasm_lpad_context102 103 // Field addresses of struct _Unwind_LandingPadContext104 Value *LPadIndexField = nullptr; // lpad_index field105 Value *LSDAField = nullptr; // lsda field106 Value *SelectorField = nullptr; // selector107 108 Function *ThrowF = nullptr; // wasm.throw() intrinsic109 Function *LPadIndexF = nullptr; // wasm.landingpad.index() intrinsic110 Function *LSDAF = nullptr; // wasm.lsda() intrinsic111 Function *GetExnF = nullptr; // wasm.get.exception() intrinsic112 Function *CatchF = nullptr; // wasm.catch() intrinsic113 Function *GetSelectorF = nullptr; // wasm.get.ehselector() intrinsic114 FunctionCallee CallPersonalityF =115 nullptr; // _Unwind_CallPersonality() wrapper116 117 bool prepareThrows(Function &F);118 bool prepareEHPads(Function &F);119 void prepareEHPad(BasicBlock *BB, bool NeedPersonality, unsigned Index = 0);120 121public:122 WasmEHPrepareImpl() = default;123 WasmEHPrepareImpl(Type *LPadContextTy_) : LPadContextTy(LPadContextTy_) {}124 bool runOnFunction(Function &F);125};126 127class WasmEHPrepare : public FunctionPass {128 WasmEHPrepareImpl P;129 130public:131 static char ID; // Pass identification, replacement for typeid132 133 WasmEHPrepare() : FunctionPass(ID) {}134 bool doInitialization(Module &M) override;135 bool runOnFunction(Function &F) override { return P.runOnFunction(F); }136 137 StringRef getPassName() const override {138 return "WebAssembly Exception handling preparation";139 }140};141 142} // end anonymous namespace143 144PreservedAnalyses WasmEHPreparePass::run(Function &F,145 FunctionAnalysisManager &) {146 auto &Context = F.getContext();147 auto *I32Ty = Type::getInt32Ty(Context);148 auto *PtrTy = PointerType::get(Context, 0);149 auto *LPadContextTy =150 StructType::get(I32Ty /*lpad_index*/, PtrTy /*lsda*/, I32Ty /*selector*/);151 WasmEHPrepareImpl P(LPadContextTy);152 bool Changed = P.runOnFunction(F);153 return Changed ? PreservedAnalyses::none() : PreservedAnalyses ::all();154}155 156char WasmEHPrepare::ID = 0;157INITIALIZE_PASS_BEGIN(WasmEHPrepare, DEBUG_TYPE,158 "Prepare WebAssembly exceptions", false, false)159INITIALIZE_PASS_END(WasmEHPrepare, DEBUG_TYPE, "Prepare WebAssembly exceptions",160 false, false)161 162FunctionPass *llvm::createWasmEHPass() { return new WasmEHPrepare(); }163 164bool WasmEHPrepare::doInitialization(Module &M) {165 IRBuilder<> IRB(M.getContext());166 P.LPadContextTy = StructType::get(IRB.getInt32Ty(), // lpad_index167 IRB.getPtrTy(), // lsda168 IRB.getInt32Ty() // selector169 );170 return false;171}172 173// Erase the specified BBs if the BB does not have any remaining predecessors,174// and also all its dead children.175template <typename Container>176static void eraseDeadBBsAndChildren(const Container &BBs) {177 SmallVector<BasicBlock *, 8> WL(BBs.begin(), BBs.end());178 while (!WL.empty()) {179 auto *BB = WL.pop_back_val();180 if (!pred_empty(BB))181 continue;182 WL.append(succ_begin(BB), succ_end(BB));183 DeleteDeadBlock(BB);184 }185}186 187bool WasmEHPrepareImpl::runOnFunction(Function &F) {188 bool Changed = false;189 Changed |= prepareThrows(F);190 Changed |= prepareEHPads(F);191 return Changed;192}193 194bool WasmEHPrepareImpl::prepareThrows(Function &F) {195 Module &M = *F.getParent();196 IRBuilder<> IRB(F.getContext());197 bool Changed = false;198 199 // wasm.throw() intinsic, which will be lowered to wasm 'throw' instruction.200 ThrowF = Intrinsic::getOrInsertDeclaration(&M, Intrinsic::wasm_throw);201 // Insert an unreachable instruction after a call to @llvm.wasm.throw and202 // delete all following instructions within the BB, and delete all the dead203 // children of the BB as well.204 for (User *U : ThrowF->users()) {205 auto *ThrowI = dyn_cast<CallInst>(U);206 if (!ThrowI || ThrowI->getFunction() != &F)207 continue;208 Changed = true;209 auto *BB = ThrowI->getParent();210 SmallVector<BasicBlock *, 4> Succs(successors(BB));211 BB->erase(std::next(BasicBlock::iterator(ThrowI)), BB->end());212 IRB.SetInsertPoint(BB);213 IRB.CreateUnreachable();214 eraseDeadBBsAndChildren(Succs);215 }216 217 return Changed;218}219 220bool WasmEHPrepareImpl::prepareEHPads(Function &F) {221 Module &M = *F.getParent();222 IRBuilder<> IRB(F.getContext());223 224 SmallVector<BasicBlock *, 16> CatchPads;225 SmallVector<BasicBlock *, 16> CleanupPads;226 for (BasicBlock &BB : F) {227 if (!BB.isEHPad())228 continue;229 BasicBlock::iterator Pad = BB.getFirstNonPHIIt();230 if (isa<CatchPadInst>(Pad))231 CatchPads.push_back(&BB);232 else if (isa<CleanupPadInst>(Pad))233 CleanupPads.push_back(&BB);234 }235 if (CatchPads.empty() && CleanupPads.empty())236 return false;237 238 if (!F.hasPersonalityFn() ||239 !isScopedEHPersonality(classifyEHPersonality(F.getPersonalityFn()))) {240 report_fatal_error("Function '" + F.getName() +241 "' does not have a correct Wasm personality function "242 "'__gxx_wasm_personality_v0'");243 }244 assert(F.hasPersonalityFn() && "Personality function not found");245 246 // __wasm_lpad_context global variable.247 // This variable should be thread local. If the target does not support TLS,248 // we depend on CoalesceFeaturesAndStripAtomics to downgrade it to249 // non-thread-local ones, in which case we don't allow this object to be250 // linked with other objects using shared memory.251 LPadContextGV = M.getOrInsertGlobal("__wasm_lpad_context", LPadContextTy);252 LPadContextGV->setThreadLocalMode(GlobalValue::GeneralDynamicTLSModel);253 254 LPadIndexField = LPadContextGV;255 LSDAField = IRB.CreateConstInBoundsGEP2_32(LPadContextTy, LPadContextGV, 0, 1,256 "lsda_gep");257 SelectorField = IRB.CreateConstInBoundsGEP2_32(LPadContextTy, LPadContextGV,258 0, 2, "selector_gep");259 260 // wasm.landingpad.index() intrinsic, which is to specify landingpad index261 LPadIndexF =262 Intrinsic::getOrInsertDeclaration(&M, Intrinsic::wasm_landingpad_index);263 // wasm.lsda() intrinsic. Returns the address of LSDA table for the current264 // function.265 LSDAF = Intrinsic::getOrInsertDeclaration(&M, Intrinsic::wasm_lsda);266 // wasm.get.exception() and wasm.get.ehselector() intrinsics. Calls to these267 // are generated in clang.268 GetExnF =269 Intrinsic::getOrInsertDeclaration(&M, Intrinsic::wasm_get_exception);270 GetSelectorF =271 Intrinsic::getOrInsertDeclaration(&M, Intrinsic::wasm_get_ehselector);272 273 // wasm.catch() will be lowered down to wasm 'catch' instruction in274 // instruction selection.275 CatchF = Intrinsic::getOrInsertDeclaration(&M, Intrinsic::wasm_catch);276 277 // FIXME: Verify this is really supported for current module.278 StringRef UnwindCallPersonalityName =279 RTLIB::RuntimeLibcallsInfo::getLibcallImplName(280 RTLIB::impl__Unwind_CallPersonality);281 282 // _Unwind_CallPersonality() wrapper function, which calls the personality283 CallPersonalityF = M.getOrInsertFunction(UnwindCallPersonalityName,284 IRB.getInt32Ty(), IRB.getPtrTy());285 if (Function *F = dyn_cast<Function>(CallPersonalityF.getCallee()))286 F->setDoesNotThrow();287 288 unsigned Index = 0;289 for (auto *BB : CatchPads) {290 auto *CPI = cast<CatchPadInst>(BB->getFirstNonPHIIt());291 // In case of a single catch (...), we don't need to emit a personalify292 // function call293 if (CPI->arg_size() == 1 &&294 cast<Constant>(CPI->getArgOperand(0))->isNullValue())295 prepareEHPad(BB, false);296 else297 prepareEHPad(BB, true, Index++);298 }299 300 // Cleanup pads don't need a personality function call.301 for (auto *BB : CleanupPads)302 prepareEHPad(BB, false);303 304 return true;305}306 307// Prepare an EH pad for Wasm EH handling. If NeedPersonality is false, Index is308// ignored.309void WasmEHPrepareImpl::prepareEHPad(BasicBlock *BB, bool NeedPersonality,310 unsigned Index) {311 assert(BB->isEHPad() && "BB is not an EHPad!");312 IRBuilder<> IRB(BB->getContext());313 IRB.SetInsertPoint(BB, BB->getFirstInsertionPt());314 315 auto *FPI = cast<FuncletPadInst>(BB->getFirstNonPHIIt());316 Instruction *GetExnCI = nullptr, *GetSelectorCI = nullptr;317 for (auto &U : FPI->uses()) {318 if (auto *CI = dyn_cast<CallInst>(U.getUser())) {319 if (CI->getCalledOperand() == GetExnF)320 GetExnCI = CI;321 if (CI->getCalledOperand() == GetSelectorF)322 GetSelectorCI = CI;323 }324 }325 326 // Cleanup pads do not have any of wasm.get.exception() or327 // wasm.get.ehselector() calls. We need to do nothing.328 if (!GetExnCI) {329 assert(!GetSelectorCI &&330 "wasm.get.ehselector() cannot exist w/o wasm.get.exception()");331 return;332 }333 334 // Replace wasm.get.exception intrinsic with wasm.catch intrinsic, which will335 // be lowered to wasm 'catch' instruction. We do this mainly because336 // instruction selection cannot handle wasm.get.exception intrinsic's token337 // argument.338 Instruction *CatchCI =339 IRB.CreateCall(CatchF, {IRB.getInt32(WebAssembly::CPP_EXCEPTION)}, "exn");340 GetExnCI->replaceAllUsesWith(CatchCI);341 GetExnCI->eraseFromParent();342 343 // In case it is a catchpad with single catch (...) or a cleanuppad, we don't344 // need to call personality function because we don't need a selector.345 if (!NeedPersonality) {346 if (GetSelectorCI) {347 assert(GetSelectorCI->use_empty() &&348 "wasm.get.ehselector() still has uses!");349 GetSelectorCI->eraseFromParent();350 }351 return;352 }353 IRB.SetInsertPoint(CatchCI->getNextNode());354 355 // This is to create a map of <landingpad EH label, landingpad index> in356 // SelectionDAGISel, which is to be used in EHStreamer to emit LSDA tables.357 // Pseudocode: wasm.landingpad.index(Index);358 IRB.CreateCall(LPadIndexF, {FPI, IRB.getInt32(Index)});359 360 // Pseudocode: __wasm_lpad_context.lpad_index = index;361 IRB.CreateStore(IRB.getInt32(Index), LPadIndexField);362 363 auto *CPI = cast<CatchPadInst>(FPI);364 // TODO Sometimes storing the LSDA address every time is not necessary, in365 // case it is already set in a dominating EH pad and there is no function call366 // between from that EH pad to here. Consider optimizing those cases.367 // Pseudocode: __wasm_lpad_context.lsda = wasm.lsda();368 IRB.CreateStore(IRB.CreateCall(LSDAF), LSDAField);369 370 // Pseudocode: _Unwind_CallPersonality(exn);371 CallInst *PersCI = IRB.CreateCall(CallPersonalityF, CatchCI,372 OperandBundleDef("funclet", CPI));373 PersCI->setDoesNotThrow();374 375 // Pseudocode: int selector = __wasm_lpad_context.selector;376 Instruction *Selector =377 IRB.CreateLoad(IRB.getInt32Ty(), SelectorField, "selector");378 379 // Replace the return value from wasm.get.ehselector() with the selector value380 // loaded from __wasm_lpad_context.selector.381 assert(GetSelectorCI && "wasm.get.ehselector() call does not exist");382 GetSelectorCI->replaceAllUsesWith(Selector);383 GetSelectorCI->eraseFromParent();384}385 386void llvm::calculateWasmEHInfo(const Function *F, WasmEHFuncInfo &EHInfo) {387 // If an exception is not caught by a catchpad (i.e., it is a foreign388 // exception), it will unwind to its parent catchswitch's unwind destination.389 // We don't record an unwind destination for cleanuppads because every390 // exception should be caught by it.391 for (const auto &BB : *F) {392 if (!BB.isEHPad())393 continue;394 const Instruction *Pad = &*BB.getFirstNonPHIIt();395 396 if (const auto *CatchPad = dyn_cast<CatchPadInst>(Pad)) {397 const auto *UnwindBB = CatchPad->getCatchSwitch()->getUnwindDest();398 if (!UnwindBB)399 continue;400 const Instruction *UnwindPad = &*UnwindBB->getFirstNonPHIIt();401 if (const auto *CatchSwitch = dyn_cast<CatchSwitchInst>(UnwindPad))402 // Currently there should be only one handler per a catchswitch.403 EHInfo.setUnwindDest(&BB, *CatchSwitch->handlers().begin());404 else // cleanuppad405 EHInfo.setUnwindDest(&BB, UnwindBB);406 }407 }408}409