[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 01:59:29 PST 2025


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

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.

>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] [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 ) {



More information about the llvm-commits mailing list