[lld] [lld][WebAssembly] Preserve LTO stub deps for bitcode symbols (PR #173235)

Heejin Ahn via llvm-commits llvm-commits at lists.llvm.org
Mon Dec 22 13:54:08 PST 2025


https://github.com/aheejin updated https://github.com/llvm/llvm-project/pull/173235

>From 5ef9c3fb5b674e9692d9f14a5628f9053e547d46 Mon Sep 17 00:00:00 2001
From: Heejin Ahn <aheejin at gmail.com>
Date: Sun, 21 Dec 2025 03:36:12 +0000
Subject: [PATCH 1/3] [lld][WebAssembly] Preserve LTO stub deps for bitcode
 symbols

When a stub .so file contains
```
A: B
```

And `A` is defined in bitcode that's pulled in for LTO, but both `A` and
`B` are removed in `LTO::linkRegularLTO` due to not being dead:
https://github.com/llvm/llvm-project/blob/24297bea9672722d8fbaaff137b301b0becaae9c/llvm/lib/LTO/LTO.cpp#L1042-L1054
Then the symbol `A` becomes undefined after LTO, `processStubLibraries`
tries to import `A` from JS, and tries to export its dependency `B`:
https://github.com/llvm/llvm-project/blob/24297bea9672722d8fbaaff137b301b0becaae9c/lld/wasm/Driver.cpp#L1108-L1109
But `B` is gone, causing this error:
```console
wasm-ld: error: ....: undefined symbol: B. Required by A
```

This PR marks `B` as required not only when `A` is null or undefined but
also `A` is in bitcode, because bitcode functions can be DCE'd, causing
the linker to import the symbol and export its dependencies.
---
 .../wasm/lto/Inputs/stub-dependency-main.s    |  4 +++
 lld/test/wasm/lto/stub-dependency.ll          | 33 +++++++++++++++++++
 lld/wasm/Driver.cpp                           |  6 +++-
 3 files changed, 42 insertions(+), 1 deletion(-)
 create mode 100644 lld/test/wasm/lto/Inputs/stub-dependency-main.s
 create mode 100644 lld/test/wasm/lto/stub-dependency.ll

diff --git a/lld/test/wasm/lto/Inputs/stub-dependency-main.s b/lld/test/wasm/lto/Inputs/stub-dependency-main.s
new file mode 100644
index 0000000000000..9e84901a6fe80
--- /dev/null
+++ b/lld/test/wasm/lto/Inputs/stub-dependency-main.s
@@ -0,0 +1,4 @@
+.globl _start
+_start:
+  .functype _start () -> ()
+  end_function
diff --git a/lld/test/wasm/lto/stub-dependency.ll b/lld/test/wasm/lto/stub-dependency.ll
new file mode 100644
index 0000000000000..3b2daf39cc03a
--- /dev/null
+++ b/lld/test/wasm/lto/stub-dependency.ll
@@ -0,0 +1,33 @@
+; RUN: llvm-as %s -o %t.bc
+; RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown %p/Inputs/stub-dependency-main.s -o %t.o
+; RUN: echo "#STUB" > %t.so
+; RUN: echo "A: B" >> %t.so
+; RUN: wasm-ld %t.o %t.bc %t.so -o %t.wasm
+; RUN: obj2yaml %t.wasm | FileCheck %s
+
+; Test if LTO stub dependencies are preserved if a symbol they depend on is
+; defined in bitcode and DCE'd and become undefined in the LTO process. Here 'B'
+; should be preserved and exported.
+
+target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20"
+target triple = "wasm32-unknown-unknown"
+
+define void @A() {
+  ret void
+}
+
+define void @B() {
+  ret void
+}
+
+; CHECK:      - Type:            EXPORT
+; CHECK-NEXT:   Exports:
+; CHECK-NEXT:     - Name:            memory
+; CHECK-NEXT:       Kind:            MEMORY
+; CHECK-NEXT:       Index:           0
+; CHECK-NEXT:     - Name:            _start
+; CHECK-NEXT:       Kind:            FUNCTION
+; CHECK-NEXT:       Index:           0
+; CHECK-NEXT:     - Name:            B
+; CHECK-NEXT:       Kind:            FUNCTION
+; CHECK-NEXT:       Index:           1
diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index 97e50783985a8..71daa30f1dec9 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -1028,7 +1028,11 @@ static void processStubLibrariesPreLTO() {
       // If the symbol is not present at all (yet), or if it is present but
       // undefined, then mark the dependent symbols as used by a regular
       // object so they will be preserved and exported by the LTO process.
-      if (!sym || sym->isUndefined()) {
+      // If the symbol is defined and in bitcode, it can be DCE'd during LTO and
+      // become undefined, so mark the dependent symbols as used by a regular
+      // object as well.
+      if (!sym || sym->isUndefined() ||
+          (sym->isDefined() && isa_and_nonnull<BitcodeFile>(sym->getFile()))) {
         for (const auto dep : deps) {
           auto* needed = symtab->find(dep);
           if (needed ) {

>From b16180f6c511dd18f02d6427555d858ccded03e3 Mon Sep 17 00:00:00 2001
From: Heejin Ahn <aheejin at gmail.com>
Date: Mon, 22 Dec 2025 21:14:43 +0000
Subject: [PATCH 2/3] Use existing start.s

---
 lld/test/wasm/lto/Inputs/stub-dependency-main.s | 4 ----
 lld/test/wasm/lto/stub-dependency.ll            | 2 +-
 2 files changed, 1 insertion(+), 5 deletions(-)
 delete mode 100644 lld/test/wasm/lto/Inputs/stub-dependency-main.s

diff --git a/lld/test/wasm/lto/Inputs/stub-dependency-main.s b/lld/test/wasm/lto/Inputs/stub-dependency-main.s
deleted file mode 100644
index 9e84901a6fe80..0000000000000
--- a/lld/test/wasm/lto/Inputs/stub-dependency-main.s
+++ /dev/null
@@ -1,4 +0,0 @@
-.globl _start
-_start:
-  .functype _start () -> ()
-  end_function
diff --git a/lld/test/wasm/lto/stub-dependency.ll b/lld/test/wasm/lto/stub-dependency.ll
index 3b2daf39cc03a..d88cb9e187327 100644
--- a/lld/test/wasm/lto/stub-dependency.ll
+++ b/lld/test/wasm/lto/stub-dependency.ll
@@ -1,5 +1,5 @@
 ; RUN: llvm-as %s -o %t.bc
-; RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown %p/Inputs/stub-dependency-main.s -o %t.o
+; RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown %p/../Inputs/start.s -o %t.o
 ; RUN: echo "#STUB" > %t.so
 ; RUN: echo "A: B" >> %t.so
 ; RUN: wasm-ld %t.o %t.bc %t.so -o %t.wasm

>From a02b3f014933a78874e17a1d85691790cafb64e4 Mon Sep 17 00:00:00 2001
From: Heejin Ahn <aheejin at gmail.com>
Date: Mon, 22 Dec 2025 21:52:49 +0000
Subject: [PATCH 3/3] isa_and_nonnull -> isa

---
 lld/wasm/Driver.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index 71daa30f1dec9..6d5447be812aa 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -1032,7 +1032,7 @@ static void processStubLibrariesPreLTO() {
       // become undefined, so mark the dependent symbols as used by a regular
       // object as well.
       if (!sym || sym->isUndefined() ||
-          (sym->isDefined() && isa_and_nonnull<BitcodeFile>(sym->getFile()))) {
+          (sym->isDefined() && isa<BitcodeFile>(sym->getFile()))) {
         for (const auto dep : deps) {
           auto* needed = symtab->find(dep);
           if (needed ) {



More information about the llvm-commits mailing list