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