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