Skip to content

Commit

Permalink
[WebAssembly] Exception handling: Switch to the new proposal
Browse files Browse the repository at this point in the history
Summary:
This switches the EH implementation to the new proposal:
https://github.com/WebAssembly/exception-handling/blob/master/proposals/Exceptions.md
(The previous proposal was
 https://github.com/WebAssembly/exception-handling/blob/master/proposals/old/Exceptions.md)

- Instruction changes
  - Now we have one single `catch` instruction that returns a except_ref
    value
  - `throw` now can take variable number of operations
  - `rethrow` does not have 'depth' argument anymore
  - `br_on_exn` queries an except_ref to see if it matches the tag and
    branches to the given label if true.
  - `extract_exception` is a pseudo instruction that simulates popping
    values from wasm stack. This is to make `br_on_exn`, a very special
    instruction, work: `br_on_exn` puts values onto the stack only if it
    is taken, and the # of values can vay depending on the tag.

- Now there's only one `catch` per `try`, this patch removes all special
  handling for terminate pad with a call to `__clang_call_terminate`.
  Before it was the only case there are two catch clauses (a normal
  `catch` and `catch_all` per `try`).

- Make `rethrow` act as a terminator like `throw`. This splits BB after
  `rethrow` in WasmEHPrepare, and deletes an unnecessary `unreachable`
  after `rethrow` in LateEHPrepare.

- Now we stop at all catchpads (because we add wasm `catch` instruction
  that catches all exceptions), this creates new
  `findWasmUnwindDestinations` function in SelectionDAGBuilder.

- Now we use `br_on_exn` instrution to figure out if an except_ref
  matches the current tag or not, LateEHPrepare generates this sequence
  for catch pads:
```
  catch
  block i32
  br_on_exn $__cpp_exception
  end_block
  extract_exception
```

- Branch analysis for `br_on_exn` in WebAssemblyInstrInfo

- Other various misc. changes to switch to the new proposal.

Reviewers: dschuff

Subscribers: sbc100, jgravelle-google, sunfish, llvm-commits

Differential Revision: https://reviews.llvm.org/D57134

llvm-svn: 352598
  • Loading branch information
aheejin committed Jan 30, 2019
1 parent 6d8e1b4 commit d6f4878
Show file tree
Hide file tree
Showing 28 changed files with 726 additions and 1,317 deletions.
11 changes: 6 additions & 5 deletions llvm/include/llvm/IR/IntrinsicsWebAssembly.td
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,12 @@ def int_wasm_get_exception : Intrinsic<[llvm_ptr_ty], [llvm_token_ty],
[IntrHasSideEffects]>;
def int_wasm_get_ehselector : Intrinsic<[llvm_i32_ty], [llvm_token_ty],
[IntrHasSideEffects]>;

// wasm.catch returns the pointer to the exception object caught by wasm 'catch'
// instruction.
def int_wasm_catch : Intrinsic<[llvm_ptr_ty], [llvm_i32_ty],
[IntrHasSideEffects]>;
// This is the same as llvm.wasm.get.exception except that it does not take a
// token operand. This is only for instruction selection purpose.
// TODO Remove this redundant intrinsic and do custom lowering on
// int_wasm_get_exception instead
def int_wasm_extract_exception : Intrinsic<[llvm_ptr_ty], [],
[IntrHasSideEffects]>;

// WebAssembly EH must maintain the landingpads in the order assigned to them
// by WasmEHPrepare pass to generate landingpad table in EHStreamer. This is
Expand Down
4 changes: 2 additions & 2 deletions llvm/lib/CodeGen/AsmPrinter/WasmException.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
using namespace llvm;

void WasmException::endModule() {
// This is the symbol used in 'throw' and 'if_except' instruction to denote
// This is the symbol used in 'throw' and 'br_on_exn' instruction to denote
// this is a C++ exception. This symbol has to be emitted somewhere once in
// the module. Check if the symbol has already been created, i.e., we have at
// least one 'throw' or 'if_except' instruction in the module, and emit the
// least one 'throw' or 'br_on_exn' instruction in the module, and emit the
// symbol only if so.
SmallString<60> NameStr;
Mangler::getNameWithPrefix(NameStr, "__cpp_exception", Asm->getDataLayout());
Expand Down
38 changes: 36 additions & 2 deletions llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1456,6 +1456,36 @@ void SelectionDAGBuilder::visitCleanupPad(const CleanupPadInst &CPI) {
}
}

// For wasm, there's alwyas a single catch pad attached to a catchswitch, and
// the control flow always stops at the single catch pad, as it does for a
// cleanup pad. In case the exception caught is not of the types the catch pad
// catches, it will be rethrown by a rethrow.
static void findWasmUnwindDestinations(
FunctionLoweringInfo &FuncInfo, const BasicBlock *EHPadBB,
BranchProbability Prob,
SmallVectorImpl<std::pair<MachineBasicBlock *, BranchProbability>>
&UnwindDests) {
while (EHPadBB) {
const Instruction *Pad = EHPadBB->getFirstNonPHI();
if (isa<CleanupPadInst>(Pad)) {
// Stop on cleanup pads.
UnwindDests.emplace_back(FuncInfo.MBBMap[EHPadBB], Prob);
UnwindDests.back().first->setIsEHScopeEntry();
break;
} else if (auto *CatchSwitch = dyn_cast<CatchSwitchInst>(Pad)) {
// Add the catchpad handlers to the possible destinations. We don't
// continue to the unwind destination of the catchswitch for wasm.
for (const BasicBlock *CatchPadBB : CatchSwitch->handlers()) {
UnwindDests.emplace_back(FuncInfo.MBBMap[CatchPadBB], Prob);
UnwindDests.back().first->setIsEHScopeEntry();
}
break;
} else {
continue;
}
}
}

/// When an invoke or a cleanupret unwinds to the next EH pad, there are
/// many places it could ultimately go. In the IR, we have a single unwind
/// destination, but in the machine CFG, we enumerate all the possible blocks.
Expand All @@ -1476,6 +1506,11 @@ static void findUnwindDestinations(
bool IsWasmCXX = Personality == EHPersonality::Wasm_CXX;
bool IsSEH = isAsynchronousEHPersonality(Personality);

if (IsWasmCXX) {
findWasmUnwindDestinations(FuncInfo, EHPadBB, Prob, UnwindDests);
return;
}

while (EHPadBB) {
const Instruction *Pad = EHPadBB->getFirstNonPHI();
BasicBlock *NewEHPadBB = nullptr;
Expand All @@ -1488,8 +1523,7 @@ static void findUnwindDestinations(
// personalities.
UnwindDests.emplace_back(FuncInfo.MBBMap[EHPadBB], Prob);
UnwindDests.back().first->setIsEHScopeEntry();
if (!IsWasmCXX)
UnwindDests.back().first->setIsEHFuncletEntry();
UnwindDests.back().first->setIsEHFuncletEntry();
break;
} else if (auto *CatchSwitch = dyn_cast<CatchSwitchInst>(Pad)) {
// Add the catchpad handlers to the possible destinations.
Expand Down
169 changes: 67 additions & 102 deletions llvm/lib/CodeGen/WasmEHPrepare.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
//===----------------------------------------------------------------------===//
//
// This transformation is designed for use by code generators which use
// WebAssembly exception handling scheme.
// 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
Expand All @@ -22,53 +23,20 @@
//
// - After:
// catchpad ...
// exn = wasm.catch(0); // 0 is a tag for C++
// wasm.landingpad.index(index);
// exn = wasm.extract.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);
// int selector = __wasm.landingpad_context.selector;
// 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.
// 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
Expand Down Expand Up @@ -137,18 +105,18 @@ class WasmEHPrepare : public FunctionPass {
Value *SelectorField = nullptr; // selector

Function *ThrowF = nullptr; // wasm.throw() intrinsic
Function *CatchF = nullptr; // wasm.catch.extract() intrinsic
Function *RethrowF = nullptr; // wasm.rethrow() intrinsic
Function *LPadIndexF = nullptr; // wasm.landingpad.index() intrinsic
Function *LSDAF = nullptr; // wasm.lsda() intrinsic
Function *GetExnF = nullptr; // wasm.get.exception() intrinsic
Function *ExtractExnF = nullptr; // wasm.extract.exception() intrinsic
Function *GetSelectorF = nullptr; // wasm.get.ehselector() intrinsic
Function *CallPersonalityF = nullptr; // _Unwind_CallPersonality() wrapper
Function *ClangCallTermF = nullptr; // __clang_call_terminate() function

bool prepareEHPads(Function &F);
bool prepareThrows(Function &F);

void prepareEHPad(BasicBlock *BB, unsigned Index);
void prepareEHPad(BasicBlock *BB, bool NeedLSDA, unsigned Index = 0);
void prepareTerminateCleanupPad(BasicBlock *BB);

public:
Expand Down Expand Up @@ -208,25 +176,29 @@ bool WasmEHPrepare::prepareThrows(Function &F) {

// 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
// __builtin_wasm_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(succ_begin(BB), succ_end(BB));
auto &InstList = BB->getInstList();
InstList.erase(std::next(BasicBlock::iterator(ThrowI)), InstList.end());
IRB.SetInsertPoint(BB);
IRB.CreateUnreachable();
eraseDeadBBsAndChildren(Succs);
// wasm.rethrow() intinsic, which will be lowered to wasm 'rethrow'
// instruction.
RethrowF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_rethrow);

// Insert an unreachable instruction after a call to @llvm.wasm.throw /
// @llvm.wasm.rethrow and delete all following instructions within the BB, and
// delete all the dead children of the BB as well.
for (auto L : {ThrowF->users(), RethrowF->users()}) {
for (User *U : L) {
// 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(succ_begin(BB), succ_end(BB));
auto &InstList = BB->getInstList();
InstList.erase(std::next(BasicBlock::iterator(ThrowI)), InstList.end());
IRB.SetInsertPoint(BB);
IRB.CreateUnreachable();
eraseDeadBBsAndChildren(Succs);
}
}

return Changed;
Expand Down Expand Up @@ -262,8 +234,6 @@ bool WasmEHPrepare::prepareEHPads(Function &F) {
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
Expand All @@ -274,83 +244,78 @@ bool WasmEHPrepare::prepareEHPads(Function &F) {
GetExnF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_get_exception);
GetSelectorF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_get_ehselector);

// wasm.extract.exception() is the same as wasm.get.exception() but it does
// not take a token argument. This will be lowered down to EXTRACT_EXCEPTION
// pseudo instruction in instruction selection, which will be expanded using
// 'br_on_exn' instruction later.
ExtractExnF =
Intrinsic::getDeclaration(&M, Intrinsic::wasm_extract_exception);

// _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);
prepareEHPad(BB, false);
else
prepareEHPad(BB, Index++);
prepareEHPad(BB, true, 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.
// Cleanup pads don't need LSDA.
for (auto *BB : CleanupPads)
for (auto &I : *BB)
if (auto *CI = dyn_cast<CallInst>(&I))
if (CI->getCalledValue() == ClangCallTermF)
prepareEHPad(BB, -1);
prepareEHPad(BB, false);

return true;
}

void WasmEHPrepare::prepareEHPad(BasicBlock *BB, unsigned Index) {
// Prepare an EH pad for Wasm EH handling. If NeedLSDA is false, Index is
// ignored.
void WasmEHPrepare::prepareEHPad(BasicBlock *BB, bool NeedLSDA,
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)
if (CI->getCalledValue() == GetSelectorF)
GetSelectorCI = CI;
}
}

assert(GetExnCI && "wasm.get.exception() call does not exist");
GetExnCI->replaceAllUsesWith(Exn);
// Cleanup pads w/o __clang_call_terminate call 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;
}

Instruction *ExtractExnCI = IRB.CreateCall(ExtractExnF, {}, "exn");
GetExnCI->replaceAllUsesWith(ExtractExnCI);
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 (!NeedLSDA) {
if (GetSelectorCI) {
assert(GetSelectorCI->use_empty() &&
"wasm.get.ehselector() still has uses!");
GetSelectorCI->eraseFromParent();
}
return;
}
IRB.SetInsertPoint(Exn->getNextNode());
IRB.SetInsertPoint(ExtractExnCI->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.
Expand All @@ -372,8 +337,8 @@ void WasmEHPrepare::prepareEHPad(BasicBlock *BB, unsigned Index) {
IRB.CreateStore(IRB.CreateCall(LSDAF), LSDAField);

// Pseudocode: _Unwind_CallPersonality(exn);
CallInst *PersCI =
IRB.CreateCall(CallPersonalityF, Exn, OperandBundleDef("funclet", CPI));
CallInst *PersCI = IRB.CreateCall(CallPersonalityF, ExtractExnCI,
OperandBundleDef("funclet", CPI));
PersCI->setDoesNotThrow();

// Pseudocode: int selector = __wasm.landingpad_context.selector;
Expand All @@ -387,15 +352,15 @@ void WasmEHPrepare::prepareEHPad(BasicBlock *BB, unsigned Index) {
}

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 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.
if (const auto *CatchPad = dyn_cast<CatchPadInst>(Pad)) {
const auto *UnwindBB = CatchPad->getCatchSwitch()->getUnwindDest();
if (!UnwindBB)
Expand Down

0 comments on commit d6f4878

Please sign in to comment.