[lld] [lld][wasm] Do not extract unused stub deps (PR #217774)

Brendan Dahl via llvm-commits llvm-commits at lists.llvm.org
Thu Aug 20 15:48:45 PDT 2026


https://github.com/brendandahl updated https://github.com/llvm/llvm-project/pull/217774

>From 0c771f734e1d94f964ff3422584f95494cb8768b Mon Sep 17 00:00:00 2001
From: Brendan Dahl <brendan.dahl at gmail.com>
Date: Fri, 14 Aug 2026 23:06:04 +0000
Subject: [PATCH] [lld][wasm] Do not extract unused stub deps

Pre-LTO stub library processing treated any symbol not yet present in
the symbol table as potentially needed, which caused all unreferenced
stub symbols to extract their bitcode dependencies from archives.

Only preserve dependencies for stub symbols that are already undefined
in the symbol table or are runtime libcall symbols that may be
generated during LTO code generation.
---
 lld/test/wasm/lto/stub-library-unused.s |  40 +++++++++
 lld/wasm/Driver.cpp                     | 103 ++++++++++++++++--------
 2 files changed, 111 insertions(+), 32 deletions(-)
 create mode 100644 lld/test/wasm/lto/stub-library-unused.s

diff --git a/lld/test/wasm/lto/stub-library-unused.s b/lld/test/wasm/lto/stub-library-unused.s
new file mode 100644
index 0000000000000..5cd1158ef569e
--- /dev/null
+++ b/lld/test/wasm/lto/stub-library-unused.s
@@ -0,0 +1,40 @@
+# RUN: split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t/main.o %t/main.s
+# RUN: llvm-as %t/unused.ll -o %t/unused.o
+# RUN: rm -f %t/libunused.a
+# RUN: llvm-ar rcs %t/libunused.a %t/unused.o
+# RUN: wasm-ld %t/main.o %t/libunused.a %t/stub.so -o %t.wasm --allow-undefined --why-extract=%t/why.txt
+# RUN: obj2yaml %t.wasm | FileCheck %s
+# RUN: FileCheck --check-prefix=WHY %s < %t/why.txt
+
+## Test that an unreferenced stub library symbol does not cause its bitcode
+## archive dependencies to be extracted or exported during LTO.
+
+# CHECK:        - Name:            _start
+# CHECK-NOT:    unused_dep
+# CHECK-NOT:    unused_stub
+
+# WHY: reference	extracted	symbol
+# WHY-NOT: unused_dep
+
+#--- main.s
+.globl _start
+_start:
+    .functype _start () -> ()
+    end_function
+
+#--- unused.ll
+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"
+
+declare void @unused_stub()
+
+define void @unused_dep() {
+entry:
+  call void @unused_stub()
+  ret void
+}
+
+#--- stub.so
+#STUB
+unused_stub: unused_dep
diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index 4c2549b39def7..e3193a6976700 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -1055,35 +1055,71 @@ static void createOptionalSymbols() {
 
 static void processStubLibrariesPreLTO() {
   log("-- processStubLibrariesPreLTO");
-  for (auto &stub_file : ctx.stubFiles) {
-    LLVM_DEBUG(llvm::dbgs()
-               << "processing stub file: " << stub_file->getName() << "\n");
-    for (auto [name, deps] : stub_file->symbolDependencies) {
-      auto *sym = symtab->find(name);
-      // 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()) {
-        for (const auto dep : deps) {
-          auto *needed = symtab->find(dep);
-          if (needed) {
-            needed->isUsedInRegularObj = true;
-            // Like with handleLibcall we have to extract any LTO archive
-            // members that might need to be exported due to stub library
-            // symbols being referenced.  Without this the LTO object could be
-            // extracted during processStubLibraries, which is too late since
-            // LTO has already being performed at that point.
-            if (needed->isLazy() && isa<BitcodeFile>(needed->getFile())) {
-              if (!ctx.arg.whyExtract.empty())
-                ctx.whyExtractRecords.emplace_back(toString(stub_file),
-                                                   needed->getFile(), *needed);
-              cast<LazySymbol>(needed)->extract();
-            }
-          }
+  DenseSet<StringRef> libcallSymbols;
+  if (!ctx.bitcodeFiles.empty()) {
+    llvm::Triple TT(ctx.bitcodeFiles.front()->obj->getTargetTriple());
+    for (auto *s : lto::LTO::getRuntimeLibcallSymbols(TT))
+      libcallSymbols.insert(s);
+  }
+
+  // A stub symbol's dependencies only need to be preserved before LTO if:
+  // 1. The symbol is already present and undefined (referenced by an object),
+  // or
+  // 2. The symbol is a runtime libcall that might be newly generated during
+  // LTO.
+  auto isNeeded = [&](StringRef name) {
+    auto *sym = symtab->find(name);
+    if (sym)
+      return sym->isUndefined();
+    return libcallSymbols.contains(name);
+  };
+
+  auto handleDeps = [&](const StubFile *stub_file, ArrayRef<StringRef> deps) {
+    bool depsAdded = false;
+    for (const auto dep : deps) {
+      auto *needed = symtab->find(dep);
+      if (needed) {
+        needed->isUsedInRegularObj = true;
+        // Like with handleLibcall we have to extract any LTO archive
+        // members that might need to be exported due to stub library
+        // symbols being referenced.  Without this the LTO object could be
+        // extracted during processStubLibraries, which is too late since
+        // LTO has already being performed at that point.
+        if (needed->isLazy() && isa<BitcodeFile>(needed->getFile())) {
+          if (!ctx.arg.whyExtract.empty())
+            ctx.whyExtractRecords.emplace_back(toString(stub_file),
+                                               needed->getFile(), *needed);
+          cast<LazySymbol>(needed)->extract();
+          depsAdded = true;
         }
       }
     }
-  }
+    return depsAdded;
+  };
+
+  bool depsAdded;
+  do {
+    depsAdded = false;
+    for (auto &stub_file : ctx.stubFiles) {
+      LLVM_DEBUG(llvm::dbgs()
+                 << "processing stub file: " << stub_file->getName() << "\n");
+      for (auto [name, deps] : stub_file->symbolDependencies) {
+        if (isNeeded(name))
+          depsAdded |= handleDeps(stub_file, deps);
+      }
+    }
+
+    for (size_t i = 0; i < symtab->symbols().size(); ++i) {
+      Symbol *sym = symtab->symbols()[i];
+      if (sym->isUndefined() && sym->importName.has_value()) {
+        for (auto &stub_file : ctx.stubFiles) {
+          auto it = stub_file->symbolDependencies.find(sym->importName.value());
+          if (it != stub_file->symbolDependencies.end())
+            depsAdded |= handleDeps(stub_file, it->second);
+        }
+      }
+    }
+  } while (depsAdded);
 }
 
 static bool addStubSymbolDeps(const StubFile *stub_file, Symbol *sym,
@@ -1120,7 +1156,7 @@ static bool addStubSymbolDeps(const StubFile *stub_file, Symbol *sym,
         lazy->extract();
         if (!ctx.arg.whyExtract.empty())
           ctx.whyExtractRecords.emplace_back(toString(stub_file),
-                                             sym->getFile(), *sym);
+                                             needed->getFile(), *needed);
       }
     }
   }
@@ -1129,7 +1165,7 @@ static bool addStubSymbolDeps(const StubFile *stub_file, Symbol *sym,
 
 static void processStubLibraries() {
   log("-- processStubLibraries");
-  bool depsAdded = false;
+  bool depsAdded;
   do {
     depsAdded = false;
     for (auto &stub_file : ctx.stubFiles) {
@@ -1150,10 +1186,13 @@ static void processStubLibraries() {
                        << "stub symbol not needed: `" << name << "`\n");
         }
       }
+    }
 
-      // Secondly looks for any symbols with an `importName` that matches
-      for (Symbol *sym : symtab->symbols()) {
-        if (sym->isUndefined() && sym->importName.has_value()) {
+    // Secondly looks for any symbols with an `importName` that matches
+    for (size_t i = 0; i < symtab->symbols().size(); ++i) {
+      Symbol *sym = symtab->symbols()[i];
+      if (sym->isUndefined() && sym->importName.has_value()) {
+        for (auto &stub_file : ctx.stubFiles) {
           auto it = stub_file->symbolDependencies.find(sym->importName.value());
           if (it != stub_file->symbolDependencies.end()) {
             depsAdded |= addStubSymbolDeps(stub_file, sym, it->second);
@@ -1468,7 +1507,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
   if (errorCount())
     return;
 
-  // We process the stub libraries once beofore LTO to ensure that any possible
+  // We process the stub libraries once before LTO to ensure that any possible
   // required exports are preserved by the LTO process.
   processStubLibrariesPreLTO();
 



More information about the llvm-commits mailing list