[WebAssembly] Exception handling: Switch to the new proposal

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
This commit is contained in:
Heejin Ahn 2019-01-30 03:21:57 +00:00
parent 6d8e1b456a
commit d6f487863d
28 changed files with 724 additions and 1315 deletions

View File

@ -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

View File

@ -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());

View File

@ -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.
@ -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;
@ -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.

View File

@ -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
@ -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
@ -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:
@ -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);
// 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 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);
// 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;
@ -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
@ -274,75 +244,70 @@ 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!");
@ -350,7 +315,7 @@ void WasmEHPrepare::prepareEHPad(BasicBlock *BB, unsigned Index) {
}
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.
@ -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;
@ -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)

View File

@ -122,61 +122,48 @@ void WebAssemblyInstPrinter::printInst(const MCInst *MI, raw_ostream &OS,
}
break;
case WebAssembly::CATCH_I32:
case WebAssembly::CATCH_I32_S:
case WebAssembly::CATCH_I64:
case WebAssembly::CATCH_I64_S:
case WebAssembly::CATCH_ALL:
case WebAssembly::CATCH_ALL_S:
// There can be multiple catch instructions for one try instruction, so we
// print a label only for the first 'catch' label.
if (LastSeenEHInst != CATCH) {
if (EHPadStack.empty()) {
printAnnotation(OS, "try-catch mismatch!");
} else {
printAnnotation(OS,
"catch" + utostr(EHPadStack.pop_back_val()) + ':');
}
case WebAssembly::CATCH:
case WebAssembly::CATCH_S:
if (EHPadStack.empty()) {
printAnnotation(OS, "try-catch mismatch!");
} else {
printAnnotation(OS, "catch" + utostr(EHPadStack.pop_back_val()) + ':');
}
LastSeenEHInst = CATCH;
break;
}
// Annotate any control flow label references.
unsigned NumFixedOperands = Desc.NumOperands;
SmallSet<uint64_t, 8> Printed;
for (unsigned i = 0, e = MI->getNumOperands(); i < e; ++i) {
// See if this operand denotes a basic block target.
if (i < NumFixedOperands) {
// A non-variable_ops operand, check its type.
if (Desc.OpInfo[i].OperandType != WebAssembly::OPERAND_BASIC_BLOCK)
continue;
// rethrow instruction does not take any depth argument and rethrows to the
// nearest enclosing catch scope, if any. If there's no enclosing catch
// scope, it throws up to the caller.
if (Opc == WebAssembly::RETHROW || Opc == WebAssembly::RETHROW_S) {
if (EHPadStack.empty()) {
printAnnotation(OS, "to caller");
} else {
// A variable_ops operand, which currently can be immediates (used in
// br_table) which are basic block targets, or for call instructions
// when using -wasm-keep-registers (in which case they are registers,
// and should not be processed).
if (!MI->getOperand(i).isImm())
continue;
printAnnotation(OS, "down to catch" + utostr(EHPadStack.back()));
}
uint64_t Depth = MI->getOperand(i).getImm();
if (!Printed.insert(Depth).second)
continue;
if (Opc == WebAssembly::RETHROW || Opc == WebAssembly::RETHROW_S) {
if (Depth > EHPadStack.size()) {
printAnnotation(OS, "Invalid depth argument!");
} else if (Depth == EHPadStack.size()) {
// This can happen when rethrow instruction breaks out of all nests
// and throws up to the current function's caller.
printAnnotation(OS, utostr(Depth) + ": " + "to caller");
} else {
unsigned NumFixedOperands = Desc.NumOperands;
SmallSet<uint64_t, 8> Printed;
for (unsigned I = 0, E = MI->getNumOperands(); I < E; ++I) {
// See if this operand denotes a basic block target.
if (I < NumFixedOperands) {
// A non-variable_ops operand, check its type.
if (Desc.OpInfo[I].OperandType != WebAssembly::OPERAND_BASIC_BLOCK)
continue;
} else {
uint64_t CatchNo = EHPadStack.rbegin()[Depth];
printAnnotation(OS, utostr(Depth) + ": " + "down to catch" +
utostr(CatchNo));
// A variable_ops operand, which currently can be immediates (used in
// br_table) which are basic block targets, or for call instructions
// when using -wasm-keep-registers (in which case they are registers,
// and should not be processed).
if (!MI->getOperand(I).isImm())
continue;
}
} else {
uint64_t Depth = MI->getOperand(I).getImm();
if (!Printed.insert(Depth).second)
continue;
if (Depth >= ControlFlowStack.size()) {
printAnnotation(OS, "Invalid depth argument!");
} else {

View File

@ -43,6 +43,8 @@ using namespace llvm;
#define DEBUG_TYPE "asm-printer"
extern cl::opt<bool> WasmKeepRegisters;
//===----------------------------------------------------------------------===//
// Helpers.
//===----------------------------------------------------------------------===//
@ -304,6 +306,14 @@ void WebAssemblyAsmPrinter::EmitInstruction(const MachineInstr *MI) {
OutStreamer->AddBlankLine();
}
break;
case WebAssembly::EXTRACT_EXCEPTION_I32:
case WebAssembly::EXTRACT_EXCEPTION_I32_S:
// These are pseudo instructions that simulates popping values from stack.
// We print these only when we have -wasm-keep-registers on for assembly
// readability.
if (!WasmKeepRegisters)
break;
LLVM_FALLTHROUGH;
default: {
WebAssemblyMCInstLower MCInstLowering(OutContext, *this);
MCInst TmpInst;

View File

@ -37,6 +37,7 @@
#include "llvm/MC/MCAsmInfo.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"
#include <cstring>
using namespace llvm;
#define DEBUG_TYPE "wasm-cfg-stackify"
@ -110,11 +111,9 @@ FunctionPass *llvm::createWebAssemblyCFGStackify() {
static bool ExplicitlyBranchesTo(MachineBasicBlock *Pred,
MachineBasicBlock *MBB) {
for (MachineInstr &MI : Pred->terminators())
// Even if a rethrow takes a BB argument, it is not a branch
if (!WebAssembly::isRethrow(MI))
for (MachineOperand &MO : MI.explicit_operands())
if (MO.isMBB() && MO.getMBB() == MBB)
return true;
for (MachineOperand &MO : MI.explicit_operands())
if (MO.isMBB() && MO.getMBB() == MBB)
return true;
return false;
}
@ -217,12 +216,20 @@ void WebAssemblyCFGStackify::placeBlockMarker(MachineBasicBlock &MBB) {
// which reduces overall stack height.
MachineBasicBlock *Header = nullptr;
bool IsBranchedTo = false;
bool IsBrOnExn = false;
MachineInstr *BrOnExn = nullptr;
int MBBNumber = MBB.getNumber();
for (MachineBasicBlock *Pred : MBB.predecessors()) {
if (Pred->getNumber() < MBBNumber) {
Header = Header ? MDT.findNearestCommonDominator(Header, Pred) : Pred;
if (ExplicitlyBranchesTo(Pred, &MBB))
if (ExplicitlyBranchesTo(Pred, &MBB)) {
IsBranchedTo = true;
if (Pred->getFirstTerminator()->getOpcode() == WebAssembly::BR_ON_EXN) {
IsBrOnExn = true;
assert(!BrOnExn && "There should be only one br_on_exn per block");
BrOnExn = &*Pred->getFirstTerminator();
}
}
}
}
if (!Header)
@ -299,11 +306,27 @@ void WebAssemblyCFGStackify::placeBlockMarker(MachineBasicBlock &MBB) {
}
// Add the BLOCK.
// 'br_on_exn' extracts except_ref object and pushes variable number of values
// depending on its tag. For C++ exception, its a single i32 value, and the
// generated code will be in the form of:
// block i32
// br_on_exn 0, $__cpp_exception
// rethrow
// end_block
WebAssembly::ExprType ReturnType = WebAssembly::ExprType::Void;
if (IsBrOnExn) {
const char *TagName = BrOnExn->getOperand(1).getSymbolName();
if (std::strcmp(TagName, "__cpp_exception") != 0)
llvm_unreachable("Only C++ exception is supported");
ReturnType = WebAssembly::ExprType::I32;
}
auto InsertPos = GetLatestInsertPos(Header, BeforeSet, AfterSet);
MachineInstr *Begin =
BuildMI(*Header, InsertPos, Header->findDebugLoc(InsertPos),
TII.get(WebAssembly::BLOCK))
.addImm(int64_t(WebAssembly::ExprType::Void));
.addImm(int64_t(ReturnType));
// Decide where in Header to put the END_BLOCK.
BeforeSet.clear();
@ -416,11 +439,6 @@ void WebAssemblyCFGStackify::placeTryMarker(MachineBasicBlock &MBB) {
if (!MBB.isEHPad())
return;
// catch_all terminate pad is grouped together with catch terminate pad and
// does not need a separate TRY and END_TRY marker.
if (WebAssembly::isCatchAllTerminatePad(MBB))
return;
MachineFunction &MF = *MBB.getParent();
auto &MDT = getAnalysis<MachineDominatorTree>();
const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
@ -529,7 +547,8 @@ void WebAssemblyCFGStackify::placeTryMarker(MachineBasicBlock &MBB) {
// throw.
if (MBB.isPredecessor(Header)) {
auto TermPos = Header->getFirstTerminator();
if (TermPos == Header->end() || !WebAssembly::isRethrow(*TermPos)) {
if (TermPos == Header->end() ||
TermPos->getOpcode() != WebAssembly::RETHROW) {
for (const auto &MI : reverse(*Header)) {
if (MI.isCall()) {
AfterSet.insert(&MI);
@ -674,7 +693,6 @@ static void AppendEndToFunction(MachineFunction &MF,
/// Insert LOOP/TRY/BLOCK markers at appropriate places.
void WebAssemblyCFGStackify::placeMarkers(MachineFunction &MF) {
const MCAsmInfo *MCAI = MF.getTarget().getMCAsmInfo();
// We allocate one more than the number of blocks in the function to
// accommodate for the possible fake block we may insert at the end.
ScopeTops.resize(MF.getNumBlockIDs() + 1);
@ -682,6 +700,7 @@ void WebAssemblyCFGStackify::placeMarkers(MachineFunction &MF) {
for (auto &MBB : MF)
placeLoopMarker(MBB);
// Place the TRY for MBB if MBB is the EH pad of an exception.
const MCAsmInfo *MCAI = MF.getTarget().getMCAsmInfo();
if (MCAI->getExceptionHandlingType() == ExceptionHandling::Wasm &&
MF.getFunction().hasPersonalityFn())
for (auto &MBB : MF)
@ -692,12 +711,8 @@ void WebAssemblyCFGStackify::placeMarkers(MachineFunction &MF) {
}
void WebAssemblyCFGStackify::rewriteDepthImmediates(MachineFunction &MF) {
const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
// Now rewrite references to basic blocks to be depth immediates.
// We need two stacks: one for normal scopes and the other for EH pad scopes.
// EH pad stack is used to rewrite depths in rethrow instructions.
SmallVector<const MachineBasicBlock *, 8> Stack;
SmallVector<const MachineBasicBlock *, 8> EHPadStack;
for (auto &MBB : reverse(MF)) {
for (auto I = MBB.rbegin(), E = MBB.rend(); I != E; ++I) {
MachineInstr &MI = *I;
@ -714,26 +729,6 @@ void WebAssemblyCFGStackify::rewriteDepthImmediates(MachineFunction &MF) {
MBB.getNumber() &&
"Block/try marker should be balanced");
Stack.pop_back();
EHPadStack.pop_back();
break;
case WebAssembly::CATCH_I32:
case WebAssembly::CATCH_I64:
case WebAssembly::CATCH_ALL:
// Currently the only case there are more than one catch for a try is
// for catch terminate pad, in the form of
// try
// catch
// call @__clang_call_terminate
// unreachable
// catch_all
// call @std::terminate
// unreachable
// end
// So we shouldn't push the current BB for the second catch_all block
// here.
if (!WebAssembly::isCatchAllTerminatePad(MBB))
EHPadStack.push_back(&MBB);
break;
case WebAssembly::LOOP:
@ -750,23 +745,6 @@ void WebAssemblyCFGStackify::rewriteDepthImmediates(MachineFunction &MF) {
Stack.push_back(EndToBegin[&MI]->getParent());
break;
case WebAssembly::RETHROW: {
// Rewrite MBB operands to be depth immediates.
unsigned EHPadDepth = GetDepth(EHPadStack, MI.getOperand(0).getMBB());
MI.RemoveOperand(0);
MI.addOperand(MF, MachineOperand::CreateImm(EHPadDepth));
break;
}
case WebAssembly::RETHROW_TO_CALLER: {
MachineInstr *Rethrow =
BuildMI(MBB, MI, MI.getDebugLoc(), TII.get(WebAssembly::RETHROW))
.addImm(EHPadStack.size());
MI.eraseFromParent();
I = MachineBasicBlock::reverse_iterator(Rethrow);
break;
}
default:
if (MI.isTerminator()) {
// Rewrite MBB operands to be depth immediates.

View File

@ -77,7 +77,7 @@ bool WebAssemblyEHRestoreStackPointer::runOnMachineFunction(
// function uses the red zone, but that only happens with leaf functions,
// and we don't restore __stack_pointer in leaf functions anyway.
auto InsertPos = MBB.begin();
if (WebAssembly::isCatch(*MBB.begin()))
if (MBB.begin()->getOpcode() == WebAssembly::CATCH)
InsertPos++;
FrameLowering->writeSPToGlobal(WebAssembly::SP32, MF, MBB, InsertPos,
MBB.begin()->getDebugLoc());

View File

@ -50,10 +50,6 @@ void WebAssemblyExceptionInfo::recalculate(
MachineBasicBlock *EHPad = DomNode->getBlock();
if (!EHPad->isEHPad())
continue;
// We group catch & catch-all terminate pads together, so skip the second
// one
if (WebAssembly::isCatchAllTerminatePad(*EHPad))
continue;
auto *WE = new WebAssemblyException(EHPad);
discoverAndMapException(WE, MDT, MDF);
Exceptions.push_back(WE);
@ -104,16 +100,6 @@ void WebAssemblyExceptionInfo::discoverAndMapException(
// Map blocks that belong to a catchpad / cleanuppad
MachineBasicBlock *EHPad = WE->getEHPad();
// We group catch & catch-all terminate pads together within an exception
if (WebAssembly::isCatchTerminatePad(*EHPad)) {
assert(EHPad->succ_size() == 1 &&
"Catch terminate pad has more than one successors");
changeExceptionFor(EHPad, WE);
changeExceptionFor(*(EHPad->succ_begin()), WE);
return;
}
SmallVector<MachineBasicBlock *, 8> WL;
WL.push_back(EHPad);
while (!WL.empty()) {

View File

@ -884,13 +884,13 @@ SDValue WebAssemblyTargetLowering::LowerOperation(SDValue Op,
return LowerFRAMEADDR(Op, DAG);
case ISD::CopyToReg:
return LowerCopyToReg(Op, DAG);
case ISD::INTRINSIC_WO_CHAIN:
return LowerINTRINSIC_WO_CHAIN(Op, DAG);
case ISD::EXTRACT_VECTOR_ELT:
case ISD::INSERT_VECTOR_ELT:
return LowerAccessVectorElement(Op, DAG);
case ISD::INTRINSIC_VOID:
return LowerINTRINSIC_VOID(Op, DAG);
case ISD::INTRINSIC_WO_CHAIN:
case ISD::INTRINSIC_W_CHAIN:
return LowerIntrinsic(Op, DAG);
case ISD::SIGN_EXTEND_INREG:
return LowerSIGN_EXTEND_INREG(Op, DAG);
case ISD::BUILD_VECTOR:
@ -1035,17 +1035,28 @@ SDValue WebAssemblyTargetLowering::LowerVASTART(SDValue Op,
MachinePointerInfo(SV), 0);
}
SDValue
WebAssemblyTargetLowering::LowerINTRINSIC_WO_CHAIN(SDValue Op,
SelectionDAG &DAG) const {
unsigned IntNo = cast<ConstantSDNode>(Op.getOperand(0))->getZExtValue();
SDValue WebAssemblyTargetLowering::LowerIntrinsic(SDValue Op,
SelectionDAG &DAG) const {
MachineFunction &MF = DAG.getMachineFunction();
unsigned IntNo;
switch (Op.getOpcode()) {
case ISD::INTRINSIC_VOID:
case ISD::INTRINSIC_W_CHAIN:
IntNo = cast<ConstantSDNode>(Op.getOperand(1))->getZExtValue();
break;
case ISD::INTRINSIC_WO_CHAIN:
IntNo = cast<ConstantSDNode>(Op.getOperand(0))->getZExtValue();
break;
default:
llvm_unreachable("Invalid intrinsic");
}
SDLoc DL(Op);
switch (IntNo) {
default:
return {}; // Don't custom lower most intrinsics.
case Intrinsic::wasm_lsda: {
MachineFunction &MF = DAG.getMachineFunction();
EVT VT = Op.getValueType();
const TargetLowering &TLI = DAG.getTargetLoweringInfo();
MVT PtrVT = TLI.getPointerTy(DAG.getDataLayout());
@ -1055,43 +1066,26 @@ WebAssemblyTargetLowering::LowerINTRINSIC_WO_CHAIN(SDValue Op,
return DAG.getNode(WebAssemblyISD::Wrapper, DL, VT,
DAG.getMCSymbol(S, PtrVT));
}
}
}
SDValue
WebAssemblyTargetLowering::LowerINTRINSIC_VOID(SDValue Op,
SelectionDAG &DAG) const {
MachineFunction &MF = DAG.getMachineFunction();
unsigned IntNo = cast<ConstantSDNode>(Op.getOperand(1))->getZExtValue();
SDLoc DL(Op);
switch (IntNo) {
default:
return {}; // Don't custom lower most intrinsics.
case Intrinsic::wasm_throw: {
// We only support C++ exceptions for now
int Tag = cast<ConstantSDNode>(Op.getOperand(2).getNode())->getZExtValue();
switch (Tag) {
case CPP_EXCEPTION: {
const TargetLowering &TLI = DAG.getTargetLoweringInfo();
MVT PtrVT = TLI.getPointerTy(DAG.getDataLayout());
const char *SymName = MF.createExternalSymbolName("__cpp_exception");
SDValue SymNode =
DAG.getNode(WebAssemblyISD::Wrapper, DL, PtrVT,
DAG.getTargetExternalSymbol(
SymName, PtrVT, WebAssemblyII::MO_SYMBOL_EVENT));
return DAG.getNode(WebAssemblyISD::THROW, DL,
MVT::Other, // outchain type
{
Op.getOperand(0), // inchain
SymNode, // exception symbol
Op.getOperand(3) // thrown value
});
}
default:
if (Tag != CPP_EXCEPTION)
llvm_unreachable("Invalid tag!");
}
break;
const TargetLowering &TLI = DAG.getTargetLoweringInfo();
MVT PtrVT = TLI.getPointerTy(DAG.getDataLayout());
const char *SymName = MF.createExternalSymbolName("__cpp_exception");
SDValue SymNode =
DAG.getNode(WebAssemblyISD::Wrapper, DL, PtrVT,
DAG.getTargetExternalSymbol(
SymName, PtrVT, WebAssemblyII::MO_SYMBOL_EVENT));
return DAG.getNode(WebAssemblyISD::THROW, DL,
MVT::Other, // outchain type
{
Op.getOperand(0), // inchain
SymNode, // exception symbol
Op.getOperand(3) // thrown value
});
}
}
}

View File

@ -96,8 +96,7 @@ private:
SDValue LowerJumpTable(SDValue Op, SelectionDAG &DAG) const;
SDValue LowerVASTART(SDValue Op, SelectionDAG &DAG) const;
SDValue LowerCopyToReg(SDValue Op, SelectionDAG &DAG) const;
SDValue LowerINTRINSIC_WO_CHAIN(SDValue Op, SelectionDAG &DAG) const;
SDValue LowerINTRINSIC_VOID(SDValue Op, SelectionDAG &DAG) const;
SDValue LowerIntrinsic(SDValue Op, SelectionDAG &DAG) const;
SDValue LowerSIGN_EXTEND_INREG(SDValue Op, SelectionDAG &DAG) const;
SDValue LowerBUILD_VECTOR(SDValue Op, SelectionDAG &DAG) const;
SDValue LowerVECTOR_SHUFFLE(SDValue Op, SelectionDAG &DAG) const;

View File

@ -141,23 +141,11 @@ let Predicates = [HasExceptionHandling] in {
// Throwing an exception: throw / rethrow
let isTerminator = 1, hasCtrlDep = 1, isBarrier = 1 in {
defm THROW_I32 : I<(outs), (ins event_op:$tag, I32:$val),
(outs), (ins event_op:$tag),
[(WebAssemblythrow (WebAssemblywrapper texternalsym:$tag),
I32:$val)],
"throw \t$tag, $val", "throw \t$tag",
0x08>;
defm THROW_I64 : I<(outs), (ins event_op:$tag, I64:$val),
(outs), (ins event_op:$tag),
[(WebAssemblythrow (WebAssemblywrapper texternalsym:$tag),
I64:$val)],
"throw \t$tag, $val", "throw \t$tag",
0x08>;
defm RETHROW : NRI<(outs), (ins bb_op:$dst), [], "rethrow \t$dst", 0x09>;
let isCodeGenOnly = 1 in
// This is used when the destination for rethrow is the caller function. This
// will be converted to a rethrow in CFGStackify.
defm RETHROW_TO_CALLER : NRI<(outs), (ins), [], "rethrow">;
defm THROW : I<(outs), (ins event_op:$tag, variable_ops),
(outs), (ins event_op:$tag),
[(WebAssemblythrow (WebAssemblywrapper texternalsym:$tag))],
"throw \t$tag", "throw \t$tag", 0x08>;
defm RETHROW : NRI<(outs), (ins), [(int_wasm_rethrow)], "rethrow", 0x09>;
} // isTerminator = 1, hasCtrlDep = 1, isBarrier = 1
// Region within which an exception is caught: try / end_try
@ -166,24 +154,33 @@ defm TRY : NRI<(outs), (ins Signature:$sig), [], "try \t$sig", 0x06>;
defm END_TRY : NRI<(outs), (ins), [], "end_try", 0x0b>;
} // Uses = [VALUE_STACK], Defs = [VALUE_STACK]
// Catching an exception: catch / catch_all
let hasCtrlDep = 1, hasSideEffects = 1 in {
defm CATCH_I32 : I<(outs I32:$dst), (ins i32imm:$tag),
(outs), (ins i32imm:$tag),
[(set I32:$dst, (int_wasm_catch imm:$tag))],
"i32.catch \t$dst, $tag", "i32.catch \t$tag", 0x07>;
defm CATCH_I64 : I<(outs I64:$dst), (ins i32imm:$tag),
(outs), (ins i32imm:$tag),
[(set I64:$dst, (int_wasm_catch imm:$tag))],
"i64.catch \t$dst, $tag", "i64.catch \t$tag", 0x07>;
defm CATCH_ALL : NRI<(outs), (ins), [], "catch_all", 0x05>;
}
// Catching an exception: catch / extract_exception
let hasCtrlDep = 1, hasSideEffects = 1 in
defm CATCH : I<(outs EXCEPT_REF:$dst), (ins), (outs), (ins), [],
"catch \t$dst", "catch", 0x07>;
// Querying / extracing exception: br_on_exn
// br_on_exn queries an except_ref to see if it matches the corresponding
// exception tag index. If true it branches to the given label and pushes the
// corresponding argument values of the exception onto the stack.
let isBranch = 1, isTerminator = 1, hasCtrlDep = 1 in
defm BR_ON_EXN : I<(outs), (ins bb_op:$dst, event_op:$tag, EXCEPT_REF:$exn),
(outs), (ins bb_op:$dst, event_op:$tag), [],
"br_on_exn \t$dst, $tag, $exn", "br_on_exn \t$dst, $tag",
0x0a>;
// This is a pseudo instruction that simulates popping a value from stack, which
// has been pushed by br_on_exn
let isCodeGenOnly = 1, hasSideEffects = 1 in
defm EXTRACT_EXCEPTION_I32 : NRI<(outs I32:$dst), (ins),
[(set I32:$dst, (int_wasm_extract_exception))],
"extract_exception\t$dst">;
// Pseudo instructions: cleanupret / catchret
let isTerminator = 1, hasSideEffects = 1, isBarrier = 1, hasCtrlDep = 1,
isCodeGenOnly = 1, isEHScopeReturn = 1 in {
defm CLEANUPRET : NRI<(outs), (ins), [(cleanupret)], "", 0>;
isPseudo = 1, isEHScopeReturn = 1 in {
defm CLEANUPRET : NRI<(outs), (ins), [(cleanupret)], "cleanupret", 0>;
defm CATCHRET : NRI<(outs), (ins bb_op:$dst, bb_op:$from),
[(catchret bb:$dst, bb:$from)], "", 0>;
}
[(catchret bb:$dst, bb:$from)], "catchret", 0>;
} // isTerminator = 1, hasSideEffects = 1, isBarrier = 1, hasCtrlDep = 1,
// isPseudo = 1, isEHScopeReturn = 1
}

View File

@ -134,6 +134,17 @@ bool WebAssemblyInstrInfo::analyzeBranch(MachineBasicBlock &MBB,
else
FBB = MI.getOperand(0).getMBB();
break;
case WebAssembly::BR_ON_EXN:
if (HaveCond)
return true;
// If we're running after CFGStackify, we can't optimize further.
if (!MI.getOperand(0).isMBB())
return true;
Cond.push_back(MachineOperand::CreateImm(true));
Cond.push_back(MI.getOperand(2));
TBB = MI.getOperand(0).getMBB();
HaveCond = true;
break;
}
if (MI.isBarrier())
break;
@ -179,9 +190,22 @@ unsigned WebAssemblyInstrInfo::insertBranch(
assert(Cond.size() == 2 && "Expected a flag and a successor block");
MachineFunction &MF = *MBB.getParent();
auto &MRI = MF.getRegInfo();
bool IsBrOnExn = Cond[1].isReg() && MRI.getRegClass(Cond[1].getReg()) ==
&WebAssembly::EXCEPT_REFRegClass;
if (Cond[0].getImm()) {
BuildMI(&MBB, DL, get(WebAssembly::BR_IF)).addMBB(TBB).add(Cond[1]);
if (IsBrOnExn) {
const char *CPPExnSymbol = MF.createExternalSymbolName("__cpp_exception");
BuildMI(&MBB, DL, get(WebAssembly::BR_ON_EXN))
.addMBB(TBB)
.addExternalSymbol(CPPExnSymbol, WebAssemblyII::MO_SYMBOL_EVENT)
.add(Cond[1]);
} else
BuildMI(&MBB, DL, get(WebAssembly::BR_IF)).addMBB(TBB).add(Cond[1]);
} else {
assert(!IsBrOnExn && "br_on_exn does not have a reversed condition");
BuildMI(&MBB, DL, get(WebAssembly::BR_UNLESS)).addMBB(TBB).add(Cond[1]);
}
if (!FBB)
@ -193,7 +217,15 @@ unsigned WebAssemblyInstrInfo::insertBranch(
bool WebAssemblyInstrInfo::reverseBranchCondition(
SmallVectorImpl<MachineOperand> &Cond) const {
assert(Cond.size() == 2 && "Expected a flag and a successor block");
assert(Cond.size() == 2 && "Expected a flag and a condition expression");
// br_on_exn's condition cannot be reversed
MachineFunction &MF = *Cond[1].getParent()->getParent()->getParent();
auto &MRI = MF.getRegInfo();
if (Cond[1].isReg() &&
MRI.getRegClass(Cond[1].getReg()) == &WebAssembly::EXCEPT_REFRegClass)
return true;
Cond.front() = MachineOperand::CreateImm(!Cond.front().getImm());
return false;
}

View File

@ -66,7 +66,7 @@ def SDT_WebAssemblyArgument : SDTypeProfile<1, 1, [SDTCisVT<1, i32>]>;
def SDT_WebAssemblyReturn : SDTypeProfile<0, -1, []>;
def SDT_WebAssemblyWrapper : SDTypeProfile<1, 1, [SDTCisSameAs<0, 1>,
SDTCisPtrTy<0>]>;
def SDT_WebAssemblyThrow : SDTypeProfile<0, 2, [SDTCisPtrTy<0>]>;
def SDT_WebAssemblyThrow : SDTypeProfile<0, -1, [SDTCisPtrTy<0>]>;
//===----------------------------------------------------------------------===//
// WebAssembly-specific DAG Nodes.
@ -94,7 +94,7 @@ def WebAssemblyreturn : SDNode<"WebAssemblyISD::RETURN",
def WebAssemblywrapper : SDNode<"WebAssemblyISD::Wrapper",
SDT_WebAssemblyWrapper>;
def WebAssemblythrow : SDNode<"WebAssemblyISD::THROW", SDT_WebAssemblyThrow,
[SDNPHasChain]>;
[SDNPHasChain, SDNPVariadic]>;
//===----------------------------------------------------------------------===//
// WebAssembly-specific Operands.

View File

@ -15,6 +15,7 @@
#include "WebAssembly.h"
#include "WebAssemblySubtarget.h"
#include "WebAssemblyUtilities.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/CodeGen/WasmEHFuncInfo.h"
#include "llvm/MC/MCAsmInfo.h"
@ -25,19 +26,14 @@ using namespace llvm;
namespace {
class WebAssemblyLateEHPrepare final : public MachineFunctionPass {
StringRef getPassName() const override {
return "WebAssembly Prepare Exception";
return "WebAssembly Late Prepare Exception";
}
bool runOnMachineFunction(MachineFunction &MF) override;
bool removeUnnecessaryUnreachables(MachineFunction &MF);
bool replaceFuncletReturns(MachineFunction &MF);
bool hoistCatches(MachineFunction &MF);
bool addCatchAlls(MachineFunction &MF);
bool addRethrows(MachineFunction &MF);
bool ensureSingleBBTermPads(MachineFunction &MF);
bool mergeTerminatePads(MachineFunction &MF);
bool addCatchAllTerminatePads(MachineFunction &MF);
bool addCatches(MachineFunction &MF);
bool addExceptionExtraction(MachineFunction &MF);
public:
static char ID; // Pass identification, replacement for typeid
@ -112,15 +108,11 @@ bool WebAssemblyLateEHPrepare::runOnMachineFunction(MachineFunction &MF) {
bool Changed = false;
Changed |= removeUnnecessaryUnreachables(MF);
Changed |= addRethrows(MF);
if (!MF.getFunction().hasPersonalityFn())
return Changed;
Changed |= replaceFuncletReturns(MF);
Changed |= hoistCatches(MF);
Changed |= addCatchAlls(MF);
Changed |= ensureSingleBBTermPads(MF);
Changed |= mergeTerminatePads(MF);
Changed |= addCatchAllTerminatePads(MF);
Changed |= addCatches(MF);
Changed |= addExceptionExtraction(MF);
return Changed;
}
@ -129,7 +121,8 @@ bool WebAssemblyLateEHPrepare::removeUnnecessaryUnreachables(
bool Changed = false;
for (auto &MBB : MF) {
for (auto &MI : MBB) {
if (!WebAssembly::isThrow(MI))
if (MI.getOpcode() != WebAssembly::THROW &&
MI.getOpcode() != WebAssembly::RETHROW)
continue;
Changed = true;
@ -152,7 +145,6 @@ bool WebAssemblyLateEHPrepare::removeUnnecessaryUnreachables(
bool WebAssemblyLateEHPrepare::replaceFuncletReturns(MachineFunction &MF) {
bool Changed = false;
const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
auto *EHInfo = MF.getWasmEHFuncInfo();
for (auto &MBB : MF) {
auto Pos = MBB.getFirstTerminator();
@ -173,13 +165,7 @@ bool WebAssemblyLateEHPrepare::replaceFuncletReturns(MachineFunction &MF) {
}
case WebAssembly::CLEANUPRET: {
// Replace a cleanupret with a rethrow
if (EHInfo->hasThrowUnwindDest(&MBB))
BuildMI(MBB, TI, TI->getDebugLoc(), TII.get(WebAssembly::RETHROW))
.addMBB(EHInfo->getThrowUnwindDest(&MBB));
else
BuildMI(MBB, TI, TI->getDebugLoc(),
TII.get(WebAssembly::RETHROW_TO_CALLER));
BuildMI(MBB, TI, TI->getDebugLoc(), TII.get(WebAssembly::RETHROW));
TI->eraseFromParent();
Changed = true;
break;
@ -189,233 +175,158 @@ bool WebAssemblyLateEHPrepare::replaceFuncletReturns(MachineFunction &MF) {
return Changed;
}
// Hoist catch instructions to the beginning of their matching EH pad BBs in
// case,
// (1) catch instruction is not the first instruction in EH pad.
// ehpad:
// some_other_instruction
// ...
// %exn = catch 0
// (2) catch instruction is in a non-EH pad BB. For example,
// ehpad:
// br bb0
// bb0:
// %exn = catch 0
bool WebAssemblyLateEHPrepare::hoistCatches(MachineFunction &MF) {
bool Changed = false;
SmallVector<MachineInstr *, 16> Catches;
for (auto &MBB : MF)
for (auto &MI : MBB)
if (WebAssembly::isCatch(MI))
Catches.push_back(&MI);
for (auto *Catch : Catches) {
MachineBasicBlock *EHPad = getMatchingEHPad(Catch);
assert(EHPad && "No matching EH pad for catch");
if (EHPad->begin() == Catch)
continue;
Changed = true;
EHPad->insert(EHPad->begin(), Catch->removeFromParent());
}
return Changed;
}
// Add catch_all to beginning of cleanup pads.
bool WebAssemblyLateEHPrepare::addCatchAlls(MachineFunction &MF) {
// Add catch instruction to beginning of catchpads and cleanuppads.
bool WebAssemblyLateEHPrepare::addCatches(MachineFunction &MF) {
bool Changed = false;
const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
MachineRegisterInfo &MRI = MF.getRegInfo();
for (auto &MBB : MF) {
if (!MBB.isEHPad())
continue;
// This runs after hoistCatches(), so we assume that if there is a catch,
// that should be the first instruction in an EH pad.
if (!WebAssembly::isCatch(*MBB.begin())) {
if (MBB.isEHPad()) {
Changed = true;
unsigned DstReg =
MRI.createVirtualRegister(&WebAssembly::EXCEPT_REFRegClass);
BuildMI(MBB, MBB.begin(), MBB.begin()->getDebugLoc(),
TII.get(WebAssembly::CATCH_ALL));
TII.get(WebAssembly::CATCH), DstReg);
}
}
return Changed;
}
// Add a 'rethrow' instruction after __cxa_rethrow() call
bool WebAssemblyLateEHPrepare::addRethrows(MachineFunction &MF) {
bool Changed = false;
// Wasm uses 'br_on_exn' instruction to check the tag of an exception. It takes
// except_ref type object returned by 'catch', and branches to the destination
// if it matches a given tag. We currently use __cpp_exception symbol to
// represent the tag for all C++ exceptions.
//
// block $l (result i32)
// ...
// ;; except_ref $e is on the stack at this point
// br_on_exn $l $e ;; branch to $l with $e's arguments
// ...
// end
// ;; Here we expect the extracted values are on top of the wasm value stack
// ... Handle exception using values ...
//
// br_on_exn takes an except_ref object and branches if it matches the given
// tag. There can be multiple br_on_exn instructions if we want to match for
// another tag, but for now we only test for __cpp_exception tag, and if it does
// not match, i.e., it is a foreign exception, we rethrow it.
//
// In the destination BB that's the target of br_on_exn, extracted exception
// values (in C++'s case a single i32, which represents an exception pointer)
// are placed on top of the wasm stack. Because we can't model wasm stack in
// LLVM instruction, we use 'extract_exception' pseudo instruction to retrieve
// it. The pseudo instruction will be deleted later.
bool WebAssemblyLateEHPrepare::addExceptionExtraction(MachineFunction &MF) {
const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
auto *EHInfo = MF.getWasmEHFuncInfo();
for (auto &MBB : MF)
SmallVector<MachineInstr *, 16> ExtractInstrs;
for (auto &MBB : MF) {
for (auto &MI : MBB) {
// Check if it is a call to __cxa_rethrow()
if (!MI.isCall())
continue;
MachineOperand &CalleeOp = MI.getOperand(0);
if (!CalleeOp.isGlobal() ||
CalleeOp.getGlobal()->getName() != WebAssembly::CxaRethrowFn)
continue;
// Now we have __cxa_rethrow() call
Changed = true;
auto InsertPt = std::next(MachineBasicBlock::iterator(MI));
while (InsertPt != MBB.end() && InsertPt->isLabel()) // Skip EH_LABELs
++InsertPt;
MachineInstr *Rethrow = nullptr;
if (EHInfo->hasThrowUnwindDest(&MBB))
Rethrow = BuildMI(MBB, InsertPt, MI.getDebugLoc(),
TII.get(WebAssembly::RETHROW))
.addMBB(EHInfo->getThrowUnwindDest(&MBB));
else
Rethrow = BuildMI(MBB, InsertPt, MI.getDebugLoc(),
TII.get(WebAssembly::RETHROW_TO_CALLER));
// Because __cxa_rethrow does not return, the instruction after the
// rethrow should be an unreachable or a branch to another BB that should
// eventually lead to an unreachable. Delete it because rethrow itself is
// a terminator, and also delete non-EH pad successors if any.
MBB.erase(std::next(MachineBasicBlock::iterator(Rethrow)), MBB.end());
SmallVector<MachineBasicBlock *, 8> NonPadSuccessors;
for (auto *Succ : MBB.successors())
if (!Succ->isEHPad())
NonPadSuccessors.push_back(Succ);
for (auto *Succ : NonPadSuccessors)
MBB.removeSuccessor(Succ);
eraseDeadBBsAndChildren(NonPadSuccessors);
if (MI.getOpcode() == WebAssembly::EXTRACT_EXCEPTION_I32) {
if (MI.getOperand(0).isDead())
MI.eraseFromParent();
else
ExtractInstrs.push_back(&MI);
}
}
return Changed;
}
}
if (ExtractInstrs.empty())
return false;
// Terminate pads are an single-BB EH pad in the form of
// termpad:
// %exn = catch 0
// call @__clang_call_terminate(%exn)
// unreachable
// (There can be local.set and local.gets before the call if we didn't run
// RegStackify)
// But code transformations can change or add more control flow, so the call to
// __clang_call_terminate() function may not be in the original EH pad anymore.
// This ensures every terminate pad is a single BB in the form illustrated
// above.
bool WebAssemblyLateEHPrepare::ensureSingleBBTermPads(MachineFunction &MF) {
const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
// Find calls to __clang_call_terminate()
SmallVector<MachineInstr *, 8> ClangCallTerminateCalls;
for (auto &MBB : MF)
for (auto &MI : MBB)
// Find terminate pads.
SmallSet<MachineBasicBlock *, 8> TerminatePads;
for (auto &MBB : MF) {
for (auto &MI : MBB) {
if (MI.isCall()) {
const MachineOperand &CalleeOp = MI.getOperand(0);
if (CalleeOp.isGlobal() && CalleeOp.getGlobal()->getName() ==
WebAssembly::ClangCallTerminateFn)
ClangCallTerminateCalls.push_back(&MI);
TerminatePads.insert(getMatchingEHPad(&MI));
}
}
}
bool Changed = false;
for (auto *Call : ClangCallTerminateCalls) {
MachineBasicBlock *EHPad = getMatchingEHPad(Call);
assert(EHPad && "No matching EH pad for catch");
// If it is already the form we want, skip it
if (Call->getParent() == EHPad &&
Call->getNextNode()->getOpcode() == WebAssembly::UNREACHABLE)
continue;
// In case the __clang_call_terminate() call is not in its matching EH pad,
// move the call to the end of EH pad and add an unreachable instruction
// after that. Delete all successors and their children if any, because here
// the program terminates.
Changed = true;
for (auto *Extract : ExtractInstrs) {
MachineBasicBlock *EHPad = getMatchingEHPad(Extract);
assert(EHPad && "No matching EH pad for extract_exception");
MachineInstr *Catch = &*EHPad->begin();
// This runs after hoistCatches(), so catch instruction should be at the top
assert(WebAssembly::isCatch(*Catch));
// Takes the result register of the catch instruction as argument. There may
// have been some other local.set/local.gets in between, but at this point
// we don't care.
Call->getOperand(1).setReg(Catch->getOperand(0).getReg());
auto InsertPos = std::next(MachineBasicBlock::iterator(Catch));
EHPad->insert(InsertPos, Call->removeFromParent());
BuildMI(*EHPad, InsertPos, Call->getDebugLoc(),
TII.get(WebAssembly::UNREACHABLE));
EHPad->erase(InsertPos, EHPad->end());
SmallVector<MachineBasicBlock *, 8> Succs(EHPad->succ_begin(),
EHPad->succ_end());
for (auto *Succ : Succs)
EHPad->removeSuccessor(Succ);
eraseDeadBBsAndChildren(Succs);
}
return Changed;
}
if (Catch->getNextNode() != Extract)
EHPad->insert(Catch->getNextNode(), Extract->removeFromParent());
// In case there are multiple terminate pads, merge them into one for code size.
// This runs after ensureSingleBBTermPads() and assumes every terminate pad is a
// single BB.
// In principle this violates EH scope relationship because it can merge
// multiple inner EH scopes, each of which is in different outer EH scope. But
// getEHScopeMembership() function will not be called after this, so it is fine.
bool WebAssemblyLateEHPrepare::mergeTerminatePads(MachineFunction &MF) {
SmallVector<MachineBasicBlock *, 8> TermPads;
for (auto &MBB : MF)
if (WebAssembly::isCatchTerminatePad(MBB))
TermPads.push_back(&MBB);
if (TermPads.empty())
return false;
// - Before:
// ehpad:
// %exnref:except_ref = catch
// %exn:i32 = extract_exception
// ... use exn ...
//
// - After:
// ehpad:
// %exnref:except_ref = catch
// br_on_exn %thenbb, $__cpp_exception, %exnref
// br %elsebb
// elsebb:
// rethrow
// thenbb:
// %exn:i32 = extract_exception
// ... use exn ...
unsigned ExnRefReg = Catch->getOperand(0).getReg();
auto *ThenMBB = MF.CreateMachineBasicBlock();
auto *ElseMBB = MF.CreateMachineBasicBlock();
MF.insert(std::next(MachineFunction::iterator(EHPad)), ElseMBB);
MF.insert(std::next(MachineFunction::iterator(ElseMBB)), ThenMBB);
ThenMBB->splice(ThenMBB->end(), EHPad, Extract, EHPad->end());
ThenMBB->transferSuccessors(EHPad);
EHPad->addSuccessor(ThenMBB);
EHPad->addSuccessor(ElseMBB);
MachineBasicBlock *UniqueTermPad = TermPads.front();
for (auto *TermPad :
llvm::make_range(std::next(TermPads.begin()), TermPads.end())) {
SmallVector<MachineBasicBlock *, 2> Preds(TermPad->pred_begin(),
TermPad->pred_end());
for (auto *Pred : Preds)
Pred->replaceSuccessor(TermPad, UniqueTermPad);
TermPad->eraseFromParent();
}
return true;
}
// Terminate pads are cleanup pads, so they should start with a 'catch_all'
// instruction. But in the Itanium model, when we have a C++ exception object,
// we pass them to __clang_call_terminate function, which calls __cxa_end_catch
// with the passed exception pointer and then std::terminate. This is the reason
// that terminate pads are generated with not a catch_all but a catch
// instruction in clang and earlier llvm passes. Here we append a terminate pad
// with a catch_all after each existing terminate pad so we can also catch
// foreign exceptions. For every terminate pad:
// %exn = catch 0
// call @__clang_call_terminate(%exn)
// unreachable
// We append this BB right after that:
// catch_all
// call @std::terminate()
// unreachable
bool WebAssemblyLateEHPrepare::addCatchAllTerminatePads(MachineFunction &MF) {
const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
SmallVector<MachineBasicBlock *, 8> TermPads;
for (auto &MBB : MF)
if (WebAssembly::isCatchTerminatePad(MBB))
TermPads.push_back(&MBB);
if (TermPads.empty())
return false;
Function *StdTerminateFn =
MF.getFunction().getParent()->getFunction(WebAssembly::StdTerminateFn);
assert(StdTerminateFn && "There is no std::terminate() function");
for (auto *CatchTermPad : TermPads) {
DebugLoc DL = CatchTermPad->findDebugLoc(CatchTermPad->begin());
auto *CatchAllTermPad = MF.CreateMachineBasicBlock();
MF.insert(std::next(MachineFunction::iterator(CatchTermPad)),
CatchAllTermPad);
CatchAllTermPad->setIsEHPad();
BuildMI(CatchAllTermPad, DL, TII.get(WebAssembly::CATCH_ALL));
BuildMI(CatchAllTermPad, DL, TII.get(WebAssembly::CALL_VOID))
.addGlobalAddress(StdTerminateFn);
BuildMI(CatchAllTermPad, DL, TII.get(WebAssembly::UNREACHABLE));
// Actually this CatchAllTermPad (new terminate pad with a catch_all) is not
// a successor of an existing terminate pad. CatchAllTermPad should have all
// predecessors CatchTermPad has instead. This is a hack to force
// CatchAllTermPad be always sorted right after CatchTermPad; the correct
// predecessor-successor relationships will be restored in CFGStackify pass.
CatchTermPad->addSuccessor(CatchAllTermPad);
DebugLoc DL = Extract->getDebugLoc();
const char *CPPExnSymbol = MF.createExternalSymbolName("__cpp_exception");
BuildMI(EHPad, DL, TII.get(WebAssembly::BR_ON_EXN))
.addMBB(ThenMBB)
.addExternalSymbol(CPPExnSymbol, WebAssemblyII::MO_SYMBOL_EVENT)
.addReg(ExnRefReg);
BuildMI(EHPad, DL, TII.get(WebAssembly::BR)).addMBB(ElseMBB);
// When this is a terminate pad with __clang_call_terminate() call, we don't
// rethrow it anymore and call __clang_call_terminate() with a nullptr
// argument, which will call std::terminate().
//
// - Before:
// ehpad:
// %exnref:except_ref = catch
// %exn:i32 = extract_exception
// call @__clang_call_terminate(%exn)
// unreachable
//
// - After:
// ehpad:
// %exnref:except_ref = catch
// br_on_exn %thenbb, $__cpp_exception, %exnref
// br %elsebb
// elsebb:
// call @__clang_call_terminate(0)
// unreachable
// thenbb:
// %exn:i32 = extract_exception
// call @__clang_call_terminate(%exn)
// unreachable
if (TerminatePads.count(EHPad)) {
Function *ClangCallTerminateFn =
MF.getFunction().getParent()->getFunction(
WebAssembly::ClangCallTerminateFn);
assert(ClangCallTerminateFn &&
"There is no __clang_call_terminate() function");
BuildMI(ElseMBB, DL, TII.get(WebAssembly::CALL_VOID))
.addGlobalAddress(ClangCallTerminateFn)
.addImm(0);
BuildMI(ElseMBB, DL, TII.get(WebAssembly::UNREACHABLE));
} else {
BuildMI(ElseMBB, DL, TII.get(WebAssembly::RETHROW));
if (EHInfo->hasEHPadUnwindDest(EHPad))
EHInfo->setThrowUnwindDest(ElseMBB, EHInfo->getEHPadUnwindDest(EHPad));
}
}
return true;
}

View File

@ -36,7 +36,7 @@ using namespace llvm;
// This disables the removal of registers when lowering into MC, as required
// by some current tests.
static cl::opt<bool>
cl::opt<bool>
WasmKeepRegisters("wasm-keep-registers", cl::Hidden,
cl::desc("WebAssembly: output stack registers in"
" instruction output for test purposes only."),

View File

@ -317,6 +317,18 @@ static bool IsSafeToMove(const MachineInstr *Def, const MachineInstr *Insert,
AliasAnalysis &AA, const MachineRegisterInfo &MRI) {
assert(Def->getParent() == Insert->getParent());
// 'catch' and 'extract_exception' should be the first instruction of a BB and
// cannot move.
if (Def->getOpcode() == WebAssembly::CATCH ||
Def->getOpcode() == WebAssembly::EXTRACT_EXCEPTION_I32) {
const MachineBasicBlock *MBB = Def->getParent();
auto NextI = std::next(MachineBasicBlock::const_iterator(Def));
for (auto E = MBB->end(); NextI != E && NextI->isDebugInstr(); ++NextI)
;
if (NextI != Insert)
return false;
}
// Check for register dependencies.
SmallVector<unsigned, 4> MutableRegisters;
for (const MachineOperand &MO : Def->operands()) {
@ -819,6 +831,24 @@ bool WebAssemblyRegStackify::runOnMachineFunction(MachineFunction &MF) {
if (WebAssembly::isArgument(*Def))
continue;
// Currently catch's return value register cannot be stackified, because
// the wasm LLVM backend currently does not support live-in values
// entering blocks, which is a part of multi-value proposal.
//
// Once we support live-in values of wasm blocks, this can be:
// catch ; push except_ref value onto stack
// block except_ref -> i32
// br_on_exn $__cpp_exception ; pop the except_ref value
// end_block
//
// But because we don't support it yet, the catch instruction's dst
// register should be assigned to a local to be propagated across
// 'block' boundary now.
//
// TODO Fix this once we support the multi-value proposal.
if (Def->getOpcode() == WebAssembly::CATCH)
continue;
// Decide which strategy to take. Prefer to move a single-use value
// over cloning it, and prefer cloning over introducing a tee.
// For moving, we require the def to be in the same block as the use;

View File

@ -301,8 +301,10 @@ void WebAssemblyPassConfig::addPreEmitPass() {
addPass(createWebAssemblyFixIrreducibleControlFlow());
// Do various transformations for exception handling.
// Every CFG-changing optimizations should come before this.
addPass(createWebAssemblyLateEHPrepare());
// Preparations and optimizations related to register stackification.
if (getOptLevel() != CodeGenOpt::None) {
// LiveIntervals isn't commonly run this late. Re-establish preconditions.
addPass(createWebAssemblyPrepareForLiveIntervals());

View File

@ -242,50 +242,10 @@ bool WebAssembly::isMarker(const MachineInstr &MI) {
}
}
bool WebAssembly::isThrow(const MachineInstr &MI) {
switch (MI.getOpcode()) {
case WebAssembly::THROW_I32:
case WebAssembly::THROW_I32_S:
case WebAssembly::THROW_I64:
case WebAssembly::THROW_I64_S:
return true;
default:
return false;
}
}
bool WebAssembly::isRethrow(const MachineInstr &MI) {
switch (MI.getOpcode()) {
case WebAssembly::RETHROW:
case WebAssembly::RETHROW_S:
case WebAssembly::RETHROW_TO_CALLER:
case WebAssembly::RETHROW_TO_CALLER_S:
return true;
default:
return false;
}
}
bool WebAssembly::isCatch(const MachineInstr &MI) {
switch (MI.getOpcode()) {
case WebAssembly::CATCH_I32:
case WebAssembly::CATCH_I32_S:
case WebAssembly::CATCH_I64:
case WebAssembly::CATCH_I64_S:
case WebAssembly::CATCH_ALL:
case WebAssembly::CATCH_ALL_S:
return true;
default:
return false;
}
}
bool WebAssembly::mayThrow(const MachineInstr &MI) {
switch (MI.getOpcode()) {
case WebAssembly::THROW_I32:
case WebAssembly::THROW_I32_S:
case WebAssembly::THROW_I64:
case WebAssembly::THROW_I64_S:
case WebAssembly::THROW:
case WebAssembly::THROW_S:
case WebAssembly::RETHROW:
case WebAssembly::RETHROW_S:
return true;
@ -308,41 +268,3 @@ bool WebAssembly::mayThrow(const MachineInstr &MI) {
return false;
return true;
}
bool WebAssembly::isCatchTerminatePad(const MachineBasicBlock &MBB) {
if (!MBB.isEHPad())
return false;
bool SeenCatch = false;
for (auto &MI : MBB) {
if (MI.getOpcode() == WebAssembly::CATCH_I32 ||
MI.getOpcode() == WebAssembly::CATCH_I64 ||
MI.getOpcode() == WebAssembly::CATCH_I32_S ||
MI.getOpcode() == WebAssembly::CATCH_I64_S)
SeenCatch = true;
if (SeenCatch && MI.isCall()) {
const MachineOperand &CalleeOp = MI.getOperand(getCalleeOpNo(MI));
if (CalleeOp.isGlobal() &&
CalleeOp.getGlobal()->getName() == ClangCallTerminateFn)
return true;
}
}
return false;
}
bool WebAssembly::isCatchAllTerminatePad(const MachineBasicBlock &MBB) {
if (!MBB.isEHPad())
return false;
bool SeenCatchAll = false;
for (auto &MI : MBB) {
if (MI.getOpcode() == WebAssembly::CATCH_ALL ||
MI.getOpcode() == WebAssembly::CATCH_ALL_S)
SeenCatchAll = true;
if (SeenCatchAll && MI.isCall()) {
const MachineOperand &CalleeOp = MI.getOperand(getCalleeOpNo(MI));
if (CalleeOp.isGlobal() &&
CalleeOp.getGlobal()->getName() == StdTerminateFn)
return true;
}
}
return false;
}

View File

@ -30,22 +30,12 @@ bool isChild(const MachineInstr &MI, const WebAssemblyFunctionInfo &MFI);
bool isCallDirect(const MachineInstr &MI);
bool isCallIndirect(const MachineInstr &MI);
bool isMarker(const MachineInstr &MI);
bool isThrow(const MachineInstr &MI);
bool isRethrow(const MachineInstr &MI);
bool isCatch(const MachineInstr &MI);
bool mayThrow(const MachineInstr &MI);
/// Returns the operand number of a callee, assuming the argument is a call
/// instruction.
unsigned getCalleeOpNo(const MachineInstr &MI);
/// Returns if the given BB is a single BB terminate pad which starts with a
/// 'catch' instruction.
bool isCatchTerminatePad(const MachineBasicBlock &MBB);
/// Returns if the given BB is a single BB terminate pad which starts with a
/// 'catch_all' insrtruction.
bool isCatchAllTerminatePad(const MachineBasicBlock &MBB);
// Exception-related function names
extern const char *const ClangCallTerminateFn;
extern const char *const CxaBeginCatchFn;

View File

@ -1,94 +0,0 @@
# RUN: llc -mtriple=wasm32-unknown-unknown -start-after xray-instrumentation -wasm-keep-registers %s -o - | FileCheck %s
---
# Tests if block/loop/try/catch/end instructions are correctly printed with
# their annotations.
# CHECK: test0:
# CHECK: block
# CHECK: try
# CHECK: br 0 # 0: down to label1
# CHECK: catch_all # catch0:
# CHECK: block
# CHECK: br_if 0, 1 # 0: down to label2
# CHECK: loop # label3:
# CHECK: br_if 0, 1 # 0: up to label3
# CHECK: end_loop
# CHECK: end_block # label2:
# CHECK: try
# CHECK: rethrow 0 # 0: down to catch1
# CHECK: catch_all # catch1:
# CHECK: block
# CHECK: try
# CHECK: br 0 # 0: down to label6
# CHECK: catch_all # catch2:
# CHECK: unreachable
# CHECK: end_try # label6:
# CHECK: end_block # label5:
# CHECK: rethrow 0 # 0: to caller
# CHECK: end_try # label4:
# CHECK: end_try # label1:
# CHECK: end_block # label0:
name: test0
liveins:
- { reg: '$arguments', reg: '$value_stack' }
body: |
bb.0:
successors: %bb.7, %bb.1
BLOCK 64, implicit-def $value_stack, implicit $value_stack
TRY 64, implicit-def $value_stack, implicit $value_stack
BR 0, implicit-def $arguments
bb.1 (landing-pad):
; predecessors: %bb.0
successors: %bb.2, %bb.3
CATCH_ALL implicit-def $arguments
BLOCK 64, implicit-def $value_stack, implicit $value_stack
BR_IF 0, 1, implicit-def $arguments, implicit-def $value_stack, implicit $value_stack
bb.2:
; predecessors: %bb.1, %bb.2
successors: %bb.2, %bb.3
LOOP 64, implicit-def $value_stack, implicit $value_stack
BR_IF 0, 1, implicit-def $arguments
bb.3:
; predecessors: %bb.1, %bb.2
successors: %bb.4
END_LOOP implicit-def $value_stack, implicit $value_stack
END_BLOCK implicit-def $value_stack, implicit $value_stack
TRY 64, implicit-def $value_stack, implicit $value_stack
RETHROW 0, implicit-def $arguments
bb.4 (landing-pad):
; predecessors: %bb.3
successors: %bb.6, %bb.5
CATCH_ALL implicit-def $arguments
BLOCK 64, implicit-def $value_stack, implicit $value_stack
TRY 64, implicit-def $value_stack, implicit $value_stack
BR 0, implicit-def $arguments
bb.5 (landing-pad):
; predecessors: %bb.4
CATCH_ALL implicit-def $arguments
UNREACHABLE implicit-def dead $arguments
bb.6:
; predecessors: %bb.4
END_TRY implicit-def $value_stack, implicit $value_stack
END_BLOCK implicit-def $value_stack, implicit $value_stack
RETHROW 0, implicit-def $arguments
bb.7:
; predecessors: %bb.0
END_TRY implicit-def $value_stack, implicit $value_stack
END_TRY implicit-def $value_stack, implicit $value_stack
END_BLOCK implicit-def $value_stack, implicit $value_stack
FALLTHROUGH_RETURN_VOID implicit-def dead $arguments
END_FUNCTION implicit-def $value_stack, implicit $value_stack
...

View File

@ -7,21 +7,25 @@ target triple = "wasm32-unknown-unknown"
@_ZTId = external constant i8*
; Simple test case with two catch clauses
; void test0() {
; try {
; foo();
; } catch (int n) {
; bar();
; } catch (double d) {
; }
; }
; CHECK-LABEL: test0
; CHECK: try
; CHECK: call foo@FUNCTION
; CHECK: .LBB0_1:
; CHECK: i32.catch
; CHECK: catch $[[EXCEPT_REF:[0-9]+]]=
; CHECK: block i32
; CHECK: br_on_exn 0, __cpp_exception@EVENT, $[[EXCEPT_REF]]
; CHECK: rethrow
; CHECK: end_block
; CHECK: i32.call $drop=, _Unwind_CallPersonality@FUNCTION
; CHECK: i32.call $drop=, __cxa_begin_catch@FUNCTION
; CHECK: call bar@FUNCTION
; CHECK: call __cxa_end_catch@FUNCTION
; CHECK: .LBB0_3:
; CHECK: i32.call $drop=, __cxa_begin_catch@FUNCTION
; CHECK: call __cxa_end_catch@FUNCTION
; CHECK: .LBB0_5:
; CHECK: call __cxa_rethrow@FUNCTION
; CHECK: .LBB0_6:
; CHECK: end_try
; CHECK: return
define void @test0() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
entry:
@ -68,39 +72,45 @@ try.cont: ; preds = %entry, %catch, %cat
}
; Nested try-catches within a catch
; void test1() {
; try {
; foo();
; } catch (int n) {
; try {
; foo();
; } catch (int n) {
; foo();
; }
; }
; }
; CHECK-LABEL: test1
; CHECK: try
; CHECK: call foo@FUNCTION
; CHECK: .LBB1_1:
; CHECK: i32.catch $0=, 0
; CHECK: i32.call $drop=, _Unwind_CallPersonality@FUNCTION, $0
; CHECK: i32.call $drop=, __cxa_begin_catch@FUNCTION, $0
; CHECK: catch
; CHECK: br_on_exn 0, __cpp_exception@EVENT
; CHECK: rethrow
; CHECK: i32.call $drop=, _Unwind_CallPersonality@FUNCTION
; CHECK: try
; CHECK: call foo@FUNCTION
; CHECK: .LBB1_3:
; CHECK: i32.catch $0=, 0
; CHECK: i32.call $drop=, _Unwind_CallPersonality@FUNCTION, $0
; CHECK: i32.call $drop=, __cxa_begin_catch@FUNCTION, $0
; CHECK: catch
; CHECK: br_on_exn 0, __cpp_exception@EVENT
; CHECK: rethrow
; CHECK: i32.call $drop=, _Unwind_CallPersonality@FUNCTION
; CHECK: try
; CHECK: i32.call $drop=, __cxa_begin_catch@FUNCTION
; CHECK: try
; CHECK: call foo@FUNCTION
; CHECK: .LBB1_5:
; CHECK: catch_all
; CHECK: call __cxa_end_catch@FUNCTION
; CHECK: catch $drop=
; CHECK: rethrow
; CHECK: .LBB1_6:
; CHECK: call __cxa_rethrow@FUNCTION
; CHECK: end_try
; CHECK: catch $drop=
; CHECK: rethrow
; CHECK: .LBB1_7:
; CHECK: call __cxa_end_catch@FUNCTION
; CHECK: .LBB1_8:
; CHECK: catch_all
; CHECK: call __cxa_end_catch@FUNCTION
; CHECK: .LBB1_9:
; CHECK: call __cxa_rethrow@FUNCTION
; CHECK: rethrow
; CHECK: .LBB1_10:
; CHECK: call __cxa_end_catch@FUNCTION
; CHECK: .LBB1_11:
; CHECK: end_try
; CHECK: end_try
; CHECK: end_try
; CHECK: return
define hidden void @test1() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
define void @test1() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
entry:
invoke void @foo()
to label %try.cont11 unwind label %catch.dispatch
@ -175,30 +185,38 @@ unreachable: ; preds = %rethrow5
}
; Nested loop within a catch clause
; void test2() {
; try {
; foo();
; } catch (...) {
; for (int i = 0; i < 50; i++)
; foo();
; }
; }
; CHECK-LABEL: test2
; CHECK: try
; CHECK: call foo@FUNCTION
; CHECK: .LBB2_1:
; CHECK: i32.catch
; CHECK: i32.call $drop=, __cxa_begin_catch@FUNCTION
; CHECK: .LBB2_2:
; CHECK: catch
; CHECK: br_on_exn 0, __cpp_exception@EVENT
; CHECK: rethrow
; CHECK: loop
; CHECK: try
; CHECK: call foo@FUNCTION
; CHECK: .LBB2_4:
; CHECK: catch_all
; CHECK: catch $drop=
; CHECK: try
; CHECK: call __cxa_end_catch@FUNCTION
; CHECK: .LBB2_5:
; CHECK: i32.catch
; CHECK: catch
; CHECK: br_on_exn 0, __cpp_exception@EVENT
; CHECK: call __clang_call_terminate@FUNCTION, 0
; CHECK: unreachable
; CHECK: call __clang_call_terminate@FUNCTION
; CHECK: unreachable
; CHECK: .LBB2_6:
; CHECK: catch_all
; CHECK: call _ZSt9terminatev@FUNCTION
; CHECK: unreachable
; CHECK: .LBB2_7:
; CHECK: end_try
; CHECK: rethrow
; CHECK: .LBB2_8:
; CHECK: call __cxa_end_catch@FUNCTION
; CHECK: .LBB2_10:
; CHECK: end_try
; CHECK: end_loop
; CHECK: end_try
; CHECK: return
define void @test2() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
entry:

View File

@ -1,322 +0,0 @@
# RUN: llc -mtriple=wasm32-unknown-unknown -exception-model=wasm -run-pass wasm-cfg-stackify %s -o - | FileCheck %s
--- |
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-unknown"
@__wasm_lpad_context = external global { i32, i8*, i32 }
declare void @may_throw()
; Function Attrs: nounwind
declare void @dont_throw() #0
declare i8* @__cxa_begin_catch(i8*)
declare void @__cxa_end_catch()
declare void @__cxa_rethrow()
; Function Attrs: nounwind
declare i32 @__gxx_wasm_personality_v0(...)
declare i32 @_Unwind_CallPersonality(i8*) #0
define void @test0() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
unreachable
}
define void @test1() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
unreachable
}
define void @test2() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
unreachable
}
define void @test3() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
unreachable
}
attributes #0 = { nounwind }
---
# Simplest try-catch
# try {
# may_throw();
# } catch (...) {
# }
name: test0
# CHECK-LABEL: name: test0
liveins:
- { reg: '$arguments', reg: '$value_stack' }
body: |
bb.0:
successors: %bb.2, %bb.1
CALL_VOID @may_throw, implicit-def dead $arguments, implicit $sp32, implicit $sp64
BR %bb.2, implicit-def $arguments
; CHECK-LABEL: bb.0:
; CHECK: TRY
; CHECK-NEXT: CALL_VOID @may_throw
bb.1 (landing-pad):
; predecessors: %bb.0
successors: %bb.2
%2:i32 = CATCH_I32 0, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
%3:i32 = CALL_I32 @__cxa_begin_catch, %2:i32, implicit-def dead $arguments, implicit $sp32, implicit $sp64, implicit-def $value_stack, implicit $value_stack
DROP_I32 killed %3:i32, implicit-def $arguments
CALL_VOID @__cxa_end_catch, implicit-def dead $arguments, implicit $sp32, implicit $sp64
bb.2:
; predecessors: %bb.0, %bb.1
RETURN_VOID implicit-def dead $arguments
; CHECK-LABEL: bb.2:
; CHECK-NEXT: END_TRY
; CHECK: RETURN_VOID
...
---
# Nested try-catch inside another catch
# try {
# may_throw();
# } catch (int n) {
# try {
# may_throw();
# } catch (int n) {
# }
# }
name: test1
# CHECK-LABEL: name: test1
liveins:
- { reg: '$arguments', reg: '$value_stack' }
body: |
bb.0:
successors: %bb.9, %bb.1
CALL_VOID @may_throw, implicit-def dead $arguments, implicit $sp32, implicit $sp64
BR %bb.9, implicit-def $arguments
; CHECK-LABEL: bb.0:
; CHECK: TRY
; CHECK-NEXT: CALL_VOID @may_throw
bb.1 (landing-pad):
; predecessors: %bb.0
successors: %bb.2, %bb.7
%30:i32 = CATCH_I32 0, implicit-def dead $arguments
LOCAL_SET_I32 0, %30:i32, implicit-def $arguments
%16:i32 = CONST_I32 0, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
%27:i32 = CONST_I32 0, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
STORE_I32 2, @__wasm_lpad_context + 4, %16:i32, %27:i32, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack :: (store 4 into `i8** getelementptr inbounds ({ i32, i8*, i32 }, { i32, i8*, i32 }* @__wasm_lpad_context, i32 0, i32 1)`)
%26:i32 = CONST_I32 0, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
%25:i32 = CONST_I32 0, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
STORE_I32 2, @__wasm_lpad_context, %26:i32, %25:i32, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack :: (store 4 into `i32* getelementptr inbounds ({ i32, i8*, i32 }, { i32, i8*, i32 }* @__wasm_lpad_context, i32 0, i32 0)`)
%32:i32 = LOCAL_GET_I32 0, implicit-def $arguments
%31:i32 = CALL_I32 @_Unwind_CallPersonality, %32:i32, implicit-def dead $arguments, implicit $sp32, implicit $sp64
DROP_I32 killed %31:i32, implicit-def $arguments
%24:i32 = CONST_I32 0, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
%17:i32 = LOAD_I32 2, @__wasm_lpad_context + 8, %24:i32, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack :: (dereferenceable load 4 from `i32* getelementptr inbounds ({ i32, i8*, i32 }, { i32, i8*, i32 }* @__wasm_lpad_context, i32 0, i32 2)`)
%18:i32 = CONST_I32 1, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
%19:i32 = NE_I32 %17:i32, %18:i32, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
BR_IF %bb.7, %19:i32, implicit-def $arguments, implicit-def $value_stack, implicit $value_stack
bb.2:
; predecessors: %bb.1
successors: %bb.8, %bb.3, %bb.6
%34:i32 = LOCAL_GET_I32 0, implicit-def $arguments
%33:i32 = CALL_I32 @__cxa_begin_catch, %34:i32, implicit-def dead $arguments, implicit $sp32, implicit $sp64
DROP_I32 killed %33:i32, implicit-def $arguments
CALL_VOID @may_throw, implicit-def dead $arguments, implicit $sp32, implicit $sp64
BR %bb.8, implicit-def $arguments
; CHECK-LABEL: bb.2:
; CHECK: DROP_I32
; CHECK-NEXT: TRY
; CHECK-NEXT: TRY
; CHECK-NEXT: CALL_VOID @may_throw
bb.3 (landing-pad):
; predecessors: %bb.2
successors: %bb.4, %bb.5
%35:i32 = CATCH_I32 0, implicit-def dead $arguments
LOCAL_SET_I32 0, %35:i32, implicit-def $arguments
%21:i32 = CONST_I32 0, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
%20:i32 = CONST_I32 1, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
STORE_I32 2, @__wasm_lpad_context, %21:i32, %20:i32, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack :: (store 4 into `i32* getelementptr inbounds ({ i32, i8*, i32 }, { i32, i8*, i32 }* @__wasm_lpad_context, i32 0, i32 0)`)
%37:i32 = LOCAL_GET_I32 0, implicit-def $arguments
%36:i32 = CALL_I32 @_Unwind_CallPersonality, %37:i32, implicit-def dead $arguments, implicit $sp32, implicit $sp64
DROP_I32 killed %36:i32, implicit-def $arguments
%29:i32 = CONST_I32 0, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
%22:i32 = LOAD_I32 2, @__wasm_lpad_context + 8, %29:i32, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack :: (dereferenceable load 4 from `i32* getelementptr inbounds ({ i32, i8*, i32 }, { i32, i8*, i32 }* @__wasm_lpad_context, i32 0, i32 2)`)
%28:i32 = CONST_I32 1, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
%23:i32 = NE_I32 %22:i32, %28:i32, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
BR_IF %bb.5, %23:i32, implicit-def $arguments, implicit-def $value_stack, implicit $value_stack
bb.4:
; predecessors: %bb.3
successors: %bb.8
%39:i32 = LOCAL_GET_I32 0, implicit-def $arguments
%38:i32 = CALL_I32 @__cxa_begin_catch, %39:i32, implicit-def dead $arguments, implicit $sp32, implicit $sp64
DROP_I32 killed %38:i32, implicit-def $arguments
CALL_VOID @__cxa_end_catch, implicit-def dead $arguments, implicit $sp32, implicit $sp64
BR %bb.8, implicit-def $arguments
bb.5:
; predecessors: %bb.3
successors: %bb.6
CALL_VOID @__cxa_rethrow, implicit-def dead $arguments, implicit $sp32, implicit $sp64
RETHROW %bb.6, implicit-def $arguments
bb.6 (landing-pad):
; predecessors: %bb.2, %bb.5
CATCH_ALL implicit-def $arguments
CALL_VOID @__cxa_end_catch, implicit-def dead $arguments, implicit $sp32, implicit $sp64
RETHROW_TO_CALLER implicit-def $arguments
; CHECK-LABEL: bb.6 (landing-pad):
; CHECK-NEXT: END_TRY
bb.7:
; predecessors: %bb.1
CALL_VOID @__cxa_rethrow, implicit-def dead $arguments, implicit $sp32, implicit $sp64
RETHROW_TO_CALLER implicit-def $arguments
; CHECK-LABEL: bb.7:
; CHECK-NEXT: END_TRY
; CHECK: RETHROW 0
bb.8:
; predecessors: %bb.2, %bb.4
successors: %bb.9
CALL_VOID @__cxa_end_catch, implicit-def dead $arguments, implicit $sp32, implicit $sp64
bb.9:
; predecessors: %bb.0, %bb.8
RETURN_VOID implicit-def dead $arguments
; CHECK-LABEL: bb.9:
; CHECK-NEXT: END_TRY
...
---
# A loop within a try.
# try {
# for (int i = 0; i < n; ++i)
# may_throw();
# } catch (...) {
# }
name: test2
# CHECK-LABEL: name: test2
liveins:
- { reg: '$arguments', reg: '$value_stack' }
body: |
bb.0:
successors: %bb.1, %bb.4
%18:i32 = CONST_I32 0, implicit-def dead $arguments
LOCAL_SET_I32 1, %18:i32, implicit-def $arguments
%14:i32 = CONST_I32 0, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
%19:i32 = LOCAL_GET_I32 0, implicit-def $arguments
%9:i32 = GE_S_I32 %14:i32, %19:i32, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
BR_IF %bb.4, %9:i32, implicit-def $arguments
bb.1:
; predecessors: %bb.0, %bb.3
successors: %bb.3, %bb.2
CALL_VOID @may_throw, implicit-def dead $arguments, implicit $sp32, implicit $sp64
BR %bb.3, implicit-def $arguments
; CHECK-LABEL: bb.1:
; CHECK: LOOP
; CHECK: TRY
; CHECK-NEXT: CALL_VOID @may_throw
bb.2 (landing-pad):
; predecessors: %bb.1
successors: %bb.4
%11:i32 = CATCH_I32 0, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
%22:i32 = CALL_I32 @__cxa_begin_catch, %11:i32, implicit-def dead $arguments, implicit $sp32, implicit $sp64, implicit-def $value_stack, implicit $value_stack
DROP_I32 killed %22:i32, implicit-def $arguments
CALL_VOID @__cxa_end_catch, implicit-def dead $arguments, implicit $sp32, implicit $sp64
BR %bb.4, implicit-def $arguments
bb.3:
; predecessors: %bb.1
successors: %bb.1, %bb.4
%20:i32 = LOCAL_GET_I32 1, implicit-def $arguments
%17:i32 = CONST_I32 1, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
%16:i32 = ADD_I32 %20:i32, %17:i32, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
%15:i32 = LOCAL_TEE_I32 1, %16:i32, implicit-def $arguments
%21:i32 = LOCAL_GET_I32 0, implicit-def $arguments
%10:i32 = GE_S_I32 %15:i32, %21:i32, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
BR_UNLESS %bb.1, %10:i32, implicit-def $arguments
; CHECK-LABEL: bb.3:
; CHECK: END_TRY
bb.4:
; predecessors: %bb.2, %bb.0, %bb.3
RETURN_VOID implicit-def dead $arguments
...
---
# A loop within a catch
# try {
# may_throw();
# } catch (...) {
# for (int i = 0; i < n; ++i)
# dont_throw();
# }
name: test3
# CHECK-LABEL: name: test3
liveins:
- { reg: '$arguments', reg: '$value_stack' }
body: |
bb.0:
successors: %bb.4, %bb.1
CALL_VOID @may_throw, implicit-def dead $arguments, implicit $sp32, implicit $sp64
BR %bb.4, implicit-def $arguments
; CHECK-LABEL: bb.0:
; CHECK: TRY
; CHECK-NEXT: CALL_VOID @may_throw
bb.1 (landing-pad):
; predecessors: %bb.0
successors: %bb.2, %bb.3
%9:i32 = CATCH_I32 0, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
%18:i32 = CALL_I32 @__cxa_begin_catch, %9:i32, implicit-def dead $arguments, implicit $sp32, implicit $sp64, implicit-def $value_stack, implicit $value_stack
DROP_I32 killed %18:i32, implicit-def $arguments
%19:i32 = CONST_I32 0, implicit-def dead $arguments
LOCAL_SET_I32 1, %19:i32, implicit-def $arguments
%14:i32 = CONST_I32 0, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
%20:i32 = LOCAL_GET_I32 0, implicit-def $arguments
%10:i32 = GE_S_I32 %14:i32, %20:i32, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
BR_IF %bb.3, %10:i32, implicit-def $arguments, implicit-def $value_stack, implicit $value_stack
bb.2:
; predecessors: %bb.1, %bb.2
successors: %bb.2, %bb.3
CALL_VOID @dont_throw, implicit-def dead $arguments, implicit $sp32, implicit $sp64
%21:i32 = LOCAL_GET_I32 1, implicit-def $arguments
%17:i32 = CONST_I32 1, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
%16:i32 = ADD_I32 %21:i32, %17:i32, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
%15:i32 = LOCAL_TEE_I32 1, %16:i32, implicit-def $arguments
%22:i32 = LOCAL_GET_I32 0, implicit-def $arguments
%11:i32 = GE_S_I32 %15:i32, %22:i32, implicit-def dead $arguments, implicit-def $value_stack, implicit $value_stack
BR_UNLESS %bb.2, %11:i32, implicit-def $arguments, implicit-def $value_stack, implicit $value_stack
bb.3:
; predecessors: %bb.1, %bb.2
successors: %bb.4
CALL_VOID @__cxa_end_catch, implicit-def dead $arguments, implicit $sp32, implicit $sp64
bb.4:
; predecessors: %bb.0, %bb.3
RETURN_VOID implicit-def dead $arguments
; CHECK-LABEL: bb.4:
; CHECK: END_TRY

View File

@ -1,5 +1,5 @@
; RUN: not llc < %s -asm-verbose=false -disable-wasm-fallthrough-return-opt -wasm-keep-registers -exception-model=wasm
; RUN: llc < %s -asm-verbose=false -disable-wasm-fallthrough-return-opt -wasm-keep-registers -exception-model=wasm -mattr=+exception-handling -verify-machineinstrs | FileCheck -allow-deprecated-dag-overlap %s
; RUN: llc < %s -asm-verbose=false -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -wasm-keep-registers -exception-model=wasm -mattr=+exception-handling -verify-machineinstrs | FileCheck -allow-deprecated-dag-overlap %s
; RUN: llc < %s -disable-wasm-fallthrough-return-opt -wasm-keep-registers -exception-model=wasm -mattr=+exception-handling
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
@ -9,30 +9,39 @@ target triple = "wasm32-unknown-unknown"
@_ZTIi = external constant i8*
declare void @llvm.wasm.throw(i32, i8*)
; CHECK-LABEL: test_throw:
; CHECK: local.get $push0=, 0
; CHECK-NEXT: throw __cpp_exception@EVENT, $pop0
; CHECK: throw __cpp_exception@EVENT, $0
; CHECK-NOT: unreachable
define void @test_throw(i8* %p) {
call void @llvm.wasm.throw(i32 0, i8* %p)
ret void
}
; CHECK-LABEL: test_rethrow:
; CHECK: rethrow
; CHECK-NOT: unreachable
define void @test_rethrow(i8* %p) {
call void @llvm.wasm.rethrow()
ret void
}
; CHECK-LABEL: test_catch_rethrow:
; CHECK: global.get $push{{.+}}=, __stack_pointer@GLOBAL
; CHECK: global.get ${{.+}}=, __stack_pointer@GLOBAL
; CHECK: try
; CHECK: call foo@FUNCTION
; CHECK: i32.catch $push{{.+}}=, 0
; CHECK: catch $[[EXCEPT_REF:[0-9]+]]=
; CHECK: block i32
; CHECK: br_on_exn 0, __cpp_exception@EVENT, $[[EXCEPT_REF]]
; CHECK: rethrow
; CHECK: end_block
; CHECK: extract_exception $[[EXN:[0-9]+]]=
; CHECK: global.set __stack_pointer@GLOBAL
; CHECK-DAG: i32.store __wasm_lpad_context
; CHECK-DAG: i32.store __wasm_lpad_context+4
; CHECK: i32.call $push{{.+}}=, _Unwind_CallPersonality@FUNCTION
; CHECK: i32.call $push{{.+}}=, __cxa_begin_catch@FUNCTION
; CHECK: i32.call $drop=, _Unwind_CallPersonality@FUNCTION, $[[EXN]]
; CHECK: i32.call $drop=, __cxa_begin_catch@FUNCTION
; CHECK: call __cxa_end_catch@FUNCTION
; CHECK: call __cxa_rethrow@FUNCTION
; CHECK-NEXT: rethrow
; CHECK: end_try
define void @test_catch_rethrow() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
entry:
@ -66,9 +75,9 @@ try.cont: ; preds = %entry, %catch
; CHECK-LABEL: test_cleanup:
; CHECK: try
; CHECK: call foo@FUNCTION
; CHECK: catch_all
; CHECK: catch
; CHECK: global.set __stack_pointer@GLOBAL
; CHECK: i32.call $push{{.+}}=, _ZN7CleanupD1Ev@FUNCTION
; CHECK: i32.call $drop=, _ZN7CleanupD1Ev@FUNCTION
; CHECK: rethrow
; CHECK: end_try
define void @test_cleanup() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
@ -87,71 +96,51 @@ ehcleanup: ; preds = %entry
cleanupret from %0 unwind to caller
}
; - Tests multple terminate pads are merged into one
; - Tests a catch_all terminate pad is created after a catch terminate pad
; CHECK-LABEL: test_terminatepad
; CHECK: i32.catch
; CHECK: call __clang_call_terminate@FUNCTION
; CHECK: unreachable
; CHECK: catch_all
; CHECK: call _ZSt9terminatev@FUNCTION
; CHECK-NOT: call __clang_call_terminate@FUNCTION
define hidden i32 @test_terminatepad() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
; CHECK: catch
; CHECK: block i32
; CHECK: br_on_exn 0, __cpp_exception@EVENT
; CHECK: call __clang_call_terminate@FUNCTION, 0
; CHECK: unreachable
; CHECK: end_block
; CHECK: extract_exception
; CHECK: call __clang_call_terminate@FUNCTION
; CHECK: unreachable
define void @test_terminatepad() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
entry:
%c = alloca %struct.Cleanup, align 1
%c1 = alloca %struct.Cleanup, align 1
invoke void @foo()
to label %invoke.cont unwind label %ehcleanup
invoke.cont: ; preds = %entry
%call = invoke %struct.Cleanup* @_ZN7CleanupD1Ev(%struct.Cleanup* %c1)
to label %try.cont unwind label %catch.dispatch
ehcleanup: ; preds = %entry
%0 = cleanuppad within none []
%call4 = invoke %struct.Cleanup* @_ZN7CleanupD1Ev(%struct.Cleanup* %c1) [ "funclet"(token %0) ]
to label %invoke.cont3 unwind label %terminate
invoke.cont3: ; preds = %ehcleanup
cleanupret from %0 unwind label %catch.dispatch
catch.dispatch: ; preds = %invoke.cont3, %invoke.cont
%1 = catchswitch within none [label %catch.start] unwind label %ehcleanup7
catch.dispatch: ; preds = %entry
%0 = catchswitch within none [label %catch.start] unwind to caller
catch.start: ; preds = %catch.dispatch
%2 = catchpad within %1 [i8* null]
%3 = call i8* @llvm.wasm.get.exception(token %2)
%4 = call i32 @llvm.wasm.get.ehselector(token %2)
%5 = call i8* @__cxa_begin_catch(i8* %3) [ "funclet"(token %2) ]
invoke void @__cxa_end_catch() [ "funclet"(token %2) ]
to label %invoke.cont5 unwind label %ehcleanup7
%1 = catchpad within %0 [i8* null]
%2 = call i8* @llvm.wasm.get.exception(token %1)
%3 = call i32 @llvm.wasm.get.ehselector(token %1)
%4 = call i8* @__cxa_begin_catch(i8* %2) [ "funclet"(token %1) ]
invoke void @foo() [ "funclet"(token %1) ]
to label %invoke.cont1 unwind label %ehcleanup
invoke.cont5: ; preds = %catch.start
catchret from %2 to label %try.cont
invoke.cont1: ; preds = %catch.start
call void @__cxa_end_catch() [ "funclet"(token %1) ]
catchret from %1 to label %try.cont
try.cont: ; preds = %invoke.cont5, %invoke.cont
%call6 = call %struct.Cleanup* @_ZN7CleanupD1Ev(%struct.Cleanup* %c)
ret i32 0
try.cont: ; preds = %entry, %invoke.cont1
ret void
ehcleanup7: ; preds = %catch.start, %catch.dispatch
%6 = cleanuppad within none []
%call9 = invoke %struct.Cleanup* @_ZN7CleanupD1Ev(%struct.Cleanup* %c) [ "funclet"(token %6) ]
to label %invoke.cont8 unwind label %terminate10
ehcleanup: ; preds = %catch.start
%5 = cleanuppad within %1 []
invoke void @__cxa_end_catch() [ "funclet"(token %5) ]
to label %invoke.cont2 unwind label %terminate
invoke.cont8: ; preds = %ehcleanup7
cleanupret from %6 unwind to caller
invoke.cont2: ; preds = %ehcleanup
cleanupret from %5 unwind to caller
terminate: ; preds = %ehcleanup
%7 = cleanuppad within %0 []
%8 = call i8* @llvm.wasm.get.exception(token %7)
call void @__clang_call_terminate(i8* %8) [ "funclet"(token %7) ]
unreachable
terminate10: ; preds = %ehcleanup7
%9 = cleanuppad within %6 []
%10 = call i8* @llvm.wasm.get.exception(token %9)
call void @__clang_call_terminate(i8* %10) [ "funclet"(token %9) ]
%6 = cleanuppad within %5 []
%7 = call i8* @llvm.wasm.get.exception(token %6)
call void @__clang_call_terminate(i8* %7) [ "funclet"(token %6) ]
unreachable
}
@ -164,12 +153,12 @@ terminate10: ; preds = %ehcleanup7
; CHECK-LABEL: test_no_prolog_epilog_in_ehpad
; CHECK: try
; CHECK: call foo@FUNCTION
; CHECK: i32.catch
; CHECK: catch
; CHECK-NOT: global.get $push{{.+}}=, __stack_pointer@GLOBAL
; CHECK: global.set __stack_pointer@GLOBAL
; CHECK: try
; CHECK: call foo@FUNCTION
; CHECK: catch_all
; CHECK: catch
; CHECK-NOT: global.get $push{{.+}}=, __stack_pointer@GLOBAL
; CHECK: global.set __stack_pointer@GLOBAL
; CHECK: call __cxa_end_catch@FUNCTION
@ -251,6 +240,8 @@ try.cont: ; preds = %entry, %catch.start
declare void @foo()
declare void @bar(i32*)
declare i32 @__gxx_wasm_personality_v0(...)
declare void @llvm.wasm.throw(i32, i8*)
declare void @llvm.wasm.rethrow()
declare i8* @llvm.wasm.get.exception(token)
declare i32 @llvm.wasm.get.ehselector(token)
declare i32 @llvm.eh.typeid.for(i8*)
@ -258,7 +249,6 @@ declare i8* @__cxa_begin_catch(i8*)
declare void @__cxa_end_catch()
declare void @__cxa_rethrow()
declare void @__clang_call_terminate(i8*)
declare void @_ZSt9terminatev()
declare %struct.Cleanup* @_ZN7CleanupD1Ev(%struct.Cleanup* returned)
; CHECK: __cpp_exception:

View File

@ -29,7 +29,7 @@ catch.start: ; preds = %catch.dispatch
br i1 %matches, label %catch, label %rethrow
; CHECK: catch.start:
; CHECK-NEXT: %[[CATCHPAD:.*]] = catchpad
; CHECK-NEXT: %[[EXN:.*]] = call i8* @llvm.wasm.catch(i32 0)
; CHECK-NEXT: %[[EXN:.*]] = call i8* @llvm.wasm.extract.exception()
; CHECK-NEXT: call void @llvm.wasm.landingpad.index(token %[[CATCHPAD]], i32 0)
; CHECK-NEXT: store i32 0, i32* getelementptr inbounds ({ i32, i8*, i32 }, { i32, i8*, i32 }* @__wasm_lpad_context, i32 0, i32 0)
; CHECK-NEXT: %[[LSDA:.*]] = call i8* @llvm.wasm.lsda()
@ -76,7 +76,6 @@ catch.start: ; preds = %catch.dispatch
catchret from %1 to label %try.cont
; CHECK: catch.start:
; CHECK-NEXT: catchpad within %0 [i8* null]
; CHECK-NEXT: call i8* @llvm.wasm.catch(i32 0)
; CHECK-NOT: call void @llvm.wasm.landingpad.index
; CHECK-NOT: store {{.*}} @__wasm_lpad_context
; CHECK-NOT: call i8* @llvm.wasm.lsda()
@ -178,7 +177,6 @@ ehcleanup: ; preds = %rethrow5, %catch.di
cleanupret from %12 unwind to caller
; CHECK: ehcleanup:
; CHECK-NEXT: cleanuppad
; CHECK-NOT: call i8* @llvm.wasm.catch(i32 0)
; CHECK-NOT: call void @llvm.wasm.landingpad.index
; CHECK-NOT: store {{.*}} @__wasm_lpad_context
; CHECK-NOT: call i8* @llvm.wasm.lsda()
@ -191,7 +189,7 @@ unreachable: ; preds = %rethrow5
; A cleanuppad with a call to __clang_call_terminate().
; A call to wasm.catch() should be generated after the cleanuppad.
define hidden void @test3() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
define void @test3() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
; CHECK-LABEL: @test3
entry:
invoke void @foo()
@ -230,14 +228,14 @@ terminate: ; preds = %ehcleanup
unreachable
; CHECK: terminate:
; CHECK-NEXT: cleanuppad
; CHECK-NEXT: %[[EXN:.*]] = call i8* @llvm.wasm.catch(i32 0)
; CHECK-NEXT: %[[EXN:.*]] = call i8* @llvm.wasm.extract.exception
; CHECK-NEXT: call void @__clang_call_terminate(i8* %[[EXN]])
}
; PHI demotion test. Only the phi before catchswitch should be demoted; the phi
; before cleanuppad should NOT.
define void @test5() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
; CHECK-LABEL: @test5
define void @test4() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
; CHECK-LABEL: @test4
entry:
%c = alloca %struct.Cleanup, align 1
invoke void @foo()
@ -301,8 +299,8 @@ try.cont10: ; preds = %invoke.cont3, %catc
; Tests if instructions after a call to @llvm.wasm.throw are deleted and the
; BB's dead children are deleted.
; CHECK-LABEL: @test6
define i32 @test6(i1 %b, i8* %p) {
; CHECK-LABEL: @test5
define i32 @test5(i1 %b, i8* %p) {
entry:
br i1 %b, label %bb.true, label %bb.false
@ -326,6 +324,34 @@ merge: ; preds = %bb.true.0, %bb.fals
ret i32 0
}
; Tests if instructions after a call to @llvm.wasm.rethrow are deleted and the
; BB's dead children are deleted.
; CHECK-LABEL: @test6
define i32 @test6(i1 %b, i8* %p) {
entry:
br i1 %b, label %bb.true, label %bb.false
; CHECK: bb.true:
; CHECK-NEXT: call void @llvm.wasm.rethrow()
; CHECK-NEXT: unreachable
bb.true: ; preds = %entry
call void @llvm.wasm.rethrow()
br label %bb.true.0
; CHECK-NOT: bb.true.0
bb.true.0: ; preds = %bb.true
br label %merge
; CHECK: bb.false
bb.false: ; preds = %entry
br label %merge
; CHECK: merge
merge: ; preds = %bb.true.0, %bb.false
ret i32 0
}
declare void @foo()
declare void @func(i32)
declare %struct.Cleanup* @_ZN7CleanupD1Ev(%struct.Cleanup* returned)
@ -334,12 +360,12 @@ declare i8* @llvm.wasm.get.exception(token)
declare i32 @llvm.wasm.get.ehselector(token)
declare i32 @llvm.eh.typeid.for(i8*)
declare void @llvm.wasm.throw(i32, i8*)
declare void @llvm.wasm.rethrow()
declare i8* @__cxa_begin_catch(i8*)
declare void @__cxa_end_catch()
declare void @__cxa_rethrow()
declare void @__clang_call_terminate(i8*)
; CHECK-DAG: declare i8* @llvm.wasm.catch(i32)
; CHECK-DAG: declare void @llvm.wasm.landingpad.index(token, i32)
; CHECK-DAG: declare i8* @llvm.wasm.lsda()
; CHECK-DAG: declare i32 @_Unwind_CallPersonality(i8*)

View File

@ -0,0 +1,71 @@
# RUN: llvm-mc -triple=wasm32-unknown-unknown -mattr=+exception-handling < %s | FileCheck %s
# Tests if block/loop/try/catch/end/branch/rethrow instructions are correctly
# printed with their annotations.
.text
.section .text.test_annotation,"",@
.type test_annotation,@function
test_annotation:
.functype test_annotation () -> ()
.eventtype __cpp_exception i32
try
br 0
catch
block
br_if 0
loop
br_if 1
end_loop
end_block
try
rethrow
catch
block
try
br 0
catch
local.set 0
block i32
local.get 0
br_on_exn 0, __cpp_exception@EVENT
rethrow
end_block
end_try
end_block
rethrow
end_try
end_try
end_function
# CHECK: test_annotation:
# CHECK: try
# CHECK-NEXT: br 0 # 0: down to label0
# CHECK-NEXT: catch # catch0:
# CHECK-NEXT: block
# CHECK-NEXT: br_if 0 # 0: down to label1
# CHECK-NEXT: loop # label2:
# CHECK-NEXT: br_if 1 # 1: down to label1
# CHECK-NEXT: end_loop
# CHECK-NEXT: end_block # label1:
# CHECK-NEXT: try
# CHECK-NEXT: rethrow # down to catch1
# CHECK-NEXT: catch # catch1:
# CHECK-NEXT: block
# CHECK-NEXT: try
# CHECK-NEXT: br 0 # 0: down to label5
# CHECK-NEXT: catch # catch2:
# CHECK-NEXT: local.set 0
# CHECK-NEXT: block i32
# CHECK-NEXT: local.get 0
# CHECK-NEXT: br_on_exn 0, __cpp_exception@EVENT # 0: down to label6
# CHECK-NEXT: rethrow # to caller
# CHECK-NEXT: end_block # label6:
# CHECK-NEXT: end_try # label5:
# CHECK-NEXT: end_block # label4:
# CHECK-NEXT: rethrow # to caller
# CHECK-NEXT: end_try # label3:
# CHECK-NEXT: end_try # label0:
# CHECK-NEXT: end_function

View File

@ -71,11 +71,18 @@ test0:
i32.trunc_f32_s
try except_ref
.LBB0_3:
i32.catch 0
catch
local.set 0
block i32
local.get 0
br_on_exn 0, __cpp_exception@EVENT
rethrow
.LBB0_4:
catch_all
.LBB0_5:
end_block
end_try
i32.const 0
throw 0
.LBB0_5:
#i32.trunc_sat_f32_s
global.get __stack_pointer@GLOBAL
end_function
@ -143,11 +150,18 @@ test0:
# CHECK-NEXT: i32.trunc_f32_s
# CHECK-NEXT: try except_ref
# CHECK-NEXT: .LBB0_3:
# CHECK-NEXT: i32.catch 0
# CHECK-NEXT: catch
# CHECK-NEXT: local.set 0
# CHECK-NEXT: block i32
# CHECK-NEXT: local.get 0
# CHECK-NEXT: br_on_exn 0, __cpp_exception@EVENT
# CHECK-NEXT: rethrow
# CHECK-NEXT: .LBB0_4:
# CHECK-NEXT: catch_all
# CHECK-NEXT: .LBB0_5:
# CHECK-NEXT: end_block
# CHECK-NEXT: end_try
# CHECK-NEXT: i32.const 0
# CHECK-NEXT: throw 0
# CHECK-NEXT: .LBB0_5:
# CHECK-NEXT: global.get __stack_pointer@GLOBAL
# CHECK-NEXT: end_function

View File

@ -74,7 +74,7 @@ TEST(WebAssemblyExceptionInfoTest, TEST0) {
declare i32 @__gxx_wasm_personality_v0(...)
define hidden void @test0() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
define void @test0() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
unreachable
}
@ -100,14 +100,14 @@ body: |
; predecessors: %bb.0
successors: %bb.3, %bb.9
liveins: $value_stack
CATCH_ALL implicit-def $arguments
%0:except_ref = CATCH implicit-def $arguments
CLEANUPRET implicit-def dead $arguments
bb.3 (landing-pad):
; predecessors: %bb.2
successors: %bb.4, %bb.6
liveins: $value_stack
CATCH_ALL implicit-def $arguments
%1:except_ref = CATCH implicit-def $arguments
BR_IF %bb.4, %58:i32, implicit-def $arguments, implicit-def $value_stack, implicit $value_stack
BR %bb.6, implicit-def $arguments
@ -138,13 +138,13 @@ body: |
; predecessors: %bb.4
successors: %bb.9
liveins: $value_stack
CATCH_ALL implicit-def $arguments
%2:except_ref = CATCH implicit-def $arguments
CLEANUPRET implicit-def dead $arguments
bb.9 (landing-pad):
; predecessors: %bb.2, %bb.6, %bb.8
liveins: $value_stack
CATCH_ALL implicit-def $arguments
%3:except_ref = CATCH implicit-def $arguments
CLEANUPRET implicit-def dead $arguments
bb.10:
@ -237,7 +237,7 @@ TEST(WebAssemblyExceptionInfoTest, TEST1) {
declare i32 @__gxx_wasm_personality_v0(...)
define hidden void @test1() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
define void @test1() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
unreachable
}
@ -257,7 +257,7 @@ body: |
; predecessors: %bb.0
successors: %bb.2, %bb.8
liveins: $value_stack
%52:i32 = CATCH_I32 0, implicit-def dead $arguments
%0:except_ref = CATCH implicit-def $arguments
BR_IF %bb.2, %32:i32, implicit-def $arguments, implicit-def $value_stack, implicit $value_stack
BR %bb.8, implicit-def $arguments
@ -271,7 +271,7 @@ body: |
; predecessors: %bb.2
successors: %bb.4, %bb.6
liveins: $value_stack
CATCH_ALL implicit-def $arguments
%1:except_ref = CATCH implicit-def $arguments
BR_IF %bb.4, %43:i32, implicit-def $arguments, implicit-def $value_stack, implicit $value_stack
BR %bb.6, implicit-def $arguments
@ -313,13 +313,13 @@ body: |
; predecessors: %bb.4
successors: %bb.11
liveins: $value_stack
CATCH_ALL implicit-def $arguments
%2:except_ref = CATCH implicit-def $arguments
CLEANUPRET implicit-def dead $arguments
bb.11 (landing-pad):
; predecessors: %bb.2, %bb.6, %bb.10
liveins: $value_stack
CATCH_ALL implicit-def $arguments
%3:except_ref = CATCH implicit-def $arguments
CLEANUPRET implicit-def dead $arguments
bb.12:
@ -415,135 +415,3 @@ body: |
EXPECT_EQ(WE0_1->getParentException(), WE0);
EXPECT_EQ(WE0_1->getExceptionDepth(), (unsigned)2);
}
// Terminate pad test
TEST(WebAssemblyExceptionInfoTest, TEST2) {
std::unique_ptr<LLVMTargetMachine> TM = createTargetMachine();
ASSERT_TRUE(TM);
StringRef MIRString = R"MIR(
--- |
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-unknown"
declare i32 @__gxx_wasm_personality_v0(...)
declare void @_ZSt9terminatev()
declare void @__clang_call_terminate(i8*)
define hidden void @test2() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
unreachable
}
...
---
name: test2
liveins:
- { reg: '$arguments' }
- { reg: '$value_stack' }
body: |
bb.0:
successors: %bb.3, %bb.1
BR %bb.3, implicit-def dead $arguments
bb.1 (landing-pad):
; predecessors: %bb.0
successors: %bb.2, %bb.4
%3:i32 = CATCH_I32 0, implicit-def dead $arguments
BR %bb.2, implicit-def dead $arguments
bb.2:
; predecessors: %bb.1
successors: %bb.3(0x80000000); %bb.3(200.00%)
CATCHRET %bb.3, %bb.0, implicit-def dead $arguments
bb.3:
; predecessors: %bb.0, %bb.2
RETURN_VOID implicit-def $arguments
bb.4 (landing-pad):
; predecessors: %bb.1
successors: %bb.5, %bb.6
CATCH_ALL implicit-def $arguments
BR %bb.5, implicit-def dead $arguments
bb.5:
; predecessors: %bb.4
CLEANUPRET implicit-def dead $arguments
bb.6 (landing-pad):
; predecessors: %bb.4
successors: %bb.7(0x80000000); %bb.7(200.00%)
%6:i32 = CATCH_I32 0, implicit-def dead $arguments
CALL_VOID @__clang_call_terminate, %7:i32, implicit-def $arguments
UNREACHABLE implicit-def $arguments
bb.7 (landing-pad):
; predecessors: %bb.6
CATCH_ALL implicit-def $arguments
CALL_VOID @_ZSt9terminatev, implicit-def $arguments
UNREACHABLE implicit-def $arguments
)MIR";
LLVMContext Context;
std::unique_ptr<MIRParser> MIR;
MachineModuleInfo MMI(TM.get());
std::unique_ptr<Module> M =
parseMIR(Context, MIR, *TM, MIRString, "test2", MMI);
ASSERT_TRUE(M);
Function *F = M->getFunction("test2");
auto *MF = MMI.getMachineFunction(*F);
ASSERT_TRUE(MF);
WebAssemblyExceptionInfo WEI;
MachineDominatorTree MDT;
MachineDominanceFrontier MDF;
MDT.runOnMachineFunction(*MF);
MDF.getBase().analyze(MDT.getBase());
WEI.recalculate(MDT, MDF);
// Exception info structure:
// |- bb1 (ehpad), bb2, bb4, bb5, bb6, bb7
// |- bb4 (ehpad), bb5, bb6, bb7
// |- bb6 (ehpad), bb7
//
// Here, bb6 is a terminate pad with a 'catch' instruction, and bb7 is a
// terminate pad with a 'catch_all' instruction, In this case we put bb6 and
// bb7 into one exception.
auto *MBB1 = MF->getBlockNumbered(1);
auto *WE0 = WEI.getExceptionFor(MBB1);
ASSERT_TRUE(WE0);
EXPECT_EQ(WE0->getEHPad(), MBB1);
EXPECT_EQ(WE0->getParentException(), nullptr);
EXPECT_EQ(WE0->getExceptionDepth(), (unsigned)1);
auto *MBB2 = MF->getBlockNumbered(2);
WE0 = WEI.getExceptionFor(MBB2);
ASSERT_TRUE(WE0);
EXPECT_EQ(WE0->getEHPad(), MBB1);
auto *MBB4 = MF->getBlockNumbered(4);
auto *WE0_0 = WEI.getExceptionFor(MBB4);
ASSERT_TRUE(WE0_0);
EXPECT_EQ(WE0_0->getEHPad(), MBB4);
EXPECT_EQ(WE0_0->getParentException(), WE0);
EXPECT_EQ(WE0_0->getExceptionDepth(), (unsigned)2);
auto *MBB5 = MF->getBlockNumbered(5);
WE0_0 = WEI.getExceptionFor(MBB5);
ASSERT_TRUE(WE0_0);
EXPECT_EQ(WE0_0->getEHPad(), MBB4);
auto *MBB6 = MF->getBlockNumbered(6);
auto *WE0_0_0 = WEI.getExceptionFor(MBB6);
ASSERT_TRUE(WE0_0_0);
EXPECT_EQ(WE0_0_0->getEHPad(), MBB6);
EXPECT_EQ(WE0_0_0->getParentException(), WE0_0);
EXPECT_EQ(WE0_0_0->getExceptionDepth(), (unsigned)3);
auto *MBB7 = MF->getBlockNumbered(7);
WE0_0_0 = WEI.getExceptionFor(MBB7);
ASSERT_TRUE(WE0_0_0);
EXPECT_EQ(WE0_0_0->getEHPad(), MBB6);
}