Heejin Ahn da85b2a864
[WebAssembly] Generate __clang_call_terminate for Emscripten EH (#129020)
When an exception thrown ends up calling `std::terminate`, for example,
because an exception is thrown within a `noexcept` function or an
exception is thrown from `__cxa_end_catch` during handling the previous
exception, the libc++abi spec says we are supposed to call
`__cxa_begin_catch` before `std::terminate`:
https://libcxxabi.llvm.org/spec.html
> 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.

The default Itanium ABI generates a call to `__clang_call_terminate()`,
which is a function that calls `__cxa_begin_catch` and then
`std::terminate`:
```ll
define void @__clang_call_terminate(ptr noundef %0) {
  %2 = call ptr @__cxa_begin_catch(ptr %0)
  call void @_ZSt9terminatev()
  unreachable
}
```

But we replaced this with just a call to `std::terminate` in
561abd83ff
because this caused some tricky transformation problems for Wasm EH. The
detailed explanation why is in the commit description, but the summary
is for Wasm EH it needed a `try` with both `catch` and `catch_all` and
it was tricky to deal with.

But that commit replaced `__clang_call_terminate` with `std::terminate`
for all Wasm programs and not only the ones that use Wasm EH. So
Emscripten EH was also affected by that commit. Emscripten EH is not
able to catch foreign exceptions anyway, so this is unnecessary
compromise.

This makes we use `__clang_call_terminate` as in the default Itanium EH
for Emscripten EH. We may later fix Wasm EH too but that requires more
efforts in the backend.

Related issue:
https://github.com/emscripten-core/emscripten/issues/23720
2025-02-27 16:23:18 -08:00

429 lines
16 KiB
C++

// REQUIRES: webassembly-registered-target
// RUN: %clang -E -dM %s -target wasm32-unknown-unknown -fwasm-exceptions | FileCheck %s -check-prefix PREPROCESSOR
// PREPROCESSOR: #define __WASM_EXCEPTIONS__ 1
// RUN: %clang_cc1 %s -triple wasm32-unknown-unknown -fms-extensions -fexceptions -fcxx-exceptions -mllvm -wasm-enable-eh -exception-model=wasm -target-feature +exception-handling -emit-llvm -o - -std=c++11 | FileCheck %s
// RUN: %clang_cc1 %s -triple wasm64-unknown-unknown -fms-extensions -fexceptions -fcxx-exceptions -mllvm -wasm-enable-eh -exception-model=wasm -target-feature +exception-handling -emit-llvm -o - -std=c++11 | FileCheck %s
// Test code generation for Wasm EH using WebAssembly EH proposal.
// (https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md)
void may_throw();
void dont_throw() noexcept;
struct Cleanup {
~Cleanup() { dont_throw(); }
};
// Multiple catch clauses w/o catch-all
void test0() {
try {
may_throw();
} catch (int) {
dont_throw();
} catch (double) {
dont_throw();
}
}
// CHECK-LABEL: define void @_Z5test0v() {{.*}} personality ptr @__gxx_wasm_personality_v0
// CHECK: %[[INT_ALLOCA:.*]] = alloca i32
// CHECK: invoke void @_Z9may_throwv()
// CHECK-NEXT: to label %[[NORMAL_BB:.*]] unwind label %[[CATCHDISPATCH_BB:.*]]
// CHECK: [[CATCHDISPATCH_BB]]:
// CHECK-NEXT: %[[CATCHSWITCH:.*]] = catchswitch within none [label %[[CATCHSTART_BB:.*]]] unwind to caller
// CHECK: [[CATCHSTART_BB]]:
// CHECK-NEXT: %[[CATCHPAD:.*]] = catchpad within %[[CATCHSWITCH]] [ptr @_ZTIi, ptr @_ZTId]
// CHECK-NEXT: %[[EXN:.*]] = call ptr @llvm.wasm.get.exception(token %[[CATCHPAD]])
// CHECK-NEXT: store ptr %[[EXN]], ptr %exn.slot
// CHECK-NEXT: %[[SELECTOR:.*]] = call i32 @llvm.wasm.get.ehselector(token %[[CATCHPAD]])
// CHECK-NEXT: %[[TYPEID:.*]] = call i32 @llvm.eh.typeid.for.p0(ptr @_ZTIi) #7
// CHECK-NEXT: %[[MATCHES:.*]] = icmp eq i32 %[[SELECTOR]], %[[TYPEID]]
// CHECK-NEXT: br i1 %[[MATCHES]], label %[[CATCH_INT_BB:.*]], label %[[CATCH_FALLTHROUGH_BB:.*]]
// CHECK: [[CATCH_INT_BB]]:
// CHECK-NEXT: %[[EXN:.*]] = load ptr, ptr %exn.slot
// CHECK-NEXT: %[[ADDR:.*]] = call ptr @__cxa_begin_catch(ptr %[[EXN]]) {{.*}} [ "funclet"(token %[[CATCHPAD]]) ]
// CHECK-NEXT: %[[INT_VAL:.*]] = load i32, ptr %[[ADDR]]
// CHECK-NEXT: store i32 %[[INT_VAL]], ptr %[[INT_ALLOCA]]
// CHECK-NEXT: call void @_Z10dont_throwv() {{.*}} [ "funclet"(token %[[CATCHPAD]]) ]
// CHECK-NEXT: call void @__cxa_end_catch() {{.*}} [ "funclet"(token %[[CATCHPAD]]) ]
// CHECK-NEXT: catchret from %[[CATCHPAD]] to label %[[CATCHRET_DEST_BB0:.*]]
// CHECK: [[CATCHRET_DEST_BB0]]:
// CHECK-NEXT: br label %[[TRY_CONT_BB:.*]]
// CHECK: [[CATCH_FALLTHROUGH_BB]]
// CHECK-NEXT: %[[TYPEID:.*]] = call i32 @llvm.eh.typeid.for.p0(ptr @_ZTId) #7
// CHECK-NEXT: %[[MATCHES:.*]] = icmp eq i32 %[[SELECTOR]], %[[TYPEID]]
// CHECK-NEXT: br i1 %[[MATCHES]], label %[[CATCH_FLOAT_BB:.*]], label %[[RETHROW_BB:.*]]
// CHECK: [[CATCH_FLOAT_BB]]:
// CHECK: catchret from %[[CATCHPAD]] to label %[[CATCHRET_DEST_BB1:.*]]
// CHECK: [[CATCHRET_DEST_BB1]]:
// CHECK-NEXT: br label %[[TRY_CONT_BB]]
// CHECK: [[RETHROW_BB]]:
// CHECK-NEXT: call void @llvm.wasm.rethrow() {{.*}} [ "funclet"(token %[[CATCHPAD]]) ]
// CHECK-NEXT: unreachable
// Single catch-all
void test1() {
try {
may_throw();
} catch (...) {
dont_throw();
}
}
// CATCH-LABEL: @_Z5test1v()
// CHECK: %[[CATCHSWITCH:.*]] = catchswitch within none [label %[[CATCHSTART_BB:.*]]] unwind to caller
// CHECK: [[CATCHSTART_BB]]:
// CHECK-NEXT: %[[CATCHPAD:.*]] = catchpad within %[[CATCHSWITCH]] [ptr null]
// CHECK: br label %[[CATCH_ALL_BB:.*]]
// CHECK: [[CATCH_ALL_BB]]:
// CHECK: catchret from %[[CATCHPAD]] to label
// Multiple catch clauses w/ catch-all
void test2() {
try {
may_throw();
} catch (int) {
dont_throw();
} catch (...) {
dont_throw();
}
}
// CHECK-LABEL: @_Z5test2v()
// CHECK: %[[CATCHSWITCH:.*]] = catchswitch within none [label %[[CATCHSTART_BB:.*]]] unwind to caller
// CHECK: [[CATCHSTART_BB]]:
// CHECK-NEXT: %[[CATCHPAD:.*]] = catchpad within %[[CATCHSWITCH]] [ptr @_ZTIi, ptr null]
// CHECK: br i1 %{{.*}}, label %[[CATCH_INT_BB:.*]], label %[[CATCH_ALL_BB:.*]]
// CHECK: [[CATCH_INT_BB]]:
// CHECK: catchret from %[[CATCHPAD]] to label
// CHECK: [[CATCH_ALL_BB]]:
// CHECK: catchret from %[[CATCHPAD]] to label
// Cleanup
void test3() {
Cleanup c;
may_throw();
}
// CHECK-LABEL: @_Z5test3v()
// CHECK: invoke void @_Z9may_throwv()
// CHECK-NEXT: to label {{.*}} unwind label %[[EHCLEANUP_BB:.*]]
// CHECK: [[EHCLEANUP_BB]]:
// CHECK-NEXT: %[[CLEANUPPAD:.*]] = cleanuppad within none []
// CHECK-NEXT: call noundef ptr @_ZN7CleanupD1Ev(ptr {{[^,]*}} %{{.*}}) {{.*}} [ "funclet"(token %[[CLEANUPPAD]]) ]
// CHECK-NEXT: cleanupret from %[[CLEANUPPAD]] unwind to caller
// Possibly throwing function call within a catch
void test4() {
try {
may_throw();
} catch (int) {
may_throw();
}
}
// CHECK-LABEL: @_Z5test4v()
// CHECK: %[[CATCHSWITCH]] = catchswitch within none [label %[[CATCHSTART_BB]]] unwind to caller
// CHECK: [[CATCHSTART_BB]]:
// CHECK: %[[CATCHPAD:.*]] = catchpad within %[[CATCHSWITCH]] [ptr @_ZTIi]
// CHECK: invoke void @_Z9may_throwv() [ "funclet"(token %[[CATCHPAD]]) ]
// CHECK-NEXT: to label %[[INVOKE_CONT_BB:.*]] unwind label %[[EHCLEANUP_BB:.*]]
// CHECK: [[INVOKE_CONT_BB]]:
// CHECK-NEXT: call void @__cxa_end_catch() {{.*}} [ "funclet"(token %[[CATCHPAD]]) ]
// CHECK-NEXT: catchret from %[[CATCHPAD]] to label
// CHECK: [[EHCLEANUP_BB]]:
// CHECK-NEXT: %[[CLEANUPPAD:.*]] = cleanuppad within %[[CATCHPAD]] []
// CHECK-NEXT: call void @__cxa_end_catch() {{.*}} [ "funclet"(token %[[CLEANUPPAD]]) ]
// CHECK-NEXT: cleanupret from %[[CLEANUPPAD]] unwind to caller
// Possibly throwing function call within a catch-all
void test5() {
try {
may_throw();
} catch (...) {
may_throw();
}
}
// CHECK-LABEL: @_Z5test5v()
// CHECK: %[[CATCHSWITCH:.*]] = catchswitch within none [label %[[CATCHSTART_BB]]] unwind to caller
// CHECK: [[CATCHSTART_BB]]:
// CHECK: %[[CATCHPAD:.*]] = catchpad within %[[CATCHSWITCH]] [ptr null]
// CHECK: invoke void @_Z9may_throwv() [ "funclet"(token %[[CATCHPAD]]) ]
// CHECK-NEXT: to label %[[INVOKE_CONT_BB0:.*]] unwind label %[[EHCLEANUP_BB:.*]]
// CHECK: [[INVOKE_CONT_BB0]]:
// CHECK-NEXT: call void @__cxa_end_catch() [ "funclet"(token %[[CATCHPAD]]) ]
// CHECK-NEXT: catchret from %[[CATCHPAD]] to label
// CHECK: [[EHCLEANUP_BB]]:
// CHECK-NEXT: %[[CLEANUPPAD0:.*]] = cleanuppad within %[[CATCHPAD]] []
// CHECK-NEXT: invoke void @__cxa_end_catch() [ "funclet"(token %[[CLEANUPPAD0]]) ]
// CHECK-NEXT: to label %[[INVOKE_CONT_BB1:.*]] unwind label %[[TERMINATE_BB:.*]]
// CHECK: [[INVOKE_CONT_BB1]]:
// CHECK-NEXT: cleanupret from %[[CLEANUPPAD0]] unwind to caller
// CHECK: [[TERMINATE_BB]]:
// CHECK-NEXT: %[[CLEANUPPAD1:.*]] = cleanuppad within %[[CLEANUPPAD0]] []
// CHECK-NEXT: call void @_ZSt9terminatev() {{.*}} [ "funclet"(token %[[CLEANUPPAD1]]) ]
// CHECK-NEXT: unreachable
// Try-catch with cleanups
void test6() {
Cleanup c1;
try {
Cleanup c2;
may_throw();
} catch (int) {
Cleanup c3;
may_throw();
}
}
// CHECK-LABEL: @_Z5test6v()
// CHECK: invoke void @_Z9may_throwv()
// CHECK-NEXT: to label %{{.*}} unwind label %[[EHCLEANUP_BB0:.*]]
// CHECK: [[EHCLEANUP_BB0]]:
// CHECK-NEXT: %[[CLEANUPPAD0:.*]] = cleanuppad within none []
// CHECK-NEXT: call noundef ptr @_ZN7CleanupD1Ev(ptr {{.*}}) {{.*}} [ "funclet"(token %[[CLEANUPPAD0]]) ]
// CHECK-NEXT: cleanupret from %[[CLEANUPPAD0]] unwind label %[[CATCH_DISPATCH_BB:.*]]
// CHECK: [[CATCH_DISPATCH_BB]]:
// CHECK-NEXT: %[[CATCHSWITCH:.*]] = catchswitch within none [label %[[CATCHSTART_BB:.*]]] unwind label %[[EHCLEANUP_BB1:.*]]
// CHECK: [[CATCHSTART_BB]]:
// CHECK-NEXT: %[[CATCHPAD:.*]] = catchpad within %[[CATCHSWITCH]] [ptr @_ZTIi]
// CHECK: br i1 %{{.*}}, label %[[CATCH_INT_BB:.*]], label %[[RETHROW_BB:.*]]
// CHECK: [[CATCH_INT_BB]]:
// CHECK: invoke void @_Z9may_throwv() [ "funclet"(token %[[CATCHPAD]]) ]
// CHECK-NEXT: to label %[[INVOKE_CONT_BB:.*]] unwind label %[[EHCLEANUP_BB2:.*]]
// CHECK: [[INVOKE_CONT_BB]]:
// CHECK: catchret from %[[CATCHPAD]] to label %{{.*}}
// CHECK: [[RETHROW_BB]]:
// CHECK-NEXT: invoke void @llvm.wasm.rethrow() {{.*}} [ "funclet"(token %[[CATCHPAD]]) ]
// CHECK-NEXT: to label %[[UNREACHABLE_BB:.*]] unwind label %[[EHCLEANUP_BB1:.*]]
// CHECK: [[EHCLEANUP_BB2]]:
// CHECK-NEXT: %[[CLEANUPPAD2:.*]] = cleanuppad within %[[CATCHPAD]] []
// CHECK-NEXT: call noundef ptr @_ZN7CleanupD1Ev(ptr {{[^,]*}} %{{.*}}) {{.*}} [ "funclet"(token %[[CLEANUPPAD2]]) ]
// CHECK-NEXT: cleanupret from %[[CLEANUPPAD2]] unwind label %[[EHCLEANUP_BB3:.*]]
// CHECK: [[EHCLEANUP_BB3]]:
// CHECK-NEXT: %[[CLEANUPPAD3:.*]] = cleanuppad within %[[CATCHPAD]] []
// CHECK: cleanupret from %[[CLEANUPPAD3]] unwind label %[[EHCLEANUP_BB1:.*]]
// CHECK: [[EHCLEANUP_BB1]]:
// CHECK-NEXT: %[[CLEANUPPAD1:.*]] = cleanuppad within none []
// CHECK-NEXT: call noundef ptr @_ZN7CleanupD1Ev(ptr {{[^,]*}} %{{.*}}) {{.*}} [ "funclet"(token %[[CLEANUPPAD1]]) ]
// CHECK-NEXT: cleanupret from %[[CLEANUPPAD1]] unwind to caller
// CHECK: [[UNREACHABLE_BB]]:
// CHECK-NEXT: unreachable
// Nested try-catches within a try with cleanups
void test7() {
Cleanup c1;
may_throw();
try {
Cleanup c2;
may_throw();
try {
Cleanup c3;
may_throw();
} catch (int) {
may_throw();
} catch (double) {
may_throw();
}
} catch (int) {
may_throw();
} catch (...) {
may_throw();
}
}
// CHECK-LABEL: @_Z5test7v()
// CHECK: invoke void @_Z9may_throwv()
// CHECK: invoke void @_Z9may_throwv()
// CHECK: invoke void @_Z9may_throwv()
// CHECK: %[[CLEANUPPAD0:.*]] = cleanuppad within none []
// CHECK: cleanupret from %[[CLEANUPPAD0]] unwind label
// CHECK: %[[CATCHSWITCH0:.*]] = catchswitch within none
// CHECK: %[[CATCHPAD0:.*]] = catchpad within %[[CATCHSWITCH0]] [ptr @_ZTIi, ptr @_ZTId]
// CHECK: invoke void @_Z9may_throwv() [ "funclet"(token %[[CATCHPAD0]]) ]
// CHECK: catchret from %[[CATCHPAD0]] to label
// CHECK: invoke void @_Z9may_throwv() [ "funclet"(token %[[CATCHPAD0]]) ]
// CHECK: catchret from %[[CATCHPAD0]] to label
// CHECK: invoke void @llvm.wasm.rethrow() {{.*}} [ "funclet"(token %[[CATCHPAD0]]) ]
// CHECK: %[[CLEANUPPAD1:.*]] = cleanuppad within %[[CATCHPAD0]] []
// CHECK: cleanupret from %[[CLEANUPPAD1]] unwind label
// CHECK: %[[CLEANUPPAD2:.*]] = cleanuppad within %[[CATCHPAD0]] []
// CHECK: cleanupret from %[[CLEANUPPAD2]] unwind label
// CHECK: %[[CLEANUPPAD3:.*]] = cleanuppad within none []
// CHECK: cleanupret from %[[CLEANUPPAD3]] unwind label
// CHECK: %[[CATCHSWITCH1:.*]] = catchswitch within none
// CHECK: %[[CATCHPAD1:.*]] = catchpad within %[[CATCHSWITCH1]] [ptr @_ZTIi, ptr null]
// CHECK: invoke void @_Z9may_throwv() [ "funclet"(token %[[CATCHPAD1]]) ]
// CHECK: catchret from %[[CATCHPAD1]] to label
// CHECK: invoke void @_Z9may_throwv() [ "funclet"(token %[[CATCHPAD1]]) ]
// CHECK: invoke void @__cxa_end_catch() [ "funclet"(token %[[CATCHPAD1]]) ]
// CHECK: catchret from %[[CATCHPAD1]] to label
// CHECK: %[[CLEANUPPAD4:.*]] = cleanuppad within %[[CATCHPAD1]] []
// CHECK: invoke void @__cxa_end_catch() [ "funclet"(token %[[CLEANUPPAD4]]) ]
// CHECK: cleanupret from %[[CLEANUPPAD4]] unwind label
// CHECK: %[[CLEANUPPAD5:.*]] = cleanuppad within %[[CATCHPAD1]] []
// CHECK: cleanupret from %[[CLEANUPPAD5]] unwind label
// CHECK: %[[CLEANUPPAD6:.*]] = cleanuppad within none []
// CHECK: cleanupret from %[[CLEANUPPAD6]] unwind to caller
// CHECK: unreachable
// CHECK: %[[CLEANUPPAD7:.*]] = cleanuppad within %[[CLEANUPPAD4]] []
// CHECK: call void @_ZSt9terminatev() {{.*}} [ "funclet"(token %[[CLEANUPPAD7]]) ]
// CHECK: unreachable
// Nested try-catches within a catch
void test8() {
try {
may_throw();
} catch (int) {
try {
may_throw();
} catch (int) {
may_throw();
}
}
}
// CHECK-LABEL: @_Z5test8v()
// CHECK: invoke void @_Z9may_throwv()
// CHECK: %[[CATCHSWITCH0:.*]] = catchswitch within none
// CHECK: %[[CATCHPAD0:.*]] = catchpad within %[[CATCHSWITCH0]] [ptr @_ZTIi]
// CHECK: invoke void @_Z9may_throwv() [ "funclet"(token %[[CATCHPAD0]]) ]
// CHECK: %[[CATCHSWITCH1:.*]] = catchswitch within %[[CATCHPAD0]]
// CHECK: %[[CATCHPAD1:.*]] = catchpad within %[[CATCHSWITCH1]] [ptr @_ZTIi]
// CHECK: invoke void @_Z9may_throwv() [ "funclet"(token %[[CATCHPAD1]]) ]
// CHECK: catchret from %[[CATCHPAD1]] to label
// CHECK: invoke void @llvm.wasm.rethrow() {{.*}} [ "funclet"(token %[[CATCHPAD1]]) ]
// CHECK: catchret from %[[CATCHPAD0]] to label
// CHECK: call void @llvm.wasm.rethrow() {{.*}} [ "funclet"(token %[[CATCHPAD0]]) ]
// CHECK: unreachable
// CHECK: %[[CLEANUPPAD0:.*]] = cleanuppad within %[[CATCHPAD1]] []
// CHECK: cleanupret from %[[CLEANUPPAD0]] unwind label
// CHECK: %[[CLEANUPPAD1:.*]] = cleanuppad within %[[CATCHPAD0]] []
// CHECK: cleanupret from %[[CLEANUPPAD1]] unwind to caller
// CHECK: unreachable
void noexcept_throw() noexcept {
throw 3;
}
// CATCH-LABEL: define void @_Z14noexcept_throwv()
// CHECK: %{{.*}} = cleanuppad within none []
// CHECK-NEXT: call void @_ZSt9terminatev()
// RUN: %clang_cc1 %s -triple wasm32-unknown-unknown -fms-extensions -fexceptions -fcxx-exceptions -exception-model=wasm -target-feature +exception-handling -emit-llvm -o - -std=c++11 2>&1 | FileCheck %s --check-prefix=WARNING-DEFAULT
// RUN: %clang_cc1 %s -triple wasm32-unknown-unknown -fms-extensions -fexceptions -fcxx-exceptions -exception-model=wasm -target-feature +exception-handling -Wwasm-exception-spec -emit-llvm -o - -std=c++11 2>&1 | FileCheck %s --check-prefix=WARNING-ON
// RUN: %clang_cc1 %s -triple wasm32-unknown-unknown -fms-extensions -fexceptions -fcxx-exceptions -exception-model=wasm -target-feature +exception-handling -Wno-wasm-exception-spec -emit-llvm -o - -std=c++11 2>&1 | FileCheck %s --check-prefix=WARNING-OFF
// RUN: %clang_cc1 %s -triple wasm32-unknown-unknown -fexceptions -fcxx-exceptions -emit-llvm -o - -std=c++11 2>&1 | FileCheck %s --check-prefix=EM-EH-WARNING
// Wasm EH ignores dynamic exception specifications with types at the moment.
// This is controlled by -Wwasm-exception-spec, which is on by default. This
// warning can be suppressed with -Wno-wasm-exception-spec. Checks if a warning
// message is correctly printed or not printed depending on the options.
void test9() throw(int) {
}
// WARNING-DEFAULT: warning: dynamic exception specifications with types are currently ignored in wasm
// WARNING-ON: warning: dynamic exception specifications with types are currently ignored in wasm
// WARNING-OFF-NOT: warning: dynamic exception specifications with types are currently ignored in wasm
// EM-EH-WARNING: warning: dynamic exception specifications with types are currently ignored in wasm
// Wasm curremtly treats 'throw()' in the same way as 'noexept'. Check if the
// same warning message is printed as if when a 'noexcept' function throws.
void test10() throw() {
throw 3;
}
// WARNING-DEFAULT: warning: 'test10' has a non-throwing exception specification but can still throw
// WARNING-DEFAULT: function declared non-throwing here
// Here we only check if the command enables wasm exception handling in the
// backend so that exception handling instructions can be generated in .s file.
// RUN: %clang_cc1 %s -triple wasm32-unknown-unknown -fms-extensions -fexceptions -fcxx-exceptions -mllvm -wasm-enable-eh -exception-model=wasm -target-feature +exception-handling -S -o - -std=c++11 | FileCheck %s --check-prefix=ASSEMBLY
// ASSEMBLY: try
// ASSEMBLY: catch
// ASSEMBLY: rethrow
// ASSEMBLY: end_try