summaryrefslogtreecommitdiff
path: root/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp
diff options
context:
space:
mode:
authorHeejin Ahn <aheejin@gmail.com>2020-12-26 02:27:44 -0800
committerHeejin Ahn <aheejin@gmail.com>2021-01-09 01:48:06 -0800
commit9e4eadeb135d140b3a9e499354472170017cbe58 (patch)
tree29bcde8192815ce0073bfa32ccbfa97fb9268514 /llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp
parent052b8fe478194018944eac944af7ca853ec3ced7 (diff)
[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
Diffstat (limited to 'llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp')
-rw-r--r--llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp521
1 files changed, 12 insertions, 509 deletions
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp
index 5eafeb70c02a..0f4379e66ca4 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp
@@ -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