diff --git a/llvm/lib/Target/WebAssembly/CMakeLists.txt b/llvm/lib/Target/WebAssembly/CMakeLists.txt --- a/llvm/lib/Target/WebAssembly/CMakeLists.txt +++ b/llvm/lib/Target/WebAssembly/CMakeLists.txt @@ -22,6 +22,7 @@ WebAssemblyCFGSort.cpp WebAssemblyDebugFixup.cpp WebAssemblyDebugValueManager.cpp + WebAssemblyHandleEHTerminatePads.cpp WebAssemblyLateEHPrepare.cpp WebAssemblyExceptionInfo.cpp WebAssemblyExplicitLocals.cpp diff --git a/llvm/lib/Target/WebAssembly/WebAssembly.h b/llvm/lib/Target/WebAssembly/WebAssembly.h --- a/llvm/lib/Target/WebAssembly/WebAssembly.h +++ b/llvm/lib/Target/WebAssembly/WebAssembly.h @@ -49,6 +49,7 @@ FunctionPass *createWebAssemblyLateEHPrepare(); FunctionPass *createWebAssemblyCFGSort(); FunctionPass *createWebAssemblyCFGStackify(); +FunctionPass *createWebAssemblyHandleEHTerminatePads(); FunctionPass *createWebAssemblyExplicitLocals(); FunctionPass *createWebAssemblyLowerBrUnless(); FunctionPass *createWebAssemblyRegNumbering(); @@ -75,6 +76,7 @@ void initializeWebAssemblyExceptionInfoPass(PassRegistry &); void initializeWebAssemblyCFGSortPass(PassRegistry &); void initializeWebAssemblyCFGStackifyPass(PassRegistry &); +void initializeWebAssemblyHandleEHTerminatePadsPass(PassRegistry &); void initializeWebAssemblyExplicitLocalsPass(PassRegistry &); void initializeWebAssemblyLowerBrUnlessPass(PassRegistry &); void initializeWebAssemblyRegNumberingPass(PassRegistry &); diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyHandleEHTerminatePads.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyHandleEHTerminatePads.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/Target/WebAssembly/WebAssemblyHandleEHTerminatePads.cpp @@ -0,0 +1,152 @@ +// WebAssemblyHandleEHTerminatePads.cpp - WebAssembly Handle EH TerminatePads // +// +// 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 Add catch_all blocks to terminate pads. +/// +/// Terminate pads are cleanup pads with a __clang_call_terminate call. These +/// are reached when an exception is thrown again in the middle of processing a +/// thrown exception, to terminate the program. These are cleanup pads that +/// should run regardless whether the thrown exception is a C++ exception or +/// not. +/// +/// Because __clang_call_terminate takes an exception pointer, and +/// llvm.get.exception intrinsic is selected to 'catch' instruction in +/// instruction selection, terminate pads have a catch instruction and are in +/// this form after LateEHPrepare, even though they are cleanup pads: +/// termpad: +/// %exn = catch $__cpp_exception +/// call @__clang_call_terminate(%exn) +/// unreachable +/// +/// This pass assumes LateEHPrepare ensured every terminate pad is a single +/// BB. +/// +/// __clang_call_terminate is a function generated by clang, in the form of +/// void __clang_call_terminate(i8* %arg) { +/// call @__cxa_begin_catch(%arg) +/// call void @std::terminate() +/// unreachable +/// } +/// +/// To make the terminate pads reachable when a foreign exception is thrown, +/// this pass attaches an additional catch_all BB after this catch terminate pad +/// BB, with a call to std::terminate, because foreign exceptions don't have a +/// valid exception pointer to call __cxa_begin_catch with. So the code example +/// becomes: +/// termpad: +/// %exn = catch $__cpp_exception +/// call @__clang_call_terminate(%exn) +/// unreachable +/// termpad-catchall: +/// catch_all +/// call @std::terminate() +/// unreachable +/// +/// We do this at the very end of compilation pipeline, even after CFGStackify, +/// because even though wasm spec allows multiple catch/catch_all blocks per a +/// try instruction, it has been convenient to maintain the invariant so far +/// that there has been only a single catch or catch_all attached to a try. This +/// assumption makes ExceptionInfo generation and CFGStackify simpler, because +/// we have been always able to assume an EH pad is an end of try block and a +/// start of catch/catch_all block. +//===----------------------------------------------------------------------===// + +#include "MCTargetDesc/WebAssemblyMCTargetDesc.h" +#include "WebAssembly.h" +#include "WebAssemblySubtarget.h" +#include "WebAssemblyUtilities.h" +#include "llvm/CodeGen/MachineModuleInfo.h" +#include "llvm/MC/MCAsmInfo.h" +#include "llvm/Target/TargetMachine.h" +using namespace llvm; + +#define DEBUG_TYPE "wasm-handle-termpads" + +namespace { +class WebAssemblyHandleEHTerminatePads final : public MachineFunctionPass { + StringRef getPassName() const override { + return "WebAssembly Handle EH Terminate Pads"; + } + + bool runOnMachineFunction(MachineFunction &MF) override; + +public: + static char ID; // Pass identification, replacement for typeid + WebAssemblyHandleEHTerminatePads() : MachineFunctionPass(ID) {} +}; +} // end anonymous namespace + +char WebAssemblyHandleEHTerminatePads::ID = 0; +INITIALIZE_PASS(WebAssemblyHandleEHTerminatePads, DEBUG_TYPE, + "WebAssembly Handle EH Terminate Pads", false, false) + +FunctionPass *llvm::createWebAssemblyHandleEHTerminatePads() { + return new WebAssemblyHandleEHTerminatePads(); +} + +bool WebAssemblyHandleEHTerminatePads::runOnMachineFunction( + MachineFunction &MF) { + LLVM_DEBUG(dbgs() << "********** Handle EH Terminate Pads **********\n" + "********** Function: " + << MF.getName() << '\n'); + + if (MF.getTarget().getMCAsmInfo()->getExceptionHandlingType() != + ExceptionHandling::Wasm || + !MF.getFunction().hasPersonalityFn()) + return false; + + const auto &TII = *MF.getSubtarget().getInstrInfo(); + + // Find calls to __clang_call_terminate() + SmallVector ClangCallTerminateCalls; + 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) + ClangCallTerminateCalls.push_back(&MI); + } + } + } + + if (ClangCallTerminateCalls.empty()) + return false; + + for (auto *Call : ClangCallTerminateCalls) { + // This should be an EH pad because LateEHPrepare ensures terminate pads are + // a single BB. + MachineBasicBlock *CatchBB = Call->getParent(); + assert(CatchBB->isEHPad()); + + auto *CatchAllBB = MF.CreateMachineBasicBlock(); + MF.insert(std::next(CatchBB->getIterator()), CatchAllBB); + CatchAllBB->setIsEHPad(true); + for (auto *Pred : CatchBB->predecessors()) + Pred->addSuccessor(CatchAllBB); + + // If the definition of __clang_call_terminate exists in the module, there + // should be a declaration of std::terminate within the same module, because + // __clang_call_terminate calls it. + const auto *StdTerminateFn = + MF.getMMI().getModule()->getNamedValue(WebAssembly::StdTerminateFn); + assert(StdTerminateFn && "std::terminate() does not exist in the module"); + + // Generate a BB in the form of: + // catch_all + // call @std::terminate + // unreachable + BuildMI(CatchAllBB, Call->getDebugLoc(), TII.get(WebAssembly::CATCH_ALL)); + BuildMI(CatchAllBB, Call->getDebugLoc(), TII.get(WebAssembly::CALL)) + .addGlobalAddress(StdTerminateFn); + BuildMI(CatchAllBB, Call->getDebugLoc(), TII.get(WebAssembly::UNREACHABLE)); + } + + return true; +} diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp @@ -82,6 +82,7 @@ initializeWebAssemblyExceptionInfoPass(PR); initializeWebAssemblyCFGSortPass(PR); initializeWebAssemblyCFGStackifyPass(PR); + initializeWebAssemblyHandleEHTerminatePadsPass(PR); initializeWebAssemblyExplicitLocalsPass(PR); initializeWebAssemblyLowerBrUnlessPass(PR); initializeWebAssemblyRegNumberingPass(PR); @@ -485,6 +486,10 @@ // Insert BLOCK and LOOP markers. addPass(createWebAssemblyCFGStackify()); + // Handle terminate pads for cleanups + if (TM->Options.ExceptionModel == ExceptionHandling::Wasm) + addPass(createWebAssemblyHandleEHTerminatePads()); + // Insert explicit local.get and local.set operators. if (!WasmDisableExplicitLocals) addPass(createWebAssemblyExplicitLocals()); diff --git a/llvm/test/CodeGen/WebAssembly/exception.ll b/llvm/test/CodeGen/WebAssembly/exception.ll --- a/llvm/test/CodeGen/WebAssembly/exception.ll +++ b/llvm/test/CodeGen/WebAssembly/exception.ll @@ -131,6 +131,9 @@ ; CHECK: catch $[[EXN:[0-9]+]]=, __cpp_exception ; CHECK: call __clang_call_terminate, $[[EXN]] ; CHECK: unreachable +; CHECK: catch_all +; CHECK: call _ZSt9terminatev +; CHECK: unreachable ; CHECK: end_try ; CHECK: rethrow ; CHECK: end_try @@ -425,6 +428,7 @@ declare i8* @__cxa_begin_catch(i8*) declare void @__cxa_end_catch() declare void @__clang_call_terminate(i8*) +declare void @_ZSt9terminatev() declare %struct.Temp* @_ZN4TempD2Ev(%struct.Temp* returned) ; CHECK: __cpp_exception: