1; RUN: opt < %s -wasm-lower-em-ehsjlj -enable-emscripten-cxx-exceptions -enable-emscripten-sjlj -S | FileCheck %s
2; RUN: llc < %s -enable-emscripten-cxx-exceptions -enable-emscripten-sjlj -verify-machineinstrs
3
4; Tests for cases when exception handling and setjmp/longjmp handling are mixed.
5
6target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
7target triple = "wasm32-unknown-unknown"
8
9%struct.__jmp_buf_tag = type { [6 x i32], i32, [32 x i32] }
10@_ZTIi = external constant i8*
11
12; There is a function call (@foo) that can either throw an exception or longjmp
13; and there is also a setjmp call. When @foo throws, we have to check both for
14; exception and longjmp and jump to exception or longjmp handling BB depending
15; on the result.
16define void @setjmp_longjmp_exception() personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) {
17; CHECK-LABEL: @setjmp_longjmp_exception
18entry:
19  %buf = alloca [1 x %struct.__jmp_buf_tag], align 16
20  %arraydecay = getelementptr inbounds [1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* %buf, i32 0, i32 0
21  %call = call i32 @setjmp(%struct.__jmp_buf_tag* %arraydecay) #0
22  invoke void @foo()
23          to label %try.cont unwind label %lpad
24
25; CHECK:    entry.split.split:
26; CHECK:      %[[CMP0:.*]] = icmp ne i32 %__THREW__.val, 0
27; CHECK-NEXT: %__threwValue.val = load i32, i32* @__threwValue
28; CHECK-NEXT: %[[CMP1:.*]] = icmp ne i32 %__threwValue.val, 0
29; CHECK-NEXT: %[[CMP:.*]] = and i1 %[[CMP0]], %[[CMP1]]
30; CHECK-NEXT: br i1 %[[CMP]], label %if.then1, label %if.else1
31
32; This is exception checking part. %if.else1 leads here
33; CHECK:    entry.split.split.split:
34; CHECK-NEXT: %[[CMP:.*]] = icmp eq i32 %__THREW__.val, 1
35; CHECK-NEXT: br i1 %[[CMP]], label %lpad, label %try.cont
36
37; CHECK:    lpad:
38lpad:                                             ; preds = %entry
39  %0 = landingpad { i8*, i32 }
40          catch i8* null
41  %1 = extractvalue { i8*, i32 } %0, 0
42  %2 = extractvalue { i8*, i32 } %0, 1
43; CHECK-NOT:  call {{.*}} void @__invoke_void(void ()* @__cxa_end_catch)
44  %3 = call i8* @__cxa_begin_catch(i8* %1) #2
45  call void @__cxa_end_catch()
46  br label %try.cont
47
48try.cont:                                         ; preds = %lpad, %entry
49  ret void
50
51; longjmp checking part
52; CHECK:    if.then1:
53; CHECK:      call i32 @testSetjmp
54}
55
56; @foo can either throw an exception or longjmp. Because this function doesn't
57; have any setjmp calls, we only handle exceptions in this function. But because
58; sjlj is enabled, we check if the thrown value is longjmp and if so rethrow it
59; by calling @emscripten_longjmp.
60define void @rethrow_longjmp() personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) {
61; CHECK-LABEL: @rethrow_longjmp
62entry:
63  invoke void @foo()
64          to label %try.cont unwind label %lpad
65; CHECK:    entry:
66; CHECK:      %cmp.eq.one = icmp eq i32 %__THREW__.val, 1
67; CHECK-NEXT: %cmp.eq.zero = icmp eq i32 %__THREW__.val, 0
68; CHECK-NEXT: %or = or i1 %cmp.eq.zero, %cmp.eq.one
69; CHECK-NEXT: br i1 %or, label %tail, label %rethrow.longjmp
70
71; CHECK: try.cont:
72; CHECK-NEXT:  %phi = phi i32 [ undef, %tail ], [ undef, %lpad ]
73; CHECK-NEXT:  ret void
74
75; CHECK:    rethrow.longjmp:
76; CHECK-NEXT: %threw.phi = phi i32 [ %__THREW__.val, %entry ]
77; CHECK-NEXT: %__threwValue.val = load i32, i32* @__threwValue, align 4
78; CHECK-NEXT: call void @emscripten_longjmp(i32 %threw.phi, i32 %__threwValue.val
79; CHECK-NEXT: unreachable
80
81; CHECK:    tail:
82; CHECK-NEXT: %cmp = icmp eq i32 %__THREW__.val, 1
83; CHECK-NEXT: br i1 %cmp, label %lpad, label %try.cont
84
85lpad:                                             ; preds = %entry
86  %0 = landingpad { i8*, i32 }
87          catch i8* null
88  %1 = extractvalue { i8*, i32 } %0, 0
89  %2 = extractvalue { i8*, i32 } %0, 1
90  %3 = call i8* @__cxa_begin_catch(i8* %1) #5
91  call void @__cxa_end_catch()
92  br label %try.cont
93
94try.cont:                                         ; preds = %lpad, %entry
95 %phi = phi i32 [ undef, %entry ], [ undef, %lpad ]
96  ret void
97}
98
99; This function contains a setjmp call and no invoke, so we only handle longjmp
100; here. But @foo can also throw an exception, so we check if an exception is
101; thrown and if so rethrow it by calling @__resumeException. Also we have to
102; free the setjmpTable buffer before calling @__resumeException.
103define void @rethrow_exception() {
104; CHECK-LABEL: @rethrow_exception
105entry:
106  %buf = alloca [1 x %struct.__jmp_buf_tag], align 16
107  %arraydecay = getelementptr inbounds [1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* %buf, i32 0, i32 0
108  %call = call i32 @setjmp(%struct.__jmp_buf_tag* %arraydecay) #0
109  %cmp = icmp ne i32 %call, 0
110  br i1 %cmp, label %return, label %if.end
111
112if.end:                                           ; preds = %entry
113  call void @foo()
114  br label %return
115
116; CHECK:    if.end:
117; CHECK:      %cmp.eq.one = icmp eq i32 %__THREW__.val, 1
118; CHECK-NEXT: br i1 %cmp.eq.one, label %rethrow.exn, label %normal
119
120; CHECK:    rethrow.exn:
121; CHECK-NEXT: %exn = call i8* @__cxa_find_matching_catch_2()
122; CHECK-NEXT: %[[BUF:.*]] = bitcast i32* %setjmpTable{{.*}} to i8*
123; CHECK-NEXT: call void @free(i8* %[[BUF]])
124; CHECK-NEXT: call void @__resumeException(i8* %exn)
125; CHECK-NEXT: unreachable
126
127; CHECK:    normal:
128; CHECK-NEXT: icmp ne i32 %__THREW__.val, 0
129
130return:                                           ; preds = %if.end, %entry
131  ret void
132}
133
134; The same as 'rethrow_exception' but contains a __cxa_throw call. We have to
135; free the setjmpTable buffer before calling __cxa_throw.
136define void @rethrow_exception2() {
137; CHECK-LABEL: @rethrow_exception2
138entry:
139  %buf = alloca [1 x %struct.__jmp_buf_tag], align 16
140  %arraydecay = getelementptr inbounds [1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* %buf, i32 0, i32 0
141  %call = call i32 @setjmp(%struct.__jmp_buf_tag* %arraydecay) #0
142  %cmp = icmp ne i32 %call, 0
143  br i1 %cmp, label %throw, label %if.end
144
145if.end:                                           ; preds = %entry
146  call void @foo()
147  br label %throw
148
149throw:                                            ; preds = %if.end, %entry
150  call void @__cxa_throw(i8* null, i8* null, i8* null) #1
151  unreachable
152
153; CHECK:    throw:
154; CHECK:      %[[BUF:.*]] = bitcast i32* %setjmpTable{{.*}} to i8*
155; CHECK-NEXT: call void @free(i8* %[[BUF]])
156; CHECK-NEXT: call void @__cxa_throw(i8* null, i8* null, i8* null)
157; CHECK-NEXT: unreachable
158}
159
160; The same case with @rethrow_longjmp, but there are multiple function calls
161; that can possibly longjmp (instead of throwing exception) so we have to
162; rethrow them. Here we test if we correclty generate only one 'rethrow.longjmp'
163; BB and share it for multiple calls.
164define void @rethrow_longjmp_multi() personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) {
165; CHECK-LABEL: @rethrow_longjmp_multi
166entry:
167  invoke void @foo()
168          to label %bb unwind label %lpad
169
170bb:                                               ; preds = %entry
171  invoke void @foo()
172          to label %try.cont unwind label %lpad
173
174lpad:                                             ; preds = %bb, %entry
175  %0 = landingpad { i8*, i32 }
176          catch i8* null
177  %1 = extractvalue { i8*, i32 } %0, 0
178  %2 = extractvalue { i8*, i32 } %0, 1
179  %3 = call i8* @__cxa_begin_catch(i8* %1) #5
180  call void @__cxa_end_catch()
181  br label %try.cont
182
183try.cont:                                         ; preds = %lpad, %bb
184 %phi = phi i32 [ undef, %bb ], [ undef, %lpad ]
185  ret void
186
187; CHECK:    rethrow.longjmp:
188; CHECK-NEXT: %threw.phi = phi i32 [ %__THREW__.val, %entry ], [ %__THREW__.val1, %bb ]
189; CHECK-NEXT: %__threwValue.val = load i32, i32* @__threwValue, align 4
190; CHECK-NEXT: call void @emscripten_longjmp(i32 %threw.phi, i32 %__threwValue.val)
191; CHECK-NEXT: unreachable
192}
193
194; The same case with @rethrow_exception, but there are multiple function calls
195; that can possibly throw (instead of longjmping) so we have to rethrow them.
196; Here we test if correctly we generate only one 'rethrow.exn' BB and share it
197; for multiple calls.
198define void @rethrow_exception_multi() {
199; CHECK-LABEL: @rethrow_exception_multi
200entry:
201  %buf = alloca [1 x %struct.__jmp_buf_tag], align 16
202  %arraydecay = getelementptr inbounds [1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* %buf, i32 0, i32 0
203  %call = call i32 @setjmp(%struct.__jmp_buf_tag* %arraydecay) #0
204  %cmp = icmp ne i32 %call, 0
205  br i1 %cmp, label %return, label %if.end
206
207if.end:                                           ; preds = %entry
208  call void @foo()
209  call void @foo()
210  br label %return
211
212return:                                           ; preds = %entry, %if.end
213  ret void
214
215; CHECK:    rethrow.exn:
216; CHECK-NEXT: %exn = call i8* @__cxa_find_matching_catch_2()
217; CHECK-NEXT: %{{.*}} = bitcast i32* %setjmpTable{{.*}} to i8*
218; CHECK-NEXT: tail call void @free(i8* %{{.*}})
219; CHECK-NEXT: call void @__resumeException(i8* %exn)
220; CHECK-NEXT: unreachable
221}
222
223; int jmpval = setjmp(buf);
224; if (jmpval != 0)
225;   return;
226; try {
227;   throw 3;
228; } catch (...) {
229; }
230define void @setjmp_with_throw_try_catch() personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) {
231; CHECK-LABEL: @setjmp_with_throw_try_catch
232entry:
233  %buf = alloca [1 x %struct.__jmp_buf_tag], align 16
234  %arraydecay = getelementptr inbounds [1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* %buf, i32 0, i32 0
235  %call = call i32 @setjmp(%struct.__jmp_buf_tag* %arraydecay) #0
236  %cmp = icmp ne i32 %call, 0
237  br i1 %cmp, label %try.cont, label %if.end
238
239if.end:                                           ; preds = %entry
240  %exception = call i8* @__cxa_allocate_exception(i32 4) #2
241  %0 = bitcast i8* %exception to i32*
242  store i32 3, i32* %0, align 16
243  invoke void @__cxa_throw(i8* %exception, i8* bitcast (i8** @_ZTIi to i8*), i8* null) #1
244          to label %unreachable unwind label %lpad
245; When invoke @__cxa_throw is converted to a call to the invoke wrapper,
246; "noreturn" attribute should be removed, and there should be no 'free' call
247; before the call. We insert a 'free' call that frees 'setjmpTable' before every
248; function-exiting instruction. And invoke wrapper calls shouldn't be treated as
249; noreturn instructions, because they are supposed to return.
250; CHECK:   if.end:
251; CHECK-NOT: tail call void @free
252; CHECK-NOT: call cc99 void @"__invoke_void_i8*_i8*_i8*"(void (i8*, i8*, i8*)* @__cxa_throw, i8* %exception, i8* bitcast (i8** @_ZTIi to i8*), i8* null) #
253; CHECK:     call cc99 void @"__invoke_void_i8*_i8*_i8*"(void (i8*, i8*, i8*)* @__cxa_throw, i8* %exception, i8* bitcast (i8** @_ZTIi to i8*), i8* null)
254
255lpad:                                             ; preds = %if.end
256  %1 = landingpad { i8*, i32 }
257          catch i8* null
258  %2 = extractvalue { i8*, i32 } %1, 0
259  %3 = extractvalue { i8*, i32 } %1, 1
260  %4 = call i8* @__cxa_begin_catch(i8* %2) #2
261  call void @__cxa_end_catch()
262  br label %try.cont
263
264try.cont:                                         ; preds = %entry, %lpad
265  ret void
266
267unreachable:                                      ; preds = %if.end
268  unreachable
269}
270
271declare void @foo()
272; Function Attrs: returns_twice
273declare i32 @setjmp(%struct.__jmp_buf_tag*)
274; Function Attrs: noreturn
275declare void @longjmp(%struct.__jmp_buf_tag*, i32)
276declare i32 @__gxx_personality_v0(...)
277declare i8* @__cxa_begin_catch(i8*)
278declare void @__cxa_end_catch()
279declare void @__cxa_throw(i8*, i8*, i8*)
280declare i8* @__cxa_allocate_exception(i32)
281
282attributes #0 = { returns_twice }
283attributes #1 = { noreturn }
284attributes #2 = { nounwind }
285