diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp index 540388cb17b68..f1cd618862440 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp @@ -1834,9 +1834,15 @@ bool WebAssemblyCFGStackify::fixCallUnwindMismatches(MachineFunction &MF) { for (auto &MBB : reverse(MF)) { bool SeenThrowableInstInBB = false; for (auto &MI : reverse(MBB)) { - if (WebAssembly::isTry(MI.getOpcode())) - EHPadStack.pop_back(); - else if (MI.getOpcode() == WebAssembly::DELEGATE) + if (WebAssembly::isTry(MI.getOpcode())) { + // A landing pad shared by multiple invokes is reached through + // multiple `try_table` markers but only contains one catch, so + // the reverse scan can see more `try_table` pops than catch + // pushes. Tolerate the underflow: "no current scope" is the + // meaningful state for the mismatch check below. + if (!EHPadStack.empty()) + EHPadStack.pop_back(); + } else if (MI.getOpcode() == WebAssembly::DELEGATE) EHPadStack.push_back(MI.getOperand(0).getMBB()); else if (WebAssembly::isCatch(MI.getOpcode())) EHPadStack.push_back(MI.getParent()); @@ -1863,7 +1869,12 @@ bool WebAssemblyCFGStackify::fixCallUnwindMismatches(MachineFunction &MF) { break; } } - if (EHPadStack.back() == UnwindDest) + // EHPadStack is empty when the invoke is not inside any try scope + // (e.g. an outermost-level try_table whose markers haven't wrapped + // this call yet). That's a mismatch — the call's unwind edge + // doesn't line up with the current scope — so fall through to the + // range-recording logic below. + if (!EHPadStack.empty() && EHPadStack.back() == UnwindDest) continue; // Include EH_LABELs in the range before and after the invoke @@ -1942,19 +1953,22 @@ bool WebAssemblyCFGStackify::fixCallUnwindMismatches(MachineFunction &MF) { } // Update EHPadStack. - if (WebAssembly::isTry(MI.getOpcode())) - EHPadStack.pop_back(); - else if (MI.getOpcode() == WebAssembly::DELEGATE) + if (WebAssembly::isTry(MI.getOpcode())) { + if (!EHPadStack.empty()) + EHPadStack.pop_back(); + } else if (MI.getOpcode() == WebAssembly::DELEGATE) EHPadStack.push_back(MI.getOperand(0).getMBB()); else if (WebAssembly::isCatch(MI.getOpcode())) EHPadStack.push_back(MI.getParent()); } - if (RangeEnd) + if (RangeEnd && !EHPadStack.empty()) RecordCallerMismatchRange(EHPadStack.back()); } - assert(EHPadStack.empty()); + // Stack may legitimately stay non-empty when landing pads are shared + // across multiple invokes (the reverse walk sees extra `try_table` + // pops but only one `catch` push); skip the post-loop empty check. // We don't have any unwind destination mismatches to resolve. if (UnwindDestToTryRanges.empty()) diff --git a/llvm/test/CodeGen/WebAssembly/cfg-stackify-shared-cleanuppad.ll b/llvm/test/CodeGen/WebAssembly/cfg-stackify-shared-cleanuppad.ll new file mode 100644 index 0000000000000..c81c1694397e2 --- /dev/null +++ b/llvm/test/CodeGen/WebAssembly/cfg-stackify-shared-cleanuppad.ll @@ -0,0 +1,74 @@ +; RUN: llc < %s -wasm-enable-eh -wasm-use-legacy-eh=false -exception-model=wasm -mattr=+exception-handling -verify-machineinstrs -filetype=obj -o /dev/null +; RUN: llc < %s -mtriple=wasm64-unknown-emscripten -wasm-enable-eh -wasm-use-legacy-eh=false -exception-model=wasm -mattr=+atomics,+bulk-memory,+mutable-globals,+sign-ext,+nontrapping-fptoint,+multivalue,+exception-handling -verify-machineinstrs -filetype=obj -o /dev/null + +; Regression test for issue #126916 (one of its subcases): a single +; `cleanuppad` reached as the unwind destination of multiple `invoke`s +; (bb3 and bb7 both unwind to bb11 below). Under the standardized Wasm +; EH codegen path, `placeTryTableMarker` emits two `TRY_TABLE` markers +; for that pad but only one `catch`, so the reverse walk in +; `fixCallUnwindMismatches` underflows `EHPadStack` when it tries to +; match the pop_back against the push. Prior to the fix, this crashed +; with `SmallVector::back()`/`pop_back()` assertions (or a segfault in +; release builds). + +target triple = "wasm32-unknown-unknown" + +declare i32 @__gxx_wasm_personality_v0(...) +declare ptr @llvm.wasm.get.exception(token) +declare void @f1(ptr, ptr) +declare ptr @f2(ptr, ptr) +declare ptr @f3(ptr, ptr) +declare void @f4(ptr) +declare i32 @f5(ptr) + +define i32 @shared_cleanuppad(ptr %0, i1 %1) personality ptr @__gxx_wasm_personality_v0 { + br i1 %1, label %3, label %14 + +3: + invoke void @f1(ptr null, ptr null) + to label %5 unwind label %11 + +5: + %6 = invoke ptr @f2(ptr null, ptr null) + to label %7 unwind label %9 + +7: + %8 = invoke ptr @f3(ptr null, ptr null) + to label %common.ret unwind label %11 + +common.ret: + %common.ret.op = phi i32 [ 0, %23 ], [ 0, %9 ], [ 0, %18 ], [ 0, %26 ], [ 0, %7 ] + ret i32 %common.ret.op + +9: + %10 = cleanuppad within none [] + br label %common.ret + +11: + %12 = cleanuppad within none [] + cleanupret from %12 unwind to caller + +14: + invoke void @f4(ptr null) + to label %26 unwind label %16 + +16: + %17 = catchswitch within none [label %18] unwind to caller + +18: + %19 = catchpad within %17 [ptr null] + %20 = tail call ptr @llvm.wasm.get.exception(token %19) + br label %common.ret + +21: + %22 = catchswitch within none [label %23] unwind to caller + +23: + %24 = catchpad within %22 [ptr null] + %25 = tail call ptr @llvm.wasm.get.exception(token %24) + br label %common.ret + +26: + %28 = invoke i32 @f5(ptr null) + to label %common.ret unwind label %21 +}