Skip to content

Commit 561abd8

Browse files
committed
[WebAssembly] Disable uses of __clang_call_terminate
Background: Wasm EH, while using Windows EH (catchpad/cleanuppad based) IR, uses Itanium-based libraries and ABIs with some modifications. `__clang_call_terminate` is a wrapper generated in Clang's Itanium C++ ABI implementation. It contains this code, in C-style pseudocode: ``` void __clang_call_terminate(void *exn) { __cxa_begin_catch(exn); std::terminate(); } ``` So this function is a wrapper to call `__cxa_begin_catch` on the exception pointer before termination. In Itanium ABI, this function is called when another exception is thrown while processing an exception. The pointer for this second, violating exception is passed as the argument of this `__clang_call_terminate`, which calls `__cxa_begin_catch` with that pointer and calls `std::terminate` to terminate the program. The spec (https://libcxxabi.llvm.org/spec.html) for `__cxa_begin_catch` says, ``` When the personality routine encounters a termination condition, it will call __cxa_begin_catch() to mark the exception as handled and then call terminate(), which shall not return to its caller. ``` In wasm EH's Clang implementation, this function is called from cleanuppads that terminates the program, which we also call terminate pads. Cleanuppads normally don't access the thrown exception and the wasm backend converts them to `catch_all` blocks. But because we need the exception pointer in this cleanuppad, we generate `wasm.get.exception` intrinsic (which will eventually be lowered to `catch` instruction) as we do in the catchpads. But because terminate pads are cleanup pads and should run even when a foreign exception is thrown, so what we have been doing is: 1. In `WebAssemblyLateEHPrepare::ensureSingleBBTermPads()`, we make sure terminate pads are in this simple shape: ``` %exn = catch call @__clang_call_terminate(%exn) unreachable ``` 2. In `WebAssemblyHandleEHTerminatePads` pass at the end of the pipeline, we attach a `catch_all` to terminate pads, so they will be in this form: ``` %exn = catch call @__clang_call_terminate(%exn) unreachable catch_all call @std::terminate() unreachable ``` In `catch_all` part, we don't have the exception pointer, so we call `std::terminate()` directly. The reason we ran HandleEHTerminatePads at the end of the pipeline, separate from LateEHPrepare, was it was convenient to assume there was only a single `catch` part per `try` during CFGSort and CFGStackify. --- Problem: While it thinks terminate pads could have been possibly split or calls to `__clang_call_terminate` could have been duplicated, `WebAssemblyLateEHPrepare::ensureSingleBBTermPads()` assumes terminate pads contain no more than calls to `__clang_call_terminate` and `unreachable` instruction. I assumed that because in LLVM very limited forms of transformations are done to catchpads and cleanuppads to maintain the scoping structure. But it turned out to be incorrect; passes can merge cleanuppads into one, including terminate pads, as long as the new code has a correct scoping structure. One pass that does this I observed was `SimplifyCFG`, but there can be more. After this transformation, a single cleanuppad can contain any number of other instructions with the call to `__clang_call_terminate` and can span many BBs. It wouldn't be practical to duplicate all these BBs within the cleanuppad to generate the equivalent `catch_all` blocks, only with calls to `__clang_call_terminate` replaced by calls to `std::terminate`. Unless we do more complicated transformation to split those calls to `__clang_call_terminate` into a separate cleanuppad, it is tricky to solve. --- Solution (?): This CL just disables the generation and use of `__clang_call_terminate` and calls `std::terminate()` directly in its place. The possible downside of this approach can be, because the Itanium ABI intended to "mark" the violating exception handled, we don't do that anymore. What `__cxa_begin_catch` actually does is increment the exception's handler count and decrement the uncaught exception count, which in my opinion do not matter much given that we are about to terminate the program anyway. Also it does not affect info like stack traces that can be possibly shown to developers. And while we use a variant of Itanium EH ABI, we can make some deviations if we choose to; we are already different in that in the current version of the EH spec we don't support two-phase unwinding. We can possibly consider a more complicated transformation later to reenable this, but I don't think that has high priority. Changes in this CL contains: - In Clang, we don't generate a call to `wasm.get.exception()` intrinsic and `__clang_call_terminate` function in terminate pads anymore; we simply generate calls to `std::terminate()`, which is the default implementation of `CGCXXABI::emitTerminateForUnexpectedException`. - Remove `WebAssembly::ensureSingleBBTermPads() function and `WebAssemblyHandleEHTerminatePads` pass, because terminate pads are already `catch_all` now (because they don't need the exception pointer) and we don't need these transformations anymore. - Change tests to use `std::terminate` directly. Also removes tests that tested `LateEHPrepare::ensureSingleBBTermPads` and `HandleEHTerminatePads` pass. - Drive-by fix: Add some function attributes to EH intrinsic declarations Fixes emscripten-core/emscripten#13582. Reviewed By: dschuff, tlively Differential Revision: https://reviews.llvm.org/D97834
1 parent 2b896e3 commit 561abd8

File tree

14 files changed

+70
-417
lines changed

14 files changed

+70
-417
lines changed

clang/lib/CodeGen/CGException.cpp

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1552,17 +1552,8 @@ llvm::BasicBlock *CodeGenFunction::getTerminateFunclet() {
15521552
CurrentFuncletPad = Builder.CreateCleanupPad(ParentPad);
15531553

15541554
// Emit the __std_terminate call.
1555-
llvm::Value *Exn = nullptr;
1556-
// In case of wasm personality, we need to pass the exception value to
1557-
// __clang_call_terminate function.
1558-
if (getLangOpts().CPlusPlus &&
1559-
EHPersonality::get(*this).isWasmPersonality()) {
1560-
llvm::Function *GetExnFn =
1561-
CGM.getIntrinsic(llvm::Intrinsic::wasm_get_exception);
1562-
Exn = Builder.CreateCall(GetExnFn, CurrentFuncletPad);
1563-
}
15641555
llvm::CallInst *terminateCall =
1565-
CGM.getCXXABI().emitTerminateForUnexpectedException(*this, Exn);
1556+
CGM.getCXXABI().emitTerminateForUnexpectedException(*this, nullptr);
15661557
terminateCall->setDoesNotReturn();
15671558
Builder.CreateUnreachable();
15681559

clang/lib/CodeGen/ItaniumCXXABI.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,9 @@ class WebAssemblyCXXABI final : public ItaniumCXXABI {
515515
: ItaniumCXXABI(CGM, /*UseARMMethodPtrABI=*/true,
516516
/*UseARMGuardVarABI=*/true) {}
517517
void emitBeginCatch(CodeGenFunction &CGF, const CXXCatchStmt *C) override;
518+
llvm::CallInst *
519+
emitTerminateForUnexpectedException(CodeGenFunction &CGF,
520+
llvm::Value *Exn) override;
518521

519522
private:
520523
bool HasThisReturn(GlobalDecl GD) const override {
@@ -4640,6 +4643,18 @@ void WebAssemblyCXXABI::emitBeginCatch(CodeGenFunction &CGF,
46404643
ItaniumCXXABI::emitBeginCatch(CGF, C);
46414644
}
46424645

4646+
llvm::CallInst *
4647+
WebAssemblyCXXABI::emitTerminateForUnexpectedException(CodeGenFunction &CGF,
4648+
llvm::Value *Exn) {
4649+
// Itanium ABI calls __clang_call_terminate(), which __cxa_begin_catch() on
4650+
// the violating exception to mark it handled, but it is currently hard to do
4651+
// with wasm EH instruction structure with catch/catch_all, we just call
4652+
// std::terminate and ignore the violating exception as in CGCXXABI.
4653+
// TODO Consider code transformation that makes calling __clang_call_terminate
4654+
// possible.
4655+
return CGCXXABI::emitTerminateForUnexpectedException(CGF, Exn);
4656+
}
4657+
46434658
/// Register a global destructor as best as we know how.
46444659
void XLCXXABI::registerGlobalDtor(CodeGenFunction &CGF, const VarDecl &D,
46454660
llvm::FunctionCallee dtor,

clang/test/CodeGenCXX/wasm-eh.cpp

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -188,13 +188,7 @@ void test5() {
188188

189189
// CHECK: [[TERMINATE_BB]]:
190190
// CHECK-NEXT: %[[CLEANUPPAD1:.*]] = cleanuppad within %[[CLEANUPPAD0]] []
191-
// CHECK-NEXT: %[[EXN:.*]] = call i8* @llvm.wasm.get.exception(token %[[CLEANUPPAD1]])
192-
// CHECK-NEXT: call void @__clang_call_terminate(i8* %[[EXN]]) {{.*}} [ "funclet"(token %[[CLEANUPPAD1]]) ]
193-
// CHECK-NEXT: unreachable
194-
195-
// CHECK-LABEL: define {{.*}} void @__clang_call_terminate(i8* %0)
196-
// CHECK-NEXT: call i8* @__cxa_begin_catch(i8* %{{.*}})
197-
// CHECK-NEXT: call void @_ZSt9terminatev()
191+
// CHECK-NEXT: call void @_ZSt9terminatev() {{.*}} [ "funclet"(token %[[CLEANUPPAD1]]) ]
198192
// CHECK-NEXT: unreachable
199193

200194
// Try-catch with cleanups
@@ -336,7 +330,7 @@ void test7() {
336330
// CHECK: unreachable
337331

338332
// CHECK: %[[CLEANUPPAD7:.*]] = cleanuppad within %[[CLEANUPPAD4]] []
339-
// CHECK: call void @__clang_call_terminate(i8* %{{.*}}) {{.*}} [ "funclet"(token %[[CLEANUPPAD7]]) ]
333+
// CHECK: call void @_ZSt9terminatev() {{.*}} [ "funclet"(token %[[CLEANUPPAD7]]) ]
340334
// CHECK: unreachable
341335

342336
// Nested try-catches within a catch

llvm/lib/CodeGen/WasmEHPrepare.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,8 +297,8 @@ void WasmEHPrepare::prepareEHPad(BasicBlock *BB, bool NeedPersonality,
297297
}
298298
}
299299

300-
// Cleanup pads w/o __clang_call_terminate call do not have any of
301-
// wasm.get.exception() or wasm.get.ehselector() calls. We need to do nothing.
300+
// Cleanup pads do not have any of wasm.get.exception() or
301+
// wasm.get.ehselector() calls. We need to do nothing.
302302
if (!GetExnCI) {
303303
assert(!GetSelectorCI &&
304304
"wasm.get.ehselector() cannot exist w/o wasm.get.exception()");

llvm/lib/Target/WebAssembly/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ add_llvm_target(WebAssemblyCodeGen
2222
WebAssemblyCFGSort.cpp
2323
WebAssemblyDebugFixup.cpp
2424
WebAssemblyDebugValueManager.cpp
25-
WebAssemblyHandleEHTerminatePads.cpp
2625
WebAssemblyLateEHPrepare.cpp
2726
WebAssemblyExceptionInfo.cpp
2827
WebAssemblyExplicitLocals.cpp

llvm/lib/Target/WebAssembly/WebAssembly.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ FunctionPass *createWebAssemblyFixIrreducibleControlFlow();
4949
FunctionPass *createWebAssemblyLateEHPrepare();
5050
FunctionPass *createWebAssemblyCFGSort();
5151
FunctionPass *createWebAssemblyCFGStackify();
52-
FunctionPass *createWebAssemblyHandleEHTerminatePads();
5352
FunctionPass *createWebAssemblyExplicitLocals();
5453
FunctionPass *createWebAssemblyLowerBrUnless();
5554
FunctionPass *createWebAssemblyRegNumbering();
@@ -76,7 +75,6 @@ void initializeWebAssemblyLateEHPreparePass(PassRegistry &);
7675
void initializeWebAssemblyExceptionInfoPass(PassRegistry &);
7776
void initializeWebAssemblyCFGSortPass(PassRegistry &);
7877
void initializeWebAssemblyCFGStackifyPass(PassRegistry &);
79-
void initializeWebAssemblyHandleEHTerminatePadsPass(PassRegistry &);
8078
void initializeWebAssemblyExplicitLocalsPass(PassRegistry &);
8179
void initializeWebAssemblyLowerBrUnlessPass(PassRegistry &);
8280
void initializeWebAssemblyRegNumberingPass(PassRegistry &);

llvm/lib/Target/WebAssembly/WebAssemblyHandleEHTerminatePads.cpp

Lines changed: 0 additions & 152 deletions
This file was deleted.

llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp

Lines changed: 0 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ class WebAssemblyLateEHPrepare final : public MachineFunctionPass {
3838
bool addCatchAlls(MachineFunction &MF);
3939
bool replaceFuncletReturns(MachineFunction &MF);
4040
bool removeUnnecessaryUnreachables(MachineFunction &MF);
41-
bool ensureSingleBBTermPads(MachineFunction &MF);
4241
bool restoreStackPointer(MachineFunction &MF);
4342

4443
MachineBasicBlock *getMatchingEHPad(MachineInstr *MI);
@@ -128,7 +127,6 @@ bool WebAssemblyLateEHPrepare::runOnMachineFunction(MachineFunction &MF) {
128127
Changed |= hoistCatches(MF);
129128
Changed |= addCatchAlls(MF);
130129
Changed |= replaceFuncletReturns(MF);
131-
Changed |= ensureSingleBBTermPads(MF);
132130
}
133131
Changed |= removeUnnecessaryUnreachables(MF);
134132
if (MF.getFunction().hasPersonalityFn())
@@ -288,80 +286,6 @@ bool WebAssemblyLateEHPrepare::removeUnnecessaryUnreachables(
288286
return Changed;
289287
}
290288

291-
// Clang-generated terminate pads are an single-BB EH pad in the form of
292-
// termpad:
293-
// %exn = catch $__cpp_exception
294-
// call @__clang_call_terminate(%exn)
295-
// unreachable
296-
// (There can be local.set and local.gets before the call if we didn't run
297-
// RegStackify)
298-
// But code transformations can change or add more control flow, so the call to
299-
// __clang_call_terminate() function may not be in the original EH pad anymore.
300-
// This ensures every terminate pad is a single BB in the form illustrated
301-
// above.
302-
//
303-
// This is preparation work for the HandleEHTerminatePads pass later, which
304-
// duplicates terminate pads both for 'catch' and 'catch_all'. Refer to
305-
// WebAssemblyHandleEHTerminatePads.cpp for details.
306-
bool WebAssemblyLateEHPrepare::ensureSingleBBTermPads(MachineFunction &MF) {
307-
const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
308-
309-
// Find calls to __clang_call_terminate()
310-
SmallVector<MachineInstr *, 8> ClangCallTerminateCalls;
311-
SmallPtrSet<MachineBasicBlock *, 8> TermPads;
312-
for (auto &MBB : MF) {
313-
for (auto &MI : MBB) {
314-
if (MI.isCall()) {
315-
const MachineOperand &CalleeOp = MI.getOperand(0);
316-
if (CalleeOp.isGlobal() && CalleeOp.getGlobal()->getName() ==
317-
WebAssembly::ClangCallTerminateFn) {
318-
MachineBasicBlock *EHPad = getMatchingEHPad(&MI);
319-
assert(EHPad && "No matching EH pad for __clang_call_terminate");
320-
// In case a __clang_call_terminate call is duplicated during code
321-
// transformation so one terminate pad contains multiple
322-
// __clang_call_terminate calls, we only count one of them
323-
if (TermPads.insert(EHPad).second)
324-
ClangCallTerminateCalls.push_back(&MI);
325-
}
326-
}
327-
}
328-
}
329-
330-
bool Changed = false;
331-
for (auto *Call : ClangCallTerminateCalls) {
332-
MachineBasicBlock *EHPad = getMatchingEHPad(Call);
333-
assert(EHPad && "No matching EH pad for __clang_call_terminate");
334-
335-
// If it is already the form we want, skip it
336-
if (Call->getParent() == EHPad &&
337-
Call->getNextNode()->getOpcode() == WebAssembly::UNREACHABLE)
338-
continue;
339-
340-
// In case the __clang_call_terminate() call is not in its matching EH pad,
341-
// move the call to the end of EH pad and add an unreachable instruction
342-
// after that. Delete all successors and their children if any, because here
343-
// the program terminates.
344-
Changed = true;
345-
// This runs after hoistCatches(), so catch instruction should be at the top
346-
MachineInstr *Catch = WebAssembly::findCatch(EHPad);
347-
assert(Catch && "EH pad does not have a catch instruction");
348-
// Takes the result register of the catch instruction as argument. There may
349-
// have been some other local.set/local.gets in between, but at this point
350-
// we don't care.
351-
Call->getOperand(1).setReg(Catch->getOperand(0).getReg());
352-
auto InsertPos = std::next(MachineBasicBlock::iterator(Catch));
353-
EHPad->insert(InsertPos, Call->removeFromParent());
354-
BuildMI(*EHPad, InsertPos, Call->getDebugLoc(),
355-
TII.get(WebAssembly::UNREACHABLE));
356-
EHPad->erase(InsertPos, EHPad->end());
357-
SmallVector<MachineBasicBlock *, 8> Succs(EHPad->successors());
358-
for (auto *Succ : Succs)
359-
EHPad->removeSuccessor(Succ);
360-
eraseDeadBBsAndChildren(Succs);
361-
}
362-
return Changed;
363-
}
364-
365289
// After the stack is unwound due to a thrown exception, the __stack_pointer
366290
// global can point to an invalid address. This inserts instructions that
367291
// restore __stack_pointer global.

llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeWebAssemblyTarget() {
8282
initializeWebAssemblyExceptionInfoPass(PR);
8383
initializeWebAssemblyCFGSortPass(PR);
8484
initializeWebAssemblyCFGStackifyPass(PR);
85-
initializeWebAssemblyHandleEHTerminatePadsPass(PR);
8685
initializeWebAssemblyExplicitLocalsPass(PR);
8786
initializeWebAssemblyLowerBrUnlessPass(PR);
8887
initializeWebAssemblyRegNumberingPass(PR);
@@ -486,10 +485,6 @@ void WebAssemblyPassConfig::addPreEmitPass() {
486485
// Insert BLOCK and LOOP markers.
487486
addPass(createWebAssemblyCFGStackify());
488487

489-
// Handle terminate pads for cleanups
490-
if (TM->Options.ExceptionModel == ExceptionHandling::Wasm)
491-
addPass(createWebAssemblyHandleEHTerminatePads());
492-
493488
// Insert explicit local.get and local.set operators.
494489
if (!WasmDisableExplicitLocals)
495490
addPass(createWebAssemblyExplicitLocals());

llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
#include "llvm/MC/MCContext.h"
1919
using namespace llvm;
2020

21-
const char *const WebAssembly::ClangCallTerminateFn = "__clang_call_terminate";
2221
const char *const WebAssembly::CxaBeginCatchFn = "__cxa_begin_catch";
2322
const char *const WebAssembly::CxaRethrowFn = "__cxa_rethrow";
2423
const char *const WebAssembly::StdTerminateFn = "_ZSt9terminatev";
@@ -73,7 +72,7 @@ bool WebAssembly::mayThrow(const MachineInstr &MI) {
7372
return false;
7473
// These functions never throw
7574
if (F->getName() == CxaBeginCatchFn || F->getName() == PersonalityWrapperFn ||
76-
F->getName() == ClangCallTerminateFn || F->getName() == StdTerminateFn)
75+
F->getName() == StdTerminateFn)
7776
return false;
7877

7978
// TODO Can we exclude call instructions that are marked as 'nounwind' in the

0 commit comments

Comments
 (0)