xref: /llvm-project/llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj.ll (revision 6420f379268e9178f9f938cef223194ad3daae4e)
1; RUN: opt < %s -wasm-lower-em-ehsjlj -wasm-enable-eh -wasm-enable-sjlj -S | FileCheck %s
2; RUN: llc < %s -wasm-enable-eh -wasm-enable-sjlj -exception-model=wasm -mattr=+exception-handling -verify-machineinstrs
3
4target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
5target triple = "wasm32-unknown-unknown"
6
7%struct.__jmp_buf_tag = type { [6 x i32], i32, [32 x i32] }
8%struct.Temp = type { i8 }
9@_ZL3buf = internal global [1 x %struct.__jmp_buf_tag] zeroinitializer, align 16
10
11; void test() {
12;   int jmpval = setjmp(buf);
13;   if (jmpval != 0)
14;     return;
15;   try {
16;     foo();
17;   } catch (...) {
18;   }
19; }
20define void @setjmp_and_try() personality ptr @__gxx_wasm_personality_v0 {
21; CHECK-LABEL: @setjmp_and_try
22entry:
23  %call = call i32 @setjmp(ptr @_ZL3buf) #0
24  %cmp = icmp ne i32 %call, 0
25  br i1 %cmp, label %return, label %if.end
26
27if.end:                                           ; preds = %entry
28  invoke void @foo()
29  to label %return unwind label %catch.dispatch
30
31catch.dispatch:                                   ; preds = %if.end
32  %0 = catchswitch within none [label %catch.start] unwind to caller
33; CHECK:       catch.dispatch:
34; CHECK-NEXT:    catchswitch within none [label %catch.start] unwind label %catch.dispatch.longjmp
35
36catch.start:                                      ; preds = %catch.dispatch
37  %1 = catchpad within %0 [ptr null]
38  %2 = call ptr @llvm.wasm.get.exception(token %1)
39  %3 = call i32 @llvm.wasm.get.ehselector(token %1)
40  %4 = call ptr @__cxa_begin_catch(ptr %2) #2 [ "funclet"(token %1) ]
41  call void @__cxa_end_catch() [ "funclet"(token %1) ]
42  catchret from %1 to label %return
43; CHECK:       catch.start:
44; CHECK:         [[T0:%.*]] = catchpad within {{.*}} [ptr null]
45; CHECK:         invoke void @__cxa_end_catch() [ "funclet"(token [[T0]]) ]
46; CHECK-NEXT:    to label %.noexc unwind label %catch.dispatch.longjmp
47
48; CHECK:       .noexc:
49; CHECK-NEXT:     catchret from [[T0]] to label {{.*}}
50
51return:                                           ; preds = %catch.start, %if.end, %entry
52  ret void
53
54; CHECK:       catch.dispatch.longjmp:
55; CHECK-NEXT:    catchswitch within none [label %catch.longjmp] unwind to caller
56}
57
58; void setjmp_within_try() {
59;   try {
60;     foo();
61;     int jmpval = setjmp(buf);
62;     if (jmpval != 0)
63;       return;
64;     foo();
65;   } catch (...) {
66;   }
67; }
68define void @setjmp_within_try() personality ptr @__gxx_wasm_personality_v0 {
69; CHECK-LABEL: @setjmp_within_try
70entry:
71  %jmpval = alloca i32, align 4
72  %exn.slot = alloca ptr, align 4
73  invoke void @foo()
74  to label %invoke.cont unwind label %catch.dispatch
75
76invoke.cont:                                      ; preds = %entry
77  %call = invoke i32 @setjmp(ptr @_ZL3buf) #0
78  to label %invoke.cont1 unwind label %catch.dispatch
79
80invoke.cont1:                                     ; preds = %invoke.cont
81  store i32 %call, ptr %jmpval, align 4
82  %0 = load i32, ptr %jmpval, align 4
83  %cmp = icmp ne i32 %0, 0
84  br i1 %cmp, label %if.then, label %if.end
85
86if.then:                                          ; preds = %invoke.cont1
87  br label %try.cont
88
89if.end:                                           ; preds = %invoke.cont1
90  invoke void @foo()
91  to label %invoke.cont2 unwind label %catch.dispatch
92
93catch.dispatch:                                   ; preds = %if.end, %invoke.cont, %entry
94  %1 = catchswitch within none [label %catch.start] unwind to caller
95
96; CHECK:       catch.dispatch:
97; CHECK:         catchswitch within none [label %catch.start] unwind label %catch.dispatch.longjmp
98catch.start:                                      ; preds = %catch.dispatch
99  %2 = catchpad within %1 [ptr null]
100  %3 = call ptr @llvm.wasm.get.exception(token %2)
101  store ptr %3, ptr %exn.slot, align 4
102  %4 = call i32 @llvm.wasm.get.ehselector(token %2)
103  br label %catch
104
105catch:                                            ; preds = %catch.start
106  %exn = load ptr, ptr %exn.slot, align 4
107  %5 = call ptr @__cxa_begin_catch(ptr %exn) #2 [ "funclet"(token %2) ]
108  call void @__cxa_end_catch() [ "funclet"(token %2) ]
109  catchret from %2 to label %catchret.dest
110; CHECK: catch:                                            ; preds = %catch.start
111; CHECK-NEXT:   %exn = load ptr, ptr %exn.slot6, align 4
112; CHECK-NEXT:   %5 = call ptr @__cxa_begin_catch(ptr %exn) #6 [ "funclet"(token %2) ]
113; CHECK-NEXT:   invoke void @__cxa_end_catch() [ "funclet"(token %2) ]
114; CHECK-NEXT:           to label %.noexc unwind label %catch.dispatch.longjmp
115
116catchret.dest:                                    ; preds = %catch
117  br label %try.cont
118
119try.cont:                                         ; preds = %invoke.cont2, %catchret.dest, %if.then
120  ret void
121
122invoke.cont2:                                     ; preds = %if.end
123  br label %try.cont
124
125; CHECK:       catch.dispatch.longjmp:
126; CHECK-NEXT:    catchswitch within none [label %catch.longjmp] unwind to caller
127}
128
129; void setjmp_and_nested_try() {
130;   int jmpval = setjmp(buf);
131;   if (jmpval != 0)
132;     return;
133;   try {
134;     foo();
135;     try {
136;       foo();
137;     } catch (...) {
138;       foo();
139;     }
140;   } catch (...) {
141;   }
142; }
143define void @setjmp_and_nested_try() personality ptr @__gxx_wasm_personality_v0 {
144; CHECK-LABEL: @setjmp_and_nested_try
145entry:
146  %call = call i32 @setjmp(ptr @_ZL3buf) #0
147  %cmp = icmp ne i32 %call, 0
148  br i1 %cmp, label %try.cont10, label %if.end
149
150if.end:                                           ; preds = %entry
151  invoke void @foo()
152  to label %invoke.cont unwind label %catch.dispatch5
153
154invoke.cont:                                      ; preds = %if.end
155  invoke void @foo()
156  to label %try.cont10 unwind label %catch.dispatch
157
158catch.dispatch:                                   ; preds = %invoke.cont
159  %0 = catchswitch within none [label %catch.start] unwind label %catch.dispatch5
160
161catch.start:                                      ; preds = %catch.dispatch
162  %1 = catchpad within %0 [ptr null]
163  %2 = call ptr @llvm.wasm.get.exception(token %1)
164  %3 = call i32 @llvm.wasm.get.ehselector(token %1)
165  %4 = call ptr @__cxa_begin_catch(ptr %2) #2 [ "funclet"(token %1) ]
166  invoke void @foo() [ "funclet"(token %1) ]
167  to label %invoke.cont2 unwind label %ehcleanup
168
169invoke.cont2:                                     ; preds = %catch.start
170  invoke void @__cxa_end_catch() [ "funclet"(token %1) ]
171  to label %invoke.cont3 unwind label %catch.dispatch5
172
173invoke.cont3:                                     ; preds = %invoke.cont2
174  catchret from %1 to label %try.cont10
175
176ehcleanup:                                        ; preds = %catch.start
177  %5 = cleanuppad within %1 []
178  invoke void @__cxa_end_catch() [ "funclet"(token %5) ]
179  to label %invoke.cont4 unwind label %terminate
180; CHECK:       ehcleanup:
181; CHECK-NEXT:    [[T0:%.*]] = cleanuppad within {{.*}} []
182; CHECK-NEXT:    invoke void @__cxa_end_catch() [ "funclet"(token [[T0]]) ]
183; CHECK-NEXT:    to label %invoke.cont4 unwind label %terminate
184
185invoke.cont4:                                     ; preds = %ehcleanup
186  cleanupret from %5 unwind label %catch.dispatch5
187; CHECK:       invoke.cont4:
188; CHECK-NEXT:    cleanupret from [[T0]] unwind label %catch.dispatch5
189
190catch.dispatch5:                                  ; preds = %invoke.cont4, %invoke.cont2, %catch.dispatch, %if.end
191  %6 = catchswitch within none [label %catch.start6] unwind to caller
192; CHECK:       catch.dispatch5:
193; CHECK-NEXT:    catchswitch within none [label %catch.start6] unwind label %catch.dispatch.longjmp
194
195catch.start6:                                     ; preds = %catch.dispatch5
196  %7 = catchpad within %6 [ptr null]
197  %8 = call ptr @llvm.wasm.get.exception(token %7)
198  %9 = call i32 @llvm.wasm.get.ehselector(token %7)
199  %10 = call ptr @__cxa_begin_catch(ptr %8) #2 [ "funclet"(token %7) ]
200  call void @__cxa_end_catch() [ "funclet"(token %7) ]
201  catchret from %7 to label %try.cont10
202; CHECK:       catch.start6:
203; CHECK-NEXT:    [[T1:%.*]] = catchpad within {{.*}} [ptr null]
204; CHECK-NEXT:    call ptr @llvm.wasm.get.exception(token [[T1]])
205; CHECK-NEXT:    call i32 @llvm.wasm.get.ehselector(token [[T1]])
206; CHECK-NEXT:    call ptr @__cxa_begin_catch(ptr {{.*}}) {{.*}} [ "funclet"(token [[T1]]) ]
207; CHECK:         invoke void @__cxa_end_catch() [ "funclet"(token [[T1]]) ]
208; CHECK-NEXT:    to label %.noexc unwind label %catch.dispatch.longjmp
209
210; CHECK:       .noexc:
211; CHECK-NEXT:    catchret from [[T1]] to label {{.*}}
212
213try.cont10:                                       ; preds = %catch.start6, %invoke.cont3, %invoke.cont, %entry
214  ret void
215
216terminate:                                        ; preds = %ehcleanup
217  %11 = cleanuppad within %5 []
218  call void @terminate() #3 [ "funclet"(token %11) ]
219  unreachable
220; CHECK:       terminate:
221; CHECK-NEXT:    [[T2:%.*]] = cleanuppad within [[T0]] []
222; Note that this call unwinds not to %catch.dispatch.longjmp but to
223; %catch.dispatch5. This call is enclosed in the cleanuppad above, but there is
224; no matching catchret, which has the unwind destination. So this checks this
225; cleanuppad's parent, which is in 'ehcleanup', and unwinds to its unwind
226; destination, %catch.dispatch5.
227; This call was originally '_ZSt9terminatev', which is the mangled name for
228; 'std::terminate'. But we listed that as "cannot longjmp", we changed
229; the name of the function in this test to show the case in which a call has to
230; change to an invoke whose unwind destination is determined by its parent
231; chain.
232; CHECK-NEXT:    invoke void @terminate() {{.*}} [ "funclet"(token [[T2]]) ]
233; CHECK-NEXT:    to label %[[NOEXC:.*]] unwind label %catch.dispatch5
234
235; CHECK:       [[NOEXC]]:
236; CHECK-NEXT:    unreachable
237
238; CHECK:       catch.dispatch.longjmp:
239; CHECK-NEXT:    catchswitch within none [label %catch.longjmp] unwind to caller
240}
241
242; void @cleanuppad_no_parent {
243;   jmp_buf buf;
244;   Temp t;
245;   setjmp(buf);
246; }
247define void @cleanuppad_no_parent() personality ptr @__gxx_wasm_personality_v0 {
248; CHECK-LABEL: @cleanuppad_no_parent
249entry:
250  %buf = alloca [1 x %struct.__jmp_buf_tag], align 16
251  %t = alloca %struct.Temp, align 1
252  %call = invoke i32 @setjmp(ptr noundef %buf) #0
253          to label %invoke.cont unwind label %ehcleanup
254
255invoke.cont:                                      ; preds = %entry
256  %call1 = call noundef ptr @_ZN4TempD2Ev(ptr noundef %t) #2
257  ret void
258
259ehcleanup:                                        ; preds = %entry
260  %0 = cleanuppad within none []
261  ; After SjLj transformation, this will be converted to an invoke that
262  ; eventually unwinds to %catch.dispatch.longjmp. But in case a call has a
263  ; "funclet" attribute, we should unwind to the funclet's unwind destination
264  ; first to preserve the scoping structure. But this call's parent is %0
265  ; (cleanuppad), whose parent is 'none', so we should unwind directly to
266  ; %catch.dispatch.longjmp.
267  %call2 = call noundef ptr @_ZN4TempD2Ev(ptr noundef %t) #2 [ "funclet"(token %0) ]
268; CHECK: %call11 = invoke {{.*}} ptr @_ZN4TempD2Ev(ptr
269; CHECK-NEXT:    to label {{.*}} unwind label %catch.dispatch.longjmp
270  cleanupret from %0 unwind to caller
271}
272
273; This case was adapted from @cleanuppad_no_parent by removing allocas and
274; destructor calls, to generate a situation that there's only 'invoke @setjmp'
275; and no other longjmpable calls.
276define i32 @setjmp_only() personality ptr @__gxx_wasm_personality_v0 {
277; CHECK-LABEL: @setjmp_only
278entry:
279  %buf = alloca [1 x %struct.__jmp_buf_tag], align 16
280  %call = invoke i32 @setjmp(ptr noundef %buf) #0
281          to label %invoke.cont unwind label %ehcleanup
282
283invoke.cont:                                      ; preds = %entry
284  ret i32 %call
285; CHECK: invoke.cont:
286; The remaining setjmp call is converted to constant 0, because setjmp returns 0
287; when called directly.
288; CHECK:   ret i32 0
289
290ehcleanup:                                        ; preds = %entry
291  %0 = cleanuppad within none []
292  cleanupret from %0 unwind to caller
293}
294
295declare void @foo()
296; Function Attrs: nounwind
297declare ptr @_ZN4TempD2Ev(ptr %this) #2
298; Function Attrs: returns_twice
299declare i32 @setjmp(ptr) #0
300; Function Attrs: noreturn
301declare void @longjmp(ptr, i32) #1
302declare i32 @__gxx_wasm_personality_v0(...)
303; Function Attrs: nounwind
304declare ptr @llvm.wasm.get.exception(token) #2
305; Function Attrs: nounwind
306declare i32 @llvm.wasm.get.ehselector(token) #2
307declare ptr @__cxa_begin_catch(ptr)
308declare void @__cxa_end_catch()
309declare void @terminate()
310
311attributes #0 = { returns_twice }
312attributes #1 = { noreturn }
313attributes #2 = { nounwind }
314attributes #3 = { noreturn nounwind }
315