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 i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
21; CHECK-LABEL: @setjmp_and_try
22entry:
23  %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
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 [i8* null]
38  %2 = call i8* @llvm.wasm.get.exception(token %1)
39  %3 = call i32 @llvm.wasm.get.ehselector(token %1)
40  %4 = call i8* @__cxa_begin_catch(i8* %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 {{.*}} [i8* 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 i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
69; CHECK-LABEL: @setjmp_within_try
70entry:
71  %jmpval = alloca i32, align 4
72  %exn.slot = alloca i8*, 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(%struct.__jmp_buf_tag* getelementptr inbounds ([1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* @_ZL3buf, i32 0, i32 0)) #0
78  to label %invoke.cont1 unwind label %catch.dispatch
79
80invoke.cont1:                                     ; preds = %invoke.cont
81  store i32 %call, i32* %jmpval, align 4
82  %0 = load i32, i32* %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 [i8* null]
100  %3 = call i8* @llvm.wasm.get.exception(token %2)
101  store i8* %3, i8** %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 i8*, i8** %exn.slot, align 4
107  %5 = call i8* @__cxa_begin_catch(i8* %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 i8*, i8** %exn.slot15, align 4
112; CHECK-NEXT:   %5 = call i8* @__cxa_begin_catch(i8* %exn) #2 [ "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 i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
144; CHECK-LABEL: @setjmp_and_nested_try
145entry:
146  %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
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 [i8* null]
163  %2 = call i8* @llvm.wasm.get.exception(token %1)
164  %3 = call i32 @llvm.wasm.get.ehselector(token %1)
165  %4 = call i8* @__cxa_begin_catch(i8* %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 [i8* null]
197  %8 = call i8* @llvm.wasm.get.exception(token %7)
198  %9 = call i32 @llvm.wasm.get.ehselector(token %7)
199  %10 = call i8* @__cxa_begin_catch(i8* %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 {{.*}} [i8* null]
204; CHECK-NEXT:    call i8* @llvm.wasm.get.exception(token [[T1]])
205; CHECK-NEXT:    call i32 @llvm.wasm.get.ehselector(token [[T1]])
206; CHECK-NEXT:    call i8* @__cxa_begin_catch(i8* {{.*}}) {{.*}} [ "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 %.noexc4 unwind label %catch.dispatch5
234
235; CHECK:       .noexc4:
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 i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
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  %arraydecay = getelementptr inbounds [1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* %buf, i32 0, i32 0
253  %call = invoke i32 @setjmp(%struct.__jmp_buf_tag* noundef %arraydecay) #0
254          to label %invoke.cont unwind label %ehcleanup
255
256invoke.cont:                                      ; preds = %entry
257  %call1 = call noundef %struct.Temp* @_ZN4TempD2Ev(%struct.Temp* noundef %t) #2
258  ret void
259
260ehcleanup:                                        ; preds = %entry
261  %0 = cleanuppad within none []
262  ; After SjLj transformation, this will be converted to an invoke that
263  ; eventually unwinds to %catch.dispatch.longjmp. But in case a call has a
264  ; "funclet" attribute, we should unwind to the funclet's unwind destination
265  ; first to preserve the scoping structure. But this call's parent is %0
266  ; (cleanuppad), whose parent is 'none', so we should unwind directly to
267  ; %catch.dispatch.longjmp.
268  %call2 = call noundef %struct.Temp* @_ZN4TempD2Ev(%struct.Temp* noundef %t) #2 [ "funclet"(token %0) ]
269; CHECK: %call13 = invoke {{.*}} %struct.Temp* @_ZN4TempD2Ev(%struct.Temp*
270; CHECK-NEXT:    to label {{.*}} unwind label %catch.dispatch.longjmp
271  cleanupret from %0 unwind to caller
272}
273
274; This case was adapted from @cleanuppad_no_parent by removing allocas and
275; destructor calls, to generate a situation that there's only 'invoke @setjmp'
276; and no other longjmpable calls.
277define i32 @setjmp_only() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
278; CHECK-LABEL: @setjmp_only
279entry:
280  %buf = alloca [1 x %struct.__jmp_buf_tag], align 16
281  %arraydecay = getelementptr inbounds [1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* %buf, i32 0, i32 0
282  %call = invoke i32 @setjmp(%struct.__jmp_buf_tag* noundef %arraydecay) #0
283          to label %invoke.cont unwind label %ehcleanup
284
285invoke.cont:                                      ; preds = %entry
286  ret i32 %call
287; CHECK: invoke.cont:
288; The remaining setjmp call is converted to constant 0, because setjmp returns 0
289; when called directly.
290; CHECK:   ret i32 0
291
292ehcleanup:                                        ; preds = %entry
293  %0 = cleanuppad within none []
294  cleanupret from %0 unwind to caller
295}
296
297declare void @foo()
298; Function Attrs: nounwind
299declare %struct.Temp* @_ZN4TempD2Ev(%struct.Temp* %this) #2
300; Function Attrs: returns_twice
301declare i32 @setjmp(%struct.__jmp_buf_tag*) #0
302; Function Attrs: noreturn
303declare void @longjmp(%struct.__jmp_buf_tag*, i32) #1
304declare i32 @__gxx_wasm_personality_v0(...)
305; Function Attrs: nounwind
306declare i8* @llvm.wasm.get.exception(token) #2
307; Function Attrs: nounwind
308declare i32 @llvm.wasm.get.ehselector(token) #2
309declare i8* @__cxa_begin_catch(i8*)
310declare void @__cxa_end_catch()
311declare void @terminate()
312
313attributes #0 = { returns_twice }
314attributes #1 = { noreturn }
315attributes #2 = { nounwind }
316attributes #3 = { noreturn nounwind }
317