llvm-project/llvm/lib/CodeGen/WasmEHPrepare.cpp

332 lines
13 KiB
C++
Raw Normal View History

//===-- WasmEHPrepare - Prepare excepton handling for WebAssembly --------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This transformation is designed for use by code generators which use
// WebAssembly exception handling scheme.
//
// WebAssembly exception handling uses Windows exception IR for the middle level
// representation. This pass does the following transformation for every
// catchpad block:
// (In C-style pseudocode)
//
// - Before:
// catchpad ...
// exn = wasm.get.exception();
// selector = wasm.get.selector();
// ...
//
// - After:
// catchpad ...
// exn = wasm.catch(0); // 0 is a tag for C++
// wasm.landingpad.index(index);
// // Only add below in case it's not a single catch (...)
// __wasm_lpad_context.lpad_index = index;
// __wasm_lpad_context.lsda = wasm.lsda();
// _Unwind_CallPersonality(exn);
// int selector = __wasm.landingpad_context.selector;
// ...
//
// Also, does the following for a cleanuppad block with a call to
// __clang_call_terminate():
// - Before:
// cleanuppad ...
// exn = wasm.get.exception();
// __clang_call_terminate(exn);
//
// - After:
// cleanuppad ...
// exn = wasm.catch(0); // 0 is a tag for C++
// __clang_call_terminate(exn);
//
//
// * Background: WebAssembly EH instructions
// WebAssembly's try and catch instructions are structured as follows:
// try
// instruction*
// catch (C++ tag)
// instruction*
// ...
// catch_all
// instruction*
// try_end
//
// A catch instruction in WebAssembly does not correspond to a C++ catch clause.
// In WebAssembly, there is a single catch instruction for all C++ exceptions.
// There can be more catch instructions for exceptions in other languages, but
// they are not generated for now. catch_all catches all exceptions including
// foreign exceptions (e.g. JavaScript). We turn catchpads into catch (C++ tag)
// and cleanuppads into catch_all, with one exception: cleanuppad with a call to
// __clang_call_terminate should be both in catch (C++ tag) and catch_all.
//
//
// * Background: Direct personality function call
// In WebAssembly EH, the VM is responsible for unwinding the stack once an
// exception is thrown. After the stack is unwound, the control flow is
// transfered to WebAssembly 'catch' instruction, which returns a caught
// exception object.
//
// Unwinding the stack is not done by libunwind but the VM, so the personality
// function in libcxxabi cannot be called from libunwind during the unwinding
// process. So after a catch instruction, we insert a call to a wrapper function
// in libunwind that in turn calls the real personality function.
//
// In Itanium EH, if the personality function decides there is no matching catch
// clause in a call frame and no cleanup action to perform, the unwinder doesn't
// stop there and continues unwinding. But in Wasm EH, the unwinder stops at
// every call frame with a catch intruction, after which the personality
// function is called from the compiler-generated user code here.
//
// In libunwind, we have this struct that serves as a communincation channel
// between the compiler-generated user code and the personality function in
// libcxxabi.
//
// struct _Unwind_LandingPadContext {
// uintptr_t lpad_index;
// uintptr_t lsda;
// uintptr_t selector;
// };
// struct _Unwind_LandingPadContext __wasm_lpad_context = ...;
//
// And this wrapper in libunwind calls the personality function.
//
// _Unwind_Reason_Code _Unwind_CallPersonality(void *exception_ptr) {
// struct _Unwind_Exception *exception_obj =
// (struct _Unwind_Exception *)exception_ptr;
// _Unwind_Reason_Code ret = __gxx_personality_v0(
// 1, _UA_CLEANUP_PHASE, exception_obj->exception_class, exception_obj,
// (struct _Unwind_Context *)__wasm_lpad_context);
// return ret;
// }
//
// We pass a landing pad index, and the address of LSDA for the current function
// to the wrapper function _Unwind_CallPersonality in libunwind, and we retrieve
// the selector after it returns.
//
//===----------------------------------------------------------------------===//
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/ADT/Triple.h"
#include "llvm/CodeGen/Passes.h"
#include "llvm/CodeGen/TargetLowering.h"
#include "llvm/CodeGen/TargetSubtargetInfo.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/Pass.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
using namespace llvm;
#define DEBUG_TYPE "wasmehprepare"
namespace {
class WasmEHPrepare : public FunctionPass {
Type *LPadContextTy = nullptr; // type of 'struct _Unwind_LandingPadContext'
GlobalVariable *LPadContextGV = nullptr; // __wasm_lpad_context
// Field addresses of struct _Unwind_LandingPadContext
Value *LPadIndexField = nullptr; // lpad_index field
Value *LSDAField = nullptr; // lsda field
Value *SelectorField = nullptr; // selector
Function *CatchF = nullptr; // wasm.catch.extract() intrinsic
Function *LPadIndexF = nullptr; // wasm.landingpad.index() intrinsic
Function *LSDAF = nullptr; // wasm.lsda() intrinsic
Function *GetExnF = nullptr; // wasm.get.exception() intrinsic
Function *GetSelectorF = nullptr; // wasm.get.ehselector() intrinsic
Function *CallPersonalityF = nullptr; // _Unwind_CallPersonality() wrapper
Function *ClangCallTermF = nullptr; // __clang_call_terminate() function
void prepareEHPad(BasicBlock *BB, unsigned Index);
void prepareTerminateCleanupPad(BasicBlock *BB);
public:
static char ID; // Pass identification, replacement for typeid
WasmEHPrepare() : FunctionPass(ID) {}
bool doInitialization(Module &M) override;
bool runOnFunction(Function &F) override;
StringRef getPassName() const override {
return "WebAssembly Exception handling preparation";
}
};
} // end anonymous namespace
char WasmEHPrepare::ID = 0;
INITIALIZE_PASS(WasmEHPrepare, DEBUG_TYPE, "Prepare WebAssembly exceptions",
false, false)
FunctionPass *llvm::createWasmEHPass() { return new WasmEHPrepare(); }
bool WasmEHPrepare::doInitialization(Module &M) {
IRBuilder<> IRB(M.getContext());
LPadContextTy = StructType::get(IRB.getInt32Ty(), // lpad_index
IRB.getInt8PtrTy(), // lsda
IRB.getInt32Ty() // selector
);
return false;
}
bool WasmEHPrepare::runOnFunction(Function &F) {
SmallVector<BasicBlock *, 16> CatchPads;
SmallVector<BasicBlock *, 16> CleanupPads;
for (BasicBlock &BB : F) {
if (!BB.isEHPad())
continue;
auto *Pad = BB.getFirstNonPHI();
if (isa<CatchPadInst>(Pad))
CatchPads.push_back(&BB);
else if (isa<CleanupPadInst>(Pad))
CleanupPads.push_back(&BB);
}
if (CatchPads.empty() && CleanupPads.empty())
return false;
assert(F.hasPersonalityFn() && "Personality function not found");
Module &M = *F.getParent();
IRBuilder<> IRB(F.getContext());
// __wasm_lpad_context global variable
LPadContextGV = cast<GlobalVariable>(
M.getOrInsertGlobal("__wasm_lpad_context", LPadContextTy));
LPadIndexField = IRB.CreateConstGEP2_32(LPadContextTy, LPadContextGV, 0, 0,
"lpad_index_gep");
LSDAField =
IRB.CreateConstGEP2_32(LPadContextTy, LPadContextGV, 0, 1, "lsda_gep");
SelectorField = IRB.CreateConstGEP2_32(LPadContextTy, LPadContextGV, 0, 2,
"selector_gep");
// wasm.catch() intinsic, which will be lowered to wasm 'catch' instruction.
CatchF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_catch);
// wasm.landingpad.index() intrinsic, which is to specify landingpad index
LPadIndexF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_landingpad_index);
// wasm.lsda() intrinsic. Returns the address of LSDA table for the current
// function.
LSDAF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_lsda);
// wasm.get.exception() and wasm.get.ehselector() intrinsics. Calls to these
// are generated in clang.
GetExnF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_get_exception);
GetSelectorF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_get_ehselector);
// _Unwind_CallPersonality() wrapper function, which calls the personality
CallPersonalityF = cast<Function>(M.getOrInsertFunction(
"_Unwind_CallPersonality", IRB.getInt32Ty(), IRB.getInt8PtrTy()));
CallPersonalityF->setDoesNotThrow();
// __clang_call_terminate() function, which is inserted by clang in case a
// cleanup throws
ClangCallTermF = M.getFunction("__clang_call_terminate");
unsigned Index = 0;
for (auto *BB : CatchPads) {
auto *CPI = cast<CatchPadInst>(BB->getFirstNonPHI());
// In case of a single catch (...), we don't need to emit LSDA
if (CPI->getNumArgOperands() == 1 &&
cast<Constant>(CPI->getArgOperand(0))->isNullValue())
prepareEHPad(BB, -1);
else
prepareEHPad(BB, Index++);
}
if (!ClangCallTermF)
return !CatchPads.empty();
// Cleanuppads will turn into catch_all later, but cleanuppads with a call to
// __clang_call_terminate() is a special case. __clang_call_terminate() takes
// an exception object, so we have to duplicate call in both 'catch <C++ tag>'
// and 'catch_all' clauses. Here we only insert a call to catch; the
// duplication will be done later. In catch_all, the exception object will be
// set to null.
for (auto *BB : CleanupPads)
for (auto &I : *BB)
if (auto *CI = dyn_cast<CallInst>(&I))
if (CI->getCalledValue() == ClangCallTermF)
prepareEHPad(BB, -1);
return true;
}
void WasmEHPrepare::prepareEHPad(BasicBlock *BB, unsigned Index) {
assert(BB->isEHPad() && "BB is not an EHPad!");
IRBuilder<> IRB(BB->getContext());
IRB.SetInsertPoint(&*BB->getFirstInsertionPt());
// The argument to wasm.catch() is the tag for C++ exceptions, which we set to
// 0 for this module.
// Pseudocode: void *exn = wasm.catch(0);
Instruction *Exn = IRB.CreateCall(CatchF, IRB.getInt32(0), "exn");
// Replace the return value of wasm.get.exception() with the return value from
// wasm.catch().
auto *FPI = cast<FuncletPadInst>(BB->getFirstNonPHI());
Instruction *GetExnCI = nullptr, *GetSelectorCI = nullptr;
for (auto &U : FPI->uses()) {
if (auto *CI = dyn_cast<CallInst>(U.getUser())) {
if (CI->getCalledValue() == GetExnF)
GetExnCI = CI;
else if (CI->getCalledValue() == GetSelectorF)
GetSelectorCI = CI;
}
}
assert(GetExnCI && "wasm.get.exception() call does not exist");
GetExnCI->replaceAllUsesWith(Exn);
GetExnCI->eraseFromParent();
// In case it is a catchpad with single catch (...) or a cleanuppad, we don't
// need to call personality function because we don't need a selector.
if (FPI->getNumArgOperands() == 0 ||
(FPI->getNumArgOperands() == 1 &&
cast<Constant>(FPI->getArgOperand(0))->isNullValue())) {
if (GetSelectorCI) {
assert(GetSelectorCI->use_empty() &&
"wasm.get.ehselector() still has uses!");
GetSelectorCI->eraseFromParent();
}
return;
}
IRB.SetInsertPoint(Exn->getNextNode());
// This is to create a map of <landingpad EH label, landingpad index> in
// SelectionDAGISel, which is to be used in EHStreamer to emit LSDA tables.
// Pseudocode: wasm.landingpad.index(Index);
IRB.CreateCall(LPadIndexF, IRB.getInt32(Index));
// Pseudocode: __wasm_lpad_context.lpad_index = index;
IRB.CreateStore(IRB.getInt32(Index), LPadIndexField);
// Store LSDA address only if this catchpad belongs to a top-level
// catchswitch. If there is another catchpad that dominates this pad, we don't
// need to store LSDA address again, because they are the same throughout the
// function and have been already stored before.
// TODO Can we not store LSDA address in user function but make libcxxabi
// compute it?
auto *CPI = cast<CatchPadInst>(FPI);
if (isa<ConstantTokenNone>(CPI->getCatchSwitch()->getParentPad()))
// Pseudocode: __wasm_lpad_context.lsda = wasm.lsda();
IRB.CreateStore(IRB.CreateCall(LSDAF), LSDAField);
// Pseudocode: _Unwind_CallPersonality(exn);
CallInst *PersCI =
IRB.CreateCall(CallPersonalityF, Exn, OperandBundleDef("funclet", CPI));
PersCI->setDoesNotThrow();
// Pseudocode: int selector = __wasm.landingpad_context.selector;
Instruction *Selector = IRB.CreateLoad(SelectorField, "selector");
// Replace the return value from wasm.get.ehselector() with the selector value
// loaded from __wasm_lpad_context.selector.
assert(GetSelectorCI && "wasm.get.ehselector() call does not exist");
GetSelectorCI->replaceAllUsesWith(Selector);
GetSelectorCI->eraseFromParent();
}