xref: /llvm-project/llvm/test/CodeGen/WebAssembly/exception.ll (revision 539b2e06542f7c099885533e4472e6fb3084aa96)
1; RUN: llc < %s -asm-verbose=false -wasm-enable-eh -wasm-use-legacy-eh=false -exception-model=wasm -mattr=+exception-handling -verify-machineinstrs | FileCheck --implicit-check-not=ehgcr -allow-deprecated-dag-overlap %s
2; RUN: llc < %s -asm-verbose=false -wasm-enable-eh -wasm-use-legacy-eh=false -exception-model=wasm -mattr=+exception-handling -verify-machineinstrs -O0
3; RUN: llc < %s -wasm-enable-eh -wasm-use-legacy-eh=false -exception-model=wasm -mattr=+exception-handling
4; RUN: llc < %s -wasm-enable-eh -wasm-use-legacy-eh=false -exception-model=wasm -mattr=+exception-handling -filetype=obj
5; RUN: llc < %s -mtriple=wasm64-unknown-unknown -wasm-enable-eh -wasm-use-legacy-eh=false -exception-model=wasm -mattr=+exception-handling -verify-machineinstrs | FileCheck --implicit-check-not=ehgcr -allow-deprecated-dag-overlap %s --check-prefix=WASM64
6
7target triple = "wasm32-unknown-unknown"
8
9%struct.Temp = type { i8 }
10
11@_ZTIi = external dso_local constant ptr
12
13; CHECK: .tagtype  __cpp_exception i32
14
15; CHECK-LABEL: throw:
16; CHECK:     throw __cpp_exception
17; CHECK-NOT: unreachable
18define void @throw(ptr %p) {
19  call void @llvm.wasm.throw(i32 0, ptr %p)
20  ret void
21}
22
23; Simple test with a try-catch
24;
25; void foo();
26; void catch() {
27;   try {
28;     foo();
29;   } catch (int) {
30;   }
31; }
32
33; CHECK-LABEL: catch:
34; WASM64-LABEL: catch:
35; CHECK: global.get  __stack_pointer
36; CHECK: local.set  0
37; CHECK: block
38; CHECK:   block     () -> (i32, exnref)
39; CHECK:     try_table    (catch_ref __cpp_exception 0)
40; WASM64:  block     () -> (i64, exnref)
41; CHECK:       call  foo
42; CHECK:       br        2
43; CHECK:     end_try_table
44; CHECK:     unreachable
45; CHECK:   end_block
46; CHECK:   local.set  2
47; CHECK:   local.get  0
48; CHECK:   global.set  __stack_pointer
49; CHECK:   i32.store  __wasm_lpad_context
50; CHECK:   call  _Unwind_CallPersonality
51; CHECK:   block
52; CHECK:     br_if     0
53; CHECK:     call  __cxa_begin_catch
54; CHECK:     call  __cxa_end_catch
55; CHECK:     br        1
56; CHECK:   end_block
57; CHECK:   local.get  2
58; CHECK:   throw_ref
59; CHECK: end_block
60define void @catch() personality ptr @__gxx_wasm_personality_v0 {
61entry:
62  invoke void @foo()
63          to label %try.cont unwind label %catch.dispatch
64
65catch.dispatch:                                   ; preds = %entry
66  %0 = catchswitch within none [label %catch.start] unwind to caller
67
68catch.start:                                      ; preds = %catch.dispatch
69  %1 = catchpad within %0 [ptr @_ZTIi]
70  %2 = call ptr @llvm.wasm.get.exception(token %1)
71  %3 = call i32 @llvm.wasm.get.ehselector(token %1)
72  %4 = call i32 @llvm.eh.typeid.for(ptr @_ZTIi)
73  %matches = icmp eq i32 %3, %4
74  br i1 %matches, label %catch, label %rethrow
75
76catch:                                            ; preds = %catch.start
77  %5 = call ptr @__cxa_begin_catch(ptr %2) [ "funclet"(token %1) ]
78  call void @__cxa_end_catch() [ "funclet"(token %1) ]
79  catchret from %1 to label %try.cont
80
81rethrow:                                          ; preds = %catch.start
82  call void @llvm.wasm.rethrow() [ "funclet"(token %1) ]
83  unreachable
84
85try.cont:                                         ; preds = %catch, %entry
86  ret void
87}
88
89; Destructor (cleanup) test
90;
91; void foo();
92; struct Temp {
93;   ~Temp() {}
94; };
95; void cleanup() {
96;   Temp t;
97;   foo();
98; }
99
100; CHECK-LABEL: cleanup:
101; CHECK: block
102; CHECK:   block     exnref
103; CHECK:     try_table    (catch_all_ref 0)
104; CHECK:       call  foo
105; CHECK:       br        2
106; CHECK:     end_try_table
107; CHECK:   end_block
108; CHECK:   local.set  1
109; CHECK:   global.set  __stack_pointer
110; CHECK:   call  _ZN4TempD2Ev
111; CHECK:   local.get  1
112; CHECK:   throw_ref
113; CHECK: end_block
114; CHECK: call  _ZN4TempD2Ev
115define void @cleanup() personality ptr @__gxx_wasm_personality_v0 {
116entry:
117  %t = alloca %struct.Temp, align 1
118  invoke void @foo()
119          to label %invoke.cont unwind label %ehcleanup
120
121invoke.cont:                                      ; preds = %entry
122  %call = call ptr @_ZN4TempD2Ev(ptr %t)
123  ret void
124
125ehcleanup:                                        ; preds = %entry
126  %0 = cleanuppad within none []
127  %call1 = call ptr @_ZN4TempD2Ev(ptr %t) [ "funclet"(token %0) ]
128  cleanupret from %0 unwind to caller
129}
130
131; Calling a function that may throw within a 'catch (...)' generates a
132; terminatepad, because __cxa_end_catch() also can throw within 'catch (...)'.
133;
134; void foo();
135; void terminatepad() {
136;   try {
137;     foo();
138;   } catch (...) {
139;     foo();
140;   }
141; }
142
143; CHECK-LABEL: terminatepad
144; WASM64-LABEL: terminatepad
145; CHECK: block
146; CHECK:   block     i32
147; WASM64:  block     i64
148; CHECK:     try_table    (catch __cpp_exception 0)
149; CHECK:       call  foo
150; CHECK:       br        2
151; CHECK:     end_try_table
152; CHECK:   end_block
153; CHECK:   call  __cxa_begin_catch
154; CHECK:   block
155; CHECK:     block     exnref
156; CHECK:       try_table    (catch_all_ref 0)
157; CHECK:         call  foo
158; CHECK:         br        2
159; CHECK:       end_try_table
160; CHECK:     end_block
161; CHECK:     local.set  2
162; CHECK:     block
163; CHECK:       block
164; CHECK:         try_table    (catch_all 0)
165; CHECK:           call  __cxa_end_catch
166; CHECK:           br        2
167; CHECK:         end_try_table
168; CHECK:       end_block
169; CHECK:       call  _ZSt9terminatev
170; CHECK:       unreachable
171; CHECK:     end_block
172; CHECK:     local.get  2
173; CHECK:     throw_ref
174; CHECK:   end_block
175; CHECK:   call  __cxa_end_catch
176; CHECK: end_block
177define void @terminatepad() personality ptr @__gxx_wasm_personality_v0 {
178entry:
179  invoke void @foo()
180          to label %try.cont unwind label %catch.dispatch
181
182catch.dispatch:                                   ; preds = %entry
183  %0 = catchswitch within none [label %catch.start] unwind to caller
184
185catch.start:                                      ; preds = %catch.dispatch
186  %1 = catchpad within %0 [ptr null]
187  %2 = call ptr @llvm.wasm.get.exception(token %1)
188  %3 = call i32 @llvm.wasm.get.ehselector(token %1)
189  %4 = call ptr @__cxa_begin_catch(ptr %2) [ "funclet"(token %1) ]
190  invoke void @foo() [ "funclet"(token %1) ]
191          to label %invoke.cont1 unwind label %ehcleanup
192
193invoke.cont1:                                     ; preds = %catch.start
194  call void @__cxa_end_catch() [ "funclet"(token %1) ]
195  catchret from %1 to label %try.cont
196
197try.cont:                                         ; preds = %invoke.cont1, %entry
198  ret void
199
200ehcleanup:                                        ; preds = %catch.start
201  %5 = cleanuppad within %1 []
202  invoke void @__cxa_end_catch() [ "funclet"(token %5) ]
203          to label %invoke.cont2 unwind label %terminate
204
205invoke.cont2:                                     ; preds = %ehcleanup
206  cleanupret from %5 unwind to caller
207
208terminate:                                        ; preds = %ehcleanup
209  %6 = cleanuppad within %5 []
210  call void @_ZSt9terminatev() [ "funclet"(token %6) ]
211  unreachable
212}
213
214; Tests prologues and epilogues are not generated within EH scopes.
215; They should not be treated as funclets; BBs starting with a catch instruction
216; should not have a prologue, and BBs ending with a catchret/cleanupret should
217; not have an epilogue. This is separate from __stack_pointer restoring
218; instructions after a catch instruction.
219;
220; void bar(int) noexcept;
221; void no_prolog_epilog_in_ehpad() {
222;   int stack_var = 0;
223;   bar(stack_var);
224;   try {
225;     foo();
226;   } catch (int) {
227;     foo();
228;   }
229; }
230
231; CHECK-LABEL: no_prolog_epilog_in_ehpad
232; CHECK:   call  bar
233; CHECK:   block
234; CHECK:     block     () -> (i32, exnref)
235; CHECK:       try_table    (catch_ref __cpp_exception 0)
236; CHECK:         call  foo
237; CHECK:         br        2
238; CHECK:       end_try_table
239; CHECK:     end_block
240; CHECK:     local.set  2
241; CHECK-NOT: global.get  __stack_pointer
242; CHECK:     global.set  __stack_pointer
243; CHECK:     block
244; CHECK:       block
245; CHECK:         br_if     0
246; CHECK:         call  __cxa_begin_catch
247; CHECK:         block     exnref
248; CHECK:           try_table    (catch_all_ref 0)
249; CHECK:             call  foo
250; CHECK:             br        3
251; CHECK:           end_try_table
252; CHECK:         end_block
253; CHECK:         local.set  2
254; CHECK-NOT:     global.get  __stack_pointer
255; CHECK:         global.set  __stack_pointer
256; CHECK:         call  __cxa_end_catch
257; CHECK:         local.get  2
258; CHECK:         throw_ref
259; CHECK-NOT:     global.set  __stack_pointer
260; CHECK:       end_block
261; CHECK:       local.get  2
262; CHECK:       throw_ref
263; CHECK:     end_block
264; CHECK-NOT: global.set  __stack_pointer
265; CHECK:     call  __cxa_end_catch
266; CHECK:   end_block
267define void @no_prolog_epilog_in_ehpad() personality ptr @__gxx_wasm_personality_v0 {
268entry:
269  %stack_var = alloca i32, align 4
270  call void @bar(ptr %stack_var)
271  invoke void @foo()
272          to label %try.cont unwind label %catch.dispatch
273
274catch.dispatch:                                   ; preds = %entry
275  %0 = catchswitch within none [label %catch.start] unwind to caller
276
277catch.start:                                      ; preds = %catch.dispatch
278  %1 = catchpad within %0 [ptr @_ZTIi]
279  %2 = call ptr @llvm.wasm.get.exception(token %1)
280  %3 = call i32 @llvm.wasm.get.ehselector(token %1)
281  %4 = call i32 @llvm.eh.typeid.for(ptr @_ZTIi)
282  %matches = icmp eq i32 %3, %4
283  br i1 %matches, label %catch, label %rethrow
284
285catch:                                            ; preds = %catch.start
286  %5 = call ptr @__cxa_begin_catch(ptr %2) [ "funclet"(token %1) ]
287  %6 = load float, ptr %5, align 4
288  invoke void @foo() [ "funclet"(token %1) ]
289          to label %invoke.cont1 unwind label %ehcleanup
290
291invoke.cont1:                                     ; preds = %catch
292  call void @__cxa_end_catch() [ "funclet"(token %1) ]
293  catchret from %1 to label %try.cont
294
295rethrow:                                          ; preds = %catch.start
296  call void @llvm.wasm.rethrow() [ "funclet"(token %1) ]
297  unreachable
298
299try.cont:                                         ; preds = %invoke.cont1, %entry
300  ret void
301
302ehcleanup:                                        ; preds = %catch
303  %7 = cleanuppad within %1 []
304  call void @__cxa_end_catch() [ "funclet"(token %7) ]
305  cleanupret from %7 unwind to caller
306}
307
308; When a function does not have stack-allocated objects, it does not need to
309; store SP back to __stack_pointer global at the epilog.
310;
311; void foo();
312; void no_sp_writeback() {
313;   try {
314;     foo();
315;   } catch (...) {
316;   }
317; }
318
319; CHECK-LABEL: no_sp_writeback
320; CHECK:     block
321; CHECK:       block     i32
322; CHECK:         try_table    (catch __cpp_exception 0)
323; CHECK:           call  foo
324; CHECK:           br        2
325; CHECK:         end_try_table
326; CHECK:       end_block
327; CHECK:       call  __cxa_begin_catch
328; CHECK:       call  __cxa_end_catch
329; CHECK:     end_block
330; CHECK-NOT: global.set  __stack_pointer
331; CHECK:     end_function
332define void @no_sp_writeback() personality ptr @__gxx_wasm_personality_v0 {
333entry:
334  invoke void @foo()
335          to label %try.cont unwind label %catch.dispatch
336
337catch.dispatch:                                   ; preds = %entry
338  %0 = catchswitch within none [label %catch.start] unwind to caller
339
340catch.start:                                      ; preds = %catch.dispatch
341  %1 = catchpad within %0 [ptr null]
342  %2 = call ptr @llvm.wasm.get.exception(token %1)
343  %3 = call i32 @llvm.wasm.get.ehselector(token %1)
344  %4 = call ptr @__cxa_begin_catch(ptr %2) [ "funclet"(token %1) ]
345  call void @__cxa_end_catch() [ "funclet"(token %1) ]
346  catchret from %1 to label %try.cont
347
348try.cont:                                         ; preds = %catch.start, %entry
349  ret void
350}
351
352; When the result of @llvm.wasm.get.exception is not used. This is created to
353; fix a bug in LateEHPrepare and this should not crash.
354define void @get_exception_wo_use() personality ptr @__gxx_wasm_personality_v0 {
355entry:
356  invoke void @foo()
357          to label %try.cont unwind label %catch.dispatch
358
359catch.dispatch:                                   ; preds = %entry
360  %0 = catchswitch within none [label %catch.start] unwind to caller
361
362catch.start:                                      ; preds = %catch.dispatch
363  %1 = catchpad within %0 [ptr null]
364  %2 = call ptr @llvm.wasm.get.exception(token %1)
365  %3 = call i32 @llvm.wasm.get.ehselector(token %1)
366  catchret from %1 to label %try.cont
367
368try.cont:                                         ; preds = %catch.start, %entry
369  ret void
370}
371
372; Tests a case when a cleanup region (cleanuppad ~ clanupret) contains another
373; catchpad
374define void @complex_cleanup_region() personality ptr @__gxx_wasm_personality_v0 {
375entry:
376  invoke void @foo()
377          to label %invoke.cont unwind label %ehcleanup
378
379invoke.cont:                                      ; preds = %entry
380  ret void
381
382ehcleanup:                                        ; preds = %entry
383  %0 = cleanuppad within none []
384  invoke void @foo() [ "funclet"(token %0) ]
385          to label %ehcleanupret unwind label %catch.dispatch
386
387catch.dispatch:                                   ; preds = %ehcleanup
388  %1 = catchswitch within %0 [label %catch.start] unwind label %ehcleanup.1
389
390catch.start:                                      ; preds = %catch.dispatch
391  %2 = catchpad within %1 [ptr null]
392  %3 = call ptr @llvm.wasm.get.exception(token %2)
393  %4 = call i32 @llvm.wasm.get.ehselector(token %2)
394  catchret from %2 to label %ehcleanupret
395
396ehcleanup.1:                                      ; preds = %catch.dispatch
397  %5 = cleanuppad within %0 []
398  unreachable
399
400ehcleanupret:                                     ; preds = %catch.start, %ehcleanup
401  cleanupret from %0 unwind to caller
402}
403
404; Regression test for the bug that 'rethrow' was not treated correctly as a
405; terminator in isel.
406define void @rethrow_terminator() personality ptr @__gxx_wasm_personality_v0 {
407entry:
408  invoke void @foo()
409          to label %try.cont unwind label %catch.dispatch
410
411catch.dispatch:                                   ; preds = %entry
412  %0 = catchswitch within none [label %catch.start] unwind label %ehcleanup
413
414catch.start:                                      ; preds = %catch.dispatch
415  %1 = catchpad within %0 [ptr @_ZTIi]
416  %2 = call ptr @llvm.wasm.get.exception(token %1)
417  %3 = call i32 @llvm.wasm.get.ehselector(token %1)
418  %4 = call i32 @llvm.eh.typeid.for.p0(ptr @_ZTIi)
419  %matches = icmp eq i32 %3, %4
420  br i1 %matches, label %catch, label %rethrow
421
422catch:                                            ; preds = %catch.start
423  %5 = call ptr @__cxa_begin_catch(ptr %2) [ "funclet"(token %1) ]
424  %6 = load i32, ptr %5, align 4
425  call void @__cxa_end_catch() [ "funclet"(token %1) ]
426  catchret from %1 to label %try.cont
427
428rethrow:                                          ; preds = %catch.start
429  invoke void @llvm.wasm.rethrow() #1 [ "funclet"(token %1) ]
430          to label %unreachable unwind label %ehcleanup
431
432try.cont:                                         ; preds = %entry, %catch
433  ret void
434
435ehcleanup:                                        ; preds = %rethrow, %catch.dispatch
436  ; 'rethrow' BB is this BB's predecessor, and its
437  ; 'invoke void @llvm.wasm.rethrow()' is lowered down to a 'RETHROW' in Wasm
438  ; MIR. And this 'phi' creates 'CONST_I32' instruction in the predecessor
439  ; 'rethrow' BB. If 'RETHROW' is not treated correctly as a terminator, it can
440  ; create a BB like
441  ; bb.3.rethrow:
442  ;   RETHROW 0
443  ;   %0 = CONST_I32 20
444  ;   BR ...
445  %tmp = phi i32 [ 10, %catch.dispatch ], [ 20, %rethrow ]
446  %7 = cleanuppad within none []
447  call void @take_i32(i32 %tmp) [ "funclet"(token %7) ]
448  cleanupret from %7 unwind to caller
449
450unreachable:                                      ; preds = %rethrow
451  unreachable
452}
453
454; The bitcode below is generated when the code below is compiled and
455; Temp::~Temp() is inlined into inlined_cleanupret():
456;
457; void inlined_cleanupret() {
458; try {
459;   Temp t;
460;   throw 2;
461; } catch (...)
462; }
463;
464; Temp::~Temp() {
465;   try {
466;     throw 1;
467;   } catch (...) {
468;   }
469; }
470;
471; ~Temp() generates cleanupret, which is lowered to a 'throw_ref' later. That
472; throw_ref's argument should correctly target the top-level cleanuppad
473; (catch_all_ref). This is a regression test for the bug where we did not
474; compute throw_ref's argument correctly.
475
476; CHECK-LABEL: inlined_cleanupret:
477; CHECK: block     exnref
478; CHECK:   block
479; CHECK:     block     exnref
480; CHECK:       try_table    (catch_all_ref 0)
481; CHECK:         call  __cxa_throw
482; CHECK:       end_try_table
483; CHECK:     end_block
484; try_table (catch_all_ref 0)'s caught exception is stored in local 2
485; CHECK:     local.set  2
486; CHECK:     block
487; CHECK:       try_table    (catch_all 0)
488; CHECK:         block
489; CHECK:           block     i32
490; CHECK:             try_table    (catch __cpp_exception 0)
491; CHECK:               call  __cxa_throw
492; CHECK:             end_try_table
493; CHECK:           end_block
494; CHECK:           call  __cxa_end_catch
495; CHECK:           block     i32
496; CHECK:             try_table    (catch_all_ref 5)
497; CHECK:               try_table    (catch __cpp_exception 1)
498; Note that the throw_ref below targets the top-level catch_all_ref (local 2)
499; CHECK:                 local.get  2
500; CHECK:                 throw_ref
501; CHECK:               end_try_table
502; CHECK:             end_try_table
503; CHECK:           end_block
504; CHECK:           try_table    (catch_all_ref 4)
505; CHECK:             call  __cxa_end_catch
506; CHECK:           end_try_table
507; CHECK:           return
508; CHECK:         end_block
509; CHECK:       end_try_table
510; CHECK:     end_block
511; CHECK:     call  _ZSt9terminatev
512; CHECK:   end_block
513; CHECK: end_block
514; CHECK: throw_ref
515define void @inlined_cleanupret() personality ptr @__gxx_wasm_personality_v0 {
516entry:
517  %exception = tail call ptr @__cxa_allocate_exception(i32 4)
518  store i32 2, ptr %exception, align 16
519  invoke void @__cxa_throw(ptr nonnull %exception, ptr nonnull @_ZTIi, ptr null)
520          to label %unreachable unwind label %ehcleanup
521
522ehcleanup:                                        ; preds = %entry
523  %0 = cleanuppad within none []
524  %exception.i = call ptr @__cxa_allocate_exception(i32 4) [ "funclet"(token %0) ]
525  store i32 1, ptr %exception.i, align 16
526  invoke void @__cxa_throw(ptr nonnull %exception.i, ptr nonnull @_ZTIi, ptr null) [ "funclet"(token %0) ]
527          to label %unreachable unwind label %catch.dispatch.i
528
529catch.dispatch.i:                                 ; preds = %ehcleanup
530  %1 = catchswitch within %0 [label %catch.start.i] unwind label %terminate.i
531
532catch.start.i:                                    ; preds = %catch.dispatch.i
533  %2 = catchpad within %1 [ptr null]
534  %3 = tail call ptr @llvm.wasm.get.exception(token %2)
535  %4 = tail call i32 @llvm.wasm.get.ehselector(token %2)
536  %5 = call ptr @__cxa_begin_catch(ptr %3) [ "funclet"(token %2) ]
537  invoke void @__cxa_end_catch() [ "funclet"(token %2) ]
538          to label %invoke.cont.i unwind label %terminate.i
539
540invoke.cont.i:                                    ; preds = %catch.start.i
541  catchret from %2 to label %_ZN4TempD2Ev.exit
542
543terminate.i:                                      ; preds = %catch.start.i, %catch.dispatch.i
544  %6 = cleanuppad within %0 []
545  call void @_ZSt9terminatev() [ "funclet"(token %6) ]
546  unreachable
547
548_ZN4TempD2Ev.exit:                                ; preds = %invoke.cont.i
549  cleanupret from %0 unwind label %catch.dispatch
550
551catch.dispatch:                                   ; preds = %_ZN4TempD2Ev.exit
552  %7 = catchswitch within none [label %catch.start] unwind to caller
553
554catch.start:                                      ; preds = %catch.dispatch
555  %8 = catchpad within %7 [ptr null]
556  %9 = tail call ptr @llvm.wasm.get.exception(token %8)
557  %10 = tail call i32 @llvm.wasm.get.ehselector(token %8)
558  %11 = call ptr @__cxa_begin_catch(ptr %9) #8 [ "funclet"(token %8) ]
559  call void @__cxa_end_catch() [ "funclet"(token %8) ]
560  catchret from %8 to label %try.cont
561
562try.cont:                                         ; preds = %catch.start
563  ret void
564
565unreachable:                                      ; preds = %entry
566  unreachable
567}
568
569
570declare void @foo()
571declare void @bar(ptr)
572declare void @take_i32(i32)
573declare i32 @__gxx_wasm_personality_v0(...)
574; Function Attrs: noreturn
575declare void @llvm.wasm.throw(i32, ptr) #1
576; Function Attrs: nounwind
577declare ptr @llvm.wasm.get.exception(token) #0
578; Function Attrs: nounwind
579declare i32 @llvm.wasm.get.ehselector(token) #0
580; Function Attrs: noreturn
581declare void @llvm.wasm.rethrow() #1
582; Function Attrs: nounwind
583declare i32 @llvm.eh.typeid.for(ptr) #0
584; Function Attrs: nounwind
585declare ptr @__cxa_allocate_exception(i32) #0
586declare ptr @__cxa_begin_catch(ptr)
587declare void @__cxa_end_catch()
588; Function Attrs: noreturn
589declare void @__cxa_throw(ptr, ptr, ptr) #1
590declare void @_ZSt9terminatev()
591declare ptr @_ZN4TempD2Ev(ptr returned)
592
593attributes #0 = { nounwind }
594attributes #1 = { noreturn }
595
596; CHECK: __cpp_exception:
597