Index: lib/Target/WebAssembly/CMakeLists.txt =================================================================== --- lib/Target/WebAssembly/CMakeLists.txt +++ lib/Target/WebAssembly/CMakeLists.txt @@ -18,6 +18,7 @@ WebAssemblyCallIndirectFixup.cpp WebAssemblyCFGStackify.cpp WebAssemblyCFGSort.cpp + WebAssemblyExceptionPrepare.cpp WebAssemblyExplicitLocals.cpp WebAssemblyFastISel.cpp WebAssemblyFixIrreducibleControlFlow.cpp Index: lib/Target/WebAssembly/WebAssembly.h =================================================================== --- lib/Target/WebAssembly/WebAssembly.h +++ lib/Target/WebAssembly/WebAssembly.h @@ -46,6 +46,7 @@ FunctionPass *createWebAssemblyRegColoring(); FunctionPass *createWebAssemblyExplicitLocals(); FunctionPass *createWebAssemblyFixIrreducibleControlFlow(); +FunctionPass *createWebAssemblyExceptionPrepare(); FunctionPass *createWebAssemblyCFGSort(); FunctionPass *createWebAssemblyReplaceFuncletReturns(); FunctionPass *createWebAssemblyCFGStackify(); @@ -69,6 +70,7 @@ void initializeWebAssemblyRegColoringPass(PassRegistry &); void initializeWebAssemblyExplicitLocalsPass(PassRegistry &); void initializeWebAssemblyFixIrreducibleControlFlowPass(PassRegistry &); +void initializeWebAssemblyExceptionPreparePass(PassRegistry &); void initializeWebAssemblyCFGSortPass(PassRegistry &); void initializeWebAssemblyReplaceFuncletReturnsPass(PassRegistry &); void initializeWebAssemblyCFGStackifyPass(PassRegistry &); Index: lib/Target/WebAssembly/WebAssemblyExceptionPrepare.cpp =================================================================== --- /dev/null +++ lib/Target/WebAssembly/WebAssemblyExceptionPrepare.cpp @@ -0,0 +1,225 @@ +//=== WebAssemblyExceptionPrepare.cpp - WebAssembly Exception Preparation -===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Does various transformations for exception handling. +/// +//===----------------------------------------------------------------------===// + +#include "MCTargetDesc/WebAssemblyMCTargetDesc.h" +#include "WebAssembly.h" +#include "WebAssemblySubtarget.h" +#include "llvm/CodeGen/MachineInstrBuilder.h" +using namespace llvm; + +#define DEBUG_TYPE "wasm-exception-prepare" + +static const char *const ClangCallTerminate = "__clang_call_terminate"; +static const char *const CxaRethrow = "__cxa_rethrow"; +static const char *const StdTerminate = "_ZSt9terminatev"; + +namespace { +class WebAssemblyExceptionPrepare final : public MachineFunctionPass { + StringRef getPassName() const override { + return "WebAssembly Prepare Exception"; + } + + bool runOnMachineFunction(MachineFunction &MF) override; + + bool hoistCatches(MachineFunction &MF); + bool addCatchAlls(MachineFunction &MF); + bool addRethrows(MachineFunction &MF); + bool mergeTerminatePads(MachineFunction &MF); + bool processTerminatePads(MachineFunction &MF); + +public: + static char ID; // Pass identification, replacement for typeid + WebAssemblyExceptionPrepare() : MachineFunctionPass(ID) {} +}; +} // end anonymous namespace + +char WebAssemblyExceptionPrepare::ID = 0; +INITIALIZE_PASS(WebAssemblyExceptionPrepare, DEBUG_TYPE, + "WebAssembly Exception Preparermation", false, false) + +FunctionPass *llvm::createWebAssemblyExceptionPrepare() { + return new WebAssemblyExceptionPrepare(); +} + +bool WebAssemblyExceptionPrepare::runOnMachineFunction(MachineFunction &MF) { + bool Changed = false; + Changed |= addRethrows(MF); + if (!MF.getFunction().hasPersonalityFn()) + return Changed; + Changed |= hoistCatches(MF); + Changed |= addCatchAlls(MF); + Changed |= mergeTerminatePads(MF); + Changed |= processTerminatePads(MF); + return Changed; +} + +// Hoist catch instructions to the beginning of the BBs +bool WebAssemblyExceptionPrepare::hoistCatches(MachineFunction &MF) { + bool Changed = false; + + for (auto &MBB : MF) { + if (!MBB.isEHPad()) + continue; + assert(!MBB.empty() && "EH pad is empty"); + + auto InsertPt = MBB.begin(); + while (InsertPt->isLabel()) + InsertPt++; + MachineInstr *Catch = nullptr; + for (auto &MI : llvm::make_range(std::next(InsertPt), MBB.end())) + if (MI.getOpcode() == WebAssembly::CATCH_I32 || + MI.getOpcode() == WebAssembly::CATCH_I64) { + Catch = &MI; + Changed = true; + break; + } + if (Catch) + MBB.insert(InsertPt, Catch->removeFromParent()); + } + return Changed; +} + +// Add catch_all to beginning of cleanup pads +bool WebAssemblyExceptionPrepare::addCatchAlls(MachineFunction &MF) { + bool Changed = false; + const auto &TII = *MF.getSubtarget().getInstrInfo(); + + for (auto &MBB : MF) { + if (!MBB.isEHPad()) + continue; + bool HasCatch = false; + for (auto &MI : MBB) + if (MI.getOpcode() == WebAssembly::CATCH_I32 || + MI.getOpcode() == WebAssembly::CATCH_I64) { + HasCatch = true; + break; + } + + if (!HasCatch) { + Changed = true; + BuildMI(MBB, MBB.begin(), MBB.begin()->getDebugLoc(), + TII.get(WebAssembly::CATCH_ALL)); + } + } + return Changed; +} + +// Add a 'rethrow' instruction after __cxa_rethrow() call +bool WebAssemblyExceptionPrepare::addRethrows(MachineFunction &MF) { + bool Changed = false; + const auto &TII = *MF.getSubtarget().getInstrInfo(); + for (auto &MBB : MF) + for (auto &MI : MBB) { + if (!MI.isCall()) + continue; + MachineOperand &CalleeOp = MI.getOperand(0); + if (CalleeOp.isGlobal() && + CalleeOp.getGlobal()->getName() == CxaRethrow) { + auto InsertPt = std::next(MachineBasicBlock::iterator(MI)); + while (InsertPt != MBB.end() && InsertPt->isLabel()) + InsertPt++; + BuildMI(MBB, InsertPt, MI.getDebugLoc(), TII.get(WebAssembly::RETHROW)) + .addImm(0); + Changed = true; + break; + } + } + return Changed; +} + +// Merge multiple terminate pads into one for code size. +bool WebAssemblyExceptionPrepare::mergeTerminatePads(MachineFunction &MF) { + std::vector TermPads; + + for (auto &MBB : MF) { + if (!MBB.isEHPad()) + continue; + for (auto &MI : MBB) { + if (!MI.isCall()) + continue; + MachineOperand &CalleeOp = MI.getOperand(0); + if (CalleeOp.isGlobal() && + CalleeOp.getGlobal()->getName() == ClangCallTerminate) { + TermPads.push_back(&MBB); + break; + } + } + } + if (TermPads.size() <= 1) + return false; + + MachineBasicBlock *UniqueTermPad = TermPads.front(); + for (auto *TermPad : + llvm::make_range(std::next(TermPads.begin()), TermPads.end())) { + SmallVector Preds(TermPad->pred_begin(), + TermPad->pred_end()); + for (auto *Pred : Preds) + Pred->replaceSuccessor(TermPad, UniqueTermPad); + TermPad->removeFromParent(); + } + return true; +} + +// Terminate pads are cleanup pads, so they should start with a 'catch_all' +// instruction. But in the Itanium model, when we have a C++ exception object, +// we pass them to __clang_call_terminate function, which calls __cxa_end_catch +// and then std::terminate. This is the reason that terminate pads are generated +// with not catch_all but a catch instruction in the earlier passes. Here we add +// catch_all BB after that so we can also catch foreign exceptions. +// For every terminate pad: +// exn = catch +// call @__clang_call_terminate(exn); +// unreachable +// We append this BB right after that: +// catch_all +// call @std::terminate +// unreachable +bool WebAssemblyExceptionPrepare::processTerminatePads(MachineFunction &MF) { + const auto &TII = *MF.getSubtarget().getInstrInfo(); + std::vector TermPads; + + for (auto &MBB : MF) { + if (!MBB.isEHPad()) + continue; + for (auto &MI : MBB) { + if (!MI.isCall()) + continue; + MachineOperand &CalleeOp = MI.getOperand(0); + if (CalleeOp.isGlobal() && + CalleeOp.getGlobal()->getName() == ClangCallTerminate) { + TermPads.push_back(&MBB); + break; + } + } + } + if (TermPads.empty()) + return false; + + Function *StdTerminateFn = + MF.getFunction().getParent()->getFunction(StdTerminate); + assert(StdTerminateFn && "There is no std::terminate() function"); + for (auto *CatchTermPad : TermPads) { + DebugLoc DL = CatchTermPad->findDebugLoc(CatchTermPad->begin()); + auto *CatchAllTermPad = MF.CreateMachineBasicBlock(); + MF.insert(std::next(MachineFunction::iterator(CatchTermPad)), + CatchAllTermPad); + CatchAllTermPad->setIsEHPad(); + BuildMI(CatchAllTermPad, DL, TII.get(WebAssembly::CATCH_ALL)); + BuildMI(CatchAllTermPad, DL, TII.get(WebAssembly::CALL_VOID)) + .addGlobalAddress(StdTerminateFn); + BuildMI(CatchAllTermPad, DL, TII.get(WebAssembly::UNREACHABLE)); + CatchTermPad->addSuccessor(CatchAllTermPad); + } + return true; +} Index: lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp =================================================================== --- lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp +++ lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp @@ -65,6 +65,7 @@ initializeWebAssemblyRegColoringPass(PR); initializeWebAssemblyExplicitLocalsPass(PR); initializeWebAssemblyFixIrreducibleControlFlowPass(PR); + initializeWebAssemblyExceptionPreparePass(PR); initializeWebAssemblyCFGSortPass(PR); initializeWebAssemblyReplaceFuncletReturnsPass(PR); initializeWebAssemblyCFGStackifyPass(PR); @@ -321,6 +322,9 @@ // Insert explicit get_local and set_local operators. addPass(createWebAssemblyExplicitLocals()); + // Do various transformations for exception handling + addPass(createWebAssemblyExceptionPrepare()); + // Sort the blocks of the CFG into topological order, a prerequisite for // BLOCK and LOOP markers. addPass(createWebAssemblyCFGSort()); Index: test/CodeGen/WebAssembly/exception.ll =================================================================== --- test/CodeGen/WebAssembly/exception.ll +++ test/CodeGen/WebAssembly/exception.ll @@ -17,7 +17,7 @@ ret void } -; CHECK-LABEL: test_catch: +; CHECK-LABEL: test_catch_rethrow: ; CHECK: call foo@FUNCTION ; CHECK: i32.catch $push{{.+}}=, 0 ; CHECK-DAG: i32.store __wasm_lpad_context @@ -26,7 +26,8 @@ ; CHECK: i32.call $push{{.+}}=, __cxa_begin_catch@FUNCTION ; CHECK: call __cxa_end_catch@FUNCTION ; CHECK: call __cxa_rethrow@FUNCTION -define void @test_catch() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) { +; CHECK-NEXT: rethrow +define void @test_catch_rethrow() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) { entry: invoke void @foo() to label %try.cont unwind label %catch.dispatch @@ -58,6 +59,7 @@ ; CHECK-LABEL: test_cleanup: ; CHECK: call foo@FUNCTION ; CHECK: return +; CHECK: catch_all ; CHECK: i32.call $push20=, _ZN7CleanupD1Ev@FUNCTION ; CHECK: rethrow 0 define void @test_cleanup() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) { @@ -76,8 +78,75 @@ cleanupret from %0 unwind to caller } +; - Tests multple terminate pads are merged into one +; - Tests a catch_all terminate pad is created after a catch terminate pad + +; CHECK-LABEL: test_terminatepad +; CHECK: i32.catch +; CHECK: call __clang_call_terminate@FUNCTION +; CHECK: unreachable +; CHECK: catch_all +; CHECK: call _ZSt9terminatev@FUNCTION +; CHECK-NOT: call __clang_call_terminate@FUNCTION +define hidden i32 @test_terminatepad() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) { +entry: + %c = alloca %struct.Cleanup, align 1 + %c1 = alloca %struct.Cleanup, align 1 + invoke void @foo() + to label %invoke.cont unwind label %ehcleanup + +invoke.cont: ; preds = %entry + %call = invoke %struct.Cleanup* @_ZN7CleanupD1Ev(%struct.Cleanup* %c1) + to label %try.cont unwind label %catch.dispatch + +ehcleanup: ; preds = %entry + %0 = cleanuppad within none [] + %call4 = invoke %struct.Cleanup* @_ZN7CleanupD1Ev(%struct.Cleanup* %c1) [ "funclet"(token %0) ] + to label %invoke.cont3 unwind label %terminate + +invoke.cont3: ; preds = %ehcleanup + cleanupret from %0 unwind label %catch.dispatch + +catch.dispatch: ; preds = %invoke.cont3, %invoke.cont + %1 = catchswitch within none [label %catch.start] unwind label %ehcleanup7 + +catch.start: ; preds = %catch.dispatch + %2 = catchpad within %1 [i8* null] + %3 = call i8* @llvm.wasm.get.exception(token %2) + %4 = call i32 @llvm.wasm.get.ehselector(token %2) + %5 = call i8* @__cxa_begin_catch(i8* %3) [ "funclet"(token %2) ] + invoke void @__cxa_end_catch() [ "funclet"(token %2) ] + to label %invoke.cont5 unwind label %ehcleanup7 + +invoke.cont5: ; preds = %catch.start + catchret from %2 to label %try.cont + +try.cont: ; preds = %invoke.cont5, %invoke.cont + %call6 = call %struct.Cleanup* @_ZN7CleanupD1Ev(%struct.Cleanup* %c) + ret i32 0 + +ehcleanup7: ; preds = %catch.start, %catch.dispatch + %6 = cleanuppad within none [] + %call9 = invoke %struct.Cleanup* @_ZN7CleanupD1Ev(%struct.Cleanup* %c) [ "funclet"(token %6) ] + to label %invoke.cont8 unwind label %terminate10 + +invoke.cont8: ; preds = %ehcleanup7 + cleanupret from %6 unwind to caller + +terminate: ; preds = %ehcleanup + %7 = cleanuppad within %0 [] + %8 = call i8* @llvm.wasm.get.exception(token %7) + call void @__clang_call_terminate(i8* %8) [ "funclet"(token %7) ] + unreachable + +terminate10: ; preds = %ehcleanup7 + %9 = cleanuppad within %6 [] + %10 = call i8* @llvm.wasm.get.exception(token %9) + call void @__clang_call_terminate(i8* %10) [ "funclet"(token %9) ] + unreachable +} + declare void @foo() -declare void @func(i32) declare i32 @__gxx_wasm_personality_v0(...) declare i8* @llvm.wasm.get.exception(token) declare i32 @llvm.wasm.get.ehselector(token) @@ -86,4 +155,5 @@ declare void @__cxa_end_catch() declare void @__cxa_rethrow() declare void @__clang_call_terminate(i8*) +declare void @_ZSt9terminatev() declare %struct.Cleanup* @_ZN7CleanupD1Ev(%struct.Cleanup* returned)