diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp --- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp @@ -1637,10 +1637,32 @@ } } -// For wasm, there's always 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. +// In wasm EH, even though a catchpad may not catch an exception if a tag does +// not match, it is OK to add only the first unwind destination catchpad to the +// successors, because there will be at least one invoke instruction within the +// catch scope that points to the next unwind destination, if one exists, so +// CFGSort cannot mess up with BB sorting order. +// (All catchpads with 'catch (type)' clauses have a 'llvm.rethrow' intrinsic +// call within them, and catchpads only consisting of 'catch (...)' have a +// '__cxa_end_catch' call within them, both of which generate invokes in case +// the next unwind destination exists, i.e., the next unwind destination is not +// the caller.) +// +// Having at most one EH pad successor is also simpler and helps later +// transformations. +// +// For example, +// current: +// invoke void @foo to ... unwind label %catch.dispatch +// catch.dispatch: +// %0 = catchswitch within ... [label %catch.start] unwind label %next +// catch.start: +// ... +// ... in this BB or some other child BB dominated by this BB there will be an +// invoke that points to 'next' BB as an unwind destination +// +// next: ; We don't need to add this to 'current' BB's successor +// ... static void findWasmUnwindDestinations( FunctionLoweringInfo &FuncInfo, const BasicBlock *EHPadBB, BranchProbability Prob, diff --git a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h --- a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h +++ b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h @@ -440,6 +440,18 @@ } } +inline bool isCatch(unsigned Opc) { + switch (Opc) { + case WebAssembly::CATCH: + case WebAssembly::CATCH_S: + case WebAssembly::CATCH_ALL: + case WebAssembly::CATCH_ALL_S: + return true; + default: + return false; + } +} + } // end namespace WebAssembly } // end namespace llvm diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp @@ -183,8 +183,7 @@ assert(EHPad->isEHPad()); MachineInstr *Catch = nullptr; for (auto &MI : *EHPad) { - switch (MI.getOpcode()) { - case WebAssembly::CATCH: + if (WebAssembly::isCatch(MI.getOpcode())) { Catch = &MI; ExnReg = Catch->getOperand(0).getReg(); break; @@ -784,11 +783,15 @@ // When MBB is split into MBB and Split, we should unstackify defs in MBB that // have their uses in Split. -static void unstackifyVRegsUsedInSplitBB(MachineBasicBlock &MBB, - MachineBasicBlock &Split, - WebAssemblyFunctionInfo &MFI, - MachineRegisterInfo &MRI, - const WebAssemblyInstrInfo &TII) { +// FIXME This function will be used when fixing unwind mismatches, but the old +// version of that function was removed for the moment and the new version has +// not yet been added. So 'LLVM_ATTRIBUTE_UNUSED' is added to suppress the +// warning. Remove the attribute after the new functionality is added. +LLVM_ATTRIBUTE_UNUSED static void +unstackifyVRegsUsedInSplitBB(MachineBasicBlock &MBB, MachineBasicBlock &Split, + WebAssemblyFunctionInfo &MFI, + MachineRegisterInfo &MRI, + const WebAssemblyInstrInfo &TII) { for (auto &MI : Split) { for (auto &MO : MI.explicit_uses()) { if (!MO.isReg() || Register::isPhysicalRegister(MO.getReg())) @@ -842,508 +845,8 @@ } bool WebAssemblyCFGStackify::fixUnwindMismatches(MachineFunction &MF) { - const auto &TII = *MF.getSubtarget().getInstrInfo(); - auto &MFI = *MF.getInfo(); - MachineRegisterInfo &MRI = MF.getRegInfo(); - - // Linearizing the control flow by placing TRY / END_TRY markers can create - // mismatches in unwind destinations. There are two kinds of mismatches we - // try to solve here. - - // 1. When an instruction may throw, but the EH pad it will unwind to can be - // different from the original CFG. - // - // Example: we have the following CFG: - // bb0: - // call @foo (if it throws, unwind to bb2) - // bb1: - // call @bar (if it throws, unwind to bb3) - // bb2 (ehpad): - // catch - // ... - // bb3 (ehpad) - // catch - // handler body - // - // And the CFG is sorted in this order. Then after placing TRY markers, it - // will look like: (BB markers are omitted) - // try $label1 - // try - // call @foo - // call @bar (if it throws, unwind to bb3) - // catch <- ehpad (bb2) - // ... - // end_try - // catch <- ehpad (bb3) - // handler body - // end_try - // - // Now if bar() throws, it is going to end up ip in bb2, not bb3, where it - // is supposed to end up. We solve this problem by - // a. Split the target unwind EH pad (here bb3) so that the handler body is - // right after 'end_try', which means we extract the handler body out of - // the catch block. We do this because this handler body should be - // somewhere branch-eable from the inner scope. - // b. Wrap the call that has an incorrect unwind destination ('call @bar' - // here) with a nested try/catch/end_try scope, and within the new catch - // block, branches to the handler body. - // c. Place a branch after the newly inserted nested end_try so it can bypass - // the handler body, which is now outside of a catch block. - // - // The result will like as follows. (new: a) means this instruction is newly - // created in the process of doing 'a' above. - // - // block $label0 (new: placeBlockMarker) - // try $label1 - // try - // call @foo - // try (new: b) - // call @bar - // catch (new: b) - // local.set n / drop (new: b) - // br $label1 (new: b) - // end_try (new: b) - // catch <- ehpad (bb2) - // end_try - // br $label0 (new: c) - // catch <- ehpad (bb3) - // end_try (hoisted: a) - // handler body - // end_block (new: placeBlockMarker) - // - // Note that the new wrapping block/end_block will be generated later in - // placeBlockMarker. - // - // TODO Currently local.set and local.gets are generated to move exnref value - // created by catches. That's because we don't support yielding values from a - // block in LLVM machine IR yet, even though it is supported by wasm. Delete - // unnecessary local.get/local.sets once yielding values from a block is - // supported. The full EH spec requires multi-value support to do this, but - // for C++ we don't yet need it because we only throw a single i32. - // - // --- - // 2. The same as 1, but in this case an instruction unwinds to a caller - // function and not another EH pad. - // - // Example: we have the following CFG: - // bb0: - // call @foo (if it throws, unwind to bb2) - // bb1: - // call @bar (if it throws, unwind to caller) - // bb2 (ehpad): - // catch - // ... - // - // And the CFG is sorted in this order. Then after placing TRY markers, it - // will look like: - // try - // call @foo - // call @bar (if it throws, unwind to caller) - // catch <- ehpad (bb2) - // ... - // end_try - // - // Now if bar() throws, it is going to end up ip in bb2, when it is supposed - // throw up to the caller. - // We solve this problem by - // a. Create a new 'appendix' BB at the end of the function and put a single - // 'rethrow' instruction (+ local.get) in there. - // b. Wrap the call that has an incorrect unwind destination ('call @bar' - // here) with a nested try/catch/end_try scope, and within the new catch - // block, branches to the new appendix block. - // - // block $label0 (new: placeBlockMarker) - // try - // call @foo - // try (new: b) - // call @bar - // catch (new: b) - // local.set n (new: b) - // br $label0 (new: b) - // end_try (new: b) - // catch <- ehpad (bb2) - // ... - // end_try - // ... - // end_block (new: placeBlockMarker) - // local.get n (new: a) <- appendix block - // rethrow (new: a) - // - // In case there are multiple calls in a BB that may throw to the caller, they - // can be wrapped together in one nested try scope. (In 1, this couldn't - // happen, because may-throwing instruction there had an unwind destination, - // i.e., it was an invoke before, and there could be only one invoke within a - // BB.) - - SmallVector EHPadStack; - // Range of intructions to be wrapped in a new nested try/catch - using TryRange = std::pair; - // In original CFG, - DenseMap> UnwindDestToTryRanges; - // In new CFG, - DenseMap> BrDestToTryRanges; - // In new CFG, - DenseMap BrDestToExnReg; - - // Destinations for branches that will be newly added, for which a new - // BLOCK/END_BLOCK markers are necessary. - SmallVector BrDests; - - // Gather possibly throwing calls (i.e., previously invokes) whose current - // unwind destination is not the same as the original CFG. - for (auto &MBB : reverse(MF)) { - bool SeenThrowableInstInBB = false; - for (auto &MI : reverse(MBB)) { - if (MI.getOpcode() == WebAssembly::TRY) - EHPadStack.pop_back(); - else if (MI.getOpcode() == WebAssembly::CATCH) - EHPadStack.push_back(MI.getParent()); - - // In this loop we only gather calls that have an EH pad to unwind. So - // there will be at most 1 such call (= invoke) in a BB, so after we've - // seen one, we can skip the rest of BB. Also if MBB has no EH pad - // successor or MI does not throw, this is not an invoke. - if (SeenThrowableInstInBB || !MBB.hasEHPadSuccessor() || - !WebAssembly::mayThrow(MI)) - continue; - SeenThrowableInstInBB = true; - - // If the EH pad on the stack top is where this instruction should unwind - // next, we're good. - MachineBasicBlock *UnwindDest = nullptr; - for (auto *Succ : MBB.successors()) { - if (Succ->isEHPad()) { - UnwindDest = Succ; - break; - } - } - if (EHPadStack.back() == UnwindDest) - continue; - - // If not, record the range. - UnwindDestToTryRanges[UnwindDest].push_back(TryRange(&MI, &MI)); - } - } - - assert(EHPadStack.empty()); - - // Gather possibly throwing calls that are supposed to unwind up to the caller - // if they throw, but currently unwind to an incorrect destination. Unlike the - // loop above, there can be multiple calls within a BB that unwind to the - // caller, which we should group together in a range. - bool NeedAppendixBlock = false; - for (auto &MBB : reverse(MF)) { - MachineInstr *RangeBegin = nullptr, *RangeEnd = nullptr; // inclusive - for (auto &MI : reverse(MBB)) { - if (MI.getOpcode() == WebAssembly::TRY) - EHPadStack.pop_back(); - else if (MI.getOpcode() == WebAssembly::CATCH) - EHPadStack.push_back(MI.getParent()); - - // If MBB has an EH pad successor, this inst does not unwind to caller. - if (MBB.hasEHPadSuccessor()) - continue; - - // We wrap up the current range when we see a marker even if we haven't - // finished a BB. - if (RangeEnd && WebAssembly::isMarker(MI.getOpcode())) { - NeedAppendixBlock = true; - // Record the range. nullptr here means the unwind destination is the - // caller. - UnwindDestToTryRanges[nullptr].push_back( - TryRange(RangeBegin, RangeEnd)); - RangeBegin = RangeEnd = nullptr; // Reset range pointers - } - - // If EHPadStack is empty, that means it is correctly unwind to caller if - // it throws, so we're good. If MI does not throw, we're good too. - if (EHPadStack.empty() || !WebAssembly::mayThrow(MI)) - continue; - - // We found an instruction that unwinds to the caller but currently has an - // incorrect unwind destination. Create a new range or increment the - // currently existing range. - if (!RangeEnd) - RangeBegin = RangeEnd = &MI; - else - RangeBegin = &MI; - } - - if (RangeEnd) { - NeedAppendixBlock = true; - // Record the range. nullptr here means the unwind destination is the - // caller. - UnwindDestToTryRanges[nullptr].push_back(TryRange(RangeBegin, RangeEnd)); - RangeBegin = RangeEnd = nullptr; // Reset range pointers - } - } - - assert(EHPadStack.empty()); - // We don't have any unwind destination mismatches to resolve. - if (UnwindDestToTryRanges.empty()) - return false; - - // If we found instructions that should unwind to the caller but currently - // have incorrect unwind destination, we create an appendix block at the end - // of the function with a local.get and a rethrow instruction. - if (NeedAppendixBlock) { - auto *AppendixBB = getAppendixBlock(MF); - Register ExnReg = MRI.createVirtualRegister(&WebAssembly::EXNREFRegClass); - BuildMI(AppendixBB, DebugLoc(), TII.get(WebAssembly::RETHROW)) - .addReg(ExnReg); - // These instruction ranges should branch to this appendix BB. - for (auto Range : UnwindDestToTryRanges[nullptr]) - BrDestToTryRanges[AppendixBB].push_back(Range); - BrDestToExnReg[AppendixBB] = ExnReg; - } - - // We loop through unwind destination EH pads that are targeted from some - // inner scopes. Because these EH pads are destination of more than one scope - // now, we split them so that the handler body is after 'end_try'. - // - Before - // ehpad: - // catch - // local.set n / drop - // handler body - // ... - // cont: - // end_try - // - // - After - // ehpad: - // catch - // local.set n / drop - // brdest: (new) - // end_try (hoisted from 'cont' BB) - // handler body (taken from 'ehpad') - // ... - // cont: - for (auto &P : UnwindDestToTryRanges) { - NumUnwindMismatches += P.second.size(); - - // This means the destination is the appendix BB, which was separately - // handled above. - if (!P.first) - continue; - - MachineBasicBlock *EHPad = P.first; - Register ExnReg = 0; - MachineInstr *Catch = findCatch(EHPad, ExnReg); - auto SplitPos = std::next(Catch->getIterator()); - - // Create a new BB that's gonna be the destination for branches from the - // inner mismatched scope. - MachineInstr *BeginTry = EHPadToTry[EHPad]; - MachineInstr *EndTry = BeginToEnd[BeginTry]; - MachineBasicBlock *Cont = EndTry->getParent(); - auto *BrDest = MF.CreateMachineBasicBlock(); - MF.insert(std::next(EHPad->getIterator()), BrDest); - // Hoist up the existing 'end_try'. - BrDest->insert(BrDest->end(), EndTry->removeFromParent()); - // Take out the handler body from EH pad to the new branch destination BB. - BrDest->splice(BrDest->end(), EHPad, SplitPos, EHPad->end()); - unstackifyVRegsUsedInSplitBB(*EHPad, *BrDest, MFI, MRI, TII); - // Fix predecessor-successor relationship. - BrDest->transferSuccessors(EHPad); - EHPad->addSuccessor(BrDest); - - // All try ranges that were supposed to unwind to this EH pad now have to - // branch to this new branch dest BB. - for (auto Range : UnwindDestToTryRanges[EHPad]) - BrDestToTryRanges[BrDest].push_back(Range); - BrDestToExnReg[BrDest] = ExnReg; - - // In case we fall through to the continuation BB after the catch block, we - // now have to add a branch to it. - // - Before - // try - // ... - // (falls through to 'cont') - // catch - // handler body - // end - // <-- cont - // - // - After - // try - // ... - // br %cont (new) - // catch - // end - // handler body - // <-- cont - MachineBasicBlock *EHPadLayoutPred = &*std::prev(EHPad->getIterator()); - MachineBasicBlock *TBB = nullptr, *FBB = nullptr; - SmallVector Cond; - bool Analyzable = !TII.analyzeBranch(*EHPadLayoutPred, TBB, FBB, Cond); - if (Analyzable && !TBB && !FBB) { - DebugLoc DL = EHPadLayoutPred->empty() - ? DebugLoc() - : EHPadLayoutPred->rbegin()->getDebugLoc(); - BuildMI(EHPadLayoutPred, DL, TII.get(WebAssembly::BR)).addMBB(Cont); - BrDests.push_back(Cont); - } - } - - // For possibly throwing calls whose unwind destinations are currently - // incorrect because of CFG linearization, we wrap them with a nested - // try/catch/end_try, and within the new catch block, we branch to the correct - // handler. - // - Before - // mbb: - // call @foo <- Unwind destination mismatch! - // ehpad: - // ... - // - // - After - // mbb: - // try (new) - // call @foo - // nested-ehpad: (new) - // catch (new) - // local.set n / drop (new) - // br %brdest (new) - // nested-end: (new) - // end_try (new) - // ehpad: - // ... - for (auto &P : BrDestToTryRanges) { - MachineBasicBlock *BrDest = P.first; - auto &TryRanges = P.second; - unsigned ExnReg = BrDestToExnReg[BrDest]; - - for (auto Range : TryRanges) { - MachineInstr *RangeBegin = nullptr, *RangeEnd = nullptr; - std::tie(RangeBegin, RangeEnd) = Range; - auto *MBB = RangeBegin->getParent(); - // Store the first function call from this range, because RangeBegin can - // be moved to point EH_LABEL before the call - MachineInstr *RangeBeginCall = RangeBegin; - - // Include possible EH_LABELs in the range - if (RangeBegin->getIterator() != MBB->begin() && - std::prev(RangeBegin->getIterator())->isEHLabel()) - RangeBegin = &*std::prev(RangeBegin->getIterator()); - if (std::next(RangeEnd->getIterator()) != MBB->end() && - std::next(RangeEnd->getIterator())->isEHLabel()) - RangeEnd = &*std::next(RangeEnd->getIterator()); - - MachineBasicBlock *EHPad = nullptr; - for (auto *Succ : MBB->successors()) { - if (Succ->isEHPad()) { - EHPad = Succ; - break; - } - } - - // Local expression tree before the first call of this range should go - // after the nested TRY. - SmallPtrSet AfterSet; - AfterSet.insert(RangeBegin); - AfterSet.insert(RangeBeginCall); - for (auto I = MachineBasicBlock::iterator(RangeBeginCall), - E = MBB->begin(); - I != E; --I) { - if (std::prev(I)->isDebugInstr() || std::prev(I)->isPosition()) - continue; - if (WebAssembly::isChild(*std::prev(I), MFI)) - AfterSet.insert(&*std::prev(I)); - else - break; - } - - // Create the nested try instruction. - auto InsertPos = getLatestInsertPos( - MBB, SmallPtrSet(), AfterSet); - MachineInstr *NestedTry = - BuildMI(*MBB, InsertPos, RangeBegin->getDebugLoc(), - TII.get(WebAssembly::TRY)) - .addImm(int64_t(WebAssembly::BlockType::Void)); - - // Create the nested EH pad and fill instructions in. - MachineBasicBlock *NestedEHPad = MF.CreateMachineBasicBlock(); - MF.insert(std::next(MBB->getIterator()), NestedEHPad); - NestedEHPad->setIsEHPad(); - NestedEHPad->setIsEHScopeEntry(); - BuildMI(NestedEHPad, RangeEnd->getDebugLoc(), TII.get(WebAssembly::CATCH), - ExnReg); - BuildMI(NestedEHPad, RangeEnd->getDebugLoc(), TII.get(WebAssembly::BR)) - .addMBB(BrDest); - - // Create the nested continuation BB and end_try instruction. - MachineBasicBlock *NestedCont = MF.CreateMachineBasicBlock(); - MF.insert(std::next(NestedEHPad->getIterator()), NestedCont); - MachineInstr *NestedEndTry = - BuildMI(*NestedCont, NestedCont->begin(), RangeEnd->getDebugLoc(), - TII.get(WebAssembly::END_TRY)); - // In case MBB has more instructions after the try range, move them to the - // new nested continuation BB. - NestedCont->splice(NestedCont->end(), MBB, - std::next(RangeEnd->getIterator()), MBB->end()); - unstackifyVRegsUsedInSplitBB(*MBB, *NestedCont, MFI, MRI, TII); - registerTryScope(NestedTry, NestedEndTry, NestedEHPad); - - // Fix predecessor-successor relationship. - NestedCont->transferSuccessors(MBB); - if (EHPad) { - NestedCont->removeSuccessor(EHPad); - // If EHPad does not have any predecessors left after removing - // NextedCont predecessor, remove its successor too, because this EHPad - // is not reachable from the entry BB anyway. We can't remove EHPad BB - // itself because it can contain 'catch' or 'end', which are necessary - // for keeping try-catch-end structure. - if (EHPad->pred_empty()) - EHPad->removeSuccessor(BrDest); - } - MBB->addSuccessor(NestedEHPad); - MBB->addSuccessor(NestedCont); - NestedEHPad->addSuccessor(BrDest); - } - } - - // Renumber BBs and recalculate ScopeTop info because new BBs might have been - // created and inserted above. - MF.RenumberBlocks(); - ScopeTops.clear(); - ScopeTops.resize(MF.getNumBlockIDs()); - for (auto &MBB : reverse(MF)) { - for (auto &MI : reverse(MBB)) { - if (ScopeTops[MBB.getNumber()]) - break; - switch (MI.getOpcode()) { - case WebAssembly::END_BLOCK: - case WebAssembly::END_LOOP: - case WebAssembly::END_TRY: - ScopeTops[MBB.getNumber()] = EndToBegin[&MI]->getParent(); - break; - case WebAssembly::CATCH: - ScopeTops[MBB.getNumber()] = EHPadToTry[&MBB]->getParent(); - break; - } - } - } - - // Recompute the dominator tree. - getAnalysis().runOnMachineFunction(MF); - - // Place block markers for newly added branches, if necessary. - - // If we've created an appendix BB and a branch to it, place a block/end_block - // marker for that. For some new branches, those branch destination BBs start - // with a hoisted end_try marker, so we don't need a new marker there. - if (AppendixBB) - BrDests.push_back(AppendixBB); - - llvm::sort(BrDests, - [&](const MachineBasicBlock *A, const MachineBasicBlock *B) { - auto ANum = A->getNumber(); - auto BNum = B->getNumber(); - return ANum < BNum; - }); - for (auto *Dest : BrDests) - placeBlockMarker(*Dest); - - return true; + // TODO Implement this + return false; } static unsigned diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISD.def b/llvm/lib/Target/WebAssembly/WebAssemblyISD.def --- a/llvm/lib/Target/WebAssembly/WebAssemblyISD.def +++ b/llvm/lib/Target/WebAssembly/WebAssemblyISD.def @@ -34,6 +34,7 @@ HANDLE_NODETYPE(WIDEN_HIGH_S) HANDLE_NODETYPE(WIDEN_HIGH_U) HANDLE_NODETYPE(THROW) +HANDLE_NODETYPE(CATCH) HANDLE_NODETYPE(MEMORY_COPY) HANDLE_NODETYPE(MEMORY_FILL) diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp @@ -267,6 +267,7 @@ // Exception handling intrinsics setOperationAction(ISD::INTRINSIC_WO_CHAIN, MVT::Other, Custom); + setOperationAction(ISD::INTRINSIC_W_CHAIN, MVT::Other, Custom); setOperationAction(ISD::INTRINSIC_VOID, MVT::Other, Custom); setMaxAtomicSizeInBitsSupported(64); @@ -1461,6 +1462,21 @@ MachinePointerInfo(SV)); } +static SDValue getCppExceptionSymNode(SDValue Op, unsigned TagIndex, + SelectionDAG &DAG) { + // We only support C++ exceptions for now + int Tag = + cast(Op.getOperand(TagIndex).getNode())->getZExtValue(); + if (Tag != WebAssembly::CPP_EXCEPTION) + llvm_unreachable("Invalid tag: We only support C++ exceptions for now"); + auto &MF = DAG.getMachineFunction(); + const auto &TLI = DAG.getTargetLoweringInfo(); + MVT PtrVT = TLI.getPointerTy(DAG.getDataLayout()); + const char *SymName = MF.createExternalSymbolName("__cpp_exception"); + return DAG.getNode(WebAssemblyISD::Wrapper, SDLoc(Op), PtrVT, + DAG.getTargetExternalSymbol(SymName, PtrVT)); +} + SDValue WebAssemblyTargetLowering::LowerIntrinsic(SDValue Op, SelectionDAG &DAG) const { MachineFunction &MF = DAG.getMachineFunction(); @@ -1494,15 +1510,7 @@ } case Intrinsic::wasm_throw: { - // We only support C++ exceptions for now - int Tag = cast(Op.getOperand(2).getNode())->getZExtValue(); - if (Tag != WebAssembly::CPP_EXCEPTION) - llvm_unreachable("Invalid tag!"); - 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)); + SDValue SymNode = getCppExceptionSymNode(Op, 2, DAG); return DAG.getNode(WebAssemblyISD::THROW, DL, MVT::Other, // outchain type { @@ -1512,6 +1520,19 @@ }); } + case Intrinsic::wasm_catch: { + SDValue SymNode = getCppExceptionSymNode(Op, 2, DAG); + return DAG.getNode(WebAssemblyISD::CATCH, DL, + { + MVT::i32, // outchain type + MVT::Other // return value + }, + { + Op.getOperand(0), // inchain + SymNode // exception symbol + }); + } + case Intrinsic::wasm_shuffle: { // Drop in-chain and replace undefs, but otherwise pass through unchanged SDValue Ops[18]; diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyInstrControl.td b/llvm/lib/Target/WebAssembly/WebAssemblyInstrControl.td --- a/llvm/lib/Target/WebAssembly/WebAssemblyInstrControl.td +++ b/llvm/lib/Target/WebAssembly/WebAssemblyInstrControl.td @@ -131,14 +131,11 @@ (outs), (ins event_op:$tag), [(WebAssemblythrow (WebAssemblywrapper texternalsym:$tag))], "throw \t$tag", "throw \t$tag", 0x08>; -defm RETHROW : I<(outs), (ins EXNREF:$exn), (outs), (ins), [], - "rethrow \t$exn", "rethrow", 0x09>; -// Pseudo instruction to be the lowering target of int_wasm_rethrow intrinsic. -// Will be converted to the real rethrow instruction later. -let isPseudo = 1 in -defm RETHROW_IN_CATCH : NRI<(outs), (ins), [(int_wasm_rethrow)], - "rethrow_in_catch", 0>; +defm RETHROW : NRI<(outs), (ins i32imm:$depth), [], "rethrow \t$depth", 0x09>; } // isTerminator = 1, hasCtrlDep = 1, isBarrier = 1 +// For C++ support, we only rethrow the latest exception, thus always setting +// the depth to 0. +def : Pat<(int_wasm_rethrow), (RETHROW 0)>; // Region within which an exception is caught: try / end_try let Uses = [VALUE_STACK], Defs = [VALUE_STACK] in { @@ -146,10 +143,18 @@ defm END_TRY : NRI<(outs), (ins), [], "end_try", 0x0b>; } // Uses = [VALUE_STACK], Defs = [VALUE_STACK] -// Catching an exception: catch / extract_exception -let hasCtrlDep = 1, hasSideEffects = 1 in -defm CATCH : I<(outs EXNREF:$dst), (ins), (outs), (ins), [], - "catch \t$dst", "catch", 0x07>; +// Catching an exception: catch / catch_all +let hasCtrlDep = 1, hasSideEffects = 1 in { +// TODO Currently 'catch' can only extract an i32, which is sufficient for C++ +// support, but according to the spec 'catch' can extract any number of values +// based on the event type. +defm CATCH : I<(outs I32:$dst), (ins event_op:$tag), + (outs), (ins event_op:$tag), + [(set I32:$dst, + (WebAssemblycatch (WebAssemblywrapper texternalsym:$tag)))], + "catch \t$dst, $tag", "catch \t$tag", 0x07>; +defm CATCH_ALL : NRI<(outs), (ins), [], "catch_all", 0x05>; +} // Querying / extracing exception: br_on_exn // br_on_exn queries an exnref to see if it matches the corresponding exception diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.td b/llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.td --- a/llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.td +++ b/llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.td @@ -81,7 +81,8 @@ SDTCisPtrTy<0>]>; def SDT_WebAssemblyWrapperPIC : SDTypeProfile<1, 1, [SDTCisSameAs<0, 1>, SDTCisPtrTy<0>]>; -def SDT_WebAssemblyThrow : SDTypeProfile<0, -1, [SDTCisPtrTy<0>]>; +def SDT_WebAssemblyThrow : SDTypeProfile<0, -1, []>; +def SDT_WebAssemblyCatch : SDTypeProfile<1, 1, [SDTCisPtrTy<0>]>; //===----------------------------------------------------------------------===// // WebAssembly-specific DAG Nodes. @@ -107,6 +108,8 @@ SDT_WebAssemblyWrapperPIC>; def WebAssemblythrow : SDNode<"WebAssemblyISD::THROW", SDT_WebAssemblyThrow, [SDNPHasChain, SDNPVariadic]>; +def WebAssemblycatch : SDNode<"WebAssemblyISD::CATCH", SDT_WebAssemblyCatch, + [SDNPHasChain, SDNPSideEffect]>; //===----------------------------------------------------------------------===// // WebAssembly-specific Operands. diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp @@ -33,10 +33,10 @@ bool runOnMachineFunction(MachineFunction &MF) override; void recordCatchRetBBs(MachineFunction &MF); - bool addCatches(MachineFunction &MF); + bool hoistCatches(MachineFunction &MF); + bool addCatchAlls(MachineFunction &MF); bool replaceFuncletReturns(MachineFunction &MF); bool removeUnnecessaryUnreachables(MachineFunction &MF); - bool addExceptionExtraction(MachineFunction &MF); bool restoreStackPointer(MachineFunction &MF); MachineBasicBlock *getMatchingEHPad(MachineInstr *MI); @@ -118,14 +118,13 @@ bool Changed = false; if (MF.getFunction().hasPersonalityFn()) { recordCatchRetBBs(MF); - Changed |= addCatches(MF); + Changed |= hoistCatches(MF); + Changed |= addCatchAlls(MF); Changed |= replaceFuncletReturns(MF); } Changed |= removeUnnecessaryUnreachables(MF); - if (MF.getFunction().hasPersonalityFn()) { - Changed |= addExceptionExtraction(MF); + if (MF.getFunction().hasPersonalityFn()) Changed |= restoreStackPointer(MF); - } return Changed; } @@ -144,20 +143,62 @@ } } -// Add catch instruction to beginning of catchpads and cleanuppads. -bool WebAssemblyLateEHPrepare::addCatches(MachineFunction &MF) { +// 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.getOpcode())) + Catches.push_back(&MI); + + for (auto *Catch : Catches) { + MachineBasicBlock *EHPad = getMatchingEHPad(Catch); + assert(EHPad && "No matching EH pad for catch"); + auto InsertPos = EHPad->begin(); + // Skip EH_LABELs in the beginning of an EH pad if present. We don't use + // these labels at the moment, but other targets also seem to have an + // EH_LABEL instruction in the beginning of an EH pad. + while (InsertPos != EHPad->end() && InsertPos->isEHLabel()) + InsertPos++; + if (InsertPos == Catch) + continue; + Changed = true; + EHPad->insert(InsertPos, Catch->removeFromParent()); + } + return Changed; +} + +// Add catch_all to beginning of cleanup pads. +bool WebAssemblyLateEHPrepare::addCatchAlls(MachineFunction &MF) { bool Changed = false; const auto &TII = *MF.getSubtarget().getInstrInfo(); - MachineRegisterInfo &MRI = MF.getRegInfo(); + for (auto &MBB : MF) { - if (MBB.isEHPad()) { + if (!MBB.isEHPad()) + continue; + auto InsertPos = MBB.begin(); + // Skip EH_LABELs in the beginning of an EH pad if present. + while (InsertPos != MBB.end() && InsertPos->isEHLabel()) + InsertPos++; + // This runs after hoistCatches(), so we assume that if there is a catch, + // that should be the non-EH label first instruction in an EH pad. + if (InsertPos == MBB.end() || + !WebAssembly::isCatch(InsertPos->getOpcode())) { Changed = true; - auto InsertPos = MBB.begin(); - if (InsertPos->isEHLabel()) // EH pad starts with an EH label - ++InsertPos; - Register DstReg = MRI.createVirtualRegister(&WebAssembly::EXNREFRegClass); - BuildMI(MBB, InsertPos, MBB.begin()->getDebugLoc(), - TII.get(WebAssembly::CATCH), DstReg); + BuildMI(MBB, InsertPos, InsertPos->getDebugLoc(), + TII.get(WebAssembly::CATCH_ALL)); } } return Changed; @@ -184,17 +225,11 @@ Changed = true; break; } - case WebAssembly::CLEANUPRET: - case WebAssembly::RETHROW_IN_CATCH: { - // Replace a cleanupret/rethrow_in_catch with a rethrow - auto *EHPad = getMatchingEHPad(TI); - auto CatchPos = EHPad->begin(); - if (CatchPos->isEHLabel()) // EH pad starts with an EH label - ++CatchPos; - MachineInstr *Catch = &*CatchPos; - Register ExnReg = Catch->getOperand(0).getReg(); + case WebAssembly::CLEANUPRET: { + // Replace a cleanupret with a rethrow. For C++ support, currently + // rethrow's immediate argument is always 0 (= the latest exception). BuildMI(MBB, TI, TI->getDebugLoc(), TII.get(WebAssembly::RETHROW)) - .addReg(ExnReg); + .addImm(0); TI->eraseFromParent(); Changed = true; break; @@ -230,156 +265,6 @@ return Changed; } -// Wasm uses 'br_on_exn' instruction to check the tag of an exception. It takes -// exnref 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) -// ... -// ;; exnref $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 exnref 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(); - MachineRegisterInfo &MRI = MF.getRegInfo(); - auto *EHInfo = MF.getWasmEHFuncInfo(); - SmallVector ExtractInstrs; - SmallVector ToDelete; - for (auto &MBB : MF) { - for (auto &MI : MBB) { - if (MI.getOpcode() == WebAssembly::EXTRACT_EXCEPTION_I32) { - if (MI.getOperand(0).isDead()) - ToDelete.push_back(&MI); - else - ExtractInstrs.push_back(&MI); - } - } - } - bool Changed = !ToDelete.empty() || !ExtractInstrs.empty(); - for (auto *MI : ToDelete) - MI->eraseFromParent(); - if (ExtractInstrs.empty()) - return Changed; - - // 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) - TerminatePads.insert(getMatchingEHPad(&MI)); - } - } - } - - for (auto *Extract : ExtractInstrs) { - MachineBasicBlock *EHPad = getMatchingEHPad(Extract); - assert(EHPad && "No matching EH pad for extract_exception"); - auto CatchPos = EHPad->begin(); - if (CatchPos->isEHLabel()) // EH pad starts with an EH label - ++CatchPos; - MachineInstr *Catch = &*CatchPos; - - if (Catch->getNextNode() != Extract) - EHPad->insert(Catch->getNextNode(), Extract->removeFromParent()); - - // - Before: - // ehpad: - // %exnref:exnref = catch - // %exn:i32 = extract_exception - // ... use exn ... - // - // - After: - // ehpad: - // %exnref:exnref = catch - // br_on_exn %thenbb, $__cpp_exception, %exnref - // br %elsebb - // elsebb: - // rethrow - // thenbb: - // %exn:i32 = extract_exception - // ... use exn ... - Register ExnReg = 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) - .addReg(ExnReg); - 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:exnref = catch - // %exn:i32 = extract_exception - // call @__clang_call_terminate(%exn) - // unreachable - // - // - After: - // ehpad: - // %exnref:exnref = 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"); - Register Reg = MRI.createVirtualRegister(&WebAssembly::I32RegClass); - BuildMI(ElseMBB, DL, TII.get(WebAssembly::CONST_I32), Reg).addImm(0); - BuildMI(ElseMBB, DL, TII.get(WebAssembly::CALL)) - .addGlobalAddress(ClangCallTerminateFn) - .addReg(Reg); - BuildMI(ElseMBB, DL, TII.get(WebAssembly::UNREACHABLE)); - - } else { - BuildMI(ElseMBB, DL, TII.get(WebAssembly::RETHROW)).addReg(ExnReg); - if (EHInfo->hasEHPadUnwindDest(EHPad)) - ElseMBB->addSuccessor(EHInfo->getEHPadUnwindDest(EHPad)); - } - } - - return true; -} - // After the stack is unwound due to a thrown exception, the __stack_pointer // global can point to an invalid address. This inserts instructions that // restore __stack_pointer global. @@ -404,7 +289,7 @@ auto InsertPos = MBB.begin(); if (InsertPos->isEHLabel()) // EH pad starts with an EH label ++InsertPos; - if (InsertPos->getOpcode() == WebAssembly::CATCH) + if (WebAssembly::isCatch(InsertPos->getOpcode())) ++InsertPos; FrameLowering->writeSPToGlobal(FrameLowering->getSPReg(MF), MF, MBB, InsertPos, MBB.begin()->getDebugLoc()); diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp @@ -359,10 +359,9 @@ if (NextI == Insert) return true; - // 'catch' and 'extract_exception' should be the first instruction of a BB and - // cannot move. - if (DefI->getOpcode() == WebAssembly::CATCH || - DefI->getOpcode() == WebAssembly::EXTRACT_EXCEPTION_I32) + // 'catch' and 'catch_all' should be the first instruction of a BB and cannot + // move. + if (WebAssembly::isCatch(DefI->getOpcode())) return false; // Check for register dependencies. diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp @@ -446,7 +446,8 @@ // Do various transformations for exception handling. // Every CFG-changing optimizations should come before this. - addPass(createWebAssemblyLateEHPrepare()); + if (TM->Options.ExceptionModel == ExceptionHandling::Wasm) + addPass(createWebAssemblyLateEHPrepare()); // Now that we have a prologue and epilogue and all frame indices are // rewritten, eliminate SP and FP. This allows them to be stackified, diff --git a/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.ll b/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.ll --- a/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.ll +++ b/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.ll @@ -1,15 +1,12 @@ ; REQUIRES: asserts ; TODO Reenable disabled lines after updating the backend to the new spec ; R UN: llc < %s -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -wasm-keep-registers -disable-block-placement -verify-machineinstrs -fast-isel=false -machine-sink-split-probability-threshold=0 -cgp-freq-ratio-to-skip-merge=1000 -exception-model=wasm -mattr=+exception-handling | FileCheck %s -; R UN: llc < %s -disable-wasm-fallthrough-return-opt -disable-block-placement -verify-machineinstrs -fast-isel=false -machine-sink-split-probability-threshold=0 -cgp-freq-ratio-to-skip-merge=1000 -exception-model=wasm -mattr=+exception-handling +; RUN: llc < %s -disable-wasm-fallthrough-return-opt -disable-block-placement -verify-machineinstrs -fast-isel=false -machine-sink-split-probability-threshold=0 -cgp-freq-ratio-to-skip-merge=1000 -exception-model=wasm -mattr=+exception-handling ; R UN: llc < %s -O0 -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -wasm-keep-registers -verify-machineinstrs -exception-model=wasm -mattr=+exception-handling | FileCheck %s --check-prefix=NOOPT ; R UN: llc < %s -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -wasm-keep-registers -disable-block-placement -verify-machineinstrs -fast-isel=false -machine-sink-split-probability-threshold=0 -cgp-freq-ratio-to-skip-merge=1000 -exception-model=wasm -mattr=+exception-handling -wasm-disable-ehpad-sort | FileCheck %s --check-prefix=NOSORT ; R UN: llc < %s -disable-wasm-fallthrough-return-opt -disable-block-placement -verify-machineinstrs -fast-isel=false -machine-sink-split-probability-threshold=0 -cgp-freq-ratio-to-skip-merge=1000 -exception-model=wasm -mattr=+exception-handling -wasm-disable-ehpad-sort | FileCheck %s --check-prefix=NOSORT-LOCALS ; R UN: llc < %s -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -wasm-keep-registers -disable-block-placement -verify-machineinstrs -fast-isel=false -machine-sink-split-probability-threshold=0 -cgp-freq-ratio-to-skip-merge=1000 -exception-model=wasm -mattr=+exception-handling -wasm-disable-ehpad-sort -stats 2>&1 | FileCheck %s --check-prefix=NOSORT-STAT -; FIXME A temporary RUN line to make the test pass. Remove this later. -; RUN: true - target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" target triple = "wasm32-unknown-unknown" diff --git a/llvm/test/CodeGen/WebAssembly/eh-labels.mir b/llvm/test/CodeGen/WebAssembly/eh-labels.mir --- a/llvm/test/CodeGen/WebAssembly/eh-labels.mir +++ b/llvm/test/CodeGen/WebAssembly/eh-labels.mir @@ -34,11 +34,10 @@ bb.1 (landing-pad): ; predecessors: %bb.0 successors: %bb.2 - ; CATCH should be after an EH_LABEL at the beginning of an EH pad + ; CATCH_ALL should be after an EH_LABEL at the beginning of an EH pad ; CHECK: EH_LABEL - ; CHECK-NEXT: CATCH + ; CHECK-NEXT: CATCH_ALL EH_LABEL - dead %0:i32 = EXTRACT_EXCEPTION_I32 implicit-def dead $arguments CATCHRET %bb.2, %bb.0, implicit-def dead $arguments bb.2: diff --git a/llvm/test/CodeGen/WebAssembly/eh-lsda.ll b/llvm/test/CodeGen/WebAssembly/eh-lsda.ll --- a/llvm/test/CodeGen/WebAssembly/eh-lsda.ll +++ b/llvm/test/CodeGen/WebAssembly/eh-lsda.ll @@ -1,8 +1,4 @@ -; TODO Reenable disabled lines after updating the backend to the new spec -; R UN: llc < %s -disable-wasm-fallthrough-return-opt -wasm-keep-registers -exception-model=wasm -mattr=+exception-handling | FileCheck -allow-deprecated-dag-overlap %s - -; FIXME A temporary RUN line to make the test pass. Remove this later. -; RUN: true +; RUN: llc < %s -disable-wasm-fallthrough-return-opt -wasm-keep-registers -exception-model=wasm -mattr=+exception-handling | FileCheck -allow-deprecated-dag-overlap %s target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" target triple = "wasm32-unknown-unknown" diff --git a/llvm/test/CodeGen/WebAssembly/exception.ll b/llvm/test/CodeGen/WebAssembly/exception.ll --- a/llvm/test/CodeGen/WebAssembly/exception.ll +++ b/llvm/test/CodeGen/WebAssembly/exception.ll @@ -1,9 +1,5 @@ -; TODO Reenable disabled lines after updating the backend to the new spec -; R UN: 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 -; R UN: llc < %s -disable-wasm-fallthrough-return-opt -wasm-keep-registers -exception-model=wasm -mattr=+exception-handling - -; FIXME A temporary RUN line to make the test pass. Remove this later. -; RUN: true +; 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" target triple = "wasm32-unknown-unknown" @@ -34,15 +30,9 @@ ; CHECK: global.get ${{.+}}=, __stack_pointer ; CHECK: try ; CHECK: call foo -; CHECK: catch $[[EXNREF:[0-9]+]]= +; CHECK: catch $[[EXN:[0-9]+]]=, __cpp_exception ; CHECK: global.set __stack_pointer -; CHECK: block i32 -; CHECK: br_on_exn 0, __cpp_exception, $[[EXNREF]] -; CHECK: rethrow $[[EXNREF]] -; CHECK: end_block -; CHECK: extract_exception $[[EXN:[0-9]+]]= -; CHECK-DAG: i32.store __wasm_lpad_context -; CHECK-DAG: i32.store __wasm_lpad_context{{.+}} +; CHECK: i32.{{store|const}} {{.*}} __wasm_lpad_context ; CHECK: call $drop=, _Unwind_CallPersonality, $[[EXN]] ; CHECK: block ; CHECK: br_if 0 @@ -50,7 +40,7 @@ ; CHECK: call __cxa_end_catch ; CHECK: br 1 ; CHECK: end_block -; CHECK: rethrow $[[EXNREF]] +; CHECK: rethrow 0 ; CHECK: end_try define void @test_catch() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) { entry: @@ -95,10 +85,10 @@ ; CHECK-LABEL: test_cleanup: ; CHECK: try ; CHECK: call foo -; CHECK: catch $[[EXNREF:[0-9]+]]= +; CHECK: catch_all ; CHECK: global.set __stack_pointer ; CHECK: call $drop=, _ZN4TempD2Ev -; CHECK: rethrow $[[EXNREF]] +; CHECK: rethrow 0 ; CHECK: end_try define void @test_cleanup() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) { entry: @@ -135,17 +125,11 @@ ; CHECK: call $drop=, __cxa_begin_catch ; CHECK: try ; CHECK: call foo -; CHECK: catch +; CHECK: catch_all ; CHECK: try ; CHECK: call __cxa_end_catch -; CHECK: catch -; CHECK: block i32 -; CHECK: br_on_exn 0, __cpp_exception -; CHECK: i32.const ${{.*}}=, 0 -; CHECK: call __clang_call_terminate -; CHECK: unreachable -; CHECK: end_block -; CHECK: call __clang_call_terminate +; CHECK: catch $[[EXN:[0-9]+]]=, __cpp_exception +; CHECK: call __clang_call_terminate, $[[EXN]] ; CHECK: unreachable ; CHECK: end_try ; CHECK: rethrow diff --git a/llvm/test/MC/WebAssembly/annotations.s b/llvm/test/MC/WebAssembly/annotations.s --- a/llvm/test/MC/WebAssembly/annotations.s +++ b/llvm/test/MC/WebAssembly/annotations.s @@ -11,7 +11,7 @@ .eventtype __cpp_exception i32 try br 0 - catch + catch __cpp_exception block br_if 0 loop @@ -19,21 +19,16 @@ end_loop end_block try - rethrow - catch + rethrow 0 + catch __cpp_exception block try br 0 - catch + catch __cpp_exception local.set 0 - block i32 - local.get 0 - br_on_exn 0, __cpp_exception - rethrow - end_block end_try end_block - rethrow + rethrow 0 end_try end_try end_function @@ -42,7 +37,7 @@ # CHECK: test_annotation: # CHECK: try # CHECK-NEXT: br 0 # 0: down to label0 -# CHECK-NEXT: catch # catch0: +# CHECK-NEXT: catch __cpp_exception # catch0: # CHECK-NEXT: block # CHECK-NEXT: br_if 0 # 0: down to label1 # CHECK-NEXT: loop # label2: @@ -50,21 +45,16 @@ # CHECK-NEXT: end_loop # CHECK-NEXT: end_block # label1: # CHECK-NEXT: try -# CHECK-NEXT: rethrow # down to catch1 -# CHECK-NEXT: catch # catch1: +# CHECK-NEXT: rethrow 0 # down to catch1 +# CHECK-NEXT: catch __cpp_exception # catch1: # CHECK-NEXT: block # CHECK-NEXT: try # CHECK-NEXT: br 0 # 0: down to label5 -# CHECK-NEXT: catch # catch2: +# CHECK-NEXT: catch __cpp_exception # catch2: # CHECK-NEXT: local.set 0 -# CHECK-NEXT: block i32 -# CHECK-NEXT: local.get 0 -# CHECK-NEXT: br_on_exn 0, __cpp_exception # 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: rethrow 0 # 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 --- a/llvm/test/MC/WebAssembly/basic-assembly.s +++ b/llvm/test/MC/WebAssembly/basic-assembly.s @@ -81,24 +81,18 @@ # TODO: enable once instruction has been added. #i32x4.trunc_sat_f32x4_s i32.trunc_f32_s - try exnref + try i32.atomic.load 0 memory.atomic.notify 0 .LBB0_3: - catch + catch __cpp_exception local.set 0 - block i32 - local.get 0 - br_on_exn 0, __cpp_exception - rethrow -.LBB0_4: - end_block end_try i32.const .L.str i32.load8_u .L.str+2 i32.load16_u .L.str:p2align=0 throw 0 -.LBB0_5: +.LBB0_4: #i32.trunc_sat_f32_s global.get __stack_pointer end_function @@ -199,24 +193,18 @@ # CHECK-NEXT: end_if # CHECK-NEXT: f32x4.add # CHECK-NEXT: i32.trunc_f32_s -# CHECK-NEXT: try exnref +# CHECK-NEXT: try # CHECK-NEXT: i32.atomic.load 0 # CHECK-NEXT: memory.atomic.notify 0 # CHECK-NEXT: .LBB0_3: -# CHECK-NEXT: catch +# CHECK-NEXT: catch __cpp_exception # CHECK-NEXT: local.set 0 -# CHECK-NEXT: block i32 -# CHECK-NEXT: local.get 0 -# CHECK-NEXT: br_on_exn 0, __cpp_exception -# CHECK-NEXT: rethrow -# CHECK-NEXT: .LBB0_4: -# CHECK-NEXT: end_block # CHECK-NEXT: end_try # CHECK-NEXT: i32.const .L.str # CHECK-NEXT: i32.load8_u .L.str+2 # CHECK-NEXT: i32.load16_u .L.str:p2align=0 # CHECK-NEXT: throw 0 -# CHECK-NEXT: .LBB0_5: +# CHECK-NEXT: .LBB0_4: # CHECK-NEXT: global.get __stack_pointer # CHECK-NEXT: end_function diff --git a/llvm/unittests/Target/WebAssembly/WebAssemblyExceptionInfoTest.cpp b/llvm/unittests/Target/WebAssembly/WebAssemblyExceptionInfoTest.cpp --- a/llvm/unittests/Target/WebAssembly/WebAssemblyExceptionInfoTest.cpp +++ b/llvm/unittests/Target/WebAssembly/WebAssemblyExceptionInfoTest.cpp @@ -100,14 +100,14 @@ ; predecessors: %bb.0 successors: %bb.3, %bb.9 liveins: $value_stack - %0:exnref = CATCH implicit-def $arguments - CLEANUPRET implicit-def dead $arguments + CATCH_ALL implicit-def $arguments + RETHROW 0, implicit-def dead $arguments bb.3 (landing-pad): ; predecessors: %bb.2 successors: %bb.4, %bb.6 liveins: $value_stack - %1:exnref = CATCH implicit-def $arguments + %1:i32 = CATCH &__cpp_exception, 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 @@ -121,7 +121,7 @@ ; predecessors: %bb.4 successors: %bb.7 liveins: $value_stack - CATCHRET %bb.7, %bb.0, implicit-def dead $arguments + BR %bb.7, implicit-def dead $arguments bb.6: ; predecessors: %bb.3 @@ -138,14 +138,14 @@ ; predecessors: %bb.4 successors: %bb.9 liveins: $value_stack - %2:exnref = CATCH implicit-def $arguments - CLEANUPRET implicit-def dead $arguments + CATCH_ALL implicit-def $arguments + RETHROW 0, implicit-def dead $arguments bb.9 (landing-pad): ; predecessors: %bb.2, %bb.6, %bb.8 liveins: $value_stack - %3:exnref = CATCH implicit-def $arguments - CLEANUPRET implicit-def dead $arguments + CATCH_ALL implicit-def $arguments + RETHROW 0, implicit-def dead $arguments bb.10: ; predecessors: %bb.6 @@ -257,7 +257,7 @@ ; predecessors: %bb.0 successors: %bb.2, %bb.8 liveins: $value_stack - %0:exnref = CATCH implicit-def $arguments + %0:i32 = CATCH &__cpp_exception, 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 @@ ; predecessors: %bb.2 successors: %bb.4, %bb.6 liveins: $value_stack - %1:exnref = CATCH implicit-def $arguments + %1:i32 = CATCH &__cpp_exception, 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 @@ -285,7 +285,7 @@ ; predecessors: %bb.4 successors: %bb.7(0x80000000); %bb.7(200.00%) liveins: $value_stack - CATCHRET %bb.7, %bb.1, implicit-def dead $arguments + BR %bb.7, implicit-def dead $arguments bb.6: ; predecessors: %bb.3 @@ -297,7 +297,7 @@ ; predecessors: %bb.2, %bb.5 successors: %bb.9(0x80000000); %bb.9(200.00%) liveins: $value_stack - CATCHRET %bb.9, %bb.0, implicit-def dead $arguments + BR %bb.9, implicit-def dead $arguments bb.8: ; predecessors: %bb.1 @@ -313,14 +313,14 @@ ; predecessors: %bb.4 successors: %bb.11 liveins: $value_stack - %2:exnref = CATCH implicit-def $arguments - CLEANUPRET implicit-def dead $arguments + CATCH_ALL implicit-def $arguments + RETHROW 0, implicit-def dead $arguments bb.11 (landing-pad): ; predecessors: %bb.2, %bb.6, %bb.10 liveins: $value_stack - %3:exnref = CATCH implicit-def $arguments - CLEANUPRET implicit-def dead $arguments + CATCH_ALL implicit-def $arguments + RETHROW 0, implicit-def dead $arguments bb.12: ; predecessors: %bb.6