From d6f487863dc951d467b545b86b9ea62980569b5a Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Wed, 30 Jan 2019 03:21:57 +0000 Subject: [PATCH] [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 --- llvm/include/llvm/IR/IntrinsicsWebAssembly.td | 11 +- llvm/lib/CodeGen/AsmPrinter/WasmException.cpp | 4 +- .../CodeGen/SelectionDAG/SelectionDAGBuilder.cpp | 38 ++- llvm/lib/CodeGen/WasmEHPrepare.cpp | 169 ++++------ .../InstPrinter/WebAssemblyInstPrinter.cpp | 77 ++--- .../Target/WebAssembly/WebAssemblyAsmPrinter.cpp | 10 + .../Target/WebAssembly/WebAssemblyCFGStackify.cpp | 88 ++--- .../WebAssemblyEHRestoreStackPointer.cpp | 2 +- .../WebAssembly/WebAssemblyExceptionInfo.cpp | 14 - .../Target/WebAssembly/WebAssemblyISelLowering.cpp | 76 ++--- .../Target/WebAssembly/WebAssemblyISelLowering.h | 3 +- .../Target/WebAssembly/WebAssemblyInstrControl.td | 63 ++-- .../Target/WebAssembly/WebAssemblyInstrInfo.cpp | 36 +- .../lib/Target/WebAssembly/WebAssemblyInstrInfo.td | 4 +- .../WebAssembly/WebAssemblyLateEHPrepare.cpp | 365 ++++++++------------- .../Target/WebAssembly/WebAssemblyMCInstLower.cpp | 2 +- .../Target/WebAssembly/WebAssemblyRegStackify.cpp | 30 ++ .../WebAssembly/WebAssemblyTargetMachine.cpp | 2 + .../Target/WebAssembly/WebAssemblyUtilities.cpp | 82 +---- llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h | 10 - llvm/test/CodeGen/WebAssembly/annotations.mir | 94 ------ llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.ll | 122 ++++--- llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.mir | 322 ------------------ llvm/test/CodeGen/WebAssembly/exception.ll | 124 ++++--- llvm/test/CodeGen/WebAssembly/wasmehprepare.ll | 46 ++- llvm/test/MC/WebAssembly/annotations.s | 71 ++++ llvm/test/MC/WebAssembly/basic-assembly.s | 26 +- .../WebAssembly/WebAssemblyExceptionInfoTest.cpp | 152 +-------- 28 files changed, 726 insertions(+), 1317 deletions(-) delete mode 100644 llvm/test/CodeGen/WebAssembly/annotations.mir delete mode 100644 llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.mir create mode 100644 llvm/test/MC/WebAssembly/annotations.s diff --git a/llvm/include/llvm/IR/IntrinsicsWebAssembly.td b/llvm/include/llvm/IR/IntrinsicsWebAssembly.td index 7480728..767b4c8 100644 --- a/llvm/include/llvm/IR/IntrinsicsWebAssembly.td +++ b/llvm/include/llvm/IR/IntrinsicsWebAssembly.td @@ -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 diff --git a/llvm/lib/CodeGen/AsmPrinter/WasmException.cpp b/llvm/lib/CodeGen/AsmPrinter/WasmException.cpp index 3f863ae..444b0ed 100644 --- a/llvm/lib/CodeGen/AsmPrinter/WasmException.cpp +++ b/llvm/lib/CodeGen/AsmPrinter/WasmException.cpp @@ -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()); diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp index 9849725..155d65f 100644 --- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp @@ -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> + &UnwindDests) { + while (EHPadBB) { + const Instruction *Pad = EHPadBB->getFirstNonPHI(); + if (isa(Pad)) { + // Stop on cleanup pads. + UnwindDests.emplace_back(FuncInfo.MBBMap[EHPadBB], Prob); + UnwindDests.back().first->setIsEHScopeEntry(); + break; + } else if (auto *CatchSwitch = dyn_cast(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(Pad)) { // Add the catchpad handlers to the possible destinations. diff --git a/llvm/lib/CodeGen/WasmEHPrepare.cpp b/llvm/lib/CodeGen/WasmEHPrepare.cpp index cc78cb8..271f3d4 100644 --- a/llvm/lib/CodeGen/WasmEHPrepare.cpp +++ b/llvm/lib/CodeGen/WasmEHPrepare.cpp @@ -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); - - // 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(U); - if (ThrowI->getFunction() != &F) - continue; - Changed = true; - auto *BB = ThrowI->getParent(); - SmallVector Succs(succ_begin(BB), succ_end(BB)); - auto &InstList = BB->getInstList(); - InstList.erase(std::next(BasicBlock::iterator(ThrowI)), InstList.end()); - IRB.SetInsertPoint(BB); - IRB.CreateUnreachable(); - eraseDeadBBsAndChildren(Succs); + // wasm.rethrow() intinsic, which will be lowered to wasm 'rethrow' + // instruction. + RethrowF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_rethrow); + + // Insert an unreachable instruction after a call to @llvm.wasm.throw / + // @llvm.wasm.rethrow and delete all following instructions within the BB, and + // delete all the dead children of the BB as well. + for (auto L : {ThrowF->users(), RethrowF->users()}) { + for (User *U : L) { + // A call to @llvm.wasm.throw() is only generated from __cxa_throw() + // builtin call within libcxxabi, and cannot be an InvokeInst. + auto *ThrowI = cast(U); + if (ThrowI->getFunction() != &F) + continue; + Changed = true; + auto *BB = ThrowI->getParent(); + SmallVector 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(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(BB->getFirstNonPHI()); // In case of a single catch (...), we don't need to emit LSDA if (CPI->getNumArgOperands() == 1 && cast(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 ' - // 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(&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(BB->getFirstNonPHI()); Instruction *GetExnCI = nullptr, *GetSelectorCI = nullptr; for (auto &U : FPI->uses()) { if (auto *CI = dyn_cast(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(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 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(Pad)) { const auto *UnwindBB = CatchPad->getCatchSwitch()->getUnwindDest(); if (!UnwindBB) diff --git a/llvm/lib/Target/WebAssembly/InstPrinter/WebAssemblyInstPrinter.cpp b/llvm/lib/Target/WebAssembly/InstPrinter/WebAssemblyInstPrinter.cpp index c708900..225f4f2 100644 --- a/llvm/lib/Target/WebAssembly/InstPrinter/WebAssemblyInstPrinter.cpp +++ b/llvm/lib/Target/WebAssembly/InstPrinter/WebAssemblyInstPrinter.cpp @@ -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 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 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 { diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp index 573e4c2..f1bb524 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp @@ -43,6 +43,8 @@ using namespace llvm; #define DEBUG_TYPE "asm-printer" +extern cl::opt 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; diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp index 6b30f31..566b9a5 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp @@ -37,6 +37,7 @@ #include "llvm/MC/MCAsmInfo.h" #include "llvm/Support/Debug.h" #include "llvm/Support/raw_ostream.h" +#include 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(); const auto &TII = *MF.getSubtarget().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().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 Stack; - SmallVector 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. diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyEHRestoreStackPointer.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyEHRestoreStackPointer.cpp index dc0f05c..bed4d1e 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyEHRestoreStackPointer.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyEHRestoreStackPointer.cpp @@ -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()); diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyExceptionInfo.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyExceptionInfo.cpp index 4a316e5..0387957 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyExceptionInfo.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyExceptionInfo.cpp @@ -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 WL; WL.push_back(EHPad); while (!WL.empty()) { diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp index 7cb7f27..52d9d28 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp @@ -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(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(Op.getOperand(1))->getZExtValue(); + break; + case ISD::INTRINSIC_WO_CHAIN: + IntNo = cast(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(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(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 + }); } } } diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.h b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.h index d4b6dca..ee2e863 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.h +++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.h @@ -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; diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyInstrControl.td b/llvm/lib/Target/WebAssembly/WebAssemblyInstrControl.td index 1832c74..8160cab 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyInstrControl.td +++ b/llvm/lib/Target/WebAssembly/WebAssemblyInstrControl.td @@ -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 } diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.cpp index 9592130..995dc88 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.cpp @@ -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 &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; } diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.td b/llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.td index a609267..35d6639 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.td +++ b/llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.td @@ -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. diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp index caedd14..030f09b 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp @@ -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().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 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().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().getInstrInfo(); auto *EHInfo = MF.getWasmEHFuncInfo(); - - for (auto &MBB : MF) + SmallVector 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 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; -} - -// 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().getInstrInfo(); + } + if (ExtractInstrs.empty()) + return false; - // Find calls to __clang_call_terminate() - SmallVector ClangCallTerminateCalls; - for (auto &MBB : MF) - for (auto &MI : MBB) + // Find terminate pads. + SmallSet 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; - 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 Succs(EHPad->succ_begin(), - EHPad->succ_end()); - for (auto *Succ : Succs) - EHPad->removeSuccessor(Succ); - eraseDeadBBsAndChildren(Succs); + } } - return Changed; -} -// 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 TermPads; - for (auto &MBB : MF) - if (WebAssembly::isCatchTerminatePad(MBB)) - TermPads.push_back(&MBB); - if (TermPads.empty()) - return false; - - MachineBasicBlock *UniqueTermPad = TermPads.front(); - for (auto *TermPad : - llvm::make_range(std::next(TermPads.begin()), TermPads.end())) { - SmallVector Preds(TermPad->pred_begin(), - TermPad->pred_end()); - for (auto *Pred : Preds) - Pred->replaceSuccessor(TermPad, UniqueTermPad); - TermPad->eraseFromParent(); + for (auto *Extract : ExtractInstrs) { + MachineBasicBlock *EHPad = getMatchingEHPad(Extract); + assert(EHPad && "No matching EH pad for extract_exception"); + MachineInstr *Catch = &*EHPad->begin(); + if (Catch->getNextNode() != Extract) + EHPad->insert(Catch->getNextNode(), Extract->removeFromParent()); + + // - 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); + + 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; -} -// 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().getInstrInfo(); - SmallVector 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); - } return true; } diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp index 482131f..e34ebdb 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp @@ -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 +cl::opt WasmKeepRegisters("wasm-keep-registers", cl::Hidden, cl::desc("WebAssembly: output stack registers in" " instruction output for test purposes only."), diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp index 7b9b494..957a713 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp @@ -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 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; diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp index b2713da..c235fd2 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp @@ -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()); diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp index f1cb579..4edd546 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp @@ -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; -} diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h index 72dc945..f80b496 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h +++ b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h @@ -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; diff --git a/llvm/test/CodeGen/WebAssembly/annotations.mir b/llvm/test/CodeGen/WebAssembly/annotations.mir deleted file mode 100644 index 1ae2db8..0000000 --- a/llvm/test/CodeGen/WebAssembly/annotations.mir +++ /dev/null @@ -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 -... diff --git a/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.ll b/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.ll index e3c34f8..0377301 100644 --- a/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.ll +++ b/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.ll @@ -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: 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: call foo@FUNCTION -; CHECK: .LBB1_5: -; CHECK: catch_all -; CHECK: call __cxa_end_catch@FUNCTION +; CHECK: catch +; CHECK: br_on_exn 0, __cpp_exception@EVENT ; CHECK: rethrow -; CHECK: .LBB1_6: -; CHECK: call __cxa_rethrow@FUNCTION +; CHECK: i32.call $drop=, _Unwind_CallPersonality@FUNCTION +; CHECK: try +; CHECK: i32.call $drop=, __cxa_begin_catch@FUNCTION +; CHECK: try +; CHECK: call foo@FUNCTION +; 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: end_try +; CHECK: catch $drop= ; 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: call __clang_call_terminate@FUNCTION +; CHECK: catch +; CHECK: br_on_exn 0, __cpp_exception@EVENT +; CHECK: call __clang_call_terminate@FUNCTION, 0 ; CHECK: unreachable -; CHECK: .LBB2_6: -; CHECK: catch_all -; CHECK: call _ZSt9terminatev@FUNCTION +; CHECK: call __clang_call_terminate@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: diff --git a/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.mir b/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.mir deleted file mode 100644 index 11d9aaf..0000000 --- a/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.mir +++ /dev/null @@ -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 diff --git a/llvm/test/CodeGen/WebAssembly/exception.ll b/llvm/test/CodeGen/WebAssembly/exception.ll index de2d061..46ec468 100644 --- a/llvm/test/CodeGen/WebAssembly/exception.ll +++ b/llvm/test/CodeGen/WebAssembly/exception.ll @@ -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: diff --git a/llvm/test/CodeGen/WebAssembly/wasmehprepare.ll b/llvm/test/CodeGen/WebAssembly/wasmehprepare.ll index 6df7175..09013a0 100644 --- a/llvm/test/CodeGen/WebAssembly/wasmehprepare.ll +++ b/llvm/test/CodeGen/WebAssembly/wasmehprepare.ll @@ -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*) diff --git a/llvm/test/MC/WebAssembly/annotations.s b/llvm/test/MC/WebAssembly/annotations.s new file mode 100644 index 0000000..e049279 --- /dev/null +++ b/llvm/test/MC/WebAssembly/annotations.s @@ -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 + diff --git a/llvm/test/MC/WebAssembly/basic-assembly.s b/llvm/test/MC/WebAssembly/basic-assembly.s index 4cf9162..e5f2f5f 100644 --- a/llvm/test/MC/WebAssembly/basic-assembly.s +++ b/llvm/test/MC/WebAssembly/basic-assembly.s @@ -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 diff --git a/llvm/unittests/Target/WebAssembly/WebAssemblyExceptionInfoTest.cpp b/llvm/unittests/Target/WebAssembly/WebAssemblyExceptionInfoTest.cpp index 0ecfdad..ec94637 100644 --- a/llvm/unittests/Target/WebAssembly/WebAssemblyExceptionInfoTest.cpp +++ b/llvm/unittests/Target/WebAssembly/WebAssemblyExceptionInfoTest.cpp @@ -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 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 MIR; - MachineModuleInfo MMI(TM.get()); - std::unique_ptr 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); -} -- 2.7.4