[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