Heejin Ahn c3dfd34e54
[WebAssembly] Add unreachable before catch destinations (#123915)
When `try_table`'s catch clause's destination has a return type, as in
the case of catch with a concrete tag, catch_ref, and catch_all_ref. For
example:
```wasm
block exnref
  try_table (catch_all_ref 0)
    ...
  end_try_table
end_block
... use exnref ...
```

This code is not valid because the block's body type is not exnref. So
we add an unreachable after the 'end_try_table' to make the code valid
here:
```wasm
block exnref
  try_table (catch_all_ref 0)
    ...
  end_try_table
  unreachable                    ;; Newly added
end_block
```
Because 'unreachable' is a terminator we also need to split the BB.

---

We need to handle the same thing for unwind mismatch handling. In the
code below, we create a "trampoline BB" that will be the destination for
the nested `try_table`~`end_try_table` added to fix a unwind mismatch:
```wasm
try_table (catch ... )
  block exnref
    ...
    try_table (catch_all_ref N)
      some code
    end_try_table
    ...
  end_block                      ;; Trampoline BB
  throw_ref
end_try_table
```
While the `block` added for the trampoline BB has the return type
`exnref`, its body, which contains the nested `try_table` and other
code, wouldn't have the `exnref` return type. Most times it didn't
become a problem because the block's body ended with something like `br`
or `return`, but that may not always be the case, especially when there
is a loop. So we add an `unreachable` to make the code valid here too:
```wasm
try_table (catch ... )
  block exnref
    ...
    try_table (catch_all_ref N)
      some code
    end_try_table
    ...
    unreachable                  ;; Newly added
  end_block                      ;; Trampoline BB
  throw_ref
end_try_table
```
In this case we just append the `unreachable` at the end of the layout
predecessor BB. (This was tricky to do in the first (non-mismatch) case
because there `end_try_table` and `end_block` were added in the
beginning of an EH pad in `placeTryTableMarker` and moving
`end_try_table` and the new `unreachable` to the previous BB caused
other problems.)

---

This adds many `unreaachable`s to the output, but this adds
`unreachable` to only a few places to see if this is working. The
FileCheck lines in `exception.ll` and `cfg-stackify-eh.ll` are already
heavily redacted to only leave important control-flow instructions, so I
don't think it's worth adding `unreachable`s everywhere.
2025-01-22 22:39:43 -08:00

1557 lines
66 KiB
LLVM

; REQUIRES: asserts
; RUN: llc < %s -disable-wasm-fallthrough-return-opt -disable-block-placement -verify-machineinstrs -fast-isel=false -machine-sink-split-probability-threshold=0 -cgp-freq-ratio-to-skip-merge=1000 -wasm-enable-eh -wasm-use-legacy-eh=false -exception-model=wasm -mattr=+exception-handling,bulk-memory | FileCheck %s
; RUN: llc < %s -disable-wasm-fallthrough-return-opt -disable-block-placement -verify-machineinstrs -fast-isel=false -machine-sink-split-probability-threshold=0 -cgp-freq-ratio-to-skip-merge=1000 -wasm-enable-eh -wasm-use-legacy-eh=false -exception-model=wasm -mattr=+exception-handling,bulk-memory
; RUN: llc < %s -O0 -disable-wasm-fallthrough-return-opt -verify-machineinstrs -wasm-enable-eh -wasm-use-legacy-eh=false -exception-model=wasm -mattr=+exception-handling,-bulk-memory,-bulk-memory-opt | FileCheck %s --check-prefix=NOOPT
; RUN: llc < %s -disable-wasm-fallthrough-return-opt -disable-block-placement -verify-machineinstrs -fast-isel=false -machine-sink-split-probability-threshold=0 -cgp-freq-ratio-to-skip-merge=1000 -wasm-enable-eh -wasm-use-legacy-eh=false -exception-model=wasm -mattr=+exception-handling,-bulk-memory,-bulk-memory-opt -wasm-disable-ehpad-sort -stats 2>&1 | FileCheck %s --check-prefix=NOSORT
; RUN: llc < %s -disable-wasm-fallthrough-return-opt -disable-block-placement -verify-machineinstrs -fast-isel=false -machine-sink-split-probability-threshold=0 -cgp-freq-ratio-to-skip-merge=1000 -wasm-enable-eh -wasm-use-legacy-eh=false -exception-model=wasm -mattr=+exception-handling,-bulk-memory,-bulk-memory-opt -wasm-disable-ehpad-sort | FileCheck %s --check-prefix=NOSORT-LOCALS
target triple = "wasm32-unknown-unknown"
@_ZTIi = external constant ptr
@_ZTId = external constant ptr
%class.Object = type { i8 }
%class.MyClass = type { i32 }
; Simple test case with two catch clauses
;
; void foo();
; void two_catches() {
; try {
; foo();
; } catch (int) {
; } catch (double) {
; }
; }
; CHECK-LABEL: two_catches:
; CHECK: block
; CHECK: block () -> (i32, exnref)
; CHECK: try_table (catch_ref __cpp_exception 0) # 0: down to label[[L0:[0-9]+]]
; CHECK: call foo
; CHECK: br 2 # 2: down to label[[L1:[0-9]+]]
; CHECK: end_try_table
; CHECK: end_block # label[[L0]]:
; CHECK: local.set 2
; CHECK: local.set 1
; CHECK: local.get 0
; CHECK: call _Unwind_CallPersonality
; CHECK: block
; CHECK: br_if 0 # 0: down to label[[L2:[0-9]+]]
; CHECK: call __cxa_begin_catch
; CHECK: call __cxa_end_catch
; CHECK: br 1 # 1: down to label[[L1]]
; CHECK: end_block # label[[L2]]:
; CHECK: block
; CHECK: br_if 0 # 0: down to label[[L3:[0-9]+]]
; CHECK: call __cxa_begin_catch
; CHECK: call __cxa_end_catch
; CHECK: br 1 # 1: down to label[[L1]]
; CHECK: end_block # label[[L3]]:
; CHECK: throw_ref
; CHECK: end_block # label[[L1]]:
define void @two_catches() 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, ptr @_ZTId]
%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 %catch2, label %catch.fallthrough
catch2: ; 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
catch.fallthrough: ; preds = %catch.start
%6 = call i32 @llvm.eh.typeid.for(ptr @_ZTId)
%matches1 = icmp eq i32 %3, %6
br i1 %matches1, label %catch, label %rethrow
catch: ; preds = %catch.fallthrough
%7 = 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.fallthrough
call void @llvm.wasm.rethrow() [ "funclet"(token %1) ]
unreachable
try.cont: ; preds = %catch, %catch2, %entry
ret void
}
; Nested try-catches within a catch
; void nested_catch() {
; try {
; foo();
; } catch (int) {
; try {
; foo();
; } catch (int) {
; foo();
; }
; }
; }
; CHECK-LABEL: nested_catch:
; CHECK: block exnref
; CHECK: block
; CHECK: block () -> (i32, exnref)
; CHECK: try_table (catch_ref __cpp_exception 0) # 0: down to label[[L0:[0-9]+]]
; CHECK: call foo
; CHECK: br 2 # 2: down to label[[L1:[0-9]+]]
; CHECK: end_try_table
; CHECK: end_block # label[[L0]]:
; CHECK: call _Unwind_CallPersonality
; CHECK: block
; CHECK: block
; CHECK: br_if 0 # 0: down to label[[L2:[0-9]+]]
; CHECK: call __cxa_begin_catch
; CHECK: block exnref
; CHECK: try_table (catch_all_ref 0) # 0: down to label[[L3:[0-9]+]]
; CHECK: block () -> (i32, exnref)
; CHECK: try_table (catch_ref __cpp_exception 0) # 0: down to label[[L4:[0-9]+]]
; CHECK: call foo
; CHECK: br 5 # 5: down to label[[L5:[0-9]+]]
; CHECK: end_try_table
; CHECK: end_block # label[[L4]]:
; CHECK: call _Unwind_CallPersonality
; CHECK: block
; CHECK: block
; CHECK: br_if 0 # 0: down to label[[L6:[0-9]+]]
; CHECK: call __cxa_begin_catch
; CHECK: block exnref
; CHECK: try_table (catch_all_ref 0) # 0: down to label[[L7:[0-9]+]]
; CHECK: call foo
; CHECK: br 3 # 3: down to label[[L8:[0-9]+]]
; CHECK: end_try_table
; CHECK: end_block # label[[L7]]:
; CHECK: try_table (catch_all_ref 7) # 7: down to label[[L9:[0-9]+]]
; CHECK: call __cxa_end_catch
; CHECK: end_try_table
; CHECK: throw_ref
; CHECK: end_block # label[[L6]]:
; CHECK: throw_ref
; CHECK: end_block # label[[L8]]:
; CHECK: try_table (catch_all_ref 5) # 5: down to label[[L9]]
; CHECK: call __cxa_end_catch
; CHECK: end_try_table
; CHECK: br 3 # 3: down to label[[L5]]
; CHECK: end_try_table
; CHECK: end_block # label[[L3]]:
; CHECK: call __cxa_end_catch
; CHECK: throw_ref
; CHECK: end_block # label[[L2]]:
; CHECK: throw_ref
; CHECK: end_block # label[[L5]]:
; CHECK: call __cxa_end_catch
; CHECK: end_block # label[[L1]]:
; CHECK: return
; CHECK: end_block # label[[L9]]:
; CHECK: throw_ref
define void @nested_catch() personality ptr @__gxx_wasm_personality_v0 {
entry:
invoke void @foo()
to label %try.cont11 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 i32, ptr %5, align 4
invoke void @foo() [ "funclet"(token %1) ]
to label %try.cont unwind label %catch.dispatch2
catch.dispatch2: ; preds = %catch
%7 = catchswitch within %1 [label %catch.start3] unwind label %ehcleanup9
catch.start3: ; preds = %catch.dispatch2
%8 = catchpad within %7 [ptr @_ZTIi]
%9 = call ptr @llvm.wasm.get.exception(token %8)
%10 = call i32 @llvm.wasm.get.ehselector(token %8)
%11 = call i32 @llvm.eh.typeid.for(ptr @_ZTIi)
%matches4 = icmp eq i32 %10, %11
br i1 %matches4, label %catch6, label %rethrow5
catch6: ; preds = %catch.start3
%12 = call ptr @__cxa_begin_catch(ptr %9) [ "funclet"(token %8) ]
%13 = load i32, ptr %12, align 4
invoke void @foo() [ "funclet"(token %8) ]
to label %invoke.cont8 unwind label %ehcleanup
invoke.cont8: ; preds = %catch6
call void @__cxa_end_catch() [ "funclet"(token %8) ]
catchret from %8 to label %try.cont
rethrow5: ; preds = %catch.start3
invoke void @llvm.wasm.rethrow() [ "funclet"(token %8) ]
to label %unreachable unwind label %ehcleanup9
try.cont: ; preds = %invoke.cont8, %catch
call void @__cxa_end_catch() [ "funclet"(token %1) ]
catchret from %1 to label %try.cont11
rethrow: ; preds = %catch.start
call void @llvm.wasm.rethrow() [ "funclet"(token %1) ]
unreachable
try.cont11: ; preds = %try.cont, %entry
ret void
ehcleanup: ; preds = %catch6
%14 = cleanuppad within %8 []
call void @__cxa_end_catch() [ "funclet"(token %14) ]
cleanupret from %14 unwind label %ehcleanup9
ehcleanup9: ; preds = %ehcleanup, %rethrow5, %catch.dispatch2
%15 = cleanuppad within %1 []
call void @__cxa_end_catch() [ "funclet"(token %15) ]
cleanupret from %15 unwind to caller
unreachable: ; preds = %rethrow5
unreachable
}
; Nested try-catches within a try
; void nested_try() {
; try {
; try {
; foo();
; } catch (...) {
; }
; } catch (...) {
; }
; }
; CHECK-LABEL: nested_try:
; CHECK: block
; CHECK: block i32
; CHECK: try_table (catch __cpp_exception 0) # 0: down to label[[L0:[0-9]+]]
; CHECK: block i32
; CHECK: try_table (catch __cpp_exception 0) # 0: down to label[[L1:[0-9]+]]
; CHECK: call foo
; CHECK: br 4 # 4: down to label[[L2:[0-9]+]]
; CHECK: end_try_table
; CHECK: end_block # label[[L1]]:
; CHECK: call __cxa_begin_catch
; CHECK: call __cxa_end_catch
; CHECK: br 2 # 2: down to label[[L2]]
; CHECK: end_try_table
; CHECK: end_block # label[[L0]]:
; CHECK: call __cxa_begin_catch
; CHECK: call __cxa_end_catch
; CHECK: end_block # label[[L2]]:
define void @nested_try() personality ptr @__gxx_wasm_personality_v0 {
entry:
invoke void @foo()
to label %try.cont7 unwind label %catch.dispatch
catch.dispatch: ; preds = %entry
%0 = catchswitch within none [label %catch.start] unwind label %catch.dispatch2
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 @__cxa_end_catch() [ "funclet"(token %1) ]
to label %invoke.cont1 unwind label %catch.dispatch2
catch.dispatch2: ; preds = %catch.start, %catch.dispatch
%5 = catchswitch within none [label %catch.start3] unwind to caller
catch.start3: ; preds = %catch.dispatch2
%6 = catchpad within %5 [ptr null]
%7 = call ptr @llvm.wasm.get.exception(token %6)
%8 = call i32 @llvm.wasm.get.ehselector(token %6)
%9 = call ptr @__cxa_begin_catch(ptr %7) [ "funclet"(token %6) ]
call void @__cxa_end_catch() [ "funclet"(token %6) ]
catchret from %6 to label %try.cont7
try.cont7: ; preds = %entry, %invoke.cont1, %catch.start3
ret void
invoke.cont1: ; preds = %catch.start
catchret from %1 to label %try.cont7
}
; CHECK-LABEL: loop_within_catch:
; CHECK: block
; CHECK: block i32
; CHECK: try_table (catch __cpp_exception 0) # 0: down to label[[L0:[0-9]+]]
; CHECK: call foo
; CHECK: br 2 # 2: down to label[[L1:[0-9]+]]
; CHECK: end_try_table
; CHECK: end_block # label[[L0]]:
; CHECK: call __cxa_begin_catch
; CHECK: loop # label[[L2:[0-9]+]]:
; CHECK: block
; CHECK: block
; CHECK: br_if 0 # 0: down to label[[L3:[0-9]+]]
; CHECK: block exnref
; CHECK: try_table (catch_all_ref 0) # 0: down to label[[L4:[0-9]+]]
; CHECK: call foo
; CHECK: br 3 # 3: down to label[[L5:[0-9]+]]
; CHECK: end_try_table
; CHECK: end_block # label[[L4]]:
; CHECK: block
; CHECK: block
; CHECK: try_table (catch_all 0) # 0: down to label[[L6:[0-9]+]]
; CHECK: call __cxa_end_catch
; CHECK: br 2 # 2: down to label[[L7:[0-9]+]]
; CHECK: end_try_table
; CHECK: end_block # label[[L6]]:
; CHECK: call _ZSt9terminatev
; CHECK: unreachable
; CHECK: end_block # label[[L7]]:
; CHECK: throw_ref
; CHECK: end_block # label[[L3]]:
; CHECK: call __cxa_end_catch
; CHECK: br 2 # 2: down to label[[L1]]
; CHECK: end_block # label[[L5]]:
; CHECK: br 0 # 0: up to label[[L2]]
; CHECK: end_loop
; CHECK: end_block # label[[L1]]:
define void @loop_within_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 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) ]
br label %for.cond
for.cond: ; preds = %for.inc, %catch.start
%i.0 = phi i32 [ 0, %catch.start ], [ %inc, %for.inc ]
%cmp = icmp slt i32 %i.0, 50
br i1 %cmp, label %for.body, label %for.end
for.body: ; preds = %for.cond
invoke void @foo() [ "funclet"(token %1) ]
to label %for.inc unwind label %ehcleanup
for.inc: ; preds = %for.body
%inc = add nsw i32 %i.0, 1
br label %for.cond
for.end: ; preds = %for.cond
call void @__cxa_end_catch() [ "funclet"(token %1) ]
catchret from %1 to label %try.cont
try.cont: ; preds = %for.end, %entry
ret void
ehcleanup: ; preds = %for.body
%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() [ "funclet"(token %6) ]
unreachable
}
; Tests if block and try_table markers are correctly placed. Even if two
; predecessors of the EH pad are bb2 and bb3 and their nearest common dominator
; is bb1, the TRY_TABLE marker should be placed at bb0 because there's a branch
; from bb0 to bb2, and scopes cannot be interleaved.
; NOOPT-LABEL: block_try_table_markers:
; NOOPT: block
; NOOPT: block i32
; NOOPT: try_table (catch __cpp_exception 0)
; NOOPT: block
; NOOPT: block
; NOOPT: block
; NOOPT: end_block
; NOOPT: end_block
; NOOPT: call foo
; NOOPT: end_block
; NOOPT: call bar
; NOOPT: end_try_table
; NOOPT: end_block
; NOOPT: end_block
define void @block_try_table_markers() personality ptr @__gxx_wasm_personality_v0 {
bb0:
br i1 undef, label %bb1, label %bb2
bb1: ; preds = %bb0
br i1 undef, label %bb3, label %bb4
bb2: ; preds = %bb0
br label %try.cont
bb3: ; preds = %bb1
invoke void @foo()
to label %try.cont unwind label %catch.dispatch
bb4: ; preds = %bb1
invoke void @bar()
to label %try.cont unwind label %catch.dispatch
catch.dispatch: ; preds = %bb4, %bb3
%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, %bb4, %bb3, %bb2
ret void
}
; Tests if try_table/end_try_table markers are placed correctly wrt
; loop/end_loop markers, when try_table and loop markers are in the same BB and
; end_try_table and end_loop are in another BB.
; CHECK-LABEL: loop_try_table_markers:
; CHECK: loop
; CHECK: block i32
; CHECK: try_table (catch __cpp_exception 0)
; CHECK: call foo
; CHECK: end_try_table
; CHECK: end_block
; CHECK: end_loop
define void @loop_try_table_markers(ptr %p) personality ptr @__gxx_wasm_personality_v0 {
entry:
store volatile i32 0, ptr %p
br label %loop
loop: ; preds = %try.cont, %entry
store volatile i32 1, ptr %p
invoke void @foo()
to label %try.cont unwind label %catch.dispatch
catch.dispatch: ; preds = %loop
%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, %loop
br label %loop
}
; Some of test cases below are hand-tweaked by deleting some library calls to
; simplify tests and changing the order of basic blocks to cause unwind
; destination mismatches. And we use -wasm-disable-ehpad-sort to create maximum
; number of mismatches in several tests below.
; - Call unwind mismatch
; 'call bar''s original unwind destination was 'C0', but after control flow
; linearization, its unwind destination incorrectly becomes 'C1'. We fix this by
; wrapping the call with a nested try_table-end_try_table that targets 'C0'.
; - Catch unwind mismatch
; If 'call foo' throws a foreign exception, it will not be caught by C1, and
; should be rethrown to the caller. But after control flow linearization, it
; will instead unwind to C0, an incorrect next EH pad. We wrap the whole
; try_table-end_try_table with another try_table-end_try_table that jumps to a
; trampoline BB, from which we rethrow the exception to the caller to fix this.
; NOSORT-LABEL: unwind_mismatches_0:
; NOSORT: block exnref
; NOSORT: block
; NOSORT: block i32
; NOSORT: try_table (catch __cpp_exception 0) # 0: down to label[[L0:[0-9]+]]
; NOSORT: block exnref
; NOSORT: block i32
; --- nested try_table-end_try_table starts (catch unwind mismatch)
; NOSORT: try_table (catch_all_ref 5) # 5: down to label[[L1:[0-9]+]]
; NOSORT: try_table (catch __cpp_exception 1) # 1: down to label[[L2:[0-9]+]]
; NOSORT: call foo
; --- nested try_table-end_try_table starts (call unwind mismatch)
; NOSORT: try_table (catch_all_ref 3) # 3: down to label[[L3:[0-9]+]]
; NOSORT: call bar
; NOSORT: end_try_table
; --- nested try_table-end_try_table ends (call unwind mismatch)
; NOSORT: end_try_table
; NOSORT: end_try_table
; --- nested try_table-end_try_table ends (catch unwind mismatch)
; NOSORT: end_block # label[[L2]]:
; NOSORT: end_block # label[[L3]]:
; NOSORT: throw_ref
; NOSORT: end_try_table
; NOSORT: end_block # label[[L0]]:
; NOSORT: end_block
; NOSORT: return
; NOSORT: end_block # label[[L1]]:
; NOSORT: throw_ref
define void @unwind_mismatches_0() personality ptr @__gxx_wasm_personality_v0 {
bb0:
invoke void @foo()
to label %bb1 unwind label %catch.dispatch0
bb1: ; preds = %bb0
invoke void @bar()
to label %try.cont unwind label %catch.dispatch1
catch.dispatch0: ; preds = %bb0
%0 = catchswitch within none [label %catch.start0] unwind to caller
catch.start0: ; preds = %catch.dispatch0
%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
catch.dispatch1: ; preds = %bb1
%4 = catchswitch within none [label %catch.start1] unwind to caller
catch.start1: ; preds = %catch.dispatch1
%5 = catchpad within %4 [ptr null]
%6 = call ptr @llvm.wasm.get.exception(token %5)
%7 = call i32 @llvm.wasm.get.ehselector(token %5)
catchret from %5 to label %try.cont
try.cont: ; preds = %catch.start1, %catch.start0, %bb1
ret void
}
; 'call bar' and 'call baz''s original unwind destination was the caller, but
; after control flow linearization, their unwind destination incorrectly becomes
; 'C0'. We fix this by wrapping the calls with a nested try_table-end_try_table
; that jumps to a trampoline BB where we rethrow the exception to the caller.
; And the return value of 'baz' should NOT be stackified because the BB is split
; during fixing unwind mismatches.
; NOSORT-LABEL: unwind_mismatches_1:
; NOSORT: block exnref
; NOSORT: block i32
; NOSORT: try_table (catch __cpp_exception 0) # 0: down to label[[L0:[0-9]+]]
; NOSORT: call foo
; --- nested try_table-end_try_table starts (call unwind mismatch)
; NOSORT: try_table (catch_all_ref 2) # 2: down to label[[L1:[0-9]+]]
; NOSORT: call bar
; NOSORT: call baz
; NOSORT: local.set [[LOCAL:[0-9]+]]
; NOSORT: end_try_table
; --- nested try_table-end_try_table ends (call unwind mismatch)
; NOSORT: local.get [[LOCAL]]
; NOSORT: call nothrow
; NOSORT: return
; NOSORT: end_try_table
; NOSORT: end_block # label[[L0]]:
; NOSORT: return
; NOSORT: end_block # label[[L1]]:
; NOSORT: throw_ref
define void @unwind_mismatches_1() personality ptr @__gxx_wasm_personality_v0 {
bb0:
invoke void @foo()
to label %bb1 unwind label %catch.dispatch0
bb1: ; preds = %bb0
call void @bar()
%call = call i32 @baz()
call void @nothrow(i32 %call) #0
ret void
catch.dispatch0: ; preds = %bb0
%0 = catchswitch within none [label %catch.start0] unwind to caller
catch.start0: ; preds = %catch.dispatch0
%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.start0
ret void
}
; The same as unwind_mismatches_0, but we have one more call 'call @foo' in bb1
; which unwinds to the caller. IN this case bb1 has two call unwind mismatches:
; 'call @foo' unwinds to the caller and 'call @bar' unwinds to catch C0.
; NOSORT-LABEL: unwind_mismatches_2:
; NOSORT: block exnref
; NOSORT: block
; NOSORT: block i32
; NOSORT: try_table (catch __cpp_exception 0) # 0: down to label[[L0:[0-9]+]]
; NOSORT: block exnref
; NOSORT: block i32
; --- nested try_table-end_try_table starts (catch unwind mismatch)
; NOSORT: try_table (catch_all_ref 5) # 5: down to label[[L1:[0-9]+]]
; NOSORT: try_table (catch __cpp_exception 1) # 1: down to label[[L2:[0-9]+]]
; NOSORT: call foo
; --- nested try_table-end_try_table starts (call unwind mismatch)
; NOSORT: try_table (catch_all_ref 7) # 7: down to label[[L1]]
; NOSORT: call foo
; NOSORT: end_try_table
; --- nested try_table-end_try_table ends (call unwind mismatch)
; --- nested try_table-end_try_table starts (call unwind mismatch)
; NOSORT: try_table (catch_all_ref 3) # 3: down to label[[L3:[0-9]+]]
; NOSORT: call bar
; NOSORT: end_try_table
; --- nested try_table-end_try_table ends (call unwind mismatch)
; NOSORT: end_try_table
; NOSORT: end_try_table
; --- nested try_table-end_try_table ends (catch unwind mismatch)
; NOSORT: end_block # label[[L2]]:
; NOSORT: end_block # label[[L3]]:
; NOSORT: throw_ref
; NOSORT: end_try_table
; NOSORT: end_block # label[[L0]]:
; NOSORT: end_block
; NOSORT: return
; NOSORT: end_block # label[[L1]]:
; NOSORT: throw_ref
define void @unwind_mismatches_2() personality ptr @__gxx_wasm_personality_v0 {
bb0:
invoke void @foo()
to label %bb1 unwind label %catch.dispatch0
bb1: ; preds = %bb0
call void @foo()
invoke void @bar()
to label %try.cont unwind label %catch.dispatch1
catch.dispatch0: ; preds = %bb0
%0 = catchswitch within none [label %catch.start0] unwind to caller
catch.start0: ; preds = %catch.dispatch0
%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
catch.dispatch1: ; preds = %bb1
%4 = catchswitch within none [label %catch.start1] unwind to caller
catch.start1: ; preds = %catch.dispatch1
%5 = catchpad within %4 [ptr null]
%6 = call ptr @llvm.wasm.get.exception(token %5)
%7 = call i32 @llvm.wasm.get.ehselector(token %5)
catchret from %5 to label %try.cont
try.cont: ; preds = %catch.start1, %catch.start0, %bb1
ret void
}
; Similar situation as @unwind_mismatches_1. Here 'call @qux''s original unwind
; destination was the caller, but after control flow linearization, their unwind
; destination incorrectly becomes 'C0' within the function. We fix this by
; wrapping the call with a nested try_table-end_try_table that jumps to a
; trampoline BB where rethrow the exception to the caller.
; Because 'call @qux' pops an argument pushed by 'i32.const 5' from stack, the
; nested 'try_table' should be placed before `i32.const 5', not between
; 'i32.const 5' and 'call @qux'.
; NOSORT-LABEL: unwind_mismatches_3:
; NOSORT: block exnref
; NOSORT: block i32
; NOSORT: try_table (catch __cpp_exception 0) # 0: down to label[[L0:[0-9]+]]
; NOSORT: call foo
; --- nested try_table-end_try_table starts (call unwind mismatch)
; NOSORT: try_table (catch_all_ref 2) # 2: down to label[[L1:[0-9]+]]
; NOSORT: i32.const 5
; NOSORT: call qux
; NOSORT: end_try_table
; --- nested try_table-end_try_table ends (call unwind mismatch)
; NOSORT: return
; NOSORT: end_try_table
; NOSORT: end_block # label[[L0]]:
; NOSORT: return
; NOSORT: end_block # label[[L1]]:
; NOSORT: throw_ref
define i32 @unwind_mismatches_3() personality ptr @__gxx_wasm_personality_v0 {
bb0:
invoke void @foo()
to label %bb1 unwind label %catch.dispatch0
bb1: ; preds = %bb0
%0 = call i32 @qux(i32 5)
ret i32 %0
catch.dispatch0: ; preds = %bb0
%1 = catchswitch within none [label %catch.start0] unwind to caller
catch.start0: ; preds = %catch.dispatch0
%2 = catchpad within %1 [ptr null]
%3 = call ptr @llvm.wasm.get.exception(token %2)
%j = call i32 @llvm.wasm.get.ehselector(token %2)
catchret from %2 to label %try.cont
try.cont: ; preds = %catch.start0
ret i32 0
}
; We have two call unwind unwind mismatches:
; - A may-throw instruction unwinds to an incorrect EH pad after linearizing the
; CFG, when it is supposed to unwind to another EH pad.
; - A may-throw instruction unwinds to an incorrect EH pad after linearizing the
; CFG, when it is supposed to unwind to the caller.
; We also have a catch unwind mismatch: If an exception is not caught by the
; first catch because it is a non-C++ exception, it shouldn't unwind to the next
; catch, but it should unwind to the caller.
; NOSORT-LABEL: unwind_mismatches_4:
; NOSORT: block exnref
; NOSORT: block
; NOSORT: block i32
; NOSORT: try_table (catch __cpp_exception 0) # 0: down to label[[L0:[0-9]+]]
; NOSORT: block exnref
; NOSORT: block i32
; --- nested try_table-end_try_table starts (catch unwind mismatch)
; NOSORT: try_table (catch_all_ref 5) # 5: down to label[[L1:[0-9]+]]
; NOSORT: try_table (catch __cpp_exception 1) # 1: down to label[[L2:[0-9]+]]
; NOSORT: call foo
; --- nested try_table-end_try_table starts (call unwind mismatch)
; NOSORT: try_table (catch_all_ref 3) # 3: down to label[[L3:[0-9]+]]
; NOSORT: call bar
; NOSORT: end_try_table
; --- nested try_table-end_try_table ends (call unwind mismatch)
; NOSORT: end_try_table
; NOSORT: end_try_table
; --- nested try_table-end_try_table ends (catch unwind mismatch)
; NOSORT: end_block # label[[L2]]:
; NOSORT: call __cxa_begin_catch
; --- nested try_table-end_try_table starts (call unwind mismatch)
; NOSORT: try_table (catch_all_ref 4) # 4: down to label[[L1]]
; NOSORT: call __cxa_end_catch
; NOSORT: end_try_table
; --- nested try_table-end_try_table ends (call unwind mismatch)
; NOSORT: end_block # label[[L3]]:
; NOSORT: throw_ref
; NOSORT: end_try_table
; NOSORT: end_block # label[[L0]]:
; NOSORT: call __cxa_begin_catch
; NOSORT: call __cxa_end_catch
; NOSORT: end_block # label74:
; NOSORT: return
; NOSORT: end_block # label[[L1]]:
; NOSORT: throw_ref
define void @unwind_mismatches_4() personality ptr @__gxx_wasm_personality_v0 {
bb0:
invoke void @foo()
to label %bb1 unwind label %catch.dispatch0
bb1: ; preds = %bb0
invoke void @bar()
to label %try.cont unwind label %catch.dispatch1
catch.dispatch0: ; preds = %bb0
%0 = catchswitch within none [label %catch.start0] unwind to caller
catch.start0: ; preds = %catch.dispatch0
%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
catch.dispatch1: ; preds = %bb1
%5 = catchswitch within none [label %catch.start1] unwind to caller
catch.start1: ; preds = %catch.dispatch1
%6 = catchpad within %5 [ptr null]
%7 = call ptr @llvm.wasm.get.exception(token %6)
%8 = call i32 @llvm.wasm.get.ehselector(token %6)
%9 = call ptr @__cxa_begin_catch(ptr %7) [ "funclet"(token %6) ]
call void @__cxa_end_catch() [ "funclet"(token %6) ]
catchret from %6 to label %try.cont
try.cont: ; preds = %catch.start1, %catch.start0, %bb1
ret void
}
; This crashed when updating EHPadStack within fixCallUniwindMismatch had a bug.
; This should not crash and a nested try_table-end_try_table has to be created
; around 'call @baz', because the initial TRY_TABLE placement for 'call @quux'
; was done before 'call @baz' because 'call @baz''s return value is stackified.
; CHECK-LABEL: unwind_mismatches_5:
; CHECK: block exnref
; CHECK: block
; CHECK: block exnref
; CHECK: try_table (catch_all_ref 0) # 0: down to label[[L0:[0-9]+]]
; --- nested try_table-end_try_table starts (call unwind mismatch)
; CHECK: try_table (catch_all_ref 3) # 3: down to label[[L1:[0-9]+]]
; CHECK: call baz
; CHECK: end_try_table
; --- nested try_table-end_try_table ends (call unwind mismatch)
; CHECK: call quux
; CHECK: end_try_table
; CHECK: end_block # label[[L0]]:
; CHECK: throw_ref
; CHECK: end_block
; CHECK: unreachable
; CHECK: end_block # label[[L1]]:
; CHECK: throw_ref
define void @unwind_mismatches_5() personality ptr @__gxx_wasm_personality_v0 {
entry:
%call = call i32 @baz()
invoke void @quux(i32 %call)
to label %invoke.cont unwind label %ehcleanup
ehcleanup: ; preds = %entry
%0 = cleanuppad within none []
cleanupret from %0 unwind to caller
invoke.cont: ; preds = %entry
unreachable
}
; The structure is similar to unwind_mismatches_0, where the call to 'bar''s
; original unwind destination is catch.dispatch1 but after placing markers it
; unwinds to catch.dispatch0, which we fix. This additionally has a loop before
; the real unwind destination (catch.dispatch1). This makes sure the code
; generation works when the unwind destination has an end_loop before
; end_try_table before the mismatch fixing.
; NOSORT-LABEL: unwind_mismatches_with_loop:
; NOSORT: block exnref
; NOSORT: block i32
; NOSORT: try_table (catch __cpp_exception 0) # 0: down to label[[L0:[0-9]+]]
; NOSORT: block exnref
; NOSORT: block
; NOSORT: block i32
; --- nested try_table-end_try_table starts (catch unwind mismatch)
; NOSORT: try_table (catch_all_ref 5) # 5: down to label[[L1:[0-9]+]]
; NOSORT: try_table (catch __cpp_exception 1) # 1: down to label[[L2:[0-9]+]]
; NOSORT: call foo
; --- nested try_table-end_try_table starts (call unwind mismatch)
; NOSORT: try_table (catch_all_ref 4) # 4: down to label[[L3:[0-9]+]]
; NOSORT: call bar
; NOSORT: end_try_table
; --- nested try_table-end_try_table ends (call unwind mismatch)
; NOSORT: end_try_table
; NOSORT: end_try_table
; --- nested try_table-end_try_table ends (catch unwind mismatch)
; NOSORT: end_block # label[[L2]]:
; NOSORT: end_block
; NOSORT: loop
; NOSORT: call foo
; NOSORT: end_loop
; NOSORT: unreachable
; NOSORT: end_block # label[[L3]]:
; NOSORT: throw_ref
; NOSORT: end_try_table
; NOSORT: end_block # label[[L0]]:
; NOSORT: return
; NOSORT: end_block # label[[L1]]:
; NOSORT: throw_ref
define void @unwind_mismatches_with_loop() personality ptr @__gxx_wasm_personality_v0 {
bb0:
invoke void @foo()
to label %bb1 unwind label %catch.dispatch0
bb1: ; preds = %bb0
invoke void @bar()
to label %bb2 unwind label %catch.dispatch1
catch.dispatch0: ; preds = %bb0
%0 = catchswitch within none [label %catch.start0] unwind to caller
catch.start0: ; preds = %catch.dispatch0
%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 %bb2
bb2:
invoke void @foo()
to label %bb3 unwind label %catch.dispatch1
bb3: ; preds = %bb14
br label %bb2
catch.dispatch1: ; preds = %bb1
%4 = catchswitch within none [label %catch.start1] unwind to caller
catch.start1: ; preds = %catch.dispatch1
%5 = catchpad within %4 [ptr null]
%6 = call ptr @llvm.wasm.get.exception(token %5)
%7 = call i32 @llvm.wasm.get.ehselector(token %5)
catchret from %5 to label %try.cont
try.cont: ; preds = %catch.start1, %catch.start0, %bb1
ret void
}
; Tests the case when TEE stackifies a register in RegStackify but it gets
; unstackified in fixCallUnwindMismatches in CFGStackify.
; NOSORT-LOCALS-LABEL: unstackify_when_fixing_unwind_mismatch:
define void @unstackify_when_fixing_unwind_mismatch(i32 %x) personality ptr @__gxx_wasm_personality_v0 {
bb0:
invoke void @foo()
to label %bb1 unwind label %catch.dispatch0
bb1: ; preds = %bb0
%t = add i32 %x, 4
; This %addr is used in multiple places, so tee is introduced in RegStackify,
; which stackifies the use of %addr in store instruction. A tee has two dest
; registers, the first of which is stackified and the second is not.
; But when we introduce a nested try_table-end_try_table in
; fixCallUnwindMismatches in CFGStackify, we end up unstackifying the first
; dest register. In that case, we convert that tee into a copy.
%addr = inttoptr i32 %t to ptr
%load = load i32, ptr %addr
%call = call i32 @baz()
%add = add i32 %load, %call
store i32 %add, ptr %addr
ret void
; NOSORT-LOCALS: i32.add
; NOSORT-LOCALS-NOT: local.tee
; NOSORT-LOCALS-NEXT: local.set
catch.dispatch0: ; preds = %bb0
%0 = catchswitch within none [label %catch.start0] unwind to caller
catch.start0: ; preds = %catch.dispatch0
%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.start0
ret void
}
; In CFGSort, EH pads should be sorted as soon as it is available and
; 'Preferred' queue and should NOT be entered into 'Ready' queue unless we are
; in the middle of sorting another region that does not contain the EH pad. In
; this example, 'catch.start' should be sorted right after 'if.then' is sorted
; (before 'cont' is sorted) and there should not be any unwind destination
; mismatches in CFGStackify.
; NOOPT-LABEL: cfg_sort_order:
; NOOPT: block
; NOOPT: block
; NOOPT: block i32
; NOOPT: try_table (catch __cpp_exception 0)
; NOOPT: call foo
; NOOPT: end_try_table
; NOOPT: end_block
; NOOPT: call __cxa_begin_catch
; NOOPT: call __cxa_end_catch
; NOOPT: end_block
; NOOPT: call foo
; NOOPT: end_block
; NOOPT: return
define void @cfg_sort_order(i32 %arg) personality ptr @__gxx_wasm_personality_v0 {
entry:
%tobool = icmp ne i32 %arg, 0
br i1 %tobool, label %if.then, label %if.end
catch.dispatch: ; preds = %if.then
%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 %if.end
if.then: ; preds = %entry
invoke void @foo()
to label %cont unwind label %catch.dispatch
cont: ; preds = %if.then
call void @foo()
br label %if.end
if.end: ; preds = %cont, %catch.start, %entry
ret void
}
; Intrinsics like memcpy, memmove, and memset don't throw and are lowered into
; calls to external symbols (not global addresses) in instruction selection,
; which will be eventually lowered to library function calls.
; Because this test runs with -wasm-disable-ehpad-sort, these library calls in
; invoke.cont BB fall within try~end_try, but they shouldn't cause crashes or
; unwinding destination mismatches in CFGStackify.
; NOSORT-LABEL: mem_intrinsics:
; NOSORT: block exnref
; NOSORT: try_table (catch_all_ref 0)
; NOSORT: call foo
; NOSORT: call memcpy
; NOSORT: call memmove
; NOSORT: call memset
; NOSORT: return
; NOSORT: end_try_table
; NOSORT: end_block
; NOSORT: throw_ref
define void @mem_intrinsics(ptr %a, ptr %b) personality ptr @__gxx_wasm_personality_v0 {
entry:
%o = alloca %class.Object, align 1
invoke void @foo()
to label %invoke.cont unwind label %ehcleanup
invoke.cont: ; preds = %entry
call void @llvm.memcpy.p0.p0.i32(ptr %a, ptr %b, i32 100, i1 false)
call void @llvm.memmove.p0.p0.i32(ptr %a, ptr %b, i32 100, i1 false)
call void @llvm.memset.p0.i32(ptr %a, i8 0, i32 100, i1 false)
%call = call ptr @_ZN6ObjectD2Ev(ptr %o)
ret void
ehcleanup: ; preds = %entry
%0 = cleanuppad within none []
%call2 = call ptr @_ZN6ObjectD2Ev(ptr %o) [ "funclet"(token %0) ]
cleanupret from %0 unwind to caller
}
; Tests if 'try_table' marker is placed correctly. In this test, 'try_table'
; should be placed before the call to 'nothrow_i32' and not between the call to
; 'nothrow_i32' and 'fun', because the return value of 'nothrow_i32' is
; stackified and pushed onto the stack to be consumed by the call to 'fun'.
; CHECK-LABEL: try_table_marker_with_stackified_input:
; CHECK: try_table (catch_all 0)
; CHECK: call nothrow_i32
; CHECK: call fun
define void @try_table_marker_with_stackified_input() personality ptr @__gxx_wasm_personality_v0 {
entry:
%call = call i32 @nothrow_i32()
invoke void @fun(i32 %call)
to label %invoke.cont unwind label %terminate
invoke.cont: ; preds = %entry
ret void
terminate: ; preds = %entry
%0 = cleanuppad within none []
call void @_ZSt9terminatev() [ "funclet"(token %0) ]
unreachable
}
; This crashed on debug mode (= when NDEBUG is not defined) when the logic for
; computing the innermost region was not correct, in which a loop region
; contains an exception region. This should pass CFGSort without crashing.
define void @loop_exception_region() personality ptr @__gxx_wasm_personality_v0 {
entry:
%e = alloca %class.MyClass, align 4
br label %for.cond
for.cond: ; preds = %for.inc, %entry
%i.0 = phi i32 [ 0, %entry ], [ %inc, %for.inc ]
%cmp = icmp slt i32 %i.0, 9
br i1 %cmp, label %for.body, label %for.end
for.body: ; preds = %for.cond
invoke void @quux(i32 %i.0)
to label %for.inc unwind label %catch.dispatch
catch.dispatch: ; preds = %for.body
%0 = catchswitch within none [label %catch.start] unwind to caller
catch.start: ; preds = %catch.dispatch
%1 = catchpad within %0 [ptr @_ZTI7MyClass]
%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 @_ZTI7MyClass)
%matches = icmp eq i32 %3, %4
br i1 %matches, label %catch, label %rethrow
catch: ; preds = %catch.start
%5 = call ptr @__cxa_get_exception_ptr(ptr %2) [ "funclet"(token %1) ]
%call = call ptr @_ZN7MyClassC2ERKS_(ptr %e, ptr dereferenceable(4) %5) [ "funclet"(token %1) ]
%6 = call ptr @__cxa_begin_catch(ptr %2) [ "funclet"(token %1) ]
%7 = load i32, ptr %e, align 4
invoke void @quux(i32 %7) [ "funclet"(token %1) ]
to label %invoke.cont2 unwind label %ehcleanup
invoke.cont2: ; preds = %catch
%call3 = call ptr @_ZN7MyClassD2Ev(ptr %e) [ "funclet"(token %1) ]
call void @__cxa_end_catch() [ "funclet"(token %1) ]
catchret from %1 to label %for.inc
rethrow: ; preds = %catch.start
call void @llvm.wasm.rethrow() [ "funclet"(token %1) ]
unreachable
for.inc: ; preds = %invoke.cont2, %for.body
%inc = add nsw i32 %i.0, 1
br label %for.cond
ehcleanup: ; preds = %catch
%8 = cleanuppad within %1 []
%call4 = call ptr @_ZN7MyClassD2Ev(ptr %e) [ "funclet"(token %8) ]
invoke void @__cxa_end_catch() [ "funclet"(token %8) ]
to label %invoke.cont6 unwind label %terminate7
invoke.cont6: ; preds = %ehcleanup
cleanupret from %8 unwind to caller
for.end: ; preds = %for.cond
ret void
terminate7: ; preds = %ehcleanup
%9 = cleanuppad within %8 []
call void @_ZSt9terminatev() [ "funclet"(token %9) ]
unreachable
}
; Here exceptions are semantically contained in a loop. 'ehcleanup' BB belongs
; to the exception, but does not belong to the loop (because it does not have a
; path back to the loop header), and is placed after the loop latch block
; 'invoke.cont' intentionally. This tests if 'end_loop' marker is placed
; correctly not right after 'invoke.cont' part but after 'ehcleanup' part.
; NOSORT-LABEL: loop_contains_exception:
; NOSORT: loop
; NOSORT: try_table (catch __cpp_exception 0)
; NOSORT: end_try_table
; NOSORT: try_table (catch_all 0)
; NOSORT: end_try_table
; NOSORT: end_loop
define void @loop_contains_exception(i32 %n) personality ptr @__gxx_wasm_personality_v0 {
entry:
br label %while.cond
while.cond: ; preds = %invoke.cont, %entry
%n.addr.0 = phi i32 [ %n, %entry ], [ %dec, %invoke.cont ]
%tobool = icmp ne i32 %n.addr.0, 0
br i1 %tobool, label %while.body, label %while.end
while.body: ; preds = %while.cond
%dec = add nsw i32 %n.addr.0, -1
invoke void @foo()
to label %while.end unwind label %catch.dispatch
catch.dispatch: ; preds = %while.body
%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 @__cxa_end_catch() [ "funclet"(token %1) ]
to label %invoke.cont unwind label %ehcleanup
invoke.cont: ; preds = %catch.start
catchret from %1 to label %while.cond
ehcleanup: ; preds = %catch.start
%5 = cleanuppad within %1 []
call void @_ZSt9terminatev() [ "funclet"(token %5) ]
unreachable
while.end: ; preds = %while.body, %while.cond
ret void
}
; Regression test for WasmEHFuncInfo's reverse mapping bug. 'UnwindDestToSrc'
; should return a vector and not a single BB, which was incorrect.
; This was reduced by bugpoint and should not crash in CFGStackify.
define void @wasm_eh_func_info_regression_test() personality ptr @__gxx_wasm_personality_v0 {
entry:
invoke void @foo()
to label %invoke.cont unwind label %catch.dispatch
catch.dispatch: ; preds = %entry
%0 = catchswitch within none [label %catch.start] unwind label %ehcleanup22
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)
invoke void @__cxa_throw(ptr null, ptr null, ptr null) #1 [ "funclet"(token %1) ]
to label %unreachable unwind label %catch.dispatch2
catch.dispatch2: ; preds = %catch.start
%4 = catchswitch within %1 [label %catch.start3] unwind label %ehcleanup
catch.start3: ; preds = %catch.dispatch2
%5 = catchpad within %4 [ptr @_ZTIi]
%6 = call ptr @llvm.wasm.get.exception(token %5)
%7 = call i32 @llvm.wasm.get.ehselector(token %5)
catchret from %5 to label %try.cont
try.cont: ; preds = %catch.start3
invoke void @foo() [ "funclet"(token %1) ]
to label %invoke.cont8 unwind label %ehcleanup
invoke.cont8: ; preds = %try.cont
invoke void @__cxa_throw(ptr null, ptr null, ptr null) #1 [ "funclet"(token %1) ]
to label %unreachable unwind label %catch.dispatch11
catch.dispatch11: ; preds = %invoke.cont8
%8 = catchswitch within %1 [label %catch.start12] unwind label %ehcleanup
catch.start12: ; preds = %catch.dispatch11
%9 = catchpad within %8 [ptr @_ZTIi]
%10 = call ptr @llvm.wasm.get.exception(token %9)
%11 = call i32 @llvm.wasm.get.ehselector(token %9)
unreachable
invoke.cont: ; preds = %entry
unreachable
ehcleanup: ; preds = %catch.dispatch11, %try.cont, %catch.dispatch2
%12 = cleanuppad within %1 []
cleanupret from %12 unwind label %ehcleanup22
ehcleanup22: ; preds = %ehcleanup, %catch.dispatch
%13 = cleanuppad within none []
cleanupret from %13 unwind to caller
unreachable: ; preds = %invoke.cont8, %catch.start
unreachable
}
; void exception_grouping_0() {
; try {
; try {
; throw 0;
; } catch (int) {
; }
; } catch (int) {
; }
; }
;
; Regression test for a WebAssemblyException grouping bug. After catchswitches
; are removed, EH pad catch.start2 is dominated by catch.start, but because
; catch.start2 is the unwind destination of catch.start, it should not be
; included in catch.start's exception. Also, after we take catch.start2's
; exception out of catch.start's exception, we have to take out try.cont8 out of
; catch.start's exception, because it has a predecessor in catch.start2.
define void @exception_grouping_0() personality ptr @__gxx_wasm_personality_v0 {
entry:
%exception = call ptr @__cxa_allocate_exception(i32 4) #0
store i32 0, ptr %exception, align 16
invoke void @__cxa_throw(ptr %exception, ptr @_ZTIi, ptr null) #1
to label %unreachable unwind label %catch.dispatch
catch.dispatch: ; preds = %entry
%0 = catchswitch within none [label %catch.start] unwind label %catch.dispatch1
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) #0
%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) #0 [ "funclet"(token %1) ]
%6 = load i32, ptr %5, align 4
call void @__cxa_end_catch() #0 [ "funclet"(token %1) ]
catchret from %1 to label %catchret.dest
catchret.dest: ; preds = %catch
br label %try.cont
rethrow: ; preds = %catch.start
invoke void @llvm.wasm.rethrow() #1 [ "funclet"(token %1) ]
to label %unreachable unwind label %catch.dispatch1
catch.dispatch1: ; preds = %rethrow, %catch.dispatch
%7 = catchswitch within none [label %catch.start2] unwind to caller
catch.start2: ; preds = %catch.dispatch1
%8 = catchpad within %7 [ptr @_ZTIi]
%9 = call ptr @llvm.wasm.get.exception(token %8)
%10 = call i32 @llvm.wasm.get.ehselector(token %8)
%11 = call i32 @llvm.eh.typeid.for(ptr @_ZTIi) #0
%matches3 = icmp eq i32 %10, %11
br i1 %matches3, label %catch5, label %rethrow4
catch5: ; preds = %catch.start2
%12 = call ptr @__cxa_begin_catch(ptr %9) #0 [ "funclet"(token %8) ]
%13 = load i32, ptr %12, align 4
call void @__cxa_end_catch() #0 [ "funclet"(token %8) ]
catchret from %8 to label %catchret.dest7
catchret.dest7: ; preds = %catch5
br label %try.cont8
rethrow4: ; preds = %catch.start2
call void @llvm.wasm.rethrow() #1 [ "funclet"(token %8) ]
unreachable
try.cont8: ; preds = %try.cont, %catchret.dest7
ret void
try.cont: ; preds = %catchret.dest
br label %try.cont8
unreachable: ; preds = %rethrow, %entry
unreachable
}
; Test for WebAssemblyException grouping. This test is hand-modified to generate
; this structure:
; catch.start dominates catch.start4 and catch.start4 dominates catch.start12,
; so the after dominator-based grouping, we end up with:
; catch.start's exception > catch4.start's exception > catch12.start's exception
; (> here represents subexception relationship)
;
; But the unwind destination chain is catch.start -> catch.start4 ->
; catch.start12. So all these subexception relationship should be deconstructed.
; We have to make sure to take out catch.start4's exception out of catch.start's
; exception first, before taking out catch.start12's exception out of
; catch.start4's exception; otherwise we end up with an incorrect relationship
; of catch.start's exception > catch.start12's exception.
define void @exception_grouping_1() personality ptr @__gxx_wasm_personality_v0 {
entry:
invoke void @foo()
to label %invoke.cont unwind label %catch.dispatch
invoke.cont: ; preds = %entry
invoke void @foo()
to label %invoke.cont1 unwind label %catch.dispatch
invoke.cont1: ; preds = %invoke.cont
invoke void @foo()
to label %try.cont18 unwind label %catch.dispatch
catch.dispatch11: ; preds = %rethrow6, %catch.dispatch3
%0 = catchswitch within none [label %catch.start12] unwind to caller
catch.start12: ; preds = %catch.dispatch11
%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) #0
%matches13 = icmp eq i32 %3, %4
br i1 %matches13, label %catch15, label %rethrow14
catch15: ; preds = %catch.start12
%5 = call ptr @__cxa_begin_catch(ptr %2) #0 [ "funclet"(token %1) ]
%6 = load i32, ptr %5, align 4
call void @__cxa_end_catch() #0 [ "funclet"(token %1) ]
catchret from %1 to label %try.cont18
rethrow14: ; preds = %catch.start12
call void @llvm.wasm.rethrow() #1 [ "funclet"(token %1) ]
unreachable
catch.dispatch3: ; preds = %rethrow, %catch.dispatch
%7 = catchswitch within none [label %catch.start4] unwind label %catch.dispatch11
catch.start4: ; preds = %catch.dispatch3
%8 = catchpad within %7 [ptr @_ZTIi]
%9 = call ptr @llvm.wasm.get.exception(token %8)
%10 = call i32 @llvm.wasm.get.ehselector(token %8)
%11 = call i32 @llvm.eh.typeid.for(ptr @_ZTIi) #0
%matches5 = icmp eq i32 %10, %11
br i1 %matches5, label %catch7, label %rethrow6
catch7: ; preds = %catch.start4
%12 = call ptr @__cxa_begin_catch(ptr %9) #0 [ "funclet"(token %8) ]
%13 = load i32, ptr %12, align 4
call void @__cxa_end_catch() #0 [ "funclet"(token %8) ]
catchret from %8 to label %try.cont18
rethrow6: ; preds = %catch.start4
invoke void @llvm.wasm.rethrow() #1 [ "funclet"(token %8) ]
to label %unreachable unwind label %catch.dispatch11
catch.dispatch: ; preds = %invoke.cont1, %invoke.cont, %entry
%14 = catchswitch within none [label %catch.start] unwind label %catch.dispatch3
catch.start: ; preds = %catch.dispatch
%15 = catchpad within %14 [ptr @_ZTIi]
%16 = call ptr @llvm.wasm.get.exception(token %15)
%17 = call i32 @llvm.wasm.get.ehselector(token %15)
%18 = call i32 @llvm.eh.typeid.for(ptr @_ZTIi) #0
%matches = icmp eq i32 %17, %18
br i1 %matches, label %catch, label %rethrow
catch: ; preds = %catch.start
%19 = call ptr @__cxa_begin_catch(ptr %16) #0 [ "funclet"(token %15) ]
%20 = load i32, ptr %19, align 4
call void @__cxa_end_catch() #0 [ "funclet"(token %15) ]
catchret from %15 to label %try.cont18
rethrow: ; preds = %catch.start
invoke void @llvm.wasm.rethrow() #1 [ "funclet"(token %15) ]
to label %unreachable unwind label %catch.dispatch3
try.cont18: ; preds = %catch, %catch7, %catch15, %invoke.cont1
ret void
unreachable: ; preds = %rethrow, %rethrow6
unreachable
}
; void exception_grouping_2() {
; try {
; try {
; throw 0;
; } catch (int) { // (a)
; }
; } catch (int) { // (b)
; }
; try {
; foo();
; } catch (int) { // (c)
; }
; }
;
; Regression test for an ExceptionInfo grouping bug. Because the first (inner)
; try always throws, both EH pads (b) (catch.start2) and (c) (catch.start10) are
; dominated by EH pad (a) (catch.start), even though they are not semantically
; contained in (a)'s exception. Because (a)'s unwind destination is (b), (b)'s
; exception is taken out of (a)'s. But because (c) is reachable from (b), we
; should make sure to take out (c)'s exception out of (a)'s exception too.
define void @exception_grouping_2() personality ptr @__gxx_wasm_personality_v0 {
entry:
%exception = call ptr @__cxa_allocate_exception(i32 4) #1
store i32 0, ptr %exception, align 16
invoke void @__cxa_throw(ptr %exception, ptr @_ZTIi, ptr null) #3
to label %unreachable unwind label %catch.dispatch
catch.dispatch: ; preds = %entry
%0 = catchswitch within none [label %catch.start] unwind label %catch.dispatch1
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) #1
%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) #1 [ "funclet"(token %1) ]
%6 = load i32, ptr %5, align 4
call void @__cxa_end_catch() #1 [ "funclet"(token %1) ]
catchret from %1 to label %try.cont8
rethrow: ; preds = %catch.start
invoke void @llvm.wasm.rethrow() #3 [ "funclet"(token %1) ]
to label %unreachable unwind label %catch.dispatch1
catch.dispatch1: ; preds = %rethrow, %catch.dispatch
%7 = catchswitch within none [label %catch.start2] unwind to caller
catch.start2: ; preds = %catch.dispatch1
%8 = catchpad within %7 [ptr @_ZTIi]
%9 = call ptr @llvm.wasm.get.exception(token %8)
%10 = call i32 @llvm.wasm.get.ehselector(token %8)
%11 = call i32 @llvm.eh.typeid.for(ptr @_ZTIi) #1
%matches3 = icmp eq i32 %10, %11
br i1 %matches3, label %catch5, label %rethrow4
catch5: ; preds = %catch.start2
%12 = call ptr @__cxa_begin_catch(ptr %9) #1 [ "funclet"(token %8) ]
%13 = load i32, ptr %12, align 4
call void @__cxa_end_catch() #1 [ "funclet"(token %8) ]
catchret from %8 to label %try.cont8
rethrow4: ; preds = %catch.start2
call void @llvm.wasm.rethrow() #3 [ "funclet"(token %8) ]
unreachable
try.cont8: ; preds = %catch, %catch5
invoke void @foo()
to label %try.cont16 unwind label %catch.dispatch9
catch.dispatch9: ; preds = %try.cont8
%14 = catchswitch within none [label %catch.start10] unwind to caller
catch.start10: ; preds = %catch.dispatch9
%15 = catchpad within %14 [ptr @_ZTIi]
%16 = call ptr @llvm.wasm.get.exception(token %15)
%17 = call i32 @llvm.wasm.get.ehselector(token %15)
%18 = call i32 @llvm.eh.typeid.for(ptr @_ZTIi) #1
%matches11 = icmp eq i32 %17, %18
br i1 %matches11, label %catch13, label %rethrow12
catch13: ; preds = %catch.start10
%19 = call ptr @__cxa_begin_catch(ptr %16) #1 [ "funclet"(token %15) ]
%20 = load i32, ptr %19, align 4
call void @__cxa_end_catch() #1 [ "funclet"(token %15) ]
catchret from %15 to label %try.cont16
rethrow12: ; preds = %catch.start10
call void @llvm.wasm.rethrow() #3 [ "funclet"(token %15) ]
unreachable
try.cont16: ; preds = %try.cont8, %catch13
ret void
unreachable: ; preds = %rethrow, %entry
unreachable
}
; Check if the unwind destination mismatch stats are correct
; NOSORT: 23 wasm-cfg-stackify - Number of call unwind mismatches found
; NOSORT: 4 wasm-cfg-stackify - Number of catch unwind mismatches found
declare void @foo()
declare void @bar()
declare i32 @baz()
declare i32 @qux(i32)
declare void @quux(i32)
declare void @fun(i32)
; Function Attrs: nounwind
declare void @nothrow(i32) #0
; Function Attrs: nounwind
declare i32 @nothrow_i32() #0
; Function Attrs: nounwind
declare ptr @_ZN6ObjectD2Ev(ptr returned) #0
@_ZTI7MyClass = external constant { ptr, ptr }, align 4
; Function Attrs: nounwind
declare ptr @_ZN7MyClassD2Ev(ptr returned) #0
; Function Attrs: nounwind
declare ptr @_ZN7MyClassC2ERKS_(ptr returned, ptr dereferenceable(4)) #0
declare i32 @__gxx_wasm_personality_v0(...)
; Function Attrs: nounwind
declare ptr @llvm.wasm.get.exception(token) #0
; Function Attrs: nounwind
declare i32 @llvm.wasm.get.ehselector(token) #0
declare ptr @__cxa_allocate_exception(i32) #0
declare void @__cxa_throw(ptr, ptr, ptr)
; Function Attrs: noreturn
declare void @llvm.wasm.rethrow() #1
; Function Attrs: nounwind
declare i32 @llvm.eh.typeid.for(ptr) #0
declare ptr @__cxa_begin_catch(ptr)
declare void @__cxa_end_catch()
declare ptr @__cxa_get_exception_ptr(ptr)
declare void @_ZSt9terminatev()
; Function Attrs: nounwind
declare void @llvm.memcpy.p0.p0.i32(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i32, i1 immarg) #0
; Function Attrs: nounwind
declare void @llvm.memmove.p0.p0.i32(ptr nocapture, ptr nocapture readonly, i32, i1 immarg) #0
; Function Attrs: nounwind
declare void @llvm.memset.p0.i32(ptr nocapture writeonly, i8, i32, i1 immarg) #0
attributes #0 = { nounwind }
attributes #1 = { noreturn }