llvm-project/llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj.ll
Heejin Ahn eb675e972d [WebAssembly] Support Wasm EH + Wasm SjLj
D108960 added support for SjLj using Wasm EH instructions, which we call
Wasm SjLj going forward. (We call the old SjLj Emscripten SjLj) But it
did not support using Wasm EH and Wasm SjLj together. So far users of
Wasm EH had to use Wasm EH with Emscripten SjLj, which had a certain
limitation and it suffered from bigger code size increases as well.

This enables using Wasm EH and Wasm SjLj together.
1. This redirects `catchswitch` and `cleanupret` that unwind to caller
   to `catch.dispatch.longjmp` BB, which is a `catchswitch` BB that
   handles longjmps.
2. D108960 converted all longjmpable `call`s to `invokes` that unwind to
   `catch.dispatch.longjmp`. This CL checks if the `call` is embedded
   within another `catchpad`, and if so, makes it unwind to its nearest
   parent's unwind destination, rather than `catch.dispatch.longjmp`.
   This is necessary to preserve the scoping structure.

Reviewed By: dschuff

Differential Revision: https://reviews.llvm.org/D117610
2022-01-19 20:13:54 -08:00

260 lines
10 KiB
LLVM

; RUN: opt < %s -wasm-lower-em-ehsjlj -wasm-enable-eh -wasm-enable-sjlj -S | FileCheck %s
; RUN: llc < %s -wasm-enable-eh -wasm-enable-sjlj -exception-model=wasm -mattr=+exception-handling -verify-machineinstrs
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-unknown"
%struct.__jmp_buf_tag = type { [6 x i32], i32, [32 x i32] }
@_ZL3buf = internal global [1 x %struct.__jmp_buf_tag] zeroinitializer, align 16
; void test() {
; int jmpval = setjmp(buf);
; if (jmpval != 0)
; return;
; try {
; foo();
; } catch (...) {
; }
; }
define void @setjmp_and_try() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
; CHECK-LABEL: @setjmp_and_try(
entry:
%call = call i32 @setjmp(%struct.__jmp_buf_tag* getelementptr inbounds ([1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* @_ZL3buf, i32 0, i32 0)) #0
%cmp = icmp ne i32 %call, 0
br i1 %cmp, label %return, label %if.end
if.end: ; preds = %entry
invoke void @foo()
to label %return unwind label %catch.dispatch
catch.dispatch: ; preds = %if.end
%0 = catchswitch within none [label %catch.start] unwind to caller
; CHECK: catch.dispatch:
; CHECK-NEXT: catchswitch within none [label %catch.start] unwind label %catch.dispatch.longjmp
catch.start: ; preds = %catch.dispatch
%1 = catchpad within %0 [i8* null]
%2 = call i8* @llvm.wasm.get.exception(token %1)
%3 = call i32 @llvm.wasm.get.ehselector(token %1)
%4 = call i8* @__cxa_begin_catch(i8* %2) #2 [ "funclet"(token %1) ]
call void @__cxa_end_catch() [ "funclet"(token %1) ]
catchret from %1 to label %return
; CHECK: catch.start:
; CHECK: [[T0:%.*]] = catchpad within {{.*}} [i8* null]
; CHECK: invoke void @__cxa_end_catch() [ "funclet"(token [[T0]]) ]
; CHECK-NEXT: to label %.noexc unwind label %catch.dispatch.longjmp
; CHECK: .noexc:
; CHECK-NEXT: catchret from [[T0]] to label {{.*}}
return: ; preds = %catch.start, %if.end, %entry
ret void
; CHECK: catch.dispatch.longjmp:
; CHECK-NEXT: catchswitch within none [label %catch.longjmp] unwind to caller
}
; void setjmp_within_try() {
; try {
; foo();
; int jmpval = setjmp(buf);
; if (jmpval != 0)
; return;
; foo();
; } catch (...) {
; }
; }
define void @setjmp_within_try() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
; CHECK-LABEL: @setjmp_within_try(
entry:
%jmpval = alloca i32, align 4
%exn.slot = alloca i8*, align 4
invoke void @foo()
to label %invoke.cont unwind label %catch.dispatch
invoke.cont: ; preds = %entry
%call = invoke i32 @setjmp(%struct.__jmp_buf_tag* getelementptr inbounds ([1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* @_ZL3buf, i32 0, i32 0)) #0
to label %invoke.cont1 unwind label %catch.dispatch
invoke.cont1: ; preds = %invoke.cont
store i32 %call, i32* %jmpval, align 4
%0 = load i32, i32* %jmpval, align 4
%cmp = icmp ne i32 %0, 0
br i1 %cmp, label %if.then, label %if.end
if.then: ; preds = %invoke.cont1
br label %try.cont
if.end: ; preds = %invoke.cont1
invoke void @foo()
to label %invoke.cont2 unwind label %catch.dispatch
catch.dispatch: ; preds = %if.end, %invoke.cont, %entry
%1 = catchswitch within none [label %catch.start] unwind to caller
; CHECK: catch.dispatch:
; CHECK: catchswitch within none [label %catch.start] unwind label %catch.dispatch.longjmp
catch.start: ; preds = %catch.dispatch
%2 = catchpad within %1 [i8* null]
%3 = call i8* @llvm.wasm.get.exception(token %2)
store i8* %3, i8** %exn.slot, align 4
%4 = call i32 @llvm.wasm.get.ehselector(token %2)
br label %catch
catch: ; preds = %catch.start
%exn = load i8*, i8** %exn.slot, align 4
%5 = call i8* @__cxa_begin_catch(i8* %exn) #2 [ "funclet"(token %2) ]
call void @__cxa_end_catch() [ "funclet"(token %2) ]
catchret from %2 to label %catchret.dest
; CHECK: catch: ; preds = %catch.start
; CHECK-NEXT: %exn = load i8*, i8** %exn.slot15, align 4
; CHECK-NEXT: %5 = call i8* @__cxa_begin_catch(i8* %exn) #2 [ "funclet"(token %2) ]
; CHECK-NEXT: invoke void @__cxa_end_catch() [ "funclet"(token %2) ]
; CHECK-NEXT: to label %.noexc unwind label %catch.dispatch.longjmp
catchret.dest: ; preds = %catch
br label %try.cont
try.cont: ; preds = %invoke.cont2, %catchret.dest, %if.then
ret void
invoke.cont2: ; preds = %if.end
br label %try.cont
; CHECK: catch.dispatch.longjmp:
; CHECK-NEXT: catchswitch within none [label %catch.longjmp] unwind to caller
}
; void setjmp_and_nested_try() {
; int jmpval = setjmp(buf);
; if (jmpval != 0)
; return;
; try {
; foo();
; try {
; foo();
; } catch (...) {
; foo();
; }
; } catch (...) {
; }
; }
define void @setjmp_and_nested_try() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
; CHECK-LABEL: @setjmp_and_nested_try(
entry:
%call = call i32 @setjmp(%struct.__jmp_buf_tag* getelementptr inbounds ([1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* @_ZL3buf, i32 0, i32 0)) #0
%cmp = icmp ne i32 %call, 0
br i1 %cmp, label %try.cont10, label %if.end
if.end: ; preds = %entry
invoke void @foo()
to label %invoke.cont unwind label %catch.dispatch5
invoke.cont: ; preds = %if.end
invoke void @foo()
to label %try.cont10 unwind label %catch.dispatch
catch.dispatch: ; preds = %invoke.cont
%0 = catchswitch within none [label %catch.start] unwind label %catch.dispatch5
catch.start: ; preds = %catch.dispatch
%1 = catchpad within %0 [i8* null]
%2 = call i8* @llvm.wasm.get.exception(token %1)
%3 = call i32 @llvm.wasm.get.ehselector(token %1)
%4 = call i8* @__cxa_begin_catch(i8* %2) #2 [ "funclet"(token %1) ]
invoke void @foo() [ "funclet"(token %1) ]
to label %invoke.cont2 unwind label %ehcleanup
invoke.cont2: ; preds = %catch.start
invoke void @__cxa_end_catch() [ "funclet"(token %1) ]
to label %invoke.cont3 unwind label %catch.dispatch5
invoke.cont3: ; preds = %invoke.cont2
catchret from %1 to label %try.cont10
ehcleanup: ; preds = %catch.start
%5 = cleanuppad within %1 []
invoke void @__cxa_end_catch() [ "funclet"(token %5) ]
to label %invoke.cont4 unwind label %terminate
; CHECK: ehcleanup:
; CHECK-NEXT: [[T0:%.*]] = cleanuppad within {{.*}} []
; CHECK-NEXT: invoke void @__cxa_end_catch() [ "funclet"(token [[T0]]) ]
; CHECK-NEXT: to label %invoke.cont4 unwind label %terminate
invoke.cont4: ; preds = %ehcleanup
cleanupret from %5 unwind label %catch.dispatch5
; CHECK: invoke.cont4:
; CHECK-NEXT: cleanupret from [[T0]] unwind label %catch.dispatch5
catch.dispatch5: ; preds = %invoke.cont4, %invoke.cont2, %catch.dispatch, %if.end
%6 = catchswitch within none [label %catch.start6] unwind to caller
; CHECK: catch.dispatch5:
; CHECK-NEXT: catchswitch within none [label %catch.start6] unwind label %catch.dispatch.longjmp
catch.start6: ; preds = %catch.dispatch5
%7 = catchpad within %6 [i8* null]
%8 = call i8* @llvm.wasm.get.exception(token %7)
%9 = call i32 @llvm.wasm.get.ehselector(token %7)
%10 = call i8* @__cxa_begin_catch(i8* %8) #2 [ "funclet"(token %7) ]
call void @__cxa_end_catch() [ "funclet"(token %7) ]
catchret from %7 to label %try.cont10
; CHECK: catch.start6:
; CHECK-NEXT: [[T1:%.*]] = catchpad within {{.*}} [i8* null]
; CHECK-NEXT: call i8* @llvm.wasm.get.exception(token [[T1]])
; CHECK-NEXT: call i32 @llvm.wasm.get.ehselector(token [[T1]])
; CHECK-NEXT: call i8* @__cxa_begin_catch(i8* {{.*}}) {{.*}} [ "funclet"(token [[T1]]) ]
; CHECK: invoke void @__cxa_end_catch() [ "funclet"(token [[T1]]) ]
; CHECK-NEXT: to label %.noexc unwind label %catch.dispatch.longjmp
; CHECK: .noexc:
; CHECK-NEXT: catchret from [[T1]] to label {{.*}}
try.cont10: ; preds = %catch.start6, %invoke.cont3, %invoke.cont, %entry
ret void
terminate: ; preds = %ehcleanup
%11 = cleanuppad within %5 []
call void @terminate() #3 [ "funclet"(token %11) ]
unreachable
; CHECK: terminate:
; CHECK-NEXT: [[T2:%.*]] = cleanuppad within [[T0]] []
; Note that this call unwinds not to %catch.dispatch.longjmp but to
; %catch.dispatch5. This call is enclosed in the cleanuppad above, but there is
; no matching catchret, which has the unwind destination. So this checks this
; cleanuppad's parent, which is in 'ehcleanup', and unwinds to its unwind
; destination, %catch.dispatch5.
; This call was originally '_ZSt9terminatev', which is the mangled name for
; 'std::terminate'. But we listed that as "cannot longjmp", we changed
; the name of the function in this test to show the case in which a call has to
; change to an invoke whose unwind destination is determined by its parent
; chain.
; CHECK-NEXT: invoke void @terminate() {{.*}} [ "funclet"(token [[T2]]) ]
; CHECK-NEXT: to label %.noexc4 unwind label %catch.dispatch5
; CHECK: .noexc4:
; CHECK-NEXT: unreachable
; CHECK: catch.dispatch.longjmp:
; CHECK-NEXT: catchswitch within none [label %catch.longjmp] unwind to caller
}
declare void @foo()
; Function Attrs: returns_twice
declare i32 @setjmp(%struct.__jmp_buf_tag*) #0
; Function Attrs: noreturn
declare void @longjmp(%struct.__jmp_buf_tag*, i32) #1
declare i32 @__gxx_wasm_personality_v0(...)
; Function Attrs: nounwind
declare i8* @llvm.wasm.get.exception(token) #2
; Function Attrs: nounwind
declare i32 @llvm.wasm.get.ehselector(token) #2
declare i8* @__cxa_begin_catch(i8*)
declare void @__cxa_end_catch()
declare void @terminate()
attributes #0 = { returns_twice }
attributes #1 = { noreturn }
attributes #2 = { nounwind }
attributes #3 = { noreturn nounwind }