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