| //=== WebAssemblyLateEHPrepare.cpp - WebAssembly Exception Preparation -===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| /// |
| /// \file |
| /// \brief Does various transformations for exception handling. |
| /// |
| //===----------------------------------------------------------------------===// |
| |
| #include "MCTargetDesc/WebAssemblyMCTargetDesc.h" |
| #include "WebAssembly.h" |
| #include "WebAssemblySubtarget.h" |
| #include "WebAssemblyUtilities.h" |
| #include "llvm/ADT/SmallSet.h" |
| #include "llvm/CodeGen/MachineInstrBuilder.h" |
| #include "llvm/CodeGen/WasmEHFuncInfo.h" |
| #include "llvm/MC/MCAsmInfo.h" |
| #include "llvm/Support/Debug.h" |
| using namespace llvm; |
| |
| #define DEBUG_TYPE "wasm-late-eh-prepare" |
| |
| namespace { |
| class WebAssemblyLateEHPrepare final : public MachineFunctionPass { |
| StringRef getPassName() const override { |
| return "WebAssembly Late Prepare Exception"; |
| } |
| |
| bool runOnMachineFunction(MachineFunction &MF) override; |
| bool addCatches(MachineFunction &MF); |
| bool replaceFuncletReturns(MachineFunction &MF); |
| bool removeUnnecessaryUnreachables(MachineFunction &MF); |
| bool addExceptionExtraction(MachineFunction &MF); |
| bool restoreStackPointer(MachineFunction &MF); |
| |
| public: |
| static char ID; // Pass identification, replacement for typeid |
| WebAssemblyLateEHPrepare() : MachineFunctionPass(ID) {} |
| }; |
| } // end anonymous namespace |
| |
| char WebAssemblyLateEHPrepare::ID = 0; |
| INITIALIZE_PASS(WebAssemblyLateEHPrepare, DEBUG_TYPE, |
| "WebAssembly Late Exception Preparation", false, false) |
| |
| FunctionPass *llvm::createWebAssemblyLateEHPrepare() { |
| return new WebAssemblyLateEHPrepare(); |
| } |
| |
| // Returns the nearest EH pad that dominates this instruction. This does not use |
| // dominator analysis; it just does BFS on its predecessors until arriving at an |
| // EH pad. This assumes valid EH scopes so the first EH pad it arrives in all |
| // possible search paths should be the same. |
| // Returns nullptr in case it does not find any EH pad in the search, or finds |
| // multiple different EH pads. |
| static MachineBasicBlock *getMatchingEHPad(MachineInstr *MI) { |
| MachineFunction *MF = MI->getParent()->getParent(); |
| SmallVector<MachineBasicBlock *, 2> WL; |
| SmallPtrSet<MachineBasicBlock *, 2> Visited; |
| WL.push_back(MI->getParent()); |
| MachineBasicBlock *EHPad = nullptr; |
| while (!WL.empty()) { |
| MachineBasicBlock *MBB = WL.pop_back_val(); |
| if (Visited.count(MBB)) |
| continue; |
| Visited.insert(MBB); |
| if (MBB->isEHPad()) { |
| if (EHPad && EHPad != MBB) |
| return nullptr; |
| EHPad = MBB; |
| continue; |
| } |
| if (MBB == &MF->front()) |
| return nullptr; |
| WL.append(MBB->pred_begin(), MBB->pred_end()); |
| } |
| return EHPad; |
| } |
| |
| // Erase the specified BBs if the BB does not have any remaining predecessors, |
| // and also all its dead children. |
| template <typename Container> |
| static void eraseDeadBBsAndChildren(const Container &MBBs) { |
| SmallVector<MachineBasicBlock *, 8> WL(MBBs.begin(), MBBs.end()); |
| while (!WL.empty()) { |
| MachineBasicBlock *MBB = WL.pop_back_val(); |
| if (!MBB->pred_empty()) |
| continue; |
| SmallVector<MachineBasicBlock *, 4> Succs(MBB->succ_begin(), |
| MBB->succ_end()); |
| WL.append(MBB->succ_begin(), MBB->succ_end()); |
| for (auto *Succ : Succs) |
| MBB->removeSuccessor(Succ); |
| MBB->eraseFromParent(); |
| } |
| } |
| |
| bool WebAssemblyLateEHPrepare::runOnMachineFunction(MachineFunction &MF) { |
| LLVM_DEBUG(dbgs() << "********** Late EH Prepare **********\n" |
| "********** Function: " |
| << MF.getName() << '\n'); |
| |
| if (MF.getTarget().getMCAsmInfo()->getExceptionHandlingType() != |
| ExceptionHandling::Wasm) |
| return false; |
| |
| bool Changed = false; |
| if (MF.getFunction().hasPersonalityFn()) { |
| Changed |= addCatches(MF); |
| Changed |= replaceFuncletReturns(MF); |
| } |
| Changed |= removeUnnecessaryUnreachables(MF); |
| if (MF.getFunction().hasPersonalityFn()) { |
| Changed |= addExceptionExtraction(MF); |
| Changed |= restoreStackPointer(MF); |
| } |
| return Changed; |
| } |
| |
| // Add catch instruction to beginning of catchpads and cleanuppads. |
| bool WebAssemblyLateEHPrepare::addCatches(MachineFunction &MF) { |
| bool Changed = false; |
| const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo(); |
| MachineRegisterInfo &MRI = MF.getRegInfo(); |
| for (auto &MBB : MF) { |
| if (MBB.isEHPad()) { |
| 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); |
| } |
| } |
| return Changed; |
| } |
| |
| bool WebAssemblyLateEHPrepare::replaceFuncletReturns(MachineFunction &MF) { |
| bool Changed = false; |
| const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo(); |
| |
| for (auto &MBB : MF) { |
| auto Pos = MBB.getFirstTerminator(); |
| if (Pos == MBB.end()) |
| continue; |
| MachineInstr *TI = &*Pos; |
| |
| switch (TI->getOpcode()) { |
| case WebAssembly::CATCHRET: { |
| // Replace a catchret with a branch |
| MachineBasicBlock *TBB = TI->getOperand(0).getMBB(); |
| if (!MBB.isLayoutSuccessor(TBB)) |
| BuildMI(MBB, TI, TI->getDebugLoc(), TII.get(WebAssembly::BR)) |
| .addMBB(TBB); |
| TI->eraseFromParent(); |
| 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(); |
| BuildMI(MBB, TI, TI->getDebugLoc(), TII.get(WebAssembly::RETHROW)) |
| .addReg(ExnReg); |
| TI->eraseFromParent(); |
| Changed = true; |
| break; |
| } |
| } |
| } |
| return Changed; |
| } |
| |
| bool WebAssemblyLateEHPrepare::removeUnnecessaryUnreachables( |
| MachineFunction &MF) { |
| bool Changed = false; |
| for (auto &MBB : MF) { |
| for (auto &MI : MBB) { |
| if (MI.getOpcode() != WebAssembly::THROW && |
| MI.getOpcode() != WebAssembly::RETHROW) |
| continue; |
| Changed = true; |
| |
| // The instruction after the throw should be an unreachable or a branch to |
| // another BB that should eventually lead to an unreachable. Delete it |
| // because throw itself is a terminator, and also delete successors if |
| // any. |
| MBB.erase(std::next(MI.getIterator()), MBB.end()); |
| SmallVector<MachineBasicBlock *, 8> Succs(MBB.succ_begin(), |
| MBB.succ_end()); |
| for (auto *Succ : Succs) |
| if (!Succ->isEHPad()) |
| MBB.removeSuccessor(Succ); |
| eraseDeadBBsAndChildren(Succs); |
| } |
| } |
| |
| 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_VOID)) |
| .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. |
| bool WebAssemblyLateEHPrepare::restoreStackPointer(MachineFunction &MF) { |
| const auto *FrameLowering = static_cast<const WebAssemblyFrameLowering *>( |
| MF.getSubtarget().getFrameLowering()); |
| if (!FrameLowering->needsPrologForEH(MF)) |
| return false; |
| bool Changed = false; |
| |
| for (auto &MBB : MF) { |
| if (!MBB.isEHPad()) |
| continue; |
| Changed = true; |
| |
| // Insert __stack_pointer restoring instructions at the beginning of each EH |
| // pad, after the catch instruction. Here it is safe to assume that SP32 |
| // holds the latest value of __stack_pointer, because the only exception for |
| // this case is when a function uses the red zone, but that only happens |
| // with leaf functions, and we don't restore __stack_pointer in leaf |
| // functions anyway. |
| auto InsertPos = MBB.begin(); |
| if (InsertPos->isEHLabel()) // EH pad starts with an EH label |
| ++InsertPos; |
| if (InsertPos->getOpcode() == WebAssembly::CATCH) |
| ++InsertPos; |
| FrameLowering->writeSPToGlobal(WebAssembly::SP32, MF, MBB, InsertPos, |
| MBB.begin()->getDebugLoc()); |
| } |
| return Changed; |
| } |