forked from OSchip/llvm-project
[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:
parent
052b8fe478
commit
9e4eadeb13
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue