| ; RUN: llc < %s -asm-verbose=false -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -wasm-keep-registers -wasm-use-legacy-eh -exception-model=wasm -mattr=+exception-handling -verify-machineinstrs | FileCheck --implicit-check-not=ehgcr -allow-deprecated-dag-overlap %s | |
| ; RUN: llc < %s -asm-verbose=false -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -wasm-keep-registers -wasm-use-legacy-eh -exception-model=wasm -mattr=+exception-handling -verify-machineinstrs -O0 | |
| ; RUN: llc < %s -disable-wasm-fallthrough-return-opt -wasm-keep-registers -wasm-use-legacy-eh -exception-model=wasm -mattr=+exception-handling | |
| target triple = "wasm32-unknown-unknown" | |
| %struct.Temp = type { i8 } | |
| @_ZTIi = external dso_local constant ptr | |
| ; CHECK: .tagtype __cpp_exception i32 | |
| ; CHECK-LABEL: throw: | |
| ; CHECK: throw __cpp_exception, $0 | |
| ; CHECK-NOT: unreachable | |
| define void @throw(ptr %p) { | |
| call void @llvm.wasm.throw(i32 0, ptr %p) | |
| ret void | |
| } | |
| ; Simple test with a try-catch | |
| ; | |
| ; void foo(); | |
| ; void catch() { | |
| ; try { | |
| ; foo(); | |
| ; } catch (int) { | |
| ; } | |
| ; } | |
| ; CHECK-LABEL: catch: | |
| ; CHECK: global.get ${{.+}}=, __stack_pointer | |
| ; CHECK: try | |
| ; CHECK: call foo | |
| ; CHECK: catch $[[EXN:[0-9]+]]=, __cpp_exception | |
| ; CHECK: global.set __stack_pointer | |
| ; CHECK: call $push[[CONTEXT:.*]]=, _Unwind_GetWasmLPadContext | |
| ; CHECK: call $drop=, __gxx_wasm_personality_v0, $[[EXN]] | |
| ; CHECK: block | |
| ; CHECK: br_if 0 | |
| ; CHECK: call $drop=, __cxa_begin_catch | |
| ; CHECK: call __cxa_end_catch | |
| ; CHECK: br 1 | |
| ; CHECK: end_block | |
| ; CHECK: rethrow 0 | |
| ; CHECK: end_try | |
| define void @catch() personality ptr @__gxx_wasm_personality_v0 { | |
| entry: | |
| invoke void @foo() | |
| to label %try.cont unwind label %catch.dispatch | |
| catch.dispatch: ; preds = %entry | |
| %0 = catchswitch within none [label %catch.start] unwind to caller | |
| catch.start: ; preds = %catch.dispatch | |
| %1 = catchpad within %0 [ptr @_ZTIi] | |
| %2 = call ptr @llvm.wasm.get.exception(token %1) | |
| %3 = call i32 @llvm.wasm.get.ehselector(token %1) | |
| %4 = call i32 @llvm.eh.typeid.for(ptr @_ZTIi) | |
| %matches = icmp eq i32 %3, %4 | |
| br i1 %matches, label %catch, label %rethrow | |
| catch: ; preds = %catch.start | |
| %5 = call ptr @__cxa_begin_catch(ptr %2) [ "funclet"(token %1) ] | |
| call void @__cxa_end_catch() [ "funclet"(token %1) ] | |
| catchret from %1 to label %try.cont | |
| rethrow: ; preds = %catch.start | |
| call void @llvm.wasm.rethrow() [ "funclet"(token %1) ] | |
| unreachable | |
| try.cont: ; preds = %catch, %entry | |
| ret void | |
| } | |
| ; Destructor (cleanup) test | |
| ; | |
| ; void foo(); | |
| ; struct Temp { | |
| ; ~Temp() {} | |
| ; }; | |
| ; void cleanup() { | |
| ; Temp t; | |
| ; foo(); | |
| ; } | |
| ; CHECK-LABEL: cleanup: | |
| ; CHECK: try | |
| ; CHECK: call foo | |
| ; CHECK: catch_all | |
| ; CHECK: global.set __stack_pointer | |
| ; CHECK: call $drop=, _ZN4TempD2Ev | |
| ; CHECK: rethrow 0 | |
| ; CHECK: end_try | |
| define void @cleanup() personality ptr @__gxx_wasm_personality_v0 { | |
| entry: | |
| %t = alloca %struct.Temp, align 1 | |
| invoke void @foo() | |
| to label %invoke.cont unwind label %ehcleanup | |
| invoke.cont: ; preds = %entry | |
| %call = call ptr @_ZN4TempD2Ev(ptr %t) | |
| ret void | |
| ehcleanup: ; preds = %entry | |
| %0 = cleanuppad within none [] | |
| %call1 = call ptr @_ZN4TempD2Ev(ptr %t) [ "funclet"(token %0) ] | |
| cleanupret from %0 unwind to caller | |
| } | |
| ; Calling a function that may throw within a 'catch (...)' generates a | |
| ; terminatepad, because __cxa_end_catch() also can throw within 'catch (...)'. | |
| ; | |
| ; void foo(); | |
| ; void terminatepad() { | |
| ; try { | |
| ; foo(); | |
| ; } catch (...) { | |
| ; foo(); | |
| ; } | |
| ; } | |
| ; CHECK-LABEL: terminatepad | |
| ; CHECK: try | |
| ; CHECK: call foo | |
| ; CHECK: catch | |
| ; CHECK: call $drop=, __cxa_begin_catch | |
| ; CHECK: try | |
| ; CHECK: call foo | |
| ; CHECK: catch_all | |
| ; CHECK: try | |
| ; CHECK: call __cxa_end_catch | |
| ; CHECK: catch_all | |
| ; CHECK: call _ZSt9terminatev | |
| ; CHECK: unreachable | |
| ; CHECK: end_try | |
| ; CHECK: rethrow | |
| ; CHECK: end_try | |
| ; CHECK: call __cxa_end_catch | |
| ; CHECK: end_try | |
| define void @terminatepad() personality ptr @__gxx_wasm_personality_v0 { | |
| entry: | |
| invoke void @foo() | |
| to label %try.cont unwind label %catch.dispatch | |
| catch.dispatch: ; preds = %entry | |
| %0 = catchswitch within none [label %catch.start] unwind to caller | |
| catch.start: ; preds = %catch.dispatch | |
| %1 = catchpad within %0 [ptr null] | |
| %2 = call ptr @llvm.wasm.get.exception(token %1) | |
| %3 = call i32 @llvm.wasm.get.ehselector(token %1) | |
| %4 = call ptr @__cxa_begin_catch(ptr %2) [ "funclet"(token %1) ] | |
| invoke void @foo() [ "funclet"(token %1) ] | |
| to label %invoke.cont1 unwind label %ehcleanup | |
| invoke.cont1: ; preds = %catch.start | |
| call void @__cxa_end_catch() [ "funclet"(token %1) ] | |
| catchret from %1 to label %try.cont | |
| try.cont: ; preds = %invoke.cont1, %entry | |
| ret void | |
| ehcleanup: ; preds = %catch.start | |
| %5 = cleanuppad within %1 [] | |
| invoke void @__cxa_end_catch() [ "funclet"(token %5) ] | |
| to label %invoke.cont2 unwind label %terminate | |
| invoke.cont2: ; preds = %ehcleanup | |
| cleanupret from %5 unwind to caller | |
| terminate: ; preds = %ehcleanup | |
| %6 = cleanuppad within %5 [] | |
| call void @_ZSt9terminatev() #2 [ "funclet"(token %6) ] | |
| unreachable | |
| } | |
| ; Tests prologues and epilogues are not generated within EH scopes. | |
| ; They should not be treated as funclets; BBs starting with a catch instruction | |
| ; should not have a prologue, and BBs ending with a catchret/cleanupret should | |
| ; not have an epilogue. This is separate from __stack_pointer restoring | |
| ; instructions after a catch instruction. | |
| ; | |
| ; void bar(int) noexcept; | |
| ; void no_prolog_epilog_in_ehpad() { | |
| ; int stack_var = 0; | |
| ; bar(stack_var); | |
| ; try { | |
| ; foo(); | |
| ; } catch (int) { | |
| ; foo(); | |
| ; } | |
| ; } | |
| ; CHECK-LABEL: no_prolog_epilog_in_ehpad | |
| ; CHECK: try | |
| ; CHECK: call foo | |
| ; CHECK: catch | |
| ; CHECK-NOT: global.get $push{{.+}}=, __stack_pointer | |
| ; CHECK: global.set __stack_pointer | |
| ; CHECK: block | |
| ; CHECK: block | |
| ; CHECK: br_if 0 | |
| ; CHECK: call $drop=, __cxa_begin_catch | |
| ; CHECK: try | |
| ; CHECK: call foo | |
| ; CHECK: catch | |
| ; CHECK-NOT: global.get $push{{.+}}=, __stack_pointer | |
| ; CHECK: global.set __stack_pointer | |
| ; CHECK: call __cxa_end_catch | |
| ; CHECK: rethrow | |
| ; CHECK-NOT: global.set __stack_pointer, $pop{{.+}} | |
| ; CHECK: end_try | |
| ; CHECK: end_block | |
| ; CHECK: rethrow | |
| ; CHECK: end_block | |
| ; CHECK-NOT: global.set __stack_pointer, $pop{{.+}} | |
| ; CHECK: call __cxa_end_catch | |
| ; CHECK: end_try | |
| define void @no_prolog_epilog_in_ehpad() personality ptr @__gxx_wasm_personality_v0 { | |
| entry: | |
| %stack_var = alloca i32, align 4 | |
| call void @bar(ptr %stack_var) | |
| invoke void @foo() | |
| to label %try.cont unwind label %catch.dispatch | |
| catch.dispatch: ; preds = %entry | |
| %0 = catchswitch within none [label %catch.start] unwind to caller | |
| catch.start: ; preds = %catch.dispatch | |
| %1 = catchpad within %0 [ptr @_ZTIi] | |
| %2 = call ptr @llvm.wasm.get.exception(token %1) | |
| %3 = call i32 @llvm.wasm.get.ehselector(token %1) | |
| %4 = call i32 @llvm.eh.typeid.for(ptr @_ZTIi) | |
| %matches = icmp eq i32 %3, %4 | |
| br i1 %matches, label %catch, label %rethrow | |
| catch: ; preds = %catch.start | |
| %5 = call ptr @__cxa_begin_catch(ptr %2) [ "funclet"(token %1) ] | |
| %6 = load float, ptr %5, align 4 | |
| invoke void @foo() [ "funclet"(token %1) ] | |
| to label %invoke.cont1 unwind label %ehcleanup | |
| invoke.cont1: ; preds = %catch | |
| call void @__cxa_end_catch() [ "funclet"(token %1) ] | |
| catchret from %1 to label %try.cont | |
| rethrow: ; preds = %catch.start | |
| call void @llvm.wasm.rethrow() [ "funclet"(token %1) ] | |
| unreachable | |
| try.cont: ; preds = %invoke.cont1, %entry | |
| ret void | |
| ehcleanup: ; preds = %catch | |
| %7 = cleanuppad within %1 [] | |
| call void @__cxa_end_catch() [ "funclet"(token %7) ] | |
| cleanupret from %7 unwind to caller | |
| } | |
| ; When a function does not have stack-allocated objects, it does not need to | |
| ; store SP back to __stack_pointer global at the epilog. | |
| ; | |
| ; void foo(); | |
| ; void no_sp_writeback() { | |
| ; try { | |
| ; foo(); | |
| ; } catch (...) { | |
| ; } | |
| ; } | |
| ; CHECK-LABEL: no_sp_writeback | |
| ; CHECK: try | |
| ; CHECK: call foo | |
| ; CHECK: catch | |
| ; CHECK: call $drop=, __cxa_begin_catch | |
| ; CHECK: call __cxa_end_catch | |
| ; CHECK: end_try | |
| ; CHECK-NOT: global.set __stack_pointer | |
| ; CHECK: return | |
| define void @no_sp_writeback() personality ptr @__gxx_wasm_personality_v0 { | |
| entry: | |
| invoke void @foo() | |
| to label %try.cont unwind label %catch.dispatch | |
| catch.dispatch: ; preds = %entry | |
| %0 = catchswitch within none [label %catch.start] unwind to caller | |
| catch.start: ; preds = %catch.dispatch | |
| %1 = catchpad within %0 [ptr null] | |
| %2 = call ptr @llvm.wasm.get.exception(token %1) | |
| %3 = call i32 @llvm.wasm.get.ehselector(token %1) | |
| %4 = call ptr @__cxa_begin_catch(ptr %2) [ "funclet"(token %1) ] | |
| call void @__cxa_end_catch() [ "funclet"(token %1) ] | |
| catchret from %1 to label %try.cont | |
| try.cont: ; preds = %catch.start, %entry | |
| ret void | |
| } | |
| ; When the result of @llvm.wasm.get.exception is not used. This is created to | |
| ; fix a bug in LateEHPrepare and this should not crash. | |
| define void @get_exception_wo_use() personality ptr @__gxx_wasm_personality_v0 { | |
| entry: | |
| invoke void @foo() | |
| to label %try.cont unwind label %catch.dispatch | |
| catch.dispatch: ; preds = %entry | |
| %0 = catchswitch within none [label %catch.start] unwind to caller | |
| catch.start: ; preds = %catch.dispatch | |
| %1 = catchpad within %0 [ptr null] | |
| %2 = call ptr @llvm.wasm.get.exception(token %1) | |
| %3 = call i32 @llvm.wasm.get.ehselector(token %1) | |
| catchret from %1 to label %try.cont | |
| try.cont: ; preds = %catch.start, %entry | |
| ret void | |
| } | |
| ; Tests a case when a cleanup region (cleanuppad ~ clanupret) contains another | |
| ; catchpad | |
| define void @complex_cleanup_region() personality ptr @__gxx_wasm_personality_v0 { | |
| entry: | |
| invoke void @foo() | |
| to label %invoke.cont unwind label %ehcleanup | |
| invoke.cont: ; preds = %entry | |
| ret void | |
| ehcleanup: ; preds = %entry | |
| %0 = cleanuppad within none [] | |
| invoke void @foo() [ "funclet"(token %0) ] | |
| to label %ehcleanupret unwind label %catch.dispatch | |
| catch.dispatch: ; preds = %ehcleanup | |
| %1 = catchswitch within %0 [label %catch.start] unwind label %ehcleanup.1 | |
| catch.start: ; preds = %catch.dispatch | |
| %2 = catchpad within %1 [ptr null] | |
| %3 = call ptr @llvm.wasm.get.exception(token %2) | |
| %4 = call i32 @llvm.wasm.get.ehselector(token %2) | |
| catchret from %2 to label %ehcleanupret | |
| ehcleanup.1: ; preds = %catch.dispatch | |
| %5 = cleanuppad within %0 [] | |
| unreachable | |
| ehcleanupret: ; preds = %catch.start, %ehcleanup | |
| cleanupret from %0 unwind to caller | |
| } | |
| ; Regression test for the bug that 'rethrow' was not treated correctly as a | |
| ; terminator in isel. | |
| define void @rethrow_terminator() personality ptr @__gxx_wasm_personality_v0 { | |
| entry: | |
| invoke void @foo() | |
| to label %try.cont unwind label %catch.dispatch | |
| catch.dispatch: ; preds = %entry | |
| %0 = catchswitch within none [label %catch.start] unwind label %ehcleanup | |
| catch.start: ; preds = %catch.dispatch | |
| %1 = catchpad within %0 [ptr @_ZTIi] | |
| %2 = call ptr @llvm.wasm.get.exception(token %1) | |
| %3 = call i32 @llvm.wasm.get.ehselector(token %1) | |
| %4 = call i32 @llvm.eh.typeid.for.p0(ptr @_ZTIi) | |
| %matches = icmp eq i32 %3, %4 | |
| br i1 %matches, label %catch, label %rethrow | |
| catch: ; preds = %catch.start | |
| %5 = call ptr @__cxa_begin_catch(ptr %2) [ "funclet"(token %1) ] | |
| %6 = load i32, ptr %5, align 4 | |
| call void @__cxa_end_catch() [ "funclet"(token %1) ] | |
| catchret from %1 to label %try.cont | |
| rethrow: ; preds = %catch.start | |
| invoke void @llvm.wasm.rethrow() #1 [ "funclet"(token %1) ] | |
| to label %unreachable unwind label %ehcleanup | |
| try.cont: ; preds = %entry, %catch | |
| ret void | |
| ehcleanup: ; preds = %rethrow, %catch.dispatch | |
| ; 'rethrow' BB is this BB's predecessor, and its | |
| ; 'invoke void @llvm.wasm.rethrow()' is lowered down to a 'RETHROW' in Wasm | |
| ; MIR. And this 'phi' creates 'CONST_I32' instruction in the predecessor | |
| ; 'rethrow' BB. If 'RETHROW' is not treated correctly as a terminator, it can | |
| ; create a BB like | |
| ; bb.3.rethrow: | |
| ; RETHROW 0 | |
| ; %0 = CONST_I32 20 | |
| ; BR ... | |
| %tmp = phi i32 [ 10, %catch.dispatch ], [ 20, %rethrow ] | |
| %7 = cleanuppad within none [] | |
| call void @take_i32(i32 %tmp) [ "funclet"(token %7) ] | |
| cleanupret from %7 unwind to caller | |
| unreachable: ; preds = %rethrow | |
| unreachable | |
| } | |
| ; The bitcode below is generated when the code below is compiled and | |
| ; Temp::~Temp() is inlined into inlined_cleanupret(): | |
| ; | |
| ; void inlined_cleanupret() { | |
| ; try { | |
| ; Temp t; | |
| ; throw 2; | |
| ; } catch (...) | |
| ; } | |
| ; | |
| ; Temp::~Temp() { | |
| ; try { | |
| ; throw 1; | |
| ; } catch (...) { | |
| ; } | |
| ; } | |
| ; | |
| ; ~Temp() generates cleanupret, which is lowered to a 'rethrow' later. That | |
| ; rethrow's immediate argument should correctly target the top-level cleanuppad | |
| ; (catch_all). This is a regression test for the bug where we did not compute | |
| ; rethrow's argument correctly. | |
| ; CHECK-LABEL: inlined_cleanupret: | |
| ; CHECK: try | |
| ; CHECK: call __cxa_throw | |
| ; CHECK: catch_all | |
| ; CHECK: try | |
| ; CHECK: try | |
| ; CHECK: call __cxa_throw | |
| ; CHECK: catch | |
| ; CHECK: call __cxa_end_catch | |
| ; CHECK: try | |
| ; CHECK: try | |
| ; Note that this rethrow targets the top-level catch_all | |
| ; CHECK: rethrow 4 | |
| ; CHECK: catch | |
| ; CHECK: call __cxa_end_catch | |
| ; CHECK: return | |
| ; CHECK: end_try | |
| ; CHECK: delegate 3 | |
| ; CHECK: end_try | |
| ; CHECK: catch_all | |
| ; CHECK: call _ZSt9terminatev | |
| ; CHECK: end_try | |
| ; CHECK: end_try | |
| define void @inlined_cleanupret() personality ptr @__gxx_wasm_personality_v0 { | |
| entry: | |
| %exception = tail call ptr @__cxa_allocate_exception(i32 4) | |
| store i32 2, ptr %exception, align 16 | |
| invoke void @__cxa_throw(ptr nonnull %exception, ptr nonnull @_ZTIi, ptr null) | |
| to label %unreachable unwind label %ehcleanup | |
| ehcleanup: ; preds = %entry | |
| %0 = cleanuppad within none [] | |
| %exception.i = call ptr @__cxa_allocate_exception(i32 4) [ "funclet"(token %0) ] | |
| store i32 1, ptr %exception.i, align 16 | |
| invoke void @__cxa_throw(ptr nonnull %exception.i, ptr nonnull @_ZTIi, ptr null) [ "funclet"(token %0) ] | |
| to label %unreachable unwind label %catch.dispatch.i | |
| catch.dispatch.i: ; preds = %ehcleanup | |
| %1 = catchswitch within %0 [label %catch.start.i] unwind label %terminate.i | |
| catch.start.i: ; preds = %catch.dispatch.i | |
| %2 = catchpad within %1 [ptr null] | |
| %3 = tail call ptr @llvm.wasm.get.exception(token %2) | |
| %4 = tail call i32 @llvm.wasm.get.ehselector(token %2) | |
| %5 = call ptr @__cxa_begin_catch(ptr %3) [ "funclet"(token %2) ] | |
| invoke void @__cxa_end_catch() [ "funclet"(token %2) ] | |
| to label %invoke.cont.i unwind label %terminate.i | |
| invoke.cont.i: ; preds = %catch.start.i | |
| catchret from %2 to label %_ZN4TempD2Ev.exit | |
| terminate.i: ; preds = %catch.start.i, %catch.dispatch.i | |
| %6 = cleanuppad within %0 [] | |
| call void @_ZSt9terminatev() #2 [ "funclet"(token %6) ] | |
| unreachable | |
| _ZN4TempD2Ev.exit: ; preds = %invoke.cont.i | |
| cleanupret from %0 unwind label %catch.dispatch | |
| catch.dispatch: ; preds = %_ZN4TempD2Ev.exit | |
| %7 = catchswitch within none [label %catch.start] unwind to caller | |
| catch.start: ; preds = %catch.dispatch | |
| %8 = catchpad within %7 [ptr null] | |
| %9 = tail call ptr @llvm.wasm.get.exception(token %8) | |
| %10 = tail call i32 @llvm.wasm.get.ehselector(token %8) | |
| %11 = call ptr @__cxa_begin_catch(ptr %9) #8 [ "funclet"(token %8) ] | |
| call void @__cxa_end_catch() [ "funclet"(token %8) ] | |
| catchret from %8 to label %try.cont | |
| try.cont: ; preds = %catch.start | |
| ret void | |
| unreachable: ; preds = %entry | |
| unreachable | |
| } | |
| ; Regression test for a bug where, when an invoke unwinds to a catchswitch, the | |
| ; catchswitch's unwind destination was not included in the invoke's unwind | |
| ; destination when there was no direct link from catch.start to there. | |
| ; CHECK-LABEL: unwind_destinations: | |
| ; CHECK: try | |
| ; CHECK: try | |
| ; CHECK: call foo | |
| ; CHECK: catch $0=, __cpp_exception | |
| ; CHECK: call _ZSt9terminatev | |
| ; CHECK: unreachable | |
| ; CHECK: end_try | |
| ; Note the below is "terminate" BB and should not be DCE'd | |
| ; CHECK: catch_all | |
| ; CHECK: call _ZSt9terminatev | |
| ; CHECK: unreachable | |
| ; CHECK: end_try | |
| ; CHECK: return | |
| define void @unwind_destinations() personality ptr @__gxx_wasm_personality_v0 { | |
| entry: | |
| invoke void @foo() | |
| to label %try.cont unwind label %catch.dispatch | |
| catch.dispatch: ; preds = %entry | |
| %0 = catchswitch within none [label %catch.start] unwind label %terminate | |
| catch.start: ; preds = %catch.dispatch | |
| %1 = catchpad within %0 [ptr null] | |
| %2 = call ptr @llvm.wasm.get.exception(token %1) | |
| %3 = call ptr @__cxa_begin_catch(ptr %2) #5 [ "funclet"(token %1) ] | |
| call void @_ZSt9terminatev() #2 [ "funclet"(token %1) ] | |
| unreachable | |
| ; Even if there is no link from catch.start to this terminate BB, when there is | |
| ; an exception that catch.start does not catch (e.g. a foreign exception), it | |
| ; should end up here, so this BB should NOT be DCE'ed | |
| terminate: ; preds = %catch.dispatch | |
| %4 = cleanuppad within none [] | |
| call void @_ZSt9terminatev() #2 [ "funclet"(token %4) ] | |
| unreachable | |
| try.cont: ; preds = %entry | |
| ret void | |
| } | |
| declare void @foo() | |
| declare void @bar(ptr) | |
| declare void @take_i32(i32) | |
| declare i32 @__gxx_wasm_personality_v0(ptr) | |
| ; Function Attrs: noreturn | |
| declare void @llvm.wasm.throw(i32, ptr) #1 | |
| ; Function Attrs: nounwind | |
| declare ptr @llvm.wasm.get.exception(token) #0 | |
| ; Function Attrs: nounwind | |
| declare i32 @llvm.wasm.get.ehselector(token) #0 | |
| ; Function Attrs: noreturn | |
| declare void @llvm.wasm.rethrow() #1 | |
| ; Function Attrs: nounwind | |
| declare i32 @llvm.eh.typeid.for(ptr) #0 | |
| ; Function Attrs: nounwind | |
| declare ptr @__cxa_allocate_exception(i32) #0 | |
| declare ptr @__cxa_begin_catch(ptr) | |
| declare void @__cxa_end_catch() | |
| ; Function Attrs: noreturn | |
| declare void @__cxa_throw(ptr, ptr, ptr) #1 | |
| declare void @_ZSt9terminatev() | |
| declare ptr @_ZN4TempD2Ev(ptr returned) | |
| attributes #0 = { nounwind } | |
| attributes #1 = { noreturn } | |
| attributes #2 = { noreturn nounwind } | |
| ;; The exception tag should not be defined locally | |
| ; CHECK-NOT: __cpp_exception: |