brintos

brintos / llvm-project-archived public Read only

0
0
Text · 15.4 KiB · 2f54578 Raw
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