mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2024-11-25 12:12:47 +01:00
58ecf46111
Background: Wasm EH, while using Windows EH (catchpad/cleanuppad based) IR, uses Itanium-based libraries and ABIs with some modifications. `__clang_call_terminate` is a wrapper generated in Clang's Itanium C++ ABI implementation. It contains this code, in C-style pseudocode: ``` void __clang_call_terminate(void *exn) { __cxa_begin_catch(exn); std::terminate(); } ``` So this function is a wrapper to call `__cxa_begin_catch` on the exception pointer before termination. In Itanium ABI, this function is called when another exception is thrown while processing an exception. The pointer for this second, violating exception is passed as the argument of this `__clang_call_terminate`, which calls `__cxa_begin_catch` with that pointer and calls `std::terminate` to terminate the program. The spec (https://libcxxabi.llvm.org/spec.html) for `__cxa_begin_catch` says, ``` When the personality routine encounters a termination condition, it will call __cxa_begin_catch() to mark the exception as handled and then call terminate(), which shall not return to its caller. ``` In wasm EH's Clang implementation, this function is called from cleanuppads that terminates the program, which we also call terminate pads. Cleanuppads normally don't access the thrown exception and the wasm backend converts them to `catch_all` blocks. But because we need the exception pointer in this cleanuppad, we generate `wasm.get.exception` intrinsic (which will eventually be lowered to `catch` instruction) as we do in the catchpads. But because terminate pads are cleanup pads and should run even when a foreign exception is thrown, so what we have been doing is: 1. In `WebAssemblyLateEHPrepare::ensureSingleBBTermPads()`, we make sure terminate pads are in this simple shape: ``` %exn = catch call @__clang_call_terminate(%exn) unreachable ``` 2. In `WebAssemblyHandleEHTerminatePads` pass at the end of the pipeline, we attach a `catch_all` to terminate pads, so they will be in this form: ``` %exn = catch call @__clang_call_terminate(%exn) unreachable catch_all call @std::terminate() unreachable ``` In `catch_all` part, we don't have the exception pointer, so we call `std::terminate()` directly. The reason we ran HandleEHTerminatePads at the end of the pipeline, separate from LateEHPrepare, was it was convenient to assume there was only a single `catch` part per `try` during CFGSort and CFGStackify. --- Problem: While it thinks terminate pads could have been possibly split or calls to `__clang_call_terminate` could have been duplicated, `WebAssemblyLateEHPrepare::ensureSingleBBTermPads()` assumes terminate pads contain no more than calls to `__clang_call_terminate` and `unreachable` instruction. I assumed that because in LLVM very limited forms of transformations are done to catchpads and cleanuppads to maintain the scoping structure. But it turned out to be incorrect; passes can merge cleanuppads into one, including terminate pads, as long as the new code has a correct scoping structure. One pass that does this I observed was `SimplifyCFG`, but there can be more. After this transformation, a single cleanuppad can contain any number of other instructions with the call to `__clang_call_terminate` and can span many BBs. It wouldn't be practical to duplicate all these BBs within the cleanuppad to generate the equivalent `catch_all` blocks, only with calls to `__clang_call_terminate` replaced by calls to `std::terminate`. Unless we do more complicated transformation to split those calls to `__clang_call_terminate` into a separate cleanuppad, it is tricky to solve. --- Solution (?): This CL just disables the generation and use of `__clang_call_terminate` and calls `std::terminate()` directly in its place. The possible downside of this approach can be, because the Itanium ABI intended to "mark" the violating exception handled, we don't do that anymore. What `__cxa_begin_catch` actually does is increment the exception's handler count and decrement the uncaught exception count, which in my opinion do not matter much given that we are about to terminate the program anyway. Also it does not affect info like stack traces that can be possibly shown to developers. And while we use a variant of Itanium EH ABI, we can make some deviations if we choose to; we are already different in that in the current version of the EH spec we don't support two-phase unwinding. We can possibly consider a more complicated transformation later to reenable this, but I don't think that has high priority. Changes in this CL contains: - In Clang, we don't generate a call to `wasm.get.exception()` intrinsic and `__clang_call_terminate` function in terminate pads anymore; we simply generate calls to `std::terminate()`, which is the default implementation of `CGCXXABI::emitTerminateForUnexpectedException`. - Remove `WebAssembly::ensureSingleBBTermPads() function and `WebAssemblyHandleEHTerminatePads` pass, because terminate pads are already `catch_all` now (because they don't need the exception pointer) and we don't need these transformations anymore. - Change tests to use `std::terminate` directly. Also removes tests that tested `LateEHPrepare::ensureSingleBBTermPads` and `HandleEHTerminatePads` pass. - Drive-by fix: Add some function attributes to EH intrinsic declarations Fixes https://github.com/emscripten-core/emscripten/issues/13582. Reviewed By: dschuff, tlively Differential Revision: https://reviews.llvm.org/D97834
383 lines
14 KiB
C++
383 lines
14 KiB
C++
//===-- WasmEHPrepare - Prepare excepton handling for WebAssembly --------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This transformation is designed for use by code generators which use
|
|
// WebAssembly exception handling scheme. This currently supports C++
|
|
// exceptions.
|
|
//
|
|
// 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(WebAssembly::CPP_EXCEPTION);
|
|
// // Only add below in case it's not a single catch (...)
|
|
// wasm.landingpad.index(index);
|
|
// __wasm_lpad_context.lpad_index = index;
|
|
// __wasm_lpad_context.lsda = wasm.lsda();
|
|
// _Unwind_CallPersonality(exn);
|
|
// selector = __wasm.landingpad_context.selector;
|
|
// ...
|
|
//
|
|
//
|
|
// * 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.
|
|
//
|
|
// 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/Analysis/DomTreeUpdater.h"
|
|
#include "llvm/CodeGen/Passes.h"
|
|
#include "llvm/CodeGen/TargetLowering.h"
|
|
#include "llvm/CodeGen/TargetSubtargetInfo.h"
|
|
#include "llvm/CodeGen/WasmEHFuncInfo.h"
|
|
#include "llvm/IR/Dominators.h"
|
|
#include "llvm/IR/IRBuilder.h"
|
|
#include "llvm/IR/Intrinsics.h"
|
|
#include "llvm/IR/IntrinsicsWebAssembly.h"
|
|
#include "llvm/InitializePasses.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 *ThrowF = nullptr; // wasm.throw() intrinsic
|
|
Function *LPadIndexF = nullptr; // wasm.landingpad.index() intrinsic
|
|
Function *LSDAF = nullptr; // wasm.lsda() intrinsic
|
|
Function *GetExnF = nullptr; // wasm.get.exception() intrinsic
|
|
Function *CatchF = nullptr; // wasm.catch() intrinsic
|
|
Function *GetSelectorF = nullptr; // wasm.get.ehselector() intrinsic
|
|
FunctionCallee CallPersonalityF =
|
|
nullptr; // _Unwind_CallPersonality() wrapper
|
|
|
|
bool prepareThrows(Function &F);
|
|
bool prepareEHPads(Function &F);
|
|
void prepareEHPad(BasicBlock *BB, bool NeedPersonality, unsigned Index = 0);
|
|
|
|
public:
|
|
static char ID; // Pass identification, replacement for typeid
|
|
|
|
WasmEHPrepare() : FunctionPass(ID) {}
|
|
void getAnalysisUsage(AnalysisUsage &AU) const override;
|
|
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_BEGIN(WasmEHPrepare, DEBUG_TYPE,
|
|
"Prepare WebAssembly exceptions", false, false)
|
|
INITIALIZE_PASS_DEPENDENCY(DominatorTreeWrapperPass)
|
|
INITIALIZE_PASS_END(WasmEHPrepare, DEBUG_TYPE, "Prepare WebAssembly exceptions",
|
|
false, false)
|
|
|
|
FunctionPass *llvm::createWasmEHPass() { return new WasmEHPrepare(); }
|
|
|
|
void WasmEHPrepare::getAnalysisUsage(AnalysisUsage &AU) const {
|
|
AU.addRequired<DominatorTreeWrapperPass>();
|
|
}
|
|
|
|
bool WasmEHPrepare::doInitialization(Module &M) {
|
|
IRBuilder<> IRB(M.getContext());
|
|
LPadContextTy = StructType::get(IRB.getInt32Ty(), // lpad_index
|
|
IRB.getInt8PtrTy(), // lsda
|
|
IRB.getInt32Ty() // selector
|
|
);
|
|
return false;
|
|
}
|
|
|
|
// Erase the specified BBs if the BB does not have any remaining predecessors,
|
|
// and also all its dead children.
|
|
template <typename Container>
|
|
static void eraseDeadBBsAndChildren(const Container &BBs, DomTreeUpdater *DTU) {
|
|
SmallVector<BasicBlock *, 8> WL(BBs.begin(), BBs.end());
|
|
while (!WL.empty()) {
|
|
auto *BB = WL.pop_back_val();
|
|
if (!pred_empty(BB))
|
|
continue;
|
|
WL.append(succ_begin(BB), succ_end(BB));
|
|
DeleteDeadBlock(BB, DTU);
|
|
}
|
|
}
|
|
|
|
bool WasmEHPrepare::runOnFunction(Function &F) {
|
|
bool Changed = false;
|
|
Changed |= prepareThrows(F);
|
|
Changed |= prepareEHPads(F);
|
|
return Changed;
|
|
}
|
|
|
|
bool WasmEHPrepare::prepareThrows(Function &F) {
|
|
auto &DT = getAnalysis<DominatorTreeWrapperPass>().getDomTree();
|
|
DomTreeUpdater DTU(&DT, /*PostDominatorTree*/ nullptr,
|
|
DomTreeUpdater::UpdateStrategy::Eager);
|
|
Module &M = *F.getParent();
|
|
IRBuilder<> IRB(F.getContext());
|
|
bool Changed = false;
|
|
|
|
// wasm.throw() intinsic, which will be lowered to wasm 'throw' instruction.
|
|
ThrowF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_throw);
|
|
// Insert an unreachable instruction after a call to @llvm.wasm.throw and
|
|
// delete all following instructions within the BB, and delete all the dead
|
|
// children of the BB as well.
|
|
for (User *U : ThrowF->users()) {
|
|
// A call to @llvm.wasm.throw() is only generated from __cxa_throw()
|
|
// builtin call within libcxxabi, and cannot be an InvokeInst.
|
|
auto *ThrowI = cast<CallInst>(U);
|
|
if (ThrowI->getFunction() != &F)
|
|
continue;
|
|
Changed = true;
|
|
auto *BB = ThrowI->getParent();
|
|
SmallVector<BasicBlock *, 4> Succs(successors(BB));
|
|
auto &InstList = BB->getInstList();
|
|
InstList.erase(std::next(BasicBlock::iterator(ThrowI)), InstList.end());
|
|
IRB.SetInsertPoint(BB);
|
|
IRB.CreateUnreachable();
|
|
eraseDeadBBsAndChildren(Succs, &DTU);
|
|
}
|
|
|
|
return Changed;
|
|
}
|
|
|
|
bool WasmEHPrepare::prepareEHPads(Function &F) {
|
|
Module &M = *F.getParent();
|
|
IRBuilder<> IRB(F.getContext());
|
|
|
|
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");
|
|
|
|
// __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.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);
|
|
|
|
// wasm.catch() will be lowered down to wasm 'catch' instruction in
|
|
// instruction selection.
|
|
CatchF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_catch);
|
|
|
|
// _Unwind_CallPersonality() wrapper function, which calls the personality
|
|
CallPersonalityF = M.getOrInsertFunction(
|
|
"_Unwind_CallPersonality", IRB.getInt32Ty(), IRB.getInt8PtrTy());
|
|
if (Function *F = dyn_cast<Function>(CallPersonalityF.getCallee()))
|
|
F->setDoesNotThrow();
|
|
|
|
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 a personalify
|
|
// function call
|
|
if (CPI->getNumArgOperands() == 1 &&
|
|
cast<Constant>(CPI->getArgOperand(0))->isNullValue())
|
|
prepareEHPad(BB, false);
|
|
else
|
|
prepareEHPad(BB, true, Index++);
|
|
}
|
|
|
|
// Cleanup pads don't need a personality function call.
|
|
for (auto *BB : CleanupPads)
|
|
prepareEHPad(BB, false);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Prepare an EH pad for Wasm EH handling. If NeedPersonality is false, Index is
|
|
// ignored.
|
|
void WasmEHPrepare::prepareEHPad(BasicBlock *BB, bool NeedPersonality,
|
|
unsigned Index) {
|
|
assert(BB->isEHPad() && "BB is not an EHPad!");
|
|
IRBuilder<> IRB(BB->getContext());
|
|
IRB.SetInsertPoint(&*BB->getFirstInsertionPt());
|
|
|
|
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->getCalledOperand() == GetExnF)
|
|
GetExnCI = CI;
|
|
if (CI->getCalledOperand() == GetSelectorF)
|
|
GetSelectorCI = CI;
|
|
}
|
|
}
|
|
|
|
// Cleanup pads do not have any of wasm.get.exception() or
|
|
// wasm.get.ehselector() calls. We need to do nothing.
|
|
if (!GetExnCI) {
|
|
assert(!GetSelectorCI &&
|
|
"wasm.get.ehselector() cannot exist w/o wasm.get.exception()");
|
|
return;
|
|
}
|
|
|
|
// Replace wasm.get.exception intrinsic with wasm.catch intrinsic, which will
|
|
// be lowered to wasm 'catch' instruction. We do this mainly because
|
|
// instruction selection cannot handle wasm.get.exception intrinsic's token
|
|
// argument.
|
|
Instruction *CatchCI =
|
|
IRB.CreateCall(CatchF, {IRB.getInt32(WebAssembly::CPP_EXCEPTION)}, "exn");
|
|
GetExnCI->replaceAllUsesWith(CatchCI);
|
|
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 (!NeedPersonality) {
|
|
if (GetSelectorCI) {
|
|
assert(GetSelectorCI->use_empty() &&
|
|
"wasm.get.ehselector() still has uses!");
|
|
GetSelectorCI->eraseFromParent();
|
|
}
|
|
return;
|
|
}
|
|
IRB.SetInsertPoint(CatchCI->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, {FPI, IRB.getInt32(Index)});
|
|
|
|
// Pseudocode: __wasm_lpad_context.lpad_index = index;
|
|
IRB.CreateStore(IRB.getInt32(Index), LPadIndexField);
|
|
|
|
auto *CPI = cast<CatchPadInst>(FPI);
|
|
// TODO Sometimes storing the LSDA address every time is not necessary, in
|
|
// case it is already set in a dominating EH pad and there is no function call
|
|
// between from that EH pad to here. Consider optimizing those cases.
|
|
// Pseudocode: __wasm_lpad_context.lsda = wasm.lsda();
|
|
IRB.CreateStore(IRB.CreateCall(LSDAF), LSDAField);
|
|
|
|
// Pseudocode: _Unwind_CallPersonality(exn);
|
|
CallInst *PersCI = IRB.CreateCall(CallPersonalityF, CatchCI,
|
|
OperandBundleDef("funclet", CPI));
|
|
PersCI->setDoesNotThrow();
|
|
|
|
// Pseudocode: int selector = __wasm.landingpad_context.selector;
|
|
Instruction *Selector =
|
|
IRB.CreateLoad(IRB.getInt32Ty(), 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();
|
|
}
|
|
|
|
void llvm::calculateWasmEHInfo(const Function *F, WasmEHFuncInfo &EHInfo) {
|
|
// If an exception is not caught by a catchpad (i.e., it is a foreign
|
|
// exception), it will unwind to its parent catchswitch's unwind destination.
|
|
// We don't record an unwind destination for cleanuppads because every
|
|
// exception should be caught by it.
|
|
for (const auto &BB : *F) {
|
|
if (!BB.isEHPad())
|
|
continue;
|
|
const Instruction *Pad = BB.getFirstNonPHI();
|
|
|
|
if (const auto *CatchPad = dyn_cast<CatchPadInst>(Pad)) {
|
|
const auto *UnwindBB = CatchPad->getCatchSwitch()->getUnwindDest();
|
|
if (!UnwindBB)
|
|
continue;
|
|
const Instruction *UnwindPad = UnwindBB->getFirstNonPHI();
|
|
if (const auto *CatchSwitch = dyn_cast<CatchSwitchInst>(UnwindPad))
|
|
// Currently there should be only one handler per a catchswitch.
|
|
EHInfo.setUnwindDest(&BB, *CatchSwitch->handlers().begin());
|
|
else // cleanuppad
|
|
EHInfo.setUnwindDest(&BB, UnwindBB);
|
|
}
|
|
}
|
|
}
|