xref: /llvm-project/llvm/test/CodeGen/WebAssembly/lower-wasm-sjlj.ll (revision 6420f379268e9178f9f938cef223194ad3daae4e)
1; RUN: opt < %s -wasm-lower-em-ehsjlj -wasm-enable-sjlj -S | FileCheck %s -DPTR=i32
2; RUN: opt < %s -wasm-lower-em-ehsjlj -wasm-enable-sjlj -S --mattr=+atomics,+bulk-memory | FileCheck %s -DPTR=i32
3; RUN: opt < %s -wasm-lower-em-ehsjlj -wasm-enable-sjlj --mtriple=wasm64-unknown-unknown -data-layout="e-m:e-p:64:64-i64:64-n32:64-S128" -S | FileCheck %s -DPTR=i64
4
5target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
6target triple = "wasm32-unknown-unknown"
7
8%struct.__jmp_buf_tag = type { [6 x i32], i32, [32 x i32] }
9
10; These variables are only used in Emscripten EH/SjLj, so they shouldn't be
11; generated.
12; CHECK-NOT: @__THREW__ =
13; CHECK-NOT: @__threwValue =
14
15@global_longjmp_ptr = global ptr @longjmp, align 4
16; CHECK-DAG: @global_longjmp_ptr = global ptr @__wasm_longjmp
17
18; Test a simple setjmp - longjmp sequence
19define void @setjmp_longjmp() {
20; CHECK-LABEL: @setjmp_longjmp()
21entry:
22  %buf = alloca [1 x %struct.__jmp_buf_tag], align 16
23  %call = call i32 @setjmp(ptr %buf) #0
24  call void @longjmp(ptr %buf, i32 1) #1
25  unreachable
26
27; CHECK:    entry:
28; CHECK-NEXT: %functionInvocationId = alloca i32, align 4
29; CHECK-NEXT: br label %setjmp.dispatch
30
31; CHECK:    setjmp.dispatch:
32; CHECK-NEXT: %[[VAL2:.*]] = phi i32 [ %val, %if.end ], [ undef, %entry ]
33; CHECK-NEXT: %[[BUF:.*]] = phi ptr [ %[[BUF2:.*]], %if.end ], [ undef, %entry ]
34; CHECK-NEXT: %label.phi = phi i32 [ %label, %if.end ], [ -1, %entry ]
35; CHECK-NEXT: switch i32 %label.phi, label %entry.split [
36; CHECK-NEXT:   i32 1, label %entry.split.split
37; CHECK-NEXT: ]
38
39; CHECK:    entry.split:
40; CHECK-NEXT: %buf = alloca [1 x %struct.__jmp_buf_tag], align 16
41; CHECK-NEXT: call void @__wasm_setjmp(ptr %buf, i32 1, ptr %functionInvocationId)
42; CHECK-NEXT: br label %entry.split.split
43
44; CHECK:    entry.split.split:
45; CHECK-NEXT: %[[BUF2]] = phi ptr [ %[[BUF]], %setjmp.dispatch ], [ %buf, %entry.split ]
46; CHECK-NEXT: %setjmp.ret = phi i32 [ 0, %entry.split ], [ %[[VAL2]], %setjmp.dispatch ]
47; CHECK-NEXT: invoke void @__wasm_longjmp(ptr %[[BUF2]], i32 1)
48; CHECK-NEXT:         to label %.noexc unwind label %catch.dispatch.longjmp
49
50; CHECK:    .noexc:
51; CHECK-NEXT: unreachable
52
53; CHECK:    catch.dispatch.longjmp:
54; CHECK-NEXT: %0 = catchswitch within none [label %catch.longjmp] unwind to caller
55
56; CHECK:    catch.longjmp:
57; CHECK-NEXT: %1 = catchpad within %0 []
58; CHECK-NEXT: %thrown = call ptr @llvm.wasm.catch(i32 1)
59; CHECK-NEXT: %env_gep = getelementptr { ptr, i32 }, ptr %thrown, i32 0, i32 0
60; CHECK-NEXT: %val_gep = getelementptr { ptr, i32 }, ptr %thrown, i32 0, i32 1
61; CHECK-NEXT: %env = load ptr, ptr %env_gep, align {{.*}}
62; CHECK-NEXT: %val = load i32, ptr %val_gep, align 4
63; CHECK-NEXT: %label = call i32 @__wasm_setjmp_test(ptr %env, ptr %functionInvocationId) [ "funclet"(token %1) ]
64; CHECK-NEXT: %2 = icmp eq i32 %label, 0
65; CHECK-NEXT: br i1 %2, label %if.then, label %if.end
66
67; CHECK:    if.then:
68; CHECK-NEXT: call void @__wasm_longjmp(ptr %env, i32 %val) [ "funclet"(token %1) ]
69; CHECK-NEXT: unreachable
70
71; CHECK:    if.end:
72; CHECK-NEXT: catchret from %1 to label %setjmp.dispatch
73}
74
75; When there are multiple longjmpable calls after setjmp. This will turn each of
76; longjmpable call into an invoke whose unwind destination is
77; 'catch.dispatch.longjmp' BB.
78define void @setjmp_multiple_longjmpable_calls() {
79; CHECK-LABEL: @setjmp_multiple_longjmpable_calls
80entry:
81  %buf = alloca [1 x %struct.__jmp_buf_tag], align 16
82  %call = call i32 @setjmp(ptr %buf) #0
83  call void @foo()
84  call void @foo()
85  ret void
86
87; CHECK: entry.split.split:
88; CHECK:   invoke void @foo()
89; CHECK:           to label %{{.*}} unwind label %catch.dispatch.longjmp
90
91; CHECK: .noexc:
92; CHECK:   invoke void @foo()
93; CHECK:           to label %{{.*}} unwind label %catch.dispatch.longjmp
94}
95
96; Tests cases where longjmp function pointer is used in other ways than direct
97; calls. longjmps should be replaced with (void(*)(jmp_buf*, int))__wasm_longjmp.
98declare void @take_longjmp(ptr %arg_ptr)
99define void @indirect_longjmp() {
100; CHECK-LABEL: @indirect_longjmp
101entry:
102  %local_longjmp_ptr = alloca ptr, align 4
103  %buf0 = alloca [1 x %struct.__jmp_buf_tag], align 16
104  %buf1 = alloca [1 x %struct.__jmp_buf_tag], align 16
105
106  ; Store longjmp in a local variable, load it, and call it
107  store ptr @longjmp, ptr %local_longjmp_ptr, align 4
108  ; CHECK: store ptr @__wasm_longjmp, ptr %local_longjmp_ptr, align 4
109  %longjmp_from_local_ptr = load ptr, ptr %local_longjmp_ptr, align 4
110  call void %longjmp_from_local_ptr(ptr %buf0, i32 0)
111
112  ; Load longjmp from a global variable and call it
113  %longjmp_from_global_ptr = load ptr, ptr @global_longjmp_ptr, align 4
114  call void %longjmp_from_global_ptr(ptr %buf1, i32 0)
115
116  ; Pass longjmp as a function argument. This is a call but longjmp is not a
117  ; callee but an argument.
118  call void @take_longjmp(ptr @longjmp)
119  ; CHECK: call void @take_longjmp(ptr @__wasm_longjmp)
120  ret void
121}
122
123; Function Attrs: nounwind
124declare void @foo() #2
125; The pass removes the 'nounwind' attribute, so there should be no attributes
126; CHECK-NOT: declare void @foo #{{.*}}
127; Function Attrs: returns_twice
128declare i32 @setjmp(ptr) #0
129; Function Attrs: noreturn
130declare void @longjmp(ptr, i32) #1
131declare i32 @__gxx_personality_v0(...)
132declare ptr @__cxa_begin_catch(ptr)
133declare void @__cxa_end_catch()
134declare void @free(ptr)
135
136; Runtime glue function declarations
137; CHECK-DAG: declare void @__wasm_setjmp(ptr, i32, ptr)
138; CHECK-DAG: declare i32 @__wasm_setjmp_test(ptr, ptr)
139; CHECK-DAG: declare void @__wasm_longjmp(ptr, i32)
140
141attributes #0 = { returns_twice }
142attributes #1 = { noreturn }
143attributes #2 = { nounwind }
144