1
0
mirror of https://github.com/RPCS3/llvm-mirror.git synced 2024-10-19 02:52:53 +02:00

[WebAssembly] Update basic EH instructions for the new spec

This implements basic instructions for the new spec.

- Adds new versions of instructions: `catch`, `catch_all`, and `rethrow`
- Adds support for instruction selection for the new instructions
 - `catch` needs a custom routine for the same reason `throw` needs one,
   to encode `__cpp_exception` tag symbol.
- Updates `WebAssembly::isCatch` utility function to include `catch_all`
  and Change code that compares an instruction's opcode with `catch` to
  use that function.
- LateEHPrepare
  - Previously in LateEHPrepare we added `catch` instruction to both
    `catchpad`s (for user catches) and `cleanuppad`s (for destructors).
    In the new version `catch` is generated from `llvm.catch` intrinsic
    in instruction selection phase, so we only need to add `catch_all`
    to the beginning of cleanup pads.
  - `catch` is generated from instruction selection, but we need to
    hoist the `catch` instruction to the beginning of every EH pad,
    because `catch` can be in the middle of the EH pad or even in a
    split BB from it after various code transformations.
  - Removes `addExceptionExtraction` function, which was used to
    generate `br_on_exn` before.
- CFGStackfiy: Deletes `fixUnwindMismatches` function. Running this
  function on the new instruction causes crashes, and the new version
  will be added in a later CL, whose contents will be completely
  different. So deleting the whole function will make the diff easier to
  read.
- Reenables all disabled tests in exception.ll and eh-lsda.ll and a
  single basic test in cfg-stackify-eh.ll.
- Updates existing tests to use the new assembly format. And deletes
  `br_on_exn` instructions from the tests and FileCheck lines.

Reviewed By: dschuff, tlively

Differential Revision: https://reviews.llvm.org/D94040
This commit is contained in:
Heejin Ahn 2020-12-26 02:27:44 -08:00
parent fe59463068
commit 3675555b63
17 changed files with 214 additions and 808 deletions

View File

@ -1637,10 +1637,32 @@ void SelectionDAGBuilder::visitCleanupPad(const CleanupPadInst &CPI) {
}
}
// 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,

View File

@ -440,6 +440,18 @@ inline bool isMarker(unsigned Opc) {
}
}
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

View File

@ -183,8 +183,7 @@ static MachineInstr *findCatch(MachineBasicBlock *EHPad, Register &ExnReg) {
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 @@ static unsigned getCopyOpcode(const TargetRegisterClass *RC) {
// 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 @@ static void unstackifyVRegsUsedInSplitBB(MachineBasicBlock &MBB,
}
bool WebAssemblyCFGStackify::fixUnwindMismatches(MachineFunction &MF) {
const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
auto &MFI = *MF.getInfo<WebAssemblyFunctionInfo>();
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<const MachineBasicBlock *, 8> EHPadStack;
// Range of intructions to be wrapped in a new nested try/catch
using TryRange = std::pair<MachineInstr *, MachineInstr *>;
// In original CFG, <unwind destination BB, a vector of try ranges>
DenseMap<MachineBasicBlock *, SmallVector<TryRange, 4>> UnwindDestToTryRanges;
// In new CFG, <destination to branch to, a vector of try ranges>
DenseMap<MachineBasicBlock *, SmallVector<TryRange, 4>> BrDestToTryRanges;
// In new CFG, <destination to branch to, register containing exnref>
DenseMap<MachineBasicBlock *, unsigned> BrDestToExnReg;
// Destinations for branches that will be newly added, for which a new
// BLOCK/END_BLOCK markers are necessary.
SmallVector<MachineBasicBlock *, 8> 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<MachineOperand, 4> 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<const MachineInstr *, 4> 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<const MachineInstr *, 4>(), 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<MachineDominatorTree>().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

View File

@ -34,6 +34,7 @@ HANDLE_NODETYPE(WIDEN_LOW_U)
HANDLE_NODETYPE(WIDEN_HIGH_S)
HANDLE_NODETYPE(WIDEN_HIGH_U)
HANDLE_NODETYPE(THROW)
HANDLE_NODETYPE(CATCH)
HANDLE_NODETYPE(MEMORY_COPY)
HANDLE_NODETYPE(MEMORY_FILL)

View File

@ -267,6 +267,7 @@ WebAssemblyTargetLowering::WebAssemblyTargetLowering(
// 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 @@ SDValue WebAssemblyTargetLowering::LowerVASTART(SDValue Op,
MachinePointerInfo(SV));
}
static SDValue getCppExceptionSymNode(SDValue Op, unsigned TagIndex,
SelectionDAG &DAG) {
// We only support C++ exceptions for now
int Tag =
cast<ConstantSDNode>(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 @@ SDValue WebAssemblyTargetLowering::LowerIntrinsic(SDValue Op,
}
case Intrinsic::wasm_throw: {
// We only support C++ exceptions for now
int Tag = cast<ConstantSDNode>(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 @@ SDValue WebAssemblyTargetLowering::LowerIntrinsic(SDValue Op,
});
}
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];

View File

@ -131,14 +131,11 @@ 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 : 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 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 / 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

View File

@ -81,7 +81,8 @@ def SDT_WebAssemblyWrapper : SDTypeProfile<1, 1, [SDTCisSameAs<0, 1>,
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 @@ def WebAssemblywrapperPIC : SDNode<"WebAssemblyISD::WrapperPIC",
SDT_WebAssemblyWrapperPIC>;
def WebAssemblythrow : SDNode<"WebAssemblyISD::THROW", SDT_WebAssemblyThrow,
[SDNPHasChain, SDNPVariadic]>;
def WebAssemblycatch : SDNode<"WebAssemblyISD::CATCH", SDT_WebAssemblyCatch,
[SDNPHasChain, SDNPSideEffect]>;
//===----------------------------------------------------------------------===//
// WebAssembly-specific Operands.

View File

@ -33,10 +33,10 @@ class WebAssemblyLateEHPrepare final : public MachineFunctionPass {
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 WebAssemblyLateEHPrepare::runOnMachineFunction(MachineFunction &MF) {
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 @@ void WebAssemblyLateEHPrepare::recordCatchRetBBs(MachineFunction &MF) {
}
}
// 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<MachineInstr *, 16> 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<WebAssemblySubtarget>().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 @@ bool WebAssemblyLateEHPrepare::replaceFuncletReturns(MachineFunction &MF) {
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 @@ bool WebAssemblyLateEHPrepare::removeUnnecessaryUnreachables(
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<WebAssemblySubtarget>().getInstrInfo();
MachineRegisterInfo &MRI = MF.getRegInfo();
auto *EHInfo = MF.getWasmEHFuncInfo();
SmallVector<MachineInstr *, 16> ExtractInstrs;
SmallVector<MachineInstr *, 8> 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<MachineBasicBlock *, 8> TerminatePads;
for (auto &MBB : MF) {
for (auto &MI : MBB) {
if (MI.isCall()) {
const MachineOperand &CalleeOp = MI.getOperand(0);
if (CalleeOp.isGlobal() && CalleeOp.getGlobal()->getName() ==
WebAssembly::ClangCallTerminateFn)
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 @@ bool WebAssemblyLateEHPrepare::restoreStackPointer(MachineFunction &MF) {
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());

View File

@ -359,10 +359,9 @@ static bool isSafeToMove(const MachineOperand *Def, const MachineOperand *Use,
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.

View File

@ -446,7 +446,8 @@ void WebAssemblyPassConfig::addPreEmitPass() {
// 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,

View File

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

View File

@ -34,11 +34,10 @@ body: |
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 <mcsymbol .Ltmp2>
dead %0:i32 = EXTRACT_EXCEPTION_I32 implicit-def dead $arguments
CATCHRET %bb.2, %bb.0, implicit-def dead $arguments
bb.2:

View File

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

View File

@ -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 @@ define void @test_throw(i8* %p) {
; 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 @@ define void @test_throw(i8* %p) {
; 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 @@ try.cont: ; preds = %catch, %entry
; 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 @@ ehcleanup: ; preds = %entry
; 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

View File

@ -11,7 +11,7 @@ test_annotation:
.eventtype __cpp_exception i32
try
br 0
catch
catch __cpp_exception
block
br_if 0
loop
@ -19,21 +19,16 @@ test_annotation:
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 @@ test_annotation:
# 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 @@ test_annotation:
# 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

View File

@ -81,24 +81,18 @@ test0:
# 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 @@ empty_fref_table:
# 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

View File

@ -100,14 +100,14 @@ body: |
; 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 @@ body: |
; 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 @@ body: |
; 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 @@ body: |
; 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 @@ body: |
; 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 @@ body: |
; 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 @@ body: |
; 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 @@ body: |
; 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