diff options
| author | Heejin Ahn <aheejin@gmail.com> | 2020-12-26 02:27:44 -0800 |
|---|---|---|
| committer | Heejin Ahn <aheejin@gmail.com> | 2021-01-09 01:48:06 -0800 |
| commit | 9e4eadeb135d140b3a9e499354472170017cbe58 (patch) | |
| tree | 29bcde8192815ce0073bfa32ccbfa97fb9268514 /llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp | |
| parent | 052b8fe478194018944eac944af7ca853ec3ced7 (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.cpp | 521 |
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 |
