[llvm] [WebAssembly] Skip Wasm SjLj if EH target feature is missing (PR #178377)
Heejin Ahn via llvm-commits
llvm-commits at lists.llvm.org
Wed Jan 28 00:16:10 PST 2026
https://github.com/aheejin created https://github.com/llvm/llvm-project/pull/178377
When using LTO, if you don't use `-fwasm-exceptions` at compile time but use it only at the link time, functions don't have `target-features=+exception-handling` attribute. Then wasm-ld will run the Wasm backend, which runs Wasm SjLj transformation, producing EH instructions. These instructions cannot be selected at ISel because `+exception-handling` target feature is not enabled in those functions.
This PR detects those cases, and run Wasm SjLj transformation only when a function has `+exception-handling` target feature. (Or `-mattr=+exception-handling` is given in `opt`/`llc`) It also prints a warning message for each function.
To prevent this skipping for the existing tests, now the existing `RUN` lines have `-mattr=+exception-handling` (which overrides function attributes).
I also have thought about just erroring out, but I'm not sure whether it will break any existing mixing use cases that happen to work.
Addresses #178135 and
https://github.com/emscripten-core/emscripten/issues/26165.
>From da75b5e7887f7547086c23d5e596c2b85dac6faf Mon Sep 17 00:00:00 2001
From: Heejin Ahn <aheejin at gmail.com>
Date: Wed, 28 Jan 2026 06:29:25 +0000
Subject: [PATCH] [WebAssembly] Skip Wasm SjLj if EH target feature is missing
When using LTO, if you don't use `-fwasm-exceptions` at compile time but
use it only at the link time, functions don't have
`target-features=+exception-handling` attribute. Then wasm-ld will run
the Wasm backend, which runs Wasm SjLj transformation, producing EH
instructions. These instructions cannot be selected at ISel because
`+exception-handling` target feature is not enabled in those functions.
This PR detects those cases, and run Wasm SjLj transformation only when
a function has `+exception-handling` target feature. (Or
`-mattr=+exception-handling` is given in `opt`/`llc`) It also prints a
warning message for each function.
To prevent this skipping for the existing tests, now the existing `RUN`
lines have `-mattr=+exception-handling` (which overrides function
attributes).
I also have thought about just erroring out, but I'm not sure whether it
will break any existing mixing use cases that happen to work.
Addresses #178135 and
https://github.com/emscripten-core/emscripten/issues/26165.
---
.../WebAssemblyLowerEmscriptenEHSjLj.cpp | 25 ++++++++++++++++++-
.../WebAssembly/lower-em-sjlj-alloca.ll | 6 ++---
.../WebAssembly/lower-wasm-ehsjlj-phi.ll | 2 +-
.../CodeGen/WebAssembly/lower-wasm-ehsjlj.ll | 4 +--
.../CodeGen/WebAssembly/lower-wasm-sjlj.ll | 17 ++++++++++---
.../wasm-eh-sjlj-setjmp-within-catch.ll | 2 +-
6 files changed, 45 insertions(+), 11 deletions(-)
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
index c3990d12f2c28..2f934d16d0af5 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
@@ -272,6 +272,7 @@
#include "llvm/IR/IntrinsicsWebAssembly.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/raw_ostream.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include "llvm/Transforms/Utils/Local.h"
#include "llvm/Transforms/Utils/SSAUpdater.h"
@@ -482,6 +483,14 @@ static Value *getAddrSizeInt(Module *M, uint64_t C) {
return IRB.getIntN(M->getDataLayout().getPointerSizeInBits(), C);
}
+// Returns true if the function has "target-features"="+exception-handling"
+// attribute.
+static bool hasEHTargetFeatureAttr(const Function &F) {
+ Attribute FeaturesAttr = F.getFnAttribute("target-features");
+ return FeaturesAttr.isValid() &&
+ FeaturesAttr.getValueAsString().contains("+exception-handling");
+}
+
// Returns __cxa_find_matching_catch_N function, where N = NumClauses + 2.
// This is because a landingpad instruction contains two more arguments, a
// personality function and a cleanup bit, and __cxa_find_matching_catch_N
@@ -842,6 +851,8 @@ void WebAssemblyLowerEmscriptenEHSjLj::replaceLongjmpWith(Function *LongjmpF,
for (User *U : LongjmpF->users()) {
auto *CI = dyn_cast<CallInst>(U);
if (CI && CI->getCalledFunction() == LongjmpF) {
+ if (EnableWasmSjLj && !hasEHTargetFeatureAttr(*CI->getFunction()))
+ continue;
IRB.SetInsertPoint(CI);
Value *Env = nullptr;
if (NewF == EmLongjmpF)
@@ -861,7 +872,12 @@ void WebAssemblyLowerEmscriptenEHSjLj::replaceLongjmpWith(Function *LongjmpF,
if (!LongjmpF->uses().empty()) {
Value *NewLongjmp =
IRB.CreateBitCast(NewF, LongjmpF->getType(), "longjmp.cast");
- LongjmpF->replaceAllUsesWith(NewLongjmp);
+ LongjmpF->replaceUsesWithIf(NewLongjmp, [&](Use &U) {
+ if (EnableWasmSjLj)
+ if (auto *I = dyn_cast<Instruction>(U.getUser()))
+ return hasEHTargetFeatureAttr(*I->getFunction());
+ return true;
+ });
}
}
@@ -1280,6 +1296,13 @@ static DebugLoc getOrCreateDebugLoc(const Instruction *InsertBefore,
bool WebAssemblyLowerEmscriptenEHSjLj::runSjLjOnFunction(Function &F) {
assert(EnableEmSjLj || EnableWasmSjLj);
+ if (EnableWasmSjLj && !hasEHTargetFeatureAttr(F)) {
+ errs() << "Function " << F.getName()
+ << " was not compiled with 'exception-handling' target feature "
+ "enabled\n";
+ return false;
+ }
+
Module &M = *F.getParent();
LLVMContext &C = F.getContext();
IRBuilder<> IRB(C);
diff --git a/llvm/test/CodeGen/WebAssembly/lower-em-sjlj-alloca.ll b/llvm/test/CodeGen/WebAssembly/lower-em-sjlj-alloca.ll
index 3264fe9a20b4c..2c50275e09d61 100644
--- a/llvm/test/CodeGen/WebAssembly/lower-em-sjlj-alloca.ll
+++ b/llvm/test/CodeGen/WebAssembly/lower-em-sjlj-alloca.ll
@@ -1,12 +1,12 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
-; RUN: opt -S -wasm-lower-em-ehsjlj -wasm-enable-sjlj -mtriple=wasm32-unknown-emscripten < %s | FileCheck %s
+; RUN: opt -S -wasm-lower-em-ehsjlj -wasm-enable-sjlj -mattr=+exception-handling -mtriple=wasm32-unknown-emscripten < %s | FileCheck %s
@buf = external global i8
declare i32 @setjmp(ptr) returns_twice
declare void @dummy()
define void @test_static() {
-; CHECK-LABEL: define void @test_static() personality ptr @__gxx_wasm_personality_v0 {
+; CHECK-LABEL: define void @test_static() {{.*}} personality ptr @__gxx_wasm_personality_v0 {
; CHECK-NEXT: [[ENTRY:.*]]:
; CHECK-NEXT: [[X:%.*]] = alloca i32, align 4
; CHECK-NEXT: [[FUNCTIONINVOCATIONID:%.*]] = alloca i32, align 4
@@ -69,7 +69,7 @@ else:
define void @test_dynamic(i32 %size) {
; CHECK-LABEL: define void @test_dynamic(
-; CHECK-SAME: i32 [[SIZE:%.*]]) personality ptr @__gxx_wasm_personality_v0 {
+; CHECK-SAME: i32 [[SIZE:%.*]]) {{.*}} personality ptr @__gxx_wasm_personality_v0 {
; CHECK-NEXT: [[ENTRY:.*]]:
; CHECK-NEXT: [[FUNCTIONINVOCATIONID:%.*]] = alloca i32, align 4
; CHECK-NEXT: br label %[[SETJMP_DISPATCH:.*]]
diff --git a/llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj-phi.ll b/llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj-phi.ll
index 97f6d797a63bd..5e8ebcf247000 100644
--- a/llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj-phi.ll
+++ b/llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj-phi.ll
@@ -1,4 +1,4 @@
-; RUN: opt < %s -wasm-lower-em-ehsjlj -wasm-enable-eh -wasm-enable-sjlj -S | FileCheck %s
+; RUN: opt < %s -wasm-lower-em-ehsjlj -wasm-enable-eh -wasm-enable-sjlj -mattr=+exception-handling -S | FileCheck %s
target triple = "wasm32-unknown-emscripten"
diff --git a/llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj.ll b/llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj.ll
index 9de6652d48cbf..d5ae37d7e98bc 100644
--- a/llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj.ll
+++ b/llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj.ll
@@ -1,4 +1,4 @@
-; RUN: opt < %s -wasm-lower-em-ehsjlj -wasm-enable-eh -wasm-enable-sjlj -S | FileCheck %s
+; RUN: opt < %s -wasm-lower-em-ehsjlj -wasm-enable-eh -wasm-enable-sjlj -mattr=+exception-handling -S | FileCheck %s
; RUN: llc < %s -wasm-enable-eh -wasm-enable-sjlj -exception-model=wasm -mattr=+exception-handling -verify-machineinstrs
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
@@ -109,7 +109,7 @@ catch: ; preds = %catch.start
catchret from %2 to label %catchret.dest
; CHECK: catch: ; preds = %catch.start
; CHECK-NEXT: %exn = load ptr, ptr %exn.slot, align 4
-; CHECK-NEXT: %5 = call ptr @__cxa_begin_catch(ptr %exn) #3 [ "funclet"(token %2) ]
+; CHECK-NEXT: %5 = call ptr @__cxa_begin_catch(ptr %exn) {{.*}} [ "funclet"(token %2) ]
; CHECK-NEXT: invoke void @__cxa_end_catch() [ "funclet"(token %2) ]
; CHECK-NEXT: to label %.noexc unwind label %catch.dispatch.longjmp
diff --git a/llvm/test/CodeGen/WebAssembly/lower-wasm-sjlj.ll b/llvm/test/CodeGen/WebAssembly/lower-wasm-sjlj.ll
index e1cb859b5562f..4cc6c41f23329 100644
--- a/llvm/test/CodeGen/WebAssembly/lower-wasm-sjlj.ll
+++ b/llvm/test/CodeGen/WebAssembly/lower-wasm-sjlj.ll
@@ -1,6 +1,7 @@
-; RUN: opt < %s -wasm-lower-em-ehsjlj -wasm-enable-sjlj -S | FileCheck %s -DPTR=i32
-; RUN: opt < %s -wasm-lower-em-ehsjlj -wasm-enable-sjlj -S --mattr=+atomics,+bulk-memory | FileCheck %s -DPTR=i32
-; RUN: opt < %s -wasm-lower-em-ehsjlj -wasm-enable-sjlj --mtriple=wasm64-unknown-unknown -data-layout="e-m:e-p:64:64-i64:64-n32:64-S128" -S | FileCheck %s -DPTR=i64
+; RUN: opt < %s -wasm-lower-em-ehsjlj -wasm-enable-sjlj -mattr=+exception-handling -S | FileCheck %s -DPTR=i32
+; RUN: opt < %s -wasm-lower-em-ehsjlj -wasm-enable-sjlj -S -mattr=+exception-handling,+atomics,+bulk-memory | FileCheck %s -DPTR=i32
+; RUN: opt < %s -wasm-lower-em-ehsjlj -wasm-enable-sjlj -mattr=+exception-handling --mtriple=wasm64-unknown-unknown -data-layout="e-m:e-p:64:64-i64:64-n32:64-S128" -S | FileCheck %s -DPTR=i64
+; RUN: opt < %s -wasm-lower-em-ehsjlj -wasm-enable-sjlj -S 2>&1 | FileCheck %s --check-prefix=NO-EH-ATTR
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-unknown"
@@ -70,6 +71,16 @@ entry:
; CHECK-NEXT: catchret from %1 to label %setjmp.dispatch
}
+; If "target-features"="+exception-handling" attribute is missing and
+; -mattr=+exception-handling is not used in opt/llc command line, we emit a
+; warning and does not do Wasm SjLj transformation.
+; NO-EH-ATTR: Function setjmp_longjmp was not compiled with 'exception-handling' target feature enabled
+; NO-EH-ATTR-LABEL: @setjmp_longjmp()
+; NO-EH-ATTR: entry:
+; NO-EH-ATTR-NEXT: alloca
+; NO-EH-ATTR-NEXT: call i32 @setjmp
+; NO-EH-ATTR-NEXT: call void @longjmp
+
; When there are multiple longjmpable calls after setjmp. This will turn each of
; longjmpable call into an invoke whose unwind destination is
; 'catch.dispatch.longjmp' BB.
diff --git a/llvm/test/CodeGen/WebAssembly/wasm-eh-sjlj-setjmp-within-catch.ll b/llvm/test/CodeGen/WebAssembly/wasm-eh-sjlj-setjmp-within-catch.ll
index f0d622ad333c3..ed9b62941a9c7 100644
--- a/llvm/test/CodeGen/WebAssembly/wasm-eh-sjlj-setjmp-within-catch.ll
+++ b/llvm/test/CodeGen/WebAssembly/wasm-eh-sjlj-setjmp-within-catch.ll
@@ -1,4 +1,4 @@
-; RUN: not --crash opt < %s -wasm-lower-em-ehsjlj -wasm-enable-eh -wasm-enable-sjlj -S 2>&1 | FileCheck %s
+; RUN: not --crash opt < %s -wasm-lower-em-ehsjlj -wasm-enable-eh -wasm-enable-sjlj -mattr=+exception-handling -S 2>&1 | FileCheck %s
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-unknown"
More information about the llvm-commits
mailing list