
Unlike in Itanium EH IR, WinEH IR's unwinding instructions (e.g. `invoke`s) can have multiple possible unwind destinations. For example: ```ll entry: invoke void @foo() to label %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] ... terminate: ; preds = %catch.dispatch %2 = catchpad within none [] ... ... ``` In this case, if an exception is not caught by `catch.dispatch` (and thus `catch.start`), it should next unwind to `terminate`. `findUnwindDestination` in ISel gathers the list of this unwind destinations traversing the unwind edges:ae42f07103/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp (L2089-L2150)
But we don't use that, and instead use our custom `findWasmUnwindDestinations` that only adds the first unwind destination, `catch.start`, to the successor list of `entry`, and not `terminate`:ae42f07103/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp (L2037-L2087)
The reason behind it was, as described in the comment block in the code, it was assumed that there always would be an `invoke` that connects `catch.start` and `terminate`. In case of `catch (type)`, there will be `call void @llvm.wasm.rethrow()` in `catch.start`'s predecessor that unwinds to the next destination. For example:0db702ac8e/llvm/test/CodeGen/WebAssembly/exception.ll (L429-L430)
In case of `catch (...)`, `__cxa_end_catch` can throw, so it becomes an `invoke` that unwinds to the next destination. For example:0db702ac8e/llvm/test/CodeGen/WebAssembly/exception.ll (L537-L538)
So the unwind ordering relationship between `catch.start` and `terminate` here would be preserved. But turns out this assumption does not always hold. For example: ```ll entry: invoke void @foo() to label %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] ... call void @_ZSt9terminatev() unreachable terminate: ; preds = %catch.dispatch %2 = catchpad within none [] call void @_ZSt9terminatev() unreachable ... ``` In this case there is no `invoke` that connects `catch.start` to `terminate`. So after `catch.dispatch` BB is removed in ISel, `terminate` is considered unreachable and incorrectly removed in DCE. This makes Wasm just use the general `findUnwindDestination`. In that case `entry`'s successor is going to be [`catch.start`, `terminate`]. We can get the first unwind destination by just traversing the list from the front. --- This required another change in WinEHPrepare. WinEHPrepare demotes all PHIs in EH pads because they are funclets in Windows and funclets can't have PHIs. When used in Wasm they are not funclets so we don't need to do that wholesale but we still need to demote PHIs in `catchswitch` BBs because they are deleted during ISel. (So we created [`-demote-catchswitch-only`](a5588b6d20/llvm/lib/CodeGen/WinEHPrepare.cpp (L57-L59)
) option for that) But turns out we need to remove PHIs that have a `catchswitch` BB as an incoming block too: ```ll ... catch.dispatch: %0 = catchswitch within none [label %catch.start] unwind label %terminate catch.start: ... somebb: ... ehcleanup ; preds = %catch.dispatch, %somebb %1 = phi i32 [ 10, %catch.dispatch ], [ 20, %somebb ] ... ``` In this case the `phi` in `ehcleanup` BB should be demoted too because `catch.dispatch` BB will be removed in ISel so one if its incoming block will be gone. This pattern didn't manifest before presumably due to how `findWasmUnwindDestinations` worked. (In this example, in our `findWasmUnwindDestinations`, `catch.dispatch` would have had only one successor, `catch.start`. But now `catch.dispatch` has both `catch.start` and `ehcleanup` as successors, revealing this bug. This case is [represented](ab87206c4b/llvm/test/CodeGen/WebAssembly/exception.ll (L445)
) by `rethrow_terminator` function in `exception.ll` (or `exception-legacy.ll`) and without the WinEHPrepare fix it will crash. --- Discovered by the reproducer provided in #126916, even though the bug reported there was not this one.
674 lines
23 KiB
LLVM
674 lines
23 KiB
LLVM
; RUN: llc < %s -asm-verbose=false -wasm-enable-eh -wasm-use-legacy-eh=false -exception-model=wasm -mattr=+exception-handling -verify-machineinstrs | FileCheck --implicit-check-not=ehgcr -allow-deprecated-dag-overlap %s
|
|
; RUN: llc < %s -asm-verbose=false -wasm-enable-eh -wasm-use-legacy-eh=false -exception-model=wasm -mattr=+exception-handling -verify-machineinstrs -O0
|
|
; RUN: llc < %s -wasm-enable-eh -wasm-use-legacy-eh=false -exception-model=wasm -mattr=+exception-handling
|
|
; RUN: llc < %s -wasm-enable-eh -wasm-use-legacy-eh=false -exception-model=wasm -mattr=+exception-handling -filetype=obj
|
|
; RUN: llc < %s -mtriple=wasm64-unknown-unknown -wasm-enable-eh -wasm-use-legacy-eh=false -exception-model=wasm -mattr=+exception-handling -verify-machineinstrs | FileCheck --implicit-check-not=ehgcr -allow-deprecated-dag-overlap %s --check-prefix=WASM64
|
|
|
|
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
|
|
; 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:
|
|
; WASM64-LABEL: catch:
|
|
; CHECK: global.get __stack_pointer
|
|
; CHECK: local.set 0
|
|
; CHECK: block
|
|
; CHECK: block () -> (i32, exnref)
|
|
; CHECK: try_table (catch_ref __cpp_exception 0)
|
|
; WASM64: block () -> (i64, exnref)
|
|
; CHECK: call foo
|
|
; CHECK: br 2
|
|
; CHECK: end_try_table
|
|
; CHECK: unreachable
|
|
; CHECK: end_block
|
|
; CHECK: local.set 2
|
|
; CHECK: local.get 0
|
|
; CHECK: global.set __stack_pointer
|
|
; CHECK: i32.store __wasm_lpad_context
|
|
; CHECK: call _Unwind_CallPersonality
|
|
; CHECK: block
|
|
; CHECK: br_if 0
|
|
; CHECK: call __cxa_begin_catch
|
|
; CHECK: call __cxa_end_catch
|
|
; CHECK: br 1
|
|
; CHECK: end_block
|
|
; CHECK: local.get 2
|
|
; CHECK: throw_ref
|
|
; CHECK: end_block
|
|
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: block
|
|
; CHECK: block exnref
|
|
; CHECK: try_table (catch_all_ref 0)
|
|
; CHECK: call foo
|
|
; CHECK: br 2
|
|
; CHECK: end_try_table
|
|
; CHECK: end_block
|
|
; CHECK: local.set 1
|
|
; CHECK: global.set __stack_pointer
|
|
; CHECK: call _ZN4TempD2Ev
|
|
; CHECK: local.get 1
|
|
; CHECK: throw_ref
|
|
; CHECK: end_block
|
|
; CHECK: call _ZN4TempD2Ev
|
|
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
|
|
; WASM64-LABEL: terminatepad
|
|
; CHECK: block
|
|
; CHECK: block i32
|
|
; WASM64: block i64
|
|
; CHECK: try_table (catch __cpp_exception 0)
|
|
; CHECK: call foo
|
|
; CHECK: br 2
|
|
; CHECK: end_try_table
|
|
; CHECK: end_block
|
|
; CHECK: call __cxa_begin_catch
|
|
; CHECK: block
|
|
; CHECK: block exnref
|
|
; CHECK: try_table (catch_all_ref 0)
|
|
; CHECK: call foo
|
|
; CHECK: br 2
|
|
; CHECK: end_try_table
|
|
; CHECK: end_block
|
|
; CHECK: local.set 2
|
|
; CHECK: block
|
|
; CHECK: block
|
|
; CHECK: try_table (catch_all 0)
|
|
; CHECK: call __cxa_end_catch
|
|
; CHECK: br 2
|
|
; CHECK: end_try_table
|
|
; CHECK: end_block
|
|
; CHECK: call _ZSt9terminatev
|
|
; CHECK: unreachable
|
|
; CHECK: end_block
|
|
; CHECK: local.get 2
|
|
; CHECK: throw_ref
|
|
; CHECK: end_block
|
|
; CHECK: call __cxa_end_catch
|
|
; CHECK: end_block
|
|
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: call bar
|
|
; CHECK: block
|
|
; CHECK: block () -> (i32, exnref)
|
|
; CHECK: try_table (catch_ref __cpp_exception 0)
|
|
; CHECK: call foo
|
|
; CHECK: br 2
|
|
; CHECK: end_try_table
|
|
; CHECK: end_block
|
|
; CHECK: local.set 2
|
|
; CHECK-NOT: global.get __stack_pointer
|
|
; CHECK: global.set __stack_pointer
|
|
; CHECK: block
|
|
; CHECK: block
|
|
; CHECK: br_if 0
|
|
; CHECK: call __cxa_begin_catch
|
|
; CHECK: block exnref
|
|
; CHECK: try_table (catch_all_ref 0)
|
|
; CHECK: call foo
|
|
; CHECK: br 3
|
|
; CHECK: end_try_table
|
|
; CHECK: end_block
|
|
; CHECK: local.set 2
|
|
; CHECK-NOT: global.get __stack_pointer
|
|
; CHECK: global.set __stack_pointer
|
|
; CHECK: call __cxa_end_catch
|
|
; CHECK: local.get 2
|
|
; CHECK: throw_ref
|
|
; CHECK-NOT: global.set __stack_pointer
|
|
; CHECK: end_block
|
|
; CHECK: local.get 2
|
|
; CHECK: throw_ref
|
|
; CHECK: end_block
|
|
; CHECK-NOT: global.set __stack_pointer
|
|
; CHECK: call __cxa_end_catch
|
|
; CHECK: end_block
|
|
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: block
|
|
; CHECK: block i32
|
|
; CHECK: try_table (catch __cpp_exception 0)
|
|
; CHECK: call foo
|
|
; CHECK: br 2
|
|
; CHECK: end_try_table
|
|
; CHECK: end_block
|
|
; CHECK: call __cxa_begin_catch
|
|
; CHECK: call __cxa_end_catch
|
|
; CHECK: end_block
|
|
; CHECK-NOT: global.set __stack_pointer
|
|
; CHECK: end_function
|
|
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 'throw_ref' later. That
|
|
; throw_ref's argument should correctly target the top-level cleanuppad
|
|
; (catch_all_ref). This is a regression test for the bug where we did not
|
|
; compute throw_ref's argument correctly.
|
|
|
|
; CHECK-LABEL: inlined_cleanupret:
|
|
; CHECK: block exnref
|
|
; CHECK: block
|
|
; CHECK: block exnref
|
|
; CHECK: try_table (catch_all_ref 0)
|
|
; CHECK: call __cxa_throw
|
|
; CHECK: end_try_table
|
|
; CHECK: end_block
|
|
; try_table (catch_all_ref 0)'s caught exception is stored in local 2
|
|
; CHECK: local.set 2
|
|
; CHECK: block
|
|
; CHECK: try_table (catch_all 0)
|
|
; CHECK: block
|
|
; CHECK: block i32
|
|
; CHECK: try_table (catch __cpp_exception 0)
|
|
; CHECK: call __cxa_throw
|
|
; CHECK: end_try_table
|
|
; CHECK: end_block
|
|
; CHECK: call __cxa_end_catch
|
|
; CHECK: block i32
|
|
; CHECK: try_table (catch_all_ref 5)
|
|
; CHECK: try_table (catch __cpp_exception 1)
|
|
; Note that the throw_ref below targets the top-level catch_all_ref (local 2)
|
|
; CHECK: local.get 2
|
|
; CHECK: throw_ref
|
|
; CHECK: end_try_table
|
|
; CHECK: end_try_table
|
|
; CHECK: end_block
|
|
; CHECK: try_table (catch_all_ref 4)
|
|
; CHECK: call __cxa_end_catch
|
|
; CHECK: end_try_table
|
|
; CHECK: return
|
|
; CHECK: end_block
|
|
; CHECK: end_try_table
|
|
; CHECK: end_block
|
|
; CHECK: call _ZSt9terminatev
|
|
; CHECK: end_block
|
|
; CHECK: end_block
|
|
; CHECK: throw_ref
|
|
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
|
|
}
|
|
|
|
; This tests whether llvm.wasm.throw intrinsic can invoked and iseled correctly.
|
|
|
|
; CHECK-LABEL: invoke_throw:
|
|
; CHECK: try_table (catch __cpp_exception 0)
|
|
; CHECK: local.get 0
|
|
; CHECK: throw __cpp_exception
|
|
; CHECK: end_try_table
|
|
define void @invoke_throw(ptr %p) personality ptr @__gxx_wasm_personality_v0 {
|
|
entry:
|
|
invoke void @llvm.wasm.throw(i32 0, ptr %p)
|
|
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) #4 [ "funclet"(token %1) ]
|
|
call void @__cxa_end_catch() [ "funclet"(token %1) ]
|
|
catchret from %1 to label %try.cont
|
|
|
|
try.cont: ; preds = %catch, %entry
|
|
ret void
|
|
}
|
|
|
|
; 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: block
|
|
; CHECK: block
|
|
; CHECK: try_table (catch_all 0)
|
|
; CHECK: block i32
|
|
; CHECK: try_table (catch __cpp_exception 0)
|
|
; CHECK: call foo
|
|
; CHECK: end_try_table
|
|
; CHECK: unreachable
|
|
; CHECK: end_block
|
|
; CHECK: call _ZSt9terminatev
|
|
; CHECK: unreachable
|
|
; CHECK: end_try_table
|
|
; CHECK: unreachable
|
|
; CHECK: end_block
|
|
; Note the below is "terminate" BB and should not be DCE'd
|
|
; CHECK: call _ZSt9terminatev
|
|
; CHECK: unreachable
|
|
; CHECK: end_block
|
|
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(...)
|
|
; 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 }
|
|
|
|
; CHECK: __cpp_exception:
|