forked from OSchip/llvm-project
[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:
parent
6d8e1b456a
commit
d6f487863d
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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."),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
...
|
|
@ -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:
|
||||
|
|
|
@ -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
|
|
@ -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:
|
||||
|
|
|
@ -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*)
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue