https://github.com/mysterymath updated 
https://github.com/llvm/llvm-project/pull/164916

>From 82af3bd68ccfbb7bed200da7b594816d4ddf4158 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Thu, 7 Aug 2025 16:05:09 -0700
Subject: [PATCH 01/22] [LTO][LLD] Prevent invalid LTO libfunc transforms

This patch ensures that:
1) New bitcode is not extracted for libfuncs after LTO occurs, and
2) Extracted bitcode for libfuncs is considered external, since new
   calls to it may be emitted.
---
 clang/lib/CodeGen/BackendUtil.cpp             | 10 +--
 lld/COFF/InputFiles.cpp                       |  4 +-
 lld/ELF/Driver.cpp                            | 19 ++++-
 lld/ELF/LTO.cpp                               |  5 +-
 lld/ELF/LTO.h                                 |  3 +-
 lld/test/ELF/lto/libcall-archive-bitcode.test | 41 +++++++++++
 llvm/include/llvm/LTO/LTO.h                   | 27 +++++--
 llvm/include/llvm/LTO/LTOBackend.h            |  7 +-
 llvm/lib/LTO/LTO.cpp                          | 70 ++++++++++++++-----
 llvm/lib/LTO/LTOBackend.cpp                   | 43 ++++++++++--
 llvm/lib/LTO/LTOCodeGenerator.cpp             |  4 +-
 llvm/lib/LTO/ThinLTOCodeGenerator.cpp         |  4 +-
 llvm/lib/Object/CMakeLists.txt                |  1 +
 .../X86/libcall-external-bitcode.ll           | 20 ++++++
 .../X86/libcall-external-not-bitcode.ll       | 20 ++++++
 llvm/test/LTO/Resolution/X86/libcall-in-tu.ll | 34 +++++++++
 llvm/tools/llvm-lto2/llvm-lto2.cpp            |  7 ++
 17 files changed, 274 insertions(+), 45 deletions(-)
 create mode 100644 lld/test/ELF/lto/libcall-archive-bitcode.test
 create mode 100644 llvm/test/LTO/Resolution/X86/libcall-external-bitcode.ll
 create mode 100644 llvm/test/LTO/Resolution/X86/libcall-external-not-bitcode.ll
 create mode 100644 llvm/test/LTO/Resolution/X86/libcall-in-tu.ll

diff --git a/clang/lib/CodeGen/BackendUtil.cpp 
b/clang/lib/CodeGen/BackendUtil.cpp
index 754bcc5d12b9b..cfd12d6778d1a 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -1411,11 +1411,11 @@ runThinLTOBackend(CompilerInstance &CI, 
ModuleSummaryIndex *CombinedIndex,
   // FIXME: Both ExecuteAction and thinBackend set up optimization remarks for
   // the same context.
   finalizeLLVMOptimizationRemarks(M->getContext());
-  if (Error E =
-          thinBackend(Conf, -1, AddStream, *M, *CombinedIndex, ImportList,
-                      ModuleToDefinedGVSummaries[M->getModuleIdentifier()],
-                      /*ModuleMap=*/nullptr, Conf.CodeGenOnly,
-                      /*IRAddStream=*/nullptr, CGOpts.CmdArgs)) {
+  if (Error E = thinBackend(
+          Conf, -1, AddStream, *M, *CombinedIndex, ImportList,
+          ModuleToDefinedGVSummaries[M->getModuleIdentifier()],
+          /*ModuleMap=*/nullptr, Conf.CodeGenOnly, /*BitcodeLibFuncs=*/{},
+          /*IRAddStream=*/nullptr, CGOpts.CmdArgs)) {
     handleAllErrors(std::move(E), [&](ErrorInfoBase &EIB) {
       errs() << "Error running ThinLTO backend: " << EIB.message() << '\n';
     });
diff --git a/lld/COFF/InputFiles.cpp b/lld/COFF/InputFiles.cpp
index 492b2ad80166d..0a40309174a67 100644
--- a/lld/COFF/InputFiles.cpp
+++ b/lld/COFF/InputFiles.cpp
@@ -1397,6 +1397,8 @@ void BitcodeFile::parse() {
     comdat[i] =
         symtab.addComdat(this, saver.save(obj->getComdatTable()[i].first));
   Triple tt(obj->getTargetTriple());
+  TargetLibraryInfoImpl tlii(tt);
+  TargetLibraryInfo tli(tlii);
   RTLIB::RuntimeLibcallsInfo libcalls(tt);
   for (const lto::InputFile::Symbol &objSym : obj->symbols()) {
     StringRef symName = saver.save(objSym.getName());
@@ -1447,7 +1449,7 @@ void BitcodeFile::parse() {
           symtab.addRegular(this, symName, nullptr, fakeSC, 0, 
objSym.isWeak());
     }
     symbols.push_back(sym);
-    if (objSym.isUsed() || objSym.isLibcall(libcalls))
+    if (objSym.isUsed() || objSym.isLibcall(tli, libcalls))
       symtab.ctx.config.gcroot.push_back(sym);
   }
   directives = saver.save(obj->getCOFFLinkerOpts());
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index d7bfa7357d4ed..e7f5408bcf893 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -2703,15 +2703,30 @@ static void markBuffersAsDontNeed(Ctx &ctx, bool 
skipLinkedOutput) {
 template <class ELFT>
 void LinkerDriver::compileBitcodeFiles(bool skipLinkedOutput) {
   llvm::TimeTraceScope timeScope("LTO");
+  // Capture the triple before moving the bitcode into the bitcode compiler.
+  std::optional<llvm::Triple> tt;
+  if (!ctx.bitcodeFiles.empty())
+    tt = llvm::Triple(ctx.bitcodeFiles.front()->obj->getTargetTriple());
   // Compile bitcode files and replace bitcode symbols.
   lto.reset(new BitcodeCompiler(ctx));
   for (BitcodeFile *file : ctx.bitcodeFiles)
     lto->add(*file);
 
-  if (!ctx.bitcodeFiles.empty())
+  llvm::BumpPtrAllocator alloc;
+  llvm::StringSaver saver(alloc);
+  SmallVector<StringRef> bitcodeLibFuncs;
+  if (!ctx.bitcodeFiles.empty()) {
     markBuffersAsDontNeed(ctx, skipLinkedOutput);
+    for (StringRef libFunc : lto::LTO::getLibFuncSymbols(*tt, saver)) {
+      Symbol *sym = ctx.symtab->find(libFunc);
+      if (!sym)
+        continue;
+      if (isa<BitcodeFile>(sym->file))
+        bitcodeLibFuncs.push_back(libFunc);
+    }
+  }
 
-  ltoObjectFiles = lto->compile();
+  ltoObjectFiles = lto->compile(bitcodeLibFuncs);
   for (auto &file : ltoObjectFiles) {
     auto *obj = cast<ObjFile<ELFT>>(file.get());
     obj->parse(/*ignoreComdats=*/true);
diff --git a/lld/ELF/LTO.cpp b/lld/ELF/LTO.cpp
index ff8d44e0d7f64..2401e49e4cd30 100644
--- a/lld/ELF/LTO.cpp
+++ b/lld/ELF/LTO.cpp
@@ -317,7 +317,10 @@ static void thinLTOCreateEmptyIndexFiles(Ctx &ctx) {
 
 // Merge all the bitcode files we have seen, codegen the result
 // and return the resulting ObjectFile(s).
-SmallVector<std::unique_ptr<InputFile>, 0> BitcodeCompiler::compile() {
+SmallVector<std::unique_ptr<InputFile>, 0>
+BitcodeCompiler::compile(const SmallVector<StringRef> &bitcodeLibFuncs) {
+  ltoObj->setBitcodeLibFuncs(bitcodeLibFuncs);
+
   unsigned maxTasks = ltoObj->getMaxTasks();
   buf.resize(maxTasks);
   files.resize(maxTasks);
diff --git a/lld/ELF/LTO.h b/lld/ELF/LTO.h
index acf3bcff7f2f1..8207e91460785 100644
--- a/lld/ELF/LTO.h
+++ b/lld/ELF/LTO.h
@@ -42,7 +42,8 @@ class BitcodeCompiler {
   ~BitcodeCompiler();
 
   void add(BitcodeFile &f);
-  SmallVector<std::unique_ptr<InputFile>, 0> compile();
+  SmallVector<std::unique_ptr<InputFile>, 0>
+  compile(const SmallVector<StringRef> &bitcodeLibFuncs);
 
 private:
   Ctx &ctx;
diff --git a/lld/test/ELF/lto/libcall-archive-bitcode.test 
b/lld/test/ELF/lto/libcall-archive-bitcode.test
new file mode 100644
index 0000000000000..20735b5c89c99
--- /dev/null
+++ b/lld/test/ELF/lto/libcall-archive-bitcode.test
@@ -0,0 +1,41 @@
+; REQUIRES: x86
+
+; RUN: rm -rf %t && split-file %s %t && cd %t
+; RUN: llvm-as main.ll -o main.o
+; RUN: llvm-as bcmp.ll -o bcmp.o
+; RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux-gnu memcmp.s -o 
memcmp.o
+; RUN: llvm-ar rc libc.a bcmp.o memcmp.o
+
+;; Ensure that no memcmp->bcmp translation occurs during LTO because bcmp is in
+;; bitcode, but was not brought into the link. This would fail the link by
+;; extracting bitcode after LTO.
+; RUN: ld.lld -o out main.o -L. -lc
+; RUN: llvm-nm out | FileCheck %s
+
+;--- bcmp.ll
+target datalayout = 
"e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+define i32 @bcmp(ptr %0, ptr %1, i64 %2) {
+  ret i32 0
+}
+
+;--- memcmp.s
+.globl memcmp
+memcmp:
+  ret
+
+;--- main.ll
+target datalayout = 
"e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+define i1 @_start(ptr %0, ptr %1, i64 %2) {
+  %cmp = call i32 @memcmp(ptr %0, ptr %1, i64 %2)
+  %eq = icmp eq i32 %cmp, 0
+  ret i1 %eq
+}
+
+; CHECK-NOT: bcmp
+; CHECK: memcmp
+declare i32 @memcmp(ptr, ptr, i64)
+
diff --git a/llvm/include/llvm/LTO/LTO.h b/llvm/include/llvm/LTO/LTO.h
index 38349b604b6b3..6cb063f68071f 100644
--- a/llvm/include/llvm/LTO/LTO.h
+++ b/llvm/include/llvm/LTO/LTO.h
@@ -179,7 +179,8 @@ class InputFile {
     // may emit references to. Such symbols must be considered external, as
     // removing them or modifying their interfaces would invalidate the code
     // generator's knowledge about them.
-    bool isLibcall(const RTLIB::RuntimeLibcallsInfo &Libcalls) const;
+    bool isLibcall(const TargetLibraryInfo &TLI,
+                   const RTLIB::RuntimeLibcallsInfo &Libcalls) const;
   };
 
   /// A range over the symbols in this InputFile.
@@ -308,7 +309,8 @@ class ThinBackendProc {
 using ThinBackendFunction = std::function<std::unique_ptr<ThinBackendProc>(
     const Config &C, ModuleSummaryIndex &CombinedIndex,
     const DenseMap<StringRef, GVSummaryMapTy> &ModuleToDefinedGVSummaries,
-    AddStreamFn AddStream, FileCache Cache)>;
+    AddStreamFn AddStream, FileCache Cache,
+    const SmallVector<StringRef> &BitcodeLibFuncs)>;
 
 /// This type defines the behavior following the thin-link phase during 
ThinLTO.
 /// It encapsulates a backend function and a strategy for thread pool
@@ -323,10 +325,11 @@ struct ThinBackend {
   std::unique_ptr<ThinBackendProc> operator()(
       const Config &Conf, ModuleSummaryIndex &CombinedIndex,
       const DenseMap<StringRef, GVSummaryMapTy> &ModuleToDefinedGVSummaries,
-      AddStreamFn AddStream, FileCache Cache) {
+      AddStreamFn AddStream, FileCache Cache,
+      const SmallVector<StringRef> &BitcodeLibFuncs) {
     assert(isValid() && "Invalid backend function");
     return Func(Conf, CombinedIndex, ModuleToDefinedGVSummaries,
-                std::move(AddStream), std::move(Cache));
+                std::move(AddStream), std::move(Cache), BitcodeLibFuncs);
   }
   ThreadPoolStrategy getParallelism() const { return Parallelism; }
   bool isValid() const { return static_cast<bool>(Func); }
@@ -444,6 +447,12 @@ class LTO {
   LLVM_ABI Error add(std::unique_ptr<InputFile> Obj,
                      ArrayRef<SymbolResolution> Res);
 
+  /// Set the list of functions implemented in bitcode across the link, whether
+  /// extracted or not. Such functions may not be referenced if they were not
+  /// extracted by the time LTO occurs.
+  LLVM_ABI void
+  setBitcodeLibFuncs(const SmallVector<StringRef> &BitcodeLibFuncs);
+
   /// Returns an upper bound on the number of tasks that the client may expect.
   /// This may only be called after all IR object files have been added. For a
   /// full description of tasks see LTOBackend.h.
@@ -464,6 +473,14 @@ class LTO {
   LLVM_ABI static SmallVector<const char *>
   getRuntimeLibcallSymbols(const Triple &TT);
 
+  /// Static method that returns a list of library function symbols that can be
+  /// generated by LTO but might not be visible from bitcode symbol table.
+  /// Unlike the runtime libcalls, the linker can report to the code generator
+  /// which of these are actually available in the link, and the code generator
+  /// can then only reference that set of symbols.
+  LLVM_ABI static SmallVector<StringRef>
+  getLibFuncSymbols(const Triple &TT, llvm::StringSaver &Saver);
+
 protected:
   // Called at the start of run().
   virtual Error serializeInputsForDistribution() { return Error::success(); }
@@ -655,6 +672,8 @@ class LTO {
   // Setup optimization remarks according to the provided configuration.
   Error setupOptimizationRemarks();
 
+  SmallVector<StringRef> BitcodeLibFuncs;
+
 public:
   /// Helper to emit an optimization remark during the LTO link when outside of
   /// the standard optimization pass pipeline.
diff --git a/llvm/include/llvm/LTO/LTOBackend.h 
b/llvm/include/llvm/LTO/LTOBackend.h
index 48ad5aa64f61f..6a7d7e0d87ac9 100644
--- a/llvm/include/llvm/LTO/LTOBackend.h
+++ b/llvm/include/llvm/LTO/LTOBackend.h
@@ -39,13 +39,15 @@ LLVM_ABI bool opt(const Config &Conf, TargetMachine *TM, 
unsigned Task,
                   Module &Mod, bool IsThinLTO,
                   ModuleSummaryIndex *ExportSummary,
                   const ModuleSummaryIndex *ImportSummary,
-                  const std::vector<uint8_t> &CmdArgs);
+                  const std::vector<uint8_t> &CmdArgs,
+                  const SmallVector<StringRef> &BitcodeLibFuncs);
 
 /// Runs a regular LTO backend. The regular LTO backend can also act as the
 /// regular LTO phase of ThinLTO, which may need to access the combined index.
 LLVM_ABI Error backend(const Config &C, AddStreamFn AddStream,
                        unsigned ParallelCodeGenParallelismLevel, Module &M,
-                       ModuleSummaryIndex &CombinedIndex);
+                       ModuleSummaryIndex &CombinedIndex,
+                       const SmallVector<StringRef> &BitcodeLibFuncs);
 
 /// Runs a ThinLTO backend.
 /// If \p ModuleMap is not nullptr, all the module files to be imported have
@@ -62,6 +64,7 @@ thinBackend(const Config &C, unsigned Task, AddStreamFn 
AddStream, Module &M,
             const FunctionImporter::ImportMapTy &ImportList,
             const GVSummaryMapTy &DefinedGlobals,
             MapVector<StringRef, BitcodeModule> *ModuleMap, bool CodeGenOnly,
+            const SmallVector<StringRef> &BitcodeLibFuncs,
             AddStreamFn IRAddStream = nullptr,
             const std::vector<uint8_t> &CmdArgs = std::vector<uint8_t>());
 
diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp
index 36360081fa4a0..0f3f3c344fb0a 100644
--- a/llvm/lib/LTO/LTO.cpp
+++ b/llvm/lib/LTO/LTO.cpp
@@ -653,7 +653,11 @@ Expected<std::unique_ptr<InputFile>> 
InputFile::create(MemoryBufferRef Object) {
 }
 
 bool InputFile::Symbol::isLibcall(
+    const TargetLibraryInfo &TLI,
     const RTLIB::RuntimeLibcallsInfo &Libcalls) const {
+  LibFunc F;
+  if (TLI.getLibFunc(IRName, F) && TLI.has(F))
+    return true;
   return Libcalls.getSupportedLibcallImpl(IRName) != RTLIB::Unsupported;
 }
 
@@ -715,6 +719,8 @@ void LTO::addModuleToGlobalRes(ArrayRef<InputFile::Symbol> 
Syms,
   auto *ResE = Res.end();
   (void)ResE;
   RTLIB::RuntimeLibcallsInfo Libcalls(TT);
+  TargetLibraryInfoImpl TLII(TT);
+  TargetLibraryInfo TLI(TLII);
   for (const InputFile::Symbol &Sym : Syms) {
     assert(ResI != ResE);
     SymbolResolution Res = *ResI++;
@@ -757,7 +763,7 @@ void LTO::addModuleToGlobalRes(ArrayRef<InputFile::Symbol> 
Syms,
       GlobalRes.VisibleOutsideSummary = true;
     }
 
-    bool IsLibcall = Sym.isLibcall(Libcalls);
+    bool IsLibcall = Sym.isLibcall(TLI, Libcalls);
 
     // Set the partition to external if we know it is re-defined by the linker
     // with -defsym or -wrap options, used elsewhere, e.g. it is visible to a
@@ -844,6 +850,10 @@ Error LTO::add(std::unique_ptr<InputFile> InputPtr,
   return Error::success();
 }
 
+void LTO::setBitcodeLibFuncs(const SmallVector<StringRef> &BitcodeLibFuncs) {
+  this->BitcodeLibFuncs = BitcodeLibFuncs;
+}
+
 Expected<ArrayRef<SymbolResolution>>
 LTO::addModule(InputFile &Input, ArrayRef<SymbolResolution> InputRes,
                unsigned ModI, ArrayRef<SymbolResolution> Res) {
@@ -1469,9 +1479,9 @@ Error LTO::runRegularLTO(AddStreamFn AddStream) {
   }
 
   if (!RegularLTO.EmptyCombinedModule || Conf.AlwaysEmitRegularLTOObj) {
-    if (Error Err =
-            backend(Conf, AddStream, 
RegularLTO.ParallelCodeGenParallelismLevel,
-                    *RegularLTO.CombinedModule, ThinLTO.CombinedIndex))
+    if (Error Err = backend(
+            Conf, AddStream, RegularLTO.ParallelCodeGenParallelismLevel,
+            *RegularLTO.CombinedModule, ThinLTO.CombinedIndex, 
BitcodeLibFuncs))
       return Err;
   }
 
@@ -1491,6 +1501,21 @@ SmallVector<const char *> 
LTO::getRuntimeLibcallSymbols(const Triple &TT) {
   return LibcallSymbols;
 }
 
+SmallVector<StringRef> LTO::getLibFuncSymbols(const Triple &TT,
+                                              StringSaver &Saver) {
+  auto TLII = std::make_unique<TargetLibraryInfoImpl>(TT);
+  TargetLibraryInfo TLI(*TLII);
+  SmallVector<StringRef> LibFuncSymbols;
+  LibFuncSymbols.reserve(LibFunc::NumLibFuncs);
+  for (unsigned I = 0, E = static_cast<unsigned>(LibFunc::NumLibFuncs); I != E;
+       ++I) {
+    LibFunc F = static_cast<LibFunc>(I);
+    if (TLI.has(F))
+      LibFuncSymbols.push_back(Saver.save(TLI.getName(F)).data());
+  }
+  return LibFuncSymbols;
+}
+
 Error ThinBackendProc::emitFiles(
     const FunctionImporter::ImportMapTy &ImportList, llvm::StringRef 
ModulePath,
     const std::string &NewModulePath) const {
@@ -1568,6 +1593,7 @@ class CGThinBackend : public ThinBackendProc {
 class InProcessThinBackend : public CGThinBackend {
 protected:
   FileCache Cache;
+  const SmallVector<StringRef> &BitcodeLibFuncs;
 
 public:
   InProcessThinBackend(
@@ -1575,11 +1601,12 @@ class InProcessThinBackend : public CGThinBackend {
       ThreadPoolStrategy ThinLTOParallelism,
       const DenseMap<StringRef, GVSummaryMapTy> &ModuleToDefinedGVSummaries,
       AddStreamFn AddStream, FileCache Cache, lto::IndexWriteCallback OnWrite,
-      bool ShouldEmitIndexFiles, bool ShouldEmitImportsFiles)
+      bool ShouldEmitIndexFiles, bool ShouldEmitImportsFiles,
+      const SmallVector<StringRef> &BitcodeLibFuncs)
       : CGThinBackend(Conf, CombinedIndex, ModuleToDefinedGVSummaries,
                       AddStream, OnWrite, ShouldEmitIndexFiles,
                       ShouldEmitImportsFiles, ThinLTOParallelism),
-        Cache(std::move(Cache)) {}
+        Cache(std::move(Cache)), BitcodeLibFuncs(BitcodeLibFuncs) {}
 
   virtual Error runThinLTOBackendThread(
       AddStreamFn AddStream, FileCache Cache, unsigned Task, BitcodeModule BM,
@@ -1600,7 +1627,7 @@ class InProcessThinBackend : public CGThinBackend {
 
       return thinBackend(Conf, Task, AddStream, **MOrErr, CombinedIndex,
                          ImportList, DefinedGlobals, &ModuleMap,
-                         Conf.CodeGenOnly);
+                         Conf.CodeGenOnly, BitcodeLibFuncs);
     };
     if (ShouldEmitIndexFiles) {
       if (auto E = emitFiles(ImportList, ModuleID, ModuleID.str()))
@@ -1685,13 +1712,14 @@ class FirstRoundThinBackend : public 
InProcessThinBackend {
       const Config &Conf, ModuleSummaryIndex &CombinedIndex,
       ThreadPoolStrategy ThinLTOParallelism,
       const DenseMap<StringRef, GVSummaryMapTy> &ModuleToDefinedGVSummaries,
-      AddStreamFn CGAddStream, FileCache CGCache, AddStreamFn IRAddStream,
+      AddStreamFn CGAddStream, FileCache CGCache,
+      const SmallVector<StringRef> &BitcodeLibFuncs, AddStreamFn IRAddStream,
       FileCache IRCache)
       : InProcessThinBackend(Conf, CombinedIndex, ThinLTOParallelism,
                              ModuleToDefinedGVSummaries, 
std::move(CGAddStream),
                              std::move(CGCache), /*OnWrite=*/nullptr,
                              /*ShouldEmitIndexFiles=*/false,
-                             /*ShouldEmitImportsFiles=*/false),
+                             /*ShouldEmitImportsFiles=*/false, 
BitcodeLibFuncs),
         IRAddStream(std::move(IRAddStream)), IRCache(std::move(IRCache)) {}
 
   Error runThinLTOBackendThread(
@@ -1714,7 +1742,7 @@ class FirstRoundThinBackend : public InProcessThinBackend 
{
 
       return thinBackend(Conf, Task, CGAddStream, **MOrErr, CombinedIndex,
                          ImportList, DefinedGlobals, &ModuleMap,
-                         Conf.CodeGenOnly, IRAddStream);
+                         Conf.CodeGenOnly, BitcodeLibFuncs, IRAddStream);
     };
     // Like InProcessThinBackend, we produce index files as needed for
     // FirstRoundThinBackend. However, these files are not generated for
@@ -1781,6 +1809,7 @@ class SecondRoundThinBackend : public 
InProcessThinBackend {
       ThreadPoolStrategy ThinLTOParallelism,
       const DenseMap<StringRef, GVSummaryMapTy> &ModuleToDefinedGVSummaries,
       AddStreamFn AddStream, FileCache Cache,
+      const SmallVector<StringRef> &BitcodeLibFuncs,
       std::unique_ptr<SmallVector<StringRef>> IRFiles,
       stable_hash CombinedCGDataHash)
       : InProcessThinBackend(Conf, CombinedIndex, ThinLTOParallelism,
@@ -1788,7 +1817,7 @@ class SecondRoundThinBackend : public 
InProcessThinBackend {
                              std::move(Cache),
                              /*OnWrite=*/nullptr,
                              /*ShouldEmitIndexFiles=*/false,
-                             /*ShouldEmitImportsFiles=*/false),
+                             /*ShouldEmitImportsFiles=*/false, 
BitcodeLibFuncs),
         IRFiles(std::move(IRFiles)), CombinedCGDataHash(CombinedCGDataHash) {}
 
   Error runThinLTOBackendThread(
@@ -1809,7 +1838,7 @@ class SecondRoundThinBackend : public 
InProcessThinBackend {
 
       return thinBackend(Conf, Task, AddStream, *LoadedModule, CombinedIndex,
                          ImportList, DefinedGlobals, &ModuleMap,
-                         /*CodeGenOnly=*/true);
+                         /*CodeGenOnly=*/true, BitcodeLibFuncs);
     };
     if (!Cache.isValid() || !CombinedIndex.modulePaths().count(ModuleID) ||
         all_of(CombinedIndex.getModuleHash(ModuleID),
@@ -1848,11 +1877,12 @@ ThinBackend 
lto::createInProcessThinBackend(ThreadPoolStrategy Parallelism,
   auto Func =
       [=](const Config &Conf, ModuleSummaryIndex &CombinedIndex,
           const DenseMap<StringRef, GVSummaryMapTy> 
&ModuleToDefinedGVSummaries,
-          AddStreamFn AddStream, FileCache Cache) {
+          AddStreamFn AddStream, FileCache Cache,
+          const SmallVector<StringRef> &BitcodeLibFuncs) {
         return std::make_unique<InProcessThinBackend>(
             Conf, CombinedIndex, Parallelism, ModuleToDefinedGVSummaries,
             AddStream, Cache, OnWrite, ShouldEmitIndexFiles,
-            ShouldEmitImportsFiles);
+            ShouldEmitImportsFiles, BitcodeLibFuncs);
       };
   return ThinBackend(Func, Parallelism);
 }
@@ -1969,7 +1999,8 @@ ThinBackend lto::createWriteIndexesThinBackend(
   auto Func =
       [=](const Config &Conf, ModuleSummaryIndex &CombinedIndex,
           const DenseMap<StringRef, GVSummaryMapTy> 
&ModuleToDefinedGVSummaries,
-          AddStreamFn AddStream, FileCache Cache) {
+          AddStreamFn AddStream, FileCache Cache,
+          const SmallVector<StringRef> &BitcodeLibFuncs) {
         return std::make_unique<WriteIndexesThinBackend>(
             Conf, CombinedIndex, Parallelism, ModuleToDefinedGVSummaries,
             OldPrefix, NewPrefix, NativeObjectPrefix, ShouldEmitImportsFiles,
@@ -2222,7 +2253,7 @@ Error LTO::runThinLTO(AddStreamFn AddStream, FileCache 
Cache,
   if (!CodeGenDataThinLTOTwoRounds) {
     std::unique_ptr<ThinBackendProc> BackendProc =
         ThinLTO.Backend(Conf, ThinLTO.CombinedIndex, 
ModuleToDefinedGVSummaries,
-                        AddStream, Cache);
+                        AddStream, Cache, BitcodeLibFuncs);
     return RunBackends(BackendProc.get());
   }
 
@@ -2245,7 +2276,7 @@ Error LTO::runThinLTO(AddStreamFn AddStream, FileCache 
Cache,
   LLVM_DEBUG(dbgs() << "[TwoRounds] Running the first round of codegen\n");
   auto FirstRoundLTO = std::make_unique<FirstRoundThinBackend>(
       Conf, ThinLTO.CombinedIndex, Parallelism, ModuleToDefinedGVSummaries,
-      CG.AddStream, CG.Cache, IR.AddStream, IR.Cache);
+      CG.AddStream, CG.Cache, BitcodeLibFuncs, IR.AddStream, IR.Cache);
   if (Error E = RunBackends(FirstRoundLTO.get()))
     return E;
 
@@ -2261,7 +2292,7 @@ Error LTO::runThinLTO(AddStreamFn AddStream, FileCache 
Cache,
   LLVM_DEBUG(dbgs() << "[TwoRounds] Running the second round of codegen\n");
   auto SecondRoundLTO = std::make_unique<SecondRoundThinBackend>(
       Conf, ThinLTO.CombinedIndex, Parallelism, ModuleToDefinedGVSummaries,
-      AddStream, Cache, IR.getResult(), CombinedHash);
+      AddStream, Cache, BitcodeLibFuncs, IR.getResult(), CombinedHash);
   return RunBackends(SecondRoundLTO.get());
 }
 
@@ -2768,7 +2799,8 @@ ThinBackend lto::createOutOfProcessThinBackend(
   auto Func =
       [=](const Config &Conf, ModuleSummaryIndex &CombinedIndex,
           const DenseMap<StringRef, GVSummaryMapTy> 
&ModuleToDefinedGVSummaries,
-          AddStreamFn AddStream, FileCache Cache) {
+          AddStreamFn AddStream, FileCache Cache,
+          const SmallVector<StringRef> &BitcodeLibFuncs) {
         return std::make_unique<OutOfProcessThinBackend>(
             Conf, CombinedIndex, Parallelism, ModuleToDefinedGVSummaries,
             AddStream, Cache, OnWrite, ShouldEmitIndexFiles,
diff --git a/llvm/lib/LTO/LTOBackend.cpp b/llvm/lib/LTO/LTOBackend.cpp
index 7ee2557a68bd5..8cd8a75c52cab 100644
--- a/llvm/lib/LTO/LTOBackend.cpp
+++ b/llvm/lib/LTO/LTOBackend.cpp
@@ -259,7 +259,8 @@ createTargetMachine(const Config &Conf, const Target 
*TheTarget, Module &M) {
 static void runNewPMPasses(const Config &Conf, Module &Mod, TargetMachine *TM,
                            unsigned OptLevel, bool IsThinLTO,
                            ModuleSummaryIndex *ExportSummary,
-                           const ModuleSummaryIndex *ImportSummary) {
+                           const ModuleSummaryIndex *ImportSummary,
+                           const DenseSet<StringRef> &BitcodeLibFuncs) {
   std::optional<PGOOptions> PGOOpt;
   if (!Conf.SampleProfile.empty())
     PGOOpt = PGOOptions(Conf.SampleProfile, "", Conf.ProfileRemapping,
@@ -301,6 +302,28 @@ static void runNewPMPasses(const Config &Conf, Module 
&Mod, TargetMachine *TM,
       new TargetLibraryInfoImpl(TM->getTargetTriple(), TM->Options.VecLib));
   if (Conf.Freestanding)
     TLII->disableAllFunctions();
+
+  TargetLibraryInfo TLI(*TLII);
+  for (unsigned I = 0, E = static_cast<unsigned>(LibFunc::NumLibFuncs); I != E;
+       ++I) {
+    LibFunc F = static_cast<LibFunc>(I);
+    StringRef Name = TLI.getName(F);
+    GlobalValue *Val = Mod.getNamedValue(Name);
+
+    // LibFuncs present in the current TU can always be referenced.
+    if (Val && !Val->isDeclaration())
+      continue;
+
+    // LibFuncs not implemented in bitcode can always be referenced.
+    if (!BitcodeLibFuncs.contains(Name))
+      continue;
+
+    // FIXME: Functions that are somewhere in a ThinLTO link (just not imported
+    // in this module) should not be disabled, as they have already been
+    // extracted.
+    TLII->setUnavailable(F);
+  }
+
   FAM.registerPass([&] { return TargetLibraryAnalysis(*TLII); });
 
   // Parse a custom AA pipeline if asked to.
@@ -384,7 +407,8 @@ static bool isEmptyModule(const Module &Mod) {
 bool lto::opt(const Config &Conf, TargetMachine *TM, unsigned Task, Module 
&Mod,
               bool IsThinLTO, ModuleSummaryIndex *ExportSummary,
               const ModuleSummaryIndex *ImportSummary,
-              const std::vector<uint8_t> &CmdArgs) {
+              const std::vector<uint8_t> &CmdArgs,
+              const SmallVector<StringRef> &BitcodeLibFuncs) {
   llvm::TimeTraceScope timeScope("opt");
   if (EmbedBitcode == LTOBitcodeEmbedding::EmbedPostMergePreOptimized) {
     // FIXME: the motivation for capturing post-merge bitcode and command line
@@ -409,9 +433,11 @@ bool lto::opt(const Config &Conf, TargetMachine *TM, 
unsigned Task, Module &Mod,
   // analysis in the case of a ThinLTO build where this might be an empty
   // regular LTO combined module, with a large combined index from ThinLTO.
   if (!isEmptyModule(Mod)) {
+    DenseSet<StringRef> BitcodeLibFuncsSet(BitcodeLibFuncs.begin(),
+                                           BitcodeLibFuncs.end());
     // FIXME: Plumb the combined index into the new pass manager.
     runNewPMPasses(Conf, Mod, TM, Conf.OptLevel, IsThinLTO, ExportSummary,
-                   ImportSummary);
+                   ImportSummary, BitcodeLibFuncsSet);
   }
   return !Conf.PostOptModuleHook || Conf.PostOptModuleHook(Task, Mod);
 }
@@ -577,7 +603,8 @@ Error lto::finalizeOptimizationRemarks(LLVMRemarkFileHandle 
DiagOutputFile) {
 
 Error lto::backend(const Config &C, AddStreamFn AddStream,
                    unsigned ParallelCodeGenParallelismLevel, Module &Mod,
-                   ModuleSummaryIndex &CombinedIndex) {
+                   ModuleSummaryIndex &CombinedIndex,
+                   const SmallVector<StringRef> &BitcodeLibFuncs) {
   llvm::TimeTraceScope timeScope("LTO backend");
   Expected<const Target *> TOrErr = initAndLookupTarget(C, Mod);
   if (!TOrErr)
@@ -589,7 +616,7 @@ Error lto::backend(const Config &C, AddStreamFn AddStream,
   if (!C.CodeGenOnly) {
     if (!opt(C, TM.get(), 0, Mod, /*IsThinLTO=*/false,
              /*ExportSummary=*/&CombinedIndex, /*ImportSummary=*/nullptr,
-             /*CmdArgs*/ std::vector<uint8_t>()))
+             /*CmdArgs*/ std::vector<uint8_t>(), BitcodeLibFuncs))
       return Error::success();
   }
 
@@ -629,7 +656,9 @@ Error lto::thinBackend(const Config &Conf, unsigned Task, 
AddStreamFn AddStream,
                        const FunctionImporter::ImportMapTy &ImportList,
                        const GVSummaryMapTy &DefinedGlobals,
                        MapVector<StringRef, BitcodeModule> *ModuleMap,
-                       bool CodeGenOnly, AddStreamFn IRAddStream,
+                       bool CodeGenOnly,
+                       const SmallVector<StringRef> &BitcodeLibFuncs,
+                       AddStreamFn IRAddStream,
                        const std::vector<uint8_t> &CmdArgs) {
   llvm::TimeTraceScope timeScope("Thin backend", Mod.getModuleIdentifier());
   Expected<const Target *> TOrErr = initAndLookupTarget(Conf, Mod);
@@ -668,7 +697,7 @@ Error lto::thinBackend(const Config &Conf, unsigned Task, 
AddStreamFn AddStream,
         // Perform optimization and code generation for ThinLTO.
         if (!opt(Conf, TM, Task, Mod, /*IsThinLTO=*/true,
                  /*ExportSummary=*/nullptr, /*ImportSummary=*/&CombinedIndex,
-                 CmdArgs))
+                 CmdArgs, BitcodeLibFuncs))
           return finalizeOptimizationRemarks(std::move(DiagnosticOutputFile));
 
         // Save the current module before the first codegen round.
diff --git a/llvm/lib/LTO/LTOCodeGenerator.cpp 
b/llvm/lib/LTO/LTOCodeGenerator.cpp
index 46be71da5a092..fda0d36458f6d 100644
--- a/llvm/lib/LTO/LTOCodeGenerator.cpp
+++ b/llvm/lib/LTO/LTOCodeGenerator.cpp
@@ -614,7 +614,7 @@ bool LTOCodeGenerator::optimize() {
   TargetMach = createTargetMachine();
   if (!opt(Config, TargetMach.get(), 0, *MergedModule, /*IsThinLTO=*/false,
            /*ExportSummary=*/&CombinedIndex, /*ImportSummary=*/nullptr,
-           /*CmdArgs*/ std::vector<uint8_t>())) {
+           /*CmdArgs*/ std::vector<uint8_t>(), {})) {
     emitError("LTO middle-end optimizations failed");
     return false;
   }
@@ -639,7 +639,7 @@ bool LTOCodeGenerator::compileOptimized(AddStreamFn 
AddStream,
 
   Config.CodeGenOnly = true;
   Error Err = backend(Config, AddStream, ParallelismLevel, *MergedModule,
-                      CombinedIndex);
+                      CombinedIndex, {});
   assert(!Err && "unexpected code-generation failure");
   (void)Err;
 
diff --git a/llvm/lib/LTO/ThinLTOCodeGenerator.cpp 
b/llvm/lib/LTO/ThinLTOCodeGenerator.cpp
index 93b672ae7840c..6397fdffed687 100644
--- a/llvm/lib/LTO/ThinLTOCodeGenerator.cpp
+++ b/llvm/lib/LTO/ThinLTOCodeGenerator.cpp
@@ -292,8 +292,10 @@ addUsedSymbolToPreservedGUID(const lto::InputFile &File,
                              DenseSet<GlobalValue::GUID> &PreservedGUID) {
   Triple TT(File.getTargetTriple());
   RTLIB::RuntimeLibcallsInfo Libcalls(TT);
+  TargetLibraryInfoImpl TLII(TT);
+  TargetLibraryInfo TLI(TLII);
   for (const auto &Sym : File.symbols())
-    if (Sym.isUsed() || Sym.isLibcall(Libcalls))
+    if (Sym.isUsed() || Sym.isLibcall(TLI, Libcalls))
       PreservedGUID.insert(
           GlobalValue::getGUIDAssumingExternalLinkage(Sym.getIRName()));
 }
diff --git a/llvm/lib/Object/CMakeLists.txt b/llvm/lib/Object/CMakeLists.txt
index 0f6d2f7c59a5c..c48d251249488 100644
--- a/llvm/lib/Object/CMakeLists.txt
+++ b/llvm/lib/Object/CMakeLists.txt
@@ -51,6 +51,7 @@ add_llvm_component_library(LLVMObject
   BinaryFormat
   MCParser
   Support
+  Target
   TargetParser
   TextAPI
   )
diff --git a/llvm/test/LTO/Resolution/X86/libcall-external-bitcode.ll 
b/llvm/test/LTO/Resolution/X86/libcall-external-bitcode.ll
new file mode 100644
index 0000000000000..95a599fe75e8b
--- /dev/null
+++ b/llvm/test/LTO/Resolution/X86/libcall-external-bitcode.ll
@@ -0,0 +1,20 @@
+; RUN: opt %s -o %t -module-summary -mtriple x86_64-unknown-linux-musl
+; RUN: llvm-lto2 run -o %t2 \
+; RUN:   -r %t,foo,plx \
+; RUN:   -r %t,memcmp,x \
+; RUN:   -r %t,bcmp,pl --bitcode-libfuncs=bcmp %t -save-temps
+; RUN: llvm-dis %t2.1.4.opt.bc -o - | FileCheck %s
+
+define i1 @foo(ptr %0, ptr %1, i64 %2) {
+  ; CHECK-LABEL: define{{.*}}i1 @foo
+  ; CHECK-NEXT: %cmp = {{.*}}call i32 @memcmp
+  ; CHECK-NEXT: %eq = icmp eq i32 %cmp, 0
+  ; CHECK-NEXT: ret i1 %eq
+
+  %cmp = call i32 @memcmp(ptr %0, ptr %1, i64 %2)
+  %eq = icmp eq i32 %cmp, 0
+  ret i1 %eq
+}
+
+declare i32 @memcmp(ptr, ptr, i64)
+declare i32 @bcmp(ptr, ptr, i64)
diff --git a/llvm/test/LTO/Resolution/X86/libcall-external-not-bitcode.ll 
b/llvm/test/LTO/Resolution/X86/libcall-external-not-bitcode.ll
new file mode 100644
index 0000000000000..2e6cc798d22cd
--- /dev/null
+++ b/llvm/test/LTO/Resolution/X86/libcall-external-not-bitcode.ll
@@ -0,0 +1,20 @@
+; RUN: opt %s -o %t -module-summary -mtriple x86_64-unknown-linux-musl
+; RUN: llvm-lto2 run -o %t2 \
+; RUN:   -r %t,foo,plx \
+; RUN:   -r %t,memcmp,x \
+; RUN:   -r %t,bcmp,pl %t -save-temps
+; RUN: llvm-dis %t2.1.4.opt.bc -o - | FileCheck %s
+
+define i1 @foo(ptr %0, ptr %1, i64 %2) {
+  ; CHECK-LABEL: define{{.*}}i1 @foo
+  ; CHECK-NEXT: %bcmp = {{.*}}call i32 @bcmp
+  ; CHECK-NEXT: %eq = icmp eq i32 %bcmp, 0
+  ; CHECK-NEXT: ret i1 %eq
+
+  %cmp = call i32 @memcmp(ptr %0, ptr %1, i64 %2)
+  %eq = icmp eq i32 %cmp, 0
+  ret i1 %eq
+}
+
+declare i32 @memcmp(ptr, ptr, i64)
+declare i32 @bcmp(ptr, ptr, i64)
diff --git a/llvm/test/LTO/Resolution/X86/libcall-in-tu.ll 
b/llvm/test/LTO/Resolution/X86/libcall-in-tu.ll
new file mode 100644
index 0000000000000..948f21a6536ca
--- /dev/null
+++ b/llvm/test/LTO/Resolution/X86/libcall-in-tu.ll
@@ -0,0 +1,34 @@
+;; This test comes from a real world scenario in LTO, where the definition of
+;; bcmp was deleted because it has no uses, but later instcombine re-introduced
+;; a call to bcmp() as part of SimplifyLibCalls. Such deletions must not be
+;; allowed.
+
+; RUN: opt %s -o %t -module-summary -mtriple x86_64-unknown-linux-musl
+; RUN: llvm-lto2 run -o %t2 \
+; RUN:   -r %t,foo,plx \
+; RUN:   -r %t,memcmp,x \
+; RUN:   -r %t,bcmp,pl \
+; RUN:   -r %t,bcmp_impl,x %t -save-temps
+; RUN: llvm-dis %t2.1.4.opt.bc -o - | FileCheck %s
+
+define i1 @foo(ptr %0, ptr %1, i64 %2) {
+  ; CHECK-LABEL: define{{.*}}i1 @foo
+  ; CHECK-NEXT: %bcmp = {{.*}}call i32 @bcmp
+  ; CHECK-NEXT: %eq = icmp eq i32 %bcmp, 0
+  ; CHECK-NEXT: ret i1 %eq
+
+  %cmp = call i32 @memcmp(ptr %0, ptr %1, i64 %2)
+  %eq = icmp eq i32 %cmp, 0
+  ret i1 %eq
+}
+
+declare i32 @memcmp(ptr, ptr, i64)
+declare i32 @bcmp_impl(ptr, ptr, i64)
+
+;; Ensure bcmp is not removed from module because it is external.
+; CHECK: define dso_local i32 @bcmp
+define i32 @bcmp(ptr %0, ptr %1, i64 %2) noinline {
+  %r = call i32 @bcmp_impl(ptr %0, ptr %1, i64 %2)
+  ret i32 %r
+}
+
diff --git a/llvm/tools/llvm-lto2/llvm-lto2.cpp 
b/llvm/tools/llvm-lto2/llvm-lto2.cpp
index 95e8053dab32d..66fa5789feb64 100644
--- a/llvm/tools/llvm-lto2/llvm-lto2.cpp
+++ b/llvm/tools/llvm-lto2/llvm-lto2.cpp
@@ -234,6 +234,10 @@ static cl::opt<bool>
     AllVtablesHaveTypeInfos("all-vtables-have-type-infos", cl::Hidden,
                             cl::desc("All vtables have type infos"));
 
+static cl::list<std::string>
+    BitcodeLibFuncs("bitcode-libfuncs", cl::Hidden,
+                    cl::desc("set of libfuncs implemented in bitcode"));
+
 static cl::opt<bool> TimeTrace("time-trace", cl::desc("Record time trace"));
 
 static cl::opt<unsigned> TimeTraceGranularity(
@@ -496,6 +500,9 @@ static int run(int argc, char **argv) {
   if (HasErrors)
     return 1;
 
+  Lto.setBitcodeLibFuncs(
+      SmallVector<StringRef>(BitcodeLibFuncs.begin(), BitcodeLibFuncs.end()));
+
   auto AddStream =
       [&](size_t Task,
           const Twine &ModuleName) -> std::unique_ptr<CachedFileStream> {

>From e0c8420f04d3716180b76c2410f20217fc7d3109 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Thu, 11 Dec 2025 15:33:23 -0800
Subject: [PATCH 02/22] Move bitcodeLibFuncs from compile() to setter

---
 lld/ELF/LTO.cpp | 10 ++++++----
 lld/ELF/LTO.h   |  4 ++--
 2 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/lld/ELF/LTO.cpp b/lld/ELF/LTO.cpp
index 2401e49e4cd30..23cf9a20c21c4 100644
--- a/lld/ELF/LTO.cpp
+++ b/lld/ELF/LTO.cpp
@@ -317,10 +317,7 @@ static void thinLTOCreateEmptyIndexFiles(Ctx &ctx) {
 
 // Merge all the bitcode files we have seen, codegen the result
 // and return the resulting ObjectFile(s).
-SmallVector<std::unique_ptr<InputFile>, 0>
-BitcodeCompiler::compile(const SmallVector<StringRef> &bitcodeLibFuncs) {
-  ltoObj->setBitcodeLibFuncs(bitcodeLibFuncs);
-
+SmallVector<std::unique_ptr<InputFile>, 0> BitcodeCompiler::compile() {
   unsigned maxTasks = ltoObj->getMaxTasks();
   buf.resize(maxTasks);
   files.resize(maxTasks);
@@ -431,3 +428,8 @@ BitcodeCompiler::compile(const SmallVector<StringRef> 
&bitcodeLibFuncs) {
   }
   return ret;
 }
+
+void BitcodeCompiler::setBitcodeLibFuncs(
+    const SmallVector<StringRef> &bitcodeLibFuncs) {
+  ltoObj->setBitcodeLibFuncs(bitcodeLibFuncs);
+}
diff --git a/lld/ELF/LTO.h b/lld/ELF/LTO.h
index 8207e91460785..b9547202901fd 100644
--- a/lld/ELF/LTO.h
+++ b/lld/ELF/LTO.h
@@ -42,8 +42,8 @@ class BitcodeCompiler {
   ~BitcodeCompiler();
 
   void add(BitcodeFile &f);
-  SmallVector<std::unique_ptr<InputFile>, 0>
-  compile(const SmallVector<StringRef> &bitcodeLibFuncs);
+  SmallVector<std::unique_ptr<InputFile>, 0> compile();
+  void setBitcodeLibFuncs(const SmallVector<StringRef> &bitcodeLibFuncs);
 
 private:
   Ctx &ctx;

>From f716e70d442b1a725654d73a134c9fc0419169ab Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Thu, 11 Dec 2025 15:35:22 -0800
Subject: [PATCH 03/22] Densify bitcode file finding

---
 lld/ELF/Driver.cpp | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index e7f5408bcf893..80c9c24de6ffc 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -2714,19 +2714,18 @@ void LinkerDriver::compileBitcodeFiles(bool 
skipLinkedOutput) {
 
   llvm::BumpPtrAllocator alloc;
   llvm::StringSaver saver(alloc);
-  SmallVector<StringRef> bitcodeLibFuncs;
   if (!ctx.bitcodeFiles.empty()) {
     markBuffersAsDontNeed(ctx, skipLinkedOutput);
-    for (StringRef libFunc : lto::LTO::getLibFuncSymbols(*tt, saver)) {
-      Symbol *sym = ctx.symtab->find(libFunc);
-      if (!sym)
-        continue;
-      if (isa<BitcodeFile>(sym->file))
+
+    SmallVector<StringRef> bitcodeLibFuncs;
+    for (StringRef libFunc : lto::LTO::getLibFuncSymbols(*tt, saver))
+      if (Symbol *sym = ctx.symtab->find(libFunc);
+          sym && isa<BitcodeFile>(sym->file))
         bitcodeLibFuncs.push_back(libFunc);
-    }
+    lto->setBitcodeLibFuncs(bitcodeLibFuncs);
   }
 
-  ltoObjectFiles = lto->compile(bitcodeLibFuncs);
+  ltoObjectFiles = lto->compile();
   for (auto &file : ltoObjectFiles) {
     auto *obj = cast<ObjFile<ELFT>>(file.get());
     obj->parse(/*ignoreComdats=*/true);

>From fabce6f89934eb5683104bd9e43fa0a9467e4b05 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Thu, 11 Dec 2025 15:39:54 -0800
Subject: [PATCH 04/22] Add comment about triple assumption

---
 lld/ELF/Driver.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index 80c9c24de6ffc..31b5148d66132 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -2704,6 +2704,8 @@ template <class ELFT>
 void LinkerDriver::compileBitcodeFiles(bool skipLinkedOutput) {
   llvm::TimeTraceScope timeScope("LTO");
   // Capture the triple before moving the bitcode into the bitcode compiler.
+  // Note that this assumes that the set of possible libfuncs is roughly
+  // equivalent for all bitcode translation units.
   std::optional<llvm::Triple> tt;
   if (!ctx.bitcodeFiles.empty())
     tt = llvm::Triple(ctx.bitcodeFiles.front()->obj->getTargetTriple());

>From 1ac4ab340ae9862c6f1b075c4b28e4a7ad0d667b Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Tue, 13 Jan 2026 16:26:00 -0800
Subject: [PATCH 05/22] Remove optional from triple in LLD Driver.cpp

---
 lld/ELF/Driver.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index 31b5148d66132..5b07972c59972 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -2706,7 +2706,7 @@ void LinkerDriver::compileBitcodeFiles(bool 
skipLinkedOutput) {
   // Capture the triple before moving the bitcode into the bitcode compiler.
   // Note that this assumes that the set of possible libfuncs is roughly
   // equivalent for all bitcode translation units.
-  std::optional<llvm::Triple> tt;
+  llvm::Triple tt;
   if (!ctx.bitcodeFiles.empty())
     tt = llvm::Triple(ctx.bitcodeFiles.front()->obj->getTargetTriple());
   // Compile bitcode files and replace bitcode symbols.
@@ -2720,7 +2720,7 @@ void LinkerDriver::compileBitcodeFiles(bool 
skipLinkedOutput) {
     markBuffersAsDontNeed(ctx, skipLinkedOutput);
 
     SmallVector<StringRef> bitcodeLibFuncs;
-    for (StringRef libFunc : lto::LTO::getLibFuncSymbols(*tt, saver))
+    for (StringRef libFunc : lto::LTO::getLibFuncSymbols(tt, saver))
       if (Symbol *sym = ctx.symtab->find(libFunc);
           sym && isa<BitcodeFile>(sym->file))
         bitcodeLibFuncs.push_back(libFunc);

>From 58387798c483a562cb98706d232df98b7ed7081e Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Tue, 13 Jan 2026 16:33:46 -0800
Subject: [PATCH 06/22] Replace SmallVector& with ArrayRef

---
 lld/ELF/LTO.cpp                    |  3 +--
 lld/ELF/LTO.h                      |  2 +-
 llvm/include/llvm/LTO/LTO.h        |  7 +++----
 llvm/include/llvm/LTO/LTOBackend.h | 21 ++++++++++-----------
 llvm/lib/LTO/LTO.cpp               | 19 ++++++++++---------
 llvm/lib/LTO/LTOBackend.cpp        |  7 +++----
 6 files changed, 28 insertions(+), 31 deletions(-)

diff --git a/lld/ELF/LTO.cpp b/lld/ELF/LTO.cpp
index 23cf9a20c21c4..cb81decfba8ce 100644
--- a/lld/ELF/LTO.cpp
+++ b/lld/ELF/LTO.cpp
@@ -429,7 +429,6 @@ SmallVector<std::unique_ptr<InputFile>, 0> 
BitcodeCompiler::compile() {
   return ret;
 }
 
-void BitcodeCompiler::setBitcodeLibFuncs(
-    const SmallVector<StringRef> &bitcodeLibFuncs) {
+void BitcodeCompiler::setBitcodeLibFuncs(ArrayRef<StringRef> bitcodeLibFuncs) {
   ltoObj->setBitcodeLibFuncs(bitcodeLibFuncs);
 }
diff --git a/lld/ELF/LTO.h b/lld/ELF/LTO.h
index b9547202901fd..c8cb2156d90ca 100644
--- a/lld/ELF/LTO.h
+++ b/lld/ELF/LTO.h
@@ -43,7 +43,7 @@ class BitcodeCompiler {
 
   void add(BitcodeFile &f);
   SmallVector<std::unique_ptr<InputFile>, 0> compile();
-  void setBitcodeLibFuncs(const SmallVector<StringRef> &bitcodeLibFuncs);
+  void setBitcodeLibFuncs(ArrayRef<StringRef> bitcodeLibFuncs);
 
 private:
   Ctx &ctx;
diff --git a/llvm/include/llvm/LTO/LTO.h b/llvm/include/llvm/LTO/LTO.h
index 6cb063f68071f..bb5d38da8d9b6 100644
--- a/llvm/include/llvm/LTO/LTO.h
+++ b/llvm/include/llvm/LTO/LTO.h
@@ -310,7 +310,7 @@ using ThinBackendFunction = 
std::function<std::unique_ptr<ThinBackendProc>(
     const Config &C, ModuleSummaryIndex &CombinedIndex,
     const DenseMap<StringRef, GVSummaryMapTy> &ModuleToDefinedGVSummaries,
     AddStreamFn AddStream, FileCache Cache,
-    const SmallVector<StringRef> &BitcodeLibFuncs)>;
+    ArrayRef<StringRef> BitcodeLibFuncs)>;
 
 /// This type defines the behavior following the thin-link phase during 
ThinLTO.
 /// It encapsulates a backend function and a strategy for thread pool
@@ -326,7 +326,7 @@ struct ThinBackend {
       const Config &Conf, ModuleSummaryIndex &CombinedIndex,
       const DenseMap<StringRef, GVSummaryMapTy> &ModuleToDefinedGVSummaries,
       AddStreamFn AddStream, FileCache Cache,
-      const SmallVector<StringRef> &BitcodeLibFuncs) {
+      ArrayRef<StringRef> BitcodeLibFuncs) {
     assert(isValid() && "Invalid backend function");
     return Func(Conf, CombinedIndex, ModuleToDefinedGVSummaries,
                 std::move(AddStream), std::move(Cache), BitcodeLibFuncs);
@@ -450,8 +450,7 @@ class LTO {
   /// Set the list of functions implemented in bitcode across the link, whether
   /// extracted or not. Such functions may not be referenced if they were not
   /// extracted by the time LTO occurs.
-  LLVM_ABI void
-  setBitcodeLibFuncs(const SmallVector<StringRef> &BitcodeLibFuncs);
+  LLVM_ABI void setBitcodeLibFuncs(ArrayRef<StringRef> BitcodeLibFuncs);
 
   /// Returns an upper bound on the number of tasks that the client may expect.
   /// This may only be called after all IR object files have been added. For a
diff --git a/llvm/include/llvm/LTO/LTOBackend.h 
b/llvm/include/llvm/LTO/LTOBackend.h
index 6a7d7e0d87ac9..4bb38529ec754 100644
--- a/llvm/include/llvm/LTO/LTOBackend.h
+++ b/llvm/include/llvm/LTO/LTOBackend.h
@@ -40,14 +40,14 @@ LLVM_ABI bool opt(const Config &Conf, TargetMachine *TM, 
unsigned Task,
                   ModuleSummaryIndex *ExportSummary,
                   const ModuleSummaryIndex *ImportSummary,
                   const std::vector<uint8_t> &CmdArgs,
-                  const SmallVector<StringRef> &BitcodeLibFuncs);
+                  ArrayRef<StringRef> BitcodeLibFuncs);
 
 /// Runs a regular LTO backend. The regular LTO backend can also act as the
 /// regular LTO phase of ThinLTO, which may need to access the combined index.
 LLVM_ABI Error backend(const Config &C, AddStreamFn AddStream,
                        unsigned ParallelCodeGenParallelismLevel, Module &M,
                        ModuleSummaryIndex &CombinedIndex,
-                       const SmallVector<StringRef> &BitcodeLibFuncs);
+                       ArrayRef<StringRef> BitcodeLibFuncs);
 
 /// Runs a ThinLTO backend.
 /// If \p ModuleMap is not nullptr, all the module files to be imported have
@@ -58,15 +58,14 @@ LLVM_ABI Error backend(const Config &C, AddStreamFn 
AddStream,
 /// the backend will skip optimization and only perform code generation. If
 /// \p IRAddStream is not nullptr, it will be called just before code 
generation
 /// to serialize the optimized IR.
-LLVM_ABI Error
-thinBackend(const Config &C, unsigned Task, AddStreamFn AddStream, Module &M,
-            const ModuleSummaryIndex &CombinedIndex,
-            const FunctionImporter::ImportMapTy &ImportList,
-            const GVSummaryMapTy &DefinedGlobals,
-            MapVector<StringRef, BitcodeModule> *ModuleMap, bool CodeGenOnly,
-            const SmallVector<StringRef> &BitcodeLibFuncs,
-            AddStreamFn IRAddStream = nullptr,
-            const std::vector<uint8_t> &CmdArgs = std::vector<uint8_t>());
+LLVM_ABI Error thinBackend(
+    const Config &C, unsigned Task, AddStreamFn AddStream, Module &M,
+    const ModuleSummaryIndex &CombinedIndex,
+    const FunctionImporter::ImportMapTy &ImportList,
+    const GVSummaryMapTy &DefinedGlobals,
+    MapVector<StringRef, BitcodeModule> *ModuleMap, bool CodeGenOnly,
+    ArrayRef<StringRef> BitcodeLibFuncs, AddStreamFn IRAddStream = nullptr,
+    const std::vector<uint8_t> &CmdArgs = std::vector<uint8_t>());
 
 LLVM_ABI Error finalizeOptimizationRemarks(LLVMRemarkFileHandle 
DiagOutputFile);
 
diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp
index 0f3f3c344fb0a..35e5daa8a6cef 100644
--- a/llvm/lib/LTO/LTO.cpp
+++ b/llvm/lib/LTO/LTO.cpp
@@ -850,8 +850,9 @@ Error LTO::add(std::unique_ptr<InputFile> InputPtr,
   return Error::success();
 }
 
-void LTO::setBitcodeLibFuncs(const SmallVector<StringRef> &BitcodeLibFuncs) {
-  this->BitcodeLibFuncs = BitcodeLibFuncs;
+void LTO::setBitcodeLibFuncs(ArrayRef<StringRef> BitcodeLibFuncs) {
+  this->BitcodeLibFuncs.clear();
+  this->BitcodeLibFuncs.append(BitcodeLibFuncs.begin(), BitcodeLibFuncs.end());
 }
 
 Expected<ArrayRef<SymbolResolution>>
@@ -1593,7 +1594,7 @@ class CGThinBackend : public ThinBackendProc {
 class InProcessThinBackend : public CGThinBackend {
 protected:
   FileCache Cache;
-  const SmallVector<StringRef> &BitcodeLibFuncs;
+  ArrayRef<StringRef> BitcodeLibFuncs;
 
 public:
   InProcessThinBackend(
@@ -1602,7 +1603,7 @@ class InProcessThinBackend : public CGThinBackend {
       const DenseMap<StringRef, GVSummaryMapTy> &ModuleToDefinedGVSummaries,
       AddStreamFn AddStream, FileCache Cache, lto::IndexWriteCallback OnWrite,
       bool ShouldEmitIndexFiles, bool ShouldEmitImportsFiles,
-      const SmallVector<StringRef> &BitcodeLibFuncs)
+      ArrayRef<StringRef> BitcodeLibFuncs)
       : CGThinBackend(Conf, CombinedIndex, ModuleToDefinedGVSummaries,
                       AddStream, OnWrite, ShouldEmitIndexFiles,
                       ShouldEmitImportsFiles, ThinLTOParallelism),
@@ -1713,7 +1714,7 @@ class FirstRoundThinBackend : public InProcessThinBackend 
{
       ThreadPoolStrategy ThinLTOParallelism,
       const DenseMap<StringRef, GVSummaryMapTy> &ModuleToDefinedGVSummaries,
       AddStreamFn CGAddStream, FileCache CGCache,
-      const SmallVector<StringRef> &BitcodeLibFuncs, AddStreamFn IRAddStream,
+      ArrayRef<StringRef> BitcodeLibFuncs, AddStreamFn IRAddStream,
       FileCache IRCache)
       : InProcessThinBackend(Conf, CombinedIndex, ThinLTOParallelism,
                              ModuleToDefinedGVSummaries, 
std::move(CGAddStream),
@@ -1809,7 +1810,7 @@ class SecondRoundThinBackend : public 
InProcessThinBackend {
       ThreadPoolStrategy ThinLTOParallelism,
       const DenseMap<StringRef, GVSummaryMapTy> &ModuleToDefinedGVSummaries,
       AddStreamFn AddStream, FileCache Cache,
-      const SmallVector<StringRef> &BitcodeLibFuncs,
+      ArrayRef<StringRef> BitcodeLibFuncs,
       std::unique_ptr<SmallVector<StringRef>> IRFiles,
       stable_hash CombinedCGDataHash)
       : InProcessThinBackend(Conf, CombinedIndex, ThinLTOParallelism,
@@ -1878,7 +1879,7 @@ ThinBackend 
lto::createInProcessThinBackend(ThreadPoolStrategy Parallelism,
       [=](const Config &Conf, ModuleSummaryIndex &CombinedIndex,
           const DenseMap<StringRef, GVSummaryMapTy> 
&ModuleToDefinedGVSummaries,
           AddStreamFn AddStream, FileCache Cache,
-          const SmallVector<StringRef> &BitcodeLibFuncs) {
+          ArrayRef<StringRef> BitcodeLibFuncs) {
         return std::make_unique<InProcessThinBackend>(
             Conf, CombinedIndex, Parallelism, ModuleToDefinedGVSummaries,
             AddStream, Cache, OnWrite, ShouldEmitIndexFiles,
@@ -2000,7 +2001,7 @@ ThinBackend lto::createWriteIndexesThinBackend(
       [=](const Config &Conf, ModuleSummaryIndex &CombinedIndex,
           const DenseMap<StringRef, GVSummaryMapTy> 
&ModuleToDefinedGVSummaries,
           AddStreamFn AddStream, FileCache Cache,
-          const SmallVector<StringRef> &BitcodeLibFuncs) {
+          ArrayRef<StringRef> BitcodeLibFuncs) {
         return std::make_unique<WriteIndexesThinBackend>(
             Conf, CombinedIndex, Parallelism, ModuleToDefinedGVSummaries,
             OldPrefix, NewPrefix, NativeObjectPrefix, ShouldEmitImportsFiles,
@@ -2800,7 +2801,7 @@ ThinBackend lto::createOutOfProcessThinBackend(
       [=](const Config &Conf, ModuleSummaryIndex &CombinedIndex,
           const DenseMap<StringRef, GVSummaryMapTy> 
&ModuleToDefinedGVSummaries,
           AddStreamFn AddStream, FileCache Cache,
-          const SmallVector<StringRef> &BitcodeLibFuncs) {
+          ArrayRef<StringRef> BitcodeLibFuncs) {
         return std::make_unique<OutOfProcessThinBackend>(
             Conf, CombinedIndex, Parallelism, ModuleToDefinedGVSummaries,
             AddStream, Cache, OnWrite, ShouldEmitIndexFiles,
diff --git a/llvm/lib/LTO/LTOBackend.cpp b/llvm/lib/LTO/LTOBackend.cpp
index 8cd8a75c52cab..826313055cb82 100644
--- a/llvm/lib/LTO/LTOBackend.cpp
+++ b/llvm/lib/LTO/LTOBackend.cpp
@@ -408,7 +408,7 @@ bool lto::opt(const Config &Conf, TargetMachine *TM, 
unsigned Task, Module &Mod,
               bool IsThinLTO, ModuleSummaryIndex *ExportSummary,
               const ModuleSummaryIndex *ImportSummary,
               const std::vector<uint8_t> &CmdArgs,
-              const SmallVector<StringRef> &BitcodeLibFuncs) {
+              ArrayRef<StringRef> BitcodeLibFuncs) {
   llvm::TimeTraceScope timeScope("opt");
   if (EmbedBitcode == LTOBitcodeEmbedding::EmbedPostMergePreOptimized) {
     // FIXME: the motivation for capturing post-merge bitcode and command line
@@ -604,7 +604,7 @@ Error lto::finalizeOptimizationRemarks(LLVMRemarkFileHandle 
DiagOutputFile) {
 Error lto::backend(const Config &C, AddStreamFn AddStream,
                    unsigned ParallelCodeGenParallelismLevel, Module &Mod,
                    ModuleSummaryIndex &CombinedIndex,
-                   const SmallVector<StringRef> &BitcodeLibFuncs) {
+                   ArrayRef<StringRef> BitcodeLibFuncs) {
   llvm::TimeTraceScope timeScope("LTO backend");
   Expected<const Target *> TOrErr = initAndLookupTarget(C, Mod);
   if (!TOrErr)
@@ -656,8 +656,7 @@ Error lto::thinBackend(const Config &Conf, unsigned Task, 
AddStreamFn AddStream,
                        const FunctionImporter::ImportMapTy &ImportList,
                        const GVSummaryMapTy &DefinedGlobals,
                        MapVector<StringRef, BitcodeModule> *ModuleMap,
-                       bool CodeGenOnly,
-                       const SmallVector<StringRef> &BitcodeLibFuncs,
+                       bool CodeGenOnly, ArrayRef<StringRef> BitcodeLibFuncs,
                        AddStreamFn IRAddStream,
                        const std::vector<uint8_t> &CmdArgs) {
   llvm::TimeTraceScope timeScope("Thin backend", Mod.getModuleIdentifier());

>From 63a9813792dcc9afb25721053e41c6d7d4bbd166 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Thu, 15 Jan 2026 11:17:28 -0800
Subject: [PATCH 07/22] More idiomatic LibFunc iteration

---
 llvm/lib/LTO/LTO.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp
index 35e5daa8a6cef..462873a4b484b 100644
--- a/llvm/lib/LTO/LTO.cpp
+++ b/llvm/lib/LTO/LTO.cpp
@@ -1508,8 +1508,7 @@ SmallVector<StringRef> LTO::getLibFuncSymbols(const 
Triple &TT,
   TargetLibraryInfo TLI(*TLII);
   SmallVector<StringRef> LibFuncSymbols;
   LibFuncSymbols.reserve(LibFunc::NumLibFuncs);
-  for (unsigned I = 0, E = static_cast<unsigned>(LibFunc::NumLibFuncs); I != E;
-       ++I) {
+  for (unsigned I = LibFunc::Begin_LibFunc; I != LibFunc::End_LibFunc; ++I) {
     LibFunc F = static_cast<LibFunc>(I);
     if (TLI.has(F))
       LibFuncSymbols.push_back(Saver.save(TLI.getName(F)).data());

>From a122d5d02bafa98d69806a75763b9e2eac9d2cf3 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Tue, 20 Jan 2026 14:54:57 -0800
Subject: [PATCH 08/22] Add release note

---
 llvm/docs/ReleaseNotes.md | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index 2053e087ba23e..0d709fec08dc3 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -86,6 +86,12 @@ Changes to LLVM infrastructure
 * The ``Br`` opcode was split into two opcodes separating unconditional
   (``UncondBr``) and conditional (``CondBr``) branches.
 
+* Bitcode libraries can now implement compiler-managed library functions
+  (libcalls) without causing incorrect API manipulation or undefined references
+  ([#177046](https://github.com/llvm/llvm-project/pull/125687)). Note that
+  there are still issues with invalid compiler reasoning about some functions
+  in bitcode, e.g. `malloc`. Not yet supported on MachO or when using DTLTO. 
+
 Changes to building LLVM
 ------------------------
 

>From f6433c8c5871f2225e929b0be93b1816a8db6d5b Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Thu, 22 Jan 2026 11:59:12 -0800
Subject: [PATCH 09/22] Add FIXME for -bitcode-libfuncs clunkiness

---
 llvm/tools/llvm-lto2/llvm-lto2.cpp | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/llvm/tools/llvm-lto2/llvm-lto2.cpp 
b/llvm/tools/llvm-lto2/llvm-lto2.cpp
index 66fa5789feb64..49196a94bcb96 100644
--- a/llvm/tools/llvm-lto2/llvm-lto2.cpp
+++ b/llvm/tools/llvm-lto2/llvm-lto2.cpp
@@ -234,6 +234,11 @@ static cl::opt<bool>
     AllVtablesHaveTypeInfos("all-vtables-have-type-infos", cl::Hidden,
                             cl::desc("All vtables have type infos"));
 
+// FIXME: Listing all bitcode libfunc symbols here is clunky. A higher-level 
way
+// to indicate which TUs made it into the link might be better, but this would
+// required more detailed tracking of the sources of constructs in the IR.
+// Alternatively, there may be some other data structure that could hold this
+// information.
 static cl::list<std::string>
     BitcodeLibFuncs("bitcode-libfuncs", cl::Hidden,
                     cl::desc("set of libfuncs implemented in bitcode"));

>From d8a3056b9c1c43f8ff0ede5ace261b2138a9ba6a Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Mon, 2 Feb 2026 12:15:04 -0800
Subject: [PATCH 10/22] Remove added Target dep; missed.

---
 llvm/lib/Object/CMakeLists.txt | 1 -
 1 file changed, 1 deletion(-)

diff --git a/llvm/lib/Object/CMakeLists.txt b/llvm/lib/Object/CMakeLists.txt
index c48d251249488..0f6d2f7c59a5c 100644
--- a/llvm/lib/Object/CMakeLists.txt
+++ b/llvm/lib/Object/CMakeLists.txt
@@ -51,7 +51,6 @@ add_llvm_component_library(LLVMObject
   BinaryFormat
   MCParser
   Support
-  Target
   TargetParser
   TextAPI
   )

>From 0d8941fbaa234c937113ca7bcc7ff93d852973e6 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Tue, 10 Feb 2026 13:55:18 -0800
Subject: [PATCH 11/22] Don't make libcalls GC roots in COFF; only @llvm.used

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

diff --git a/lld/COFF/InputFiles.cpp b/lld/COFF/InputFiles.cpp
index 0a40309174a67..3041e1ce6b445 100644
--- a/lld/COFF/InputFiles.cpp
+++ b/lld/COFF/InputFiles.cpp
@@ -1449,7 +1449,7 @@ void BitcodeFile::parse() {
           symtab.addRegular(this, symName, nullptr, fakeSC, 0, 
objSym.isWeak());
     }
     symbols.push_back(sym);
-    if (objSym.isUsed() || objSym.isLibcall(tli, libcalls))
+    if (objSym.isUsed())
       symtab.ctx.config.gcroot.push_back(sym);
   }
   directives = saver.save(obj->getCOFFLinkerOpts());

>From da852a0fec8958eeb708231e428ee61a071fe4a4 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Tue, 10 Feb 2026 14:52:31 -0800
Subject: [PATCH 12/22] Merge two libcall-external-* tests into
 libcall-external.ll

---
 .../X86/libcall-external-bitcode.ll           | 20 -------------
 .../X86/libcall-external-not-bitcode.ll       | 20 -------------
 .../LTO/Resolution/X86/libcall-external.ll    | 29 +++++++++++++++++++
 3 files changed, 29 insertions(+), 40 deletions(-)
 delete mode 100644 llvm/test/LTO/Resolution/X86/libcall-external-bitcode.ll
 delete mode 100644 llvm/test/LTO/Resolution/X86/libcall-external-not-bitcode.ll
 create mode 100644 llvm/test/LTO/Resolution/X86/libcall-external.ll

diff --git a/llvm/test/LTO/Resolution/X86/libcall-external-bitcode.ll 
b/llvm/test/LTO/Resolution/X86/libcall-external-bitcode.ll
deleted file mode 100644
index 95a599fe75e8b..0000000000000
--- a/llvm/test/LTO/Resolution/X86/libcall-external-bitcode.ll
+++ /dev/null
@@ -1,20 +0,0 @@
-; RUN: opt %s -o %t -module-summary -mtriple x86_64-unknown-linux-musl
-; RUN: llvm-lto2 run -o %t2 \
-; RUN:   -r %t,foo,plx \
-; RUN:   -r %t,memcmp,x \
-; RUN:   -r %t,bcmp,pl --bitcode-libfuncs=bcmp %t -save-temps
-; RUN: llvm-dis %t2.1.4.opt.bc -o - | FileCheck %s
-
-define i1 @foo(ptr %0, ptr %1, i64 %2) {
-  ; CHECK-LABEL: define{{.*}}i1 @foo
-  ; CHECK-NEXT: %cmp = {{.*}}call i32 @memcmp
-  ; CHECK-NEXT: %eq = icmp eq i32 %cmp, 0
-  ; CHECK-NEXT: ret i1 %eq
-
-  %cmp = call i32 @memcmp(ptr %0, ptr %1, i64 %2)
-  %eq = icmp eq i32 %cmp, 0
-  ret i1 %eq
-}
-
-declare i32 @memcmp(ptr, ptr, i64)
-declare i32 @bcmp(ptr, ptr, i64)
diff --git a/llvm/test/LTO/Resolution/X86/libcall-external-not-bitcode.ll 
b/llvm/test/LTO/Resolution/X86/libcall-external-not-bitcode.ll
deleted file mode 100644
index 2e6cc798d22cd..0000000000000
--- a/llvm/test/LTO/Resolution/X86/libcall-external-not-bitcode.ll
+++ /dev/null
@@ -1,20 +0,0 @@
-; RUN: opt %s -o %t -module-summary -mtriple x86_64-unknown-linux-musl
-; RUN: llvm-lto2 run -o %t2 \
-; RUN:   -r %t,foo,plx \
-; RUN:   -r %t,memcmp,x \
-; RUN:   -r %t,bcmp,pl %t -save-temps
-; RUN: llvm-dis %t2.1.4.opt.bc -o - | FileCheck %s
-
-define i1 @foo(ptr %0, ptr %1, i64 %2) {
-  ; CHECK-LABEL: define{{.*}}i1 @foo
-  ; CHECK-NEXT: %bcmp = {{.*}}call i32 @bcmp
-  ; CHECK-NEXT: %eq = icmp eq i32 %bcmp, 0
-  ; CHECK-NEXT: ret i1 %eq
-
-  %cmp = call i32 @memcmp(ptr %0, ptr %1, i64 %2)
-  %eq = icmp eq i32 %cmp, 0
-  ret i1 %eq
-}
-
-declare i32 @memcmp(ptr, ptr, i64)
-declare i32 @bcmp(ptr, ptr, i64)
diff --git a/llvm/test/LTO/Resolution/X86/libcall-external.ll 
b/llvm/test/LTO/Resolution/X86/libcall-external.ll
new file mode 100644
index 0000000000000..8e466616f01c1
--- /dev/null
+++ b/llvm/test/LTO/Resolution/X86/libcall-external.ll
@@ -0,0 +1,29 @@
+;; When a libcall was not brought into the link, it can be used iff it is
+;; defined in native code, not bitcode.
+; RUN: opt %s -o %t -module-summary -mtriple x86_64-unknown-linux-musl
+; RUN: llvm-lto2 run -o %t.bitcode \
+; RUN:   -r %t,foo,plx \
+; RUN:   -r %t,memcmp,x \
+; RUN:   -r %t,bcmp,pl --bitcode-libfuncs=bcmp %t -save-temps
+; RUN: llvm-dis %t.bitcode.1.4.opt.bc -o - | FileCheck 
--check-prefixes=CHECK,BITCODE %s
+; RUN: llvm-lto2 run -o %t.native \
+; RUN:   -r %t,foo,plx \
+; RUN:   -r %t,memcmp,x \
+; RUN:   -r %t,bcmp,pl %t -save-temps
+; RUN: llvm-dis %t.native.1.4.opt.bc -o - | FileCheck 
--check-prefixes=CHECK,NATIVE %s
+define i1 @foo(ptr %0, ptr %1, i64 %2) {
+  ; CHECK-LABEL: define{{.*}}i1 @foo
+  ; BITCODE-NEXT: %cmp = {{.*}}call i32 @memcmp
+  ; BITCODE-NEXT: %eq = icmp eq i32 %cmp, 0
+  ; NATIVE-NEXT: %bcmp = {{.*}}call i32 @bcmp
+  ; NATIVE-NEXT: %eq = icmp eq i32 %bcmp, 0
+  ; CHECK-NEXT: ret i1 %eq
+
+
+  %cmp = call i32 @memcmp(ptr %0, ptr %1, i64 %2)
+  %eq = icmp eq i32 %cmp, 0
+  ret i1 %eq
+}
+
+declare i32 @memcmp(ptr, ptr, i64)
+declare i32 @bcmp(ptr, ptr, i64)

>From c370adebc6956f00e1244e256d7f360f5e917de8 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Tue, 10 Feb 2026 15:34:58 -0800
Subject: [PATCH 13/22] Document the semantics of the new flag and data
 structure

---
 llvm/include/llvm/LTO/LTO.h        | 4 ++++
 llvm/tools/llvm-lto2/llvm-lto2.cpp | 5 +++++
 2 files changed, 9 insertions(+)

diff --git a/llvm/include/llvm/LTO/LTO.h b/llvm/include/llvm/LTO/LTO.h
index bb5d38da8d9b6..d78a9dfcfd1bf 100644
--- a/llvm/include/llvm/LTO/LTO.h
+++ b/llvm/include/llvm/LTO/LTO.h
@@ -671,6 +671,10 @@ class LTO {
   // Setup optimization remarks according to the provided configuration.
   Error setupOptimizationRemarks();
 
+  // LibFuncs that could have been part of the LTO unit, but was not typically
+  // because they weren't extracted from their libraries. Such functions cannot
+  // safely be called, since they have already lost their only opportunity to 
be
+  // defined.
   SmallVector<StringRef> BitcodeLibFuncs;
 
 public:
diff --git a/llvm/tools/llvm-lto2/llvm-lto2.cpp 
b/llvm/tools/llvm-lto2/llvm-lto2.cpp
index 49196a94bcb96..ede47379672f8 100644
--- a/llvm/tools/llvm-lto2/llvm-lto2.cpp
+++ b/llvm/tools/llvm-lto2/llvm-lto2.cpp
@@ -234,6 +234,11 @@ static cl::opt<bool>
     AllVtablesHaveTypeInfos("all-vtables-have-type-infos", cl::Hidden,
                             cl::desc("All vtables have type infos"));
 
+// Specifying a function here states that it is a library function that could
+// have been part of the LTO unit, but was not, typically because it wasn't
+// extracted from a library. Such functions cannot safely be called, since they
+// have already lost their only opportunity to be defined.
+//
 // FIXME: Listing all bitcode libfunc symbols here is clunky. A higher-level 
way
 // to indicate which TUs made it into the link might be better, but this would
 // required more detailed tracking of the sources of constructs in the IR.

>From 32d6f63037b162c68d75be9c1d6b7b6c51e2394b Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Tue, 10 Feb 2026 16:01:24 -0800
Subject: [PATCH 14/22] Document the LTOBackend implementation decisionmaking

---
 llvm/lib/LTO/LTOBackend.cpp | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/LTO/LTOBackend.cpp b/llvm/lib/LTO/LTOBackend.cpp
index 826313055cb82..3d8d8ec5e963f 100644
--- a/llvm/lib/LTO/LTOBackend.cpp
+++ b/llvm/lib/LTO/LTOBackend.cpp
@@ -303,6 +303,10 @@ static void runNewPMPasses(const Config &Conf, Module 
&Mod, TargetMachine *TM,
   if (Conf.Freestanding)
     TLII->disableAllFunctions();
 
+  // Determine whether or not its safe to emit calls to each libfunc. Libfuncs
+  // that might have been present in the current LTO unit, but are not, have
+  // lost their only opportunity to be defined, and calls must not be emitted 
to
+  // them.
   TargetLibraryInfo TLI(*TLII);
   for (unsigned I = 0, E = static_cast<unsigned>(LibFunc::NumLibFuncs); I != E;
        ++I) {
@@ -314,7 +318,9 @@ static void runNewPMPasses(const Config &Conf, Module &Mod, 
TargetMachine *TM,
     if (Val && !Val->isDeclaration())
       continue;
 
-    // LibFuncs not implemented in bitcode can always be referenced.
+    // LibFuncs not implemented in bitcode can always be referenced, since they
+    // can safely be extracted from whatever library they reside in after LTO
+    // without changing the linker symbol table that LTO depends on.
     if (!BitcodeLibFuncs.contains(Name))
       continue;
 

>From 3f4afa27ee9462804b436ccfd3cc5a4c893afb40 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Thu, 19 Feb 2026 14:18:11 -0800
Subject: [PATCH 15/22] Correct semantics of libcall tests

---
 .../LTO/Resolution/X86/libcall-external.ll    | 20 ++++++++-----------
 llvm/test/LTO/Resolution/X86/libcall-in-tu.ll | 14 ++++++-------
 2 files changed, 15 insertions(+), 19 deletions(-)

diff --git a/llvm/test/LTO/Resolution/X86/libcall-external.ll 
b/llvm/test/LTO/Resolution/X86/libcall-external.ll
index 8e466616f01c1..46756b387668c 100644
--- a/llvm/test/LTO/Resolution/X86/libcall-external.ll
+++ b/llvm/test/LTO/Resolution/X86/libcall-external.ll
@@ -1,16 +1,13 @@
 ;; When a libcall was not brought into the link, it can be used iff it is
 ;; defined in native code, not bitcode.
-; RUN: opt %s -o %t -module-summary -mtriple x86_64-unknown-linux-musl
-; RUN: llvm-lto2 run -o %t.bitcode \
-; RUN:   -r %t,foo,plx \
-; RUN:   -r %t,memcmp,x \
-; RUN:   -r %t,bcmp,pl --bitcode-libfuncs=bcmp %t -save-temps
-; RUN: llvm-dis %t.bitcode.1.4.opt.bc -o - | FileCheck 
--check-prefixes=CHECK,BITCODE %s
-; RUN: llvm-lto2 run -o %t.native \
-; RUN:   -r %t,foo,plx \
-; RUN:   -r %t,memcmp,x \
-; RUN:   -r %t,bcmp,pl %t -save-temps
-; RUN: llvm-dis %t.native.1.4.opt.bc -o - | FileCheck 
--check-prefixes=CHECK,NATIVE %s
+; RUN: opt %s -o %t.o -mtriple x86_64-unknown-linux-musl
+; RUN: llvm-lto2 run -o %t.bitcode.o \
+; RUN:   -r %t.o,foo,plx -r %t.o,memcmp,x -save-temps %t.o \
+; RUN:   --bitcode-libfuncs=bcmp 
+; RUN: llvm-dis %t.bitcode.o.0.4.opt.bc -o - | FileCheck 
--check-prefixes=CHECK,BITCODE %s
+; RUN: llvm-lto2 run -o %t.native.o \
+; RUN:   -r %t.o,foo,plx -r %t.o,memcmp,x -save-temps %t.o
+; RUN: llvm-dis %t.native.o.0.4.opt.bc -o - | FileCheck 
--check-prefixes=CHECK,NATIVE %s
 define i1 @foo(ptr %0, ptr %1, i64 %2) {
   ; CHECK-LABEL: define{{.*}}i1 @foo
   ; BITCODE-NEXT: %cmp = {{.*}}call i32 @memcmp
@@ -26,4 +23,3 @@ define i1 @foo(ptr %0, ptr %1, i64 %2) {
 }
 
 declare i32 @memcmp(ptr, ptr, i64)
-declare i32 @bcmp(ptr, ptr, i64)
diff --git a/llvm/test/LTO/Resolution/X86/libcall-in-tu.ll 
b/llvm/test/LTO/Resolution/X86/libcall-in-tu.ll
index 948f21a6536ca..33d1dc47ff0d1 100644
--- a/llvm/test/LTO/Resolution/X86/libcall-in-tu.ll
+++ b/llvm/test/LTO/Resolution/X86/libcall-in-tu.ll
@@ -3,13 +3,13 @@
 ;; a call to bcmp() as part of SimplifyLibCalls. Such deletions must not be
 ;; allowed.
 
-; RUN: opt %s -o %t -module-summary -mtriple x86_64-unknown-linux-musl
-; RUN: llvm-lto2 run -o %t2 \
-; RUN:   -r %t,foo,plx \
-; RUN:   -r %t,memcmp,x \
-; RUN:   -r %t,bcmp,pl \
-; RUN:   -r %t,bcmp_impl,x %t -save-temps
-; RUN: llvm-dis %t2.1.4.opt.bc -o - | FileCheck %s
+; RUN: opt %s -o %t.o -mtriple x86_64-unknown-linux-musl
+; RUN: llvm-lto2 run -o %t.lto.o \
+; RUN:   -r %t.o,foo,plx \
+; RUN:   -r %t.o,memcmp,x \
+; RUN:   -r %t.o,bcmp,pl \
+; RUN:   -r %t.o,bcmp_impl,x %t.o --bitcode-libfuncs=bcmp -save-temps
+; RUN: llvm-dis %t.lto.o.0.4.opt.bc -o - | FileCheck %s
 
 define i1 @foo(ptr %0, ptr %1, i64 %2) {
   ; CHECK-LABEL: define{{.*}}i1 @foo

>From 090c7119e8b081cd2cc670fe74969c811b21cc10 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Thu, 19 Feb 2026 15:36:46 -0800
Subject: [PATCH 16/22] Correct description of --bitcode-libfuncs flag.

---
 llvm/tools/llvm-lto2/llvm-lto2.cpp | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/llvm/tools/llvm-lto2/llvm-lto2.cpp 
b/llvm/tools/llvm-lto2/llvm-lto2.cpp
index ede47379672f8..054369b289bb2 100644
--- a/llvm/tools/llvm-lto2/llvm-lto2.cpp
+++ b/llvm/tools/llvm-lto2/llvm-lto2.cpp
@@ -234,10 +234,10 @@ static cl::opt<bool>
     AllVtablesHaveTypeInfos("all-vtables-have-type-infos", cl::Hidden,
                             cl::desc("All vtables have type infos"));
 
-// Specifying a function here states that it is a library function that could
-// have been part of the LTO unit, but was not, typically because it wasn't
-// extracted from a library. Such functions cannot safely be called, since they
-// have already lost their only opportunity to be defined.
+// Specifying a symbol here states that it is a library symbol with a 
prevailing
+// definition in bitcode. If such symbols are not defined somewhere in the LTO
+// unit, (e.g., they were not extracted), they cannot safely be referenced,
+// since they have already lost their only opportunity to be defined.
 //
 // FIXME: Listing all bitcode libfunc symbols here is clunky. A higher-level 
way
 // to indicate which TUs made it into the link might be better, but this would

>From 4f5e0eb053b4b539e19661ba07aa8f51ff7c9fba Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Thu, 19 Feb 2026 14:22:18 -0800
Subject: [PATCH 17/22] Fix ThinLTO by filtering BitcodeLibfuncs in run()

---
 llvm/include/llvm/LTO/LTO.h                   |  3 +-
 llvm/lib/LTO/LTO.cpp                          |  8 +++++
 llvm/lib/LTO/LTOBackend.cpp                   | 19 ++--------
 .../Resolution/X86/libcall-in-thin-link.ll    | 35 +++++++++++++++++++
 4 files changed, 47 insertions(+), 18 deletions(-)
 create mode 100644 llvm/test/LTO/Resolution/X86/libcall-in-thin-link.ll

diff --git a/llvm/include/llvm/LTO/LTO.h b/llvm/include/llvm/LTO/LTO.h
index d78a9dfcfd1bf..72e1f45965f51 100644
--- a/llvm/include/llvm/LTO/LTO.h
+++ b/llvm/include/llvm/LTO/LTO.h
@@ -674,7 +674,8 @@ class LTO {
   // LibFuncs that could have been part of the LTO unit, but was not typically
   // because they weren't extracted from their libraries. Such functions cannot
   // safely be called, since they have already lost their only opportunity to 
be
-  // defined.
+  // defined. Before run(), this contains all libfuncs defined anywhere in
+  // bitcode; during run(), the ones defined in the LTO unit are filtered out.
   SmallVector<StringRef> BitcodeLibFuncs;
 
 public:
diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp
index 462873a4b484b..859d8c69e5401 100644
--- a/llvm/lib/LTO/LTO.cpp
+++ b/llvm/lib/LTO/LTO.cpp
@@ -1334,6 +1334,14 @@ Error LTO::run(AddStreamFn AddStream, FileCache Cache) {
   computeDeadSymbolsWithConstProp(ThinLTO.CombinedIndex, GUIDPreservedSymbols,
                                   isPrevailing, Conf.OptLevel > 0);
 
+  // Remove any prevailing definitions from BitcodeLibfuncs, as these are safe
+  // to call.
+  llvm::erase_if(BitcodeLibFuncs, [&](StringRef Name) {
+    return isPrevailing(GlobalValue::getGUIDAssumingExternalLinkage(
+               GlobalValue::dropLLVMManglingEscape(Name))) ==
+           PrevailingType::Yes;
+  });
+
   // Setup output file to emit statistics.
   auto StatsFileOrErr = setupStatsFile(Conf.StatsFile);
   if (!StatsFileOrErr)
diff --git a/llvm/lib/LTO/LTOBackend.cpp b/llvm/lib/LTO/LTOBackend.cpp
index 3d8d8ec5e963f..0b0257163dcd2 100644
--- a/llvm/lib/LTO/LTOBackend.cpp
+++ b/llvm/lib/LTO/LTOBackend.cpp
@@ -311,23 +311,8 @@ static void runNewPMPasses(const Config &Conf, Module 
&Mod, TargetMachine *TM,
   for (unsigned I = 0, E = static_cast<unsigned>(LibFunc::NumLibFuncs); I != E;
        ++I) {
     LibFunc F = static_cast<LibFunc>(I);
-    StringRef Name = TLI.getName(F);
-    GlobalValue *Val = Mod.getNamedValue(Name);
-
-    // LibFuncs present in the current TU can always be referenced.
-    if (Val && !Val->isDeclaration())
-      continue;
-
-    // LibFuncs not implemented in bitcode can always be referenced, since they
-    // can safely be extracted from whatever library they reside in after LTO
-    // without changing the linker symbol table that LTO depends on.
-    if (!BitcodeLibFuncs.contains(Name))
-      continue;
-
-    // FIXME: Functions that are somewhere in a ThinLTO link (just not imported
-    // in this module) should not be disabled, as they have already been
-    // extracted.
-    TLII->setUnavailable(F);
+    if (BitcodeLibFuncs.contains(TLI.getName(F)))
+      TLII->setUnavailable(F);
   }
 
   FAM.registerPass([&] { return TargetLibraryAnalysis(*TLII); });
diff --git a/llvm/test/LTO/Resolution/X86/libcall-in-thin-link.ll 
b/llvm/test/LTO/Resolution/X86/libcall-in-thin-link.ll
new file mode 100644
index 0000000000000..962088094c438
--- /dev/null
+++ b/llvm/test/LTO/Resolution/X86/libcall-in-thin-link.ll
@@ -0,0 +1,35 @@
+;; If a libcall was extracted in a thin link, it can be used even if not
+;; present in the current TU.
+
+; RUN: rm -rf %t && split-file %s %t && cd %t
+; RUN: opt foo.ll -o foo.o -module-summary -mtriple x86_64-unknown-linux-musl
+; RUN: opt bcmp.ll -o bcmp.o -module-summary -mtriple x86_64-unknown-linux-musl
+; RUN: llvm-lto2 run -o lto.o \
+; RUN:   -r foo.o,foo,plx \
+; RUN:   -r foo.o,memcmp,x \
+; RUN:   -r bcmp.o,bcmp,pl \
+; RUN:   -r bcmp.o,bcmp_impl,x --bitcode-libfuncs=bcmp foo.o bcmp.o -save-temps
+; RUN: llvm-dis lto.o.1.4.opt.bc -o - | FileCheck %s
+
+;--- foo.ll
+define i1 @foo(ptr %0, ptr %1, i64 %2) {
+  ; CHECK-LABEL: define{{.*}}i1 @foo
+  ; CHECK-NEXT: %bcmp = {{.*}}call i32 @bcmp
+  ; CHECK-NEXT: %eq = icmp eq i32 %bcmp, 0
+  ; CHECK-NEXT: ret i1 %eq
+
+  %cmp = call i32 @memcmp(ptr %0, ptr %1, i64 %2)
+  %eq = icmp eq i32 %cmp, 0
+  ret i1 %eq
+}
+
+declare i32 @memcmp(ptr, ptr, i64)
+
+;--- bcmp.ll
+define i32 @bcmp(ptr %0, ptr %1, i64 %2) noinline {
+  %r = call i32 @bcmp_impl(ptr %0, ptr %1, i64 %2)
+  ret i32 %r
+}
+
+declare i32 @bcmp_impl(ptr, ptr, i64)
+

>From bd34d68117234895c4b077c10cbb9b447caf6d35 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Wed, 11 Mar 2026 13:40:24 -0700
Subject: [PATCH 18/22] Change BitcodeLibFuncs semantics to only track
 unextracted symbols

---
 lld/ELF/Driver.cpp                                |  2 +-
 llvm/include/llvm/LTO/LTO.h                       | 14 ++++++--------
 llvm/lib/LTO/LTO.cpp                              |  8 --------
 .../LTO/Resolution/X86/libcall-in-thin-link.ll    |  2 +-
 llvm/test/LTO/Resolution/X86/libcall-in-tu.ll     |  2 +-
 llvm/tools/llvm-lto2/llvm-lto2.cpp                | 15 +++++++--------
 6 files changed, 16 insertions(+), 27 deletions(-)

diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index 5b07972c59972..4e9f6e08e2e64 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -2722,7 +2722,7 @@ void LinkerDriver::compileBitcodeFiles(bool 
skipLinkedOutput) {
     SmallVector<StringRef> bitcodeLibFuncs;
     for (StringRef libFunc : lto::LTO::getLibFuncSymbols(tt, saver))
       if (Symbol *sym = ctx.symtab->find(libFunc);
-          sym && isa<BitcodeFile>(sym->file))
+          sym && sym->isLazy() && isa<BitcodeFile>(sym->file))
         bitcodeLibFuncs.push_back(libFunc);
     lto->setBitcodeLibFuncs(bitcodeLibFuncs);
   }
diff --git a/llvm/include/llvm/LTO/LTO.h b/llvm/include/llvm/LTO/LTO.h
index 72e1f45965f51..b9ae6fb9a3de0 100644
--- a/llvm/include/llvm/LTO/LTO.h
+++ b/llvm/include/llvm/LTO/LTO.h
@@ -447,9 +447,9 @@ class LTO {
   LLVM_ABI Error add(std::unique_ptr<InputFile> Obj,
                      ArrayRef<SymbolResolution> Res);
 
-  /// Set the list of functions implemented in bitcode across the link, whether
-  /// extracted or not. Such functions may not be referenced if they were not
-  /// extracted by the time LTO occurs.
+  /// Set the list of functions implemented in bitcode that were not extracted
+  /// from an archive. Such functions may not be referenced, as they have
+  /// lost their opportunity to be defined.
   LLVM_ABI void setBitcodeLibFuncs(ArrayRef<StringRef> BitcodeLibFuncs);
 
   /// Returns an upper bound on the number of tasks that the client may expect.
@@ -671,11 +671,9 @@ class LTO {
   // Setup optimization remarks according to the provided configuration.
   Error setupOptimizationRemarks();
 
-  // LibFuncs that could have been part of the LTO unit, but was not typically
-  // because they weren't extracted from their libraries. Such functions cannot
-  // safely be called, since they have already lost their only opportunity to 
be
-  // defined. Before run(), this contains all libfuncs defined anywhere in
-  // bitcode; during run(), the ones defined in the LTO unit are filtered out.
+  // LibFuncs that were implemented in bitcode but were not extracted
+  // from their libraries. Such functions cannot safely be called, since
+  // they have lost their opportunity to be defined.
   SmallVector<StringRef> BitcodeLibFuncs;
 
 public:
diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp
index 859d8c69e5401..462873a4b484b 100644
--- a/llvm/lib/LTO/LTO.cpp
+++ b/llvm/lib/LTO/LTO.cpp
@@ -1334,14 +1334,6 @@ Error LTO::run(AddStreamFn AddStream, FileCache Cache) {
   computeDeadSymbolsWithConstProp(ThinLTO.CombinedIndex, GUIDPreservedSymbols,
                                   isPrevailing, Conf.OptLevel > 0);
 
-  // Remove any prevailing definitions from BitcodeLibfuncs, as these are safe
-  // to call.
-  llvm::erase_if(BitcodeLibFuncs, [&](StringRef Name) {
-    return isPrevailing(GlobalValue::getGUIDAssumingExternalLinkage(
-               GlobalValue::dropLLVMManglingEscape(Name))) ==
-           PrevailingType::Yes;
-  });
-
   // Setup output file to emit statistics.
   auto StatsFileOrErr = setupStatsFile(Conf.StatsFile);
   if (!StatsFileOrErr)
diff --git a/llvm/test/LTO/Resolution/X86/libcall-in-thin-link.ll 
b/llvm/test/LTO/Resolution/X86/libcall-in-thin-link.ll
index 962088094c438..fe8e492580d42 100644
--- a/llvm/test/LTO/Resolution/X86/libcall-in-thin-link.ll
+++ b/llvm/test/LTO/Resolution/X86/libcall-in-thin-link.ll
@@ -8,7 +8,7 @@
 ; RUN:   -r foo.o,foo,plx \
 ; RUN:   -r foo.o,memcmp,x \
 ; RUN:   -r bcmp.o,bcmp,pl \
-; RUN:   -r bcmp.o,bcmp_impl,x --bitcode-libfuncs=bcmp foo.o bcmp.o -save-temps
+; RUN:   -r bcmp.o,bcmp_impl,x foo.o bcmp.o -save-temps
 ; RUN: llvm-dis lto.o.1.4.opt.bc -o - | FileCheck %s
 
 ;--- foo.ll
diff --git a/llvm/test/LTO/Resolution/X86/libcall-in-tu.ll 
b/llvm/test/LTO/Resolution/X86/libcall-in-tu.ll
index 33d1dc47ff0d1..cddf5d30697d0 100644
--- a/llvm/test/LTO/Resolution/X86/libcall-in-tu.ll
+++ b/llvm/test/LTO/Resolution/X86/libcall-in-tu.ll
@@ -8,7 +8,7 @@
 ; RUN:   -r %t.o,foo,plx \
 ; RUN:   -r %t.o,memcmp,x \
 ; RUN:   -r %t.o,bcmp,pl \
-; RUN:   -r %t.o,bcmp_impl,x %t.o --bitcode-libfuncs=bcmp -save-temps
+; RUN:   -r %t.o,bcmp_impl,x %t.o -save-temps
 ; RUN: llvm-dis %t.lto.o.0.4.opt.bc -o - | FileCheck %s
 
 define i1 @foo(ptr %0, ptr %1, i64 %2) {
diff --git a/llvm/tools/llvm-lto2/llvm-lto2.cpp 
b/llvm/tools/llvm-lto2/llvm-lto2.cpp
index 054369b289bb2..2b89b20a87757 100644
--- a/llvm/tools/llvm-lto2/llvm-lto2.cpp
+++ b/llvm/tools/llvm-lto2/llvm-lto2.cpp
@@ -234,19 +234,18 @@ static cl::opt<bool>
     AllVtablesHaveTypeInfos("all-vtables-have-type-infos", cl::Hidden,
                             cl::desc("All vtables have type infos"));
 
-// Specifying a symbol here states that it is a library symbol with a 
prevailing
-// definition in bitcode. If such symbols are not defined somewhere in the LTO
-// unit, (e.g., they were not extracted), they cannot safely be referenced,
-// since they have already lost their only opportunity to be defined.
+// Specifying a symbol here states that it is a library symbol that had a
+// definition in bitcode, but was not extracted. Such symbols cannot safely
+// be referenced, since they have already lost their opportunity to be defined.
 //
 // FIXME: Listing all bitcode libfunc symbols here is clunky. A higher-level 
way
 // to indicate which TUs made it into the link might be better, but this would
-// required more detailed tracking of the sources of constructs in the IR.
+// require more detailed tracking of the sources of constructs in the IR.
 // Alternatively, there may be some other data structure that could hold this
 // information.
-static cl::list<std::string>
-    BitcodeLibFuncs("bitcode-libfuncs", cl::Hidden,
-                    cl::desc("set of libfuncs implemented in bitcode"));
+static cl::list<std::string> BitcodeLibFuncs(
+    "bitcode-libfuncs", cl::Hidden,
+    cl::desc("set of unextracted libfuncs implemented in bitcode"));
 
 static cl::opt<bool> TimeTrace("time-trace", cl::desc("Record time trace"));
 

>From 721adaea3cdfc6ce831530d259a2cacc0902733c Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Mon, 9 Mar 2026 20:54:40 -0700
Subject: [PATCH 19/22] Port to COFF

---
 lld/COFF/LTO.cpp                              |  4 ++
 lld/COFF/LTO.h                                |  1 +
 lld/COFF/SymbolTable.cpp                      | 21 ++++++++
 .../COFF/lto-libcall-archive-bitcode.test     | 51 +++++++++++++++++++
 4 files changed, 77 insertions(+)
 create mode 100644 lld/test/COFF/lto-libcall-archive-bitcode.test

diff --git a/lld/COFF/LTO.cpp b/lld/COFF/LTO.cpp
index d55f95493a85f..d5f643782f615 100644
--- a/lld/COFF/LTO.cpp
+++ b/lld/COFF/LTO.cpp
@@ -280,3 +280,7 @@ std::vector<InputFile *> BitcodeCompiler::compile() {
 
   return ret;
 }
+
+void BitcodeCompiler::setBitcodeLibFuncs(ArrayRef<StringRef> bitcodeLibFuncs) {
+  ltoObj->setBitcodeLibFuncs(bitcodeLibFuncs);
+}
diff --git a/lld/COFF/LTO.h b/lld/COFF/LTO.h
index 6826251b5ffa7..73e855e567b09 100644
--- a/lld/COFF/LTO.h
+++ b/lld/COFF/LTO.h
@@ -45,6 +45,7 @@ class BitcodeCompiler {
 
   void add(BitcodeFile &f);
   std::vector<InputFile *> compile();
+  void setBitcodeLibFuncs(ArrayRef<StringRef> bitcodeLibFuncs);
 
 private:
   std::unique_ptr<llvm::lto::LTO> ltoObj;
diff --git a/lld/COFF/SymbolTable.cpp b/lld/COFF/SymbolTable.cpp
index 38a43390c15ab..4c381235de48b 100644
--- a/lld/COFF/SymbolTable.cpp
+++ b/lld/COFF/SymbolTable.cpp
@@ -1437,6 +1437,10 @@ void SymbolTable::compileBitcodeFiles() {
   if (bitcodeFileInstances.empty())
     return;
 
+  // Note that this assumes that the set of possible libfuncs is roughly
+  // equivalent for all bitcode translation units.
+  llvm::Triple tt =
+      llvm::Triple(bitcodeFileInstances.front()->obj->getTargetTriple());
   ScopedTimer t(ctx.ltoTimer);
   lto.reset(new BitcodeCompiler(ctx));
   {
@@ -1444,6 +1448,23 @@ void SymbolTable::compileBitcodeFiles() {
     for (BitcodeFile *f : bitcodeFileInstances)
       lto->add(*f);
   }
+
+  llvm::BumpPtrAllocator alloc;
+  llvm::StringSaver saver(alloc);
+  SmallVector<StringRef> bitcodeLibFuncs;
+  for (StringRef libFunc : lto::LTO::getLibFuncSymbols(tt, saver)) {
+    if (Symbol *sym = find(libFunc)) {
+      if (auto *l = dyn_cast<LazyArchive>(sym)) {
+        if (isBitcode(l->getMemberBuffer()))
+          bitcodeLibFuncs.push_back(libFunc);
+      } else if (auto *o = dyn_cast<LazyObject>(sym)) {
+        if (isBitcode(o->file->mb))
+          bitcodeLibFuncs.push_back(libFunc);
+      }
+    }
+  }
+  lto->setBitcodeLibFuncs(bitcodeLibFuncs);
+
   for (InputFile *newObj : lto->compile()) {
     ObjFile *obj = cast<ObjFile>(newObj);
     obj->parse();
diff --git a/lld/test/COFF/lto-libcall-archive-bitcode.test 
b/lld/test/COFF/lto-libcall-archive-bitcode.test
new file mode 100644
index 0000000000000..bd172f1ff3955
--- /dev/null
+++ b/lld/test/COFF/lto-libcall-archive-bitcode.test
@@ -0,0 +1,51 @@
+; REQUIRES: x86
+
+; RUN: rm -rf %t && split-file %s %t && cd %t
+; RUN: llvm-as main.ll -o main.obj
+; RUN: llvm-as puts.ll -o puts.obj
+; RUN: llvm-mc -filetype=obj -triple=x86_64-pc-windows-msvc printf.s -o 
printf.obj
+; RUN: llvm-ar rcs libc.lib puts.obj printf.obj
+
+;; Ensure that no printf->puts translation occurs during LTO because puts is in
+;; bitcode, but was not brought into the link. This would fail the link by
+;; extracting bitcode after LTO.
+; RUN: lld-link -out:out.exe -entry:main -subsystem:console -lldmap:- 
-nodefaultlib main.obj libc.lib | FileCheck %s
+
+;; Test the same behavior with lazy objects.
+; RUN: lld-link -out:out-lazy.exe -entry:main -subsystem:console -lldmap:- 
-nodefaultlib main.obj /start-lib puts.obj /end-lib printf.obj | FileCheck %s
+
+;; Test that translation DOES occur when puts is extracted and brought into 
the link.
+; RUN: lld-link -out:out-extracted.exe -entry:main -subsystem:console 
-lldmap:- -nodefaultlib main.obj puts.obj printf.obj | FileCheck %s 
--check-prefix=EXTRACTED
+
+; CHECK-NOT: puts
+; CHECK: printf
+
+; EXTRACTED: printf
+; EXTRACTED: puts
+
+;--- puts.ll
+target datalayout = 
"e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-pc-windows-msvc"
+
+define i32 @puts(ptr nocapture readonly %0) noinline {
+  call void asm sideeffect "", ""()
+  ret i32 0
+}
+
+;--- printf.s
+.globl printf
+printf:
+  ret
+
+;--- main.ll
+target datalayout = 
"e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-pc-windows-msvc"
+
+@str = constant [5 x i8] c"foo\0A\00"
+
+define i32 @main() {
+  %call = call i32 (ptr, ...) @printf(ptr @str)
+  ret i32 0
+}
+
+declare i32 @printf(ptr, ...)

>From 70c0736444fc828316ba847c41e7c86d819e919c Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Wed, 11 Mar 2026 15:44:16 -0700
Subject: [PATCH 20/22] Port to WASM

---
 lld/test/wasm/lto/libcall-archive-bitcode.ll | 56 ++++++++++++++++++++
 lld/wasm/LTO.cpp                             |  4 ++
 lld/wasm/LTO.h                               |  1 +
 lld/wasm/SymbolTable.cpp                     | 20 +++++++
 4 files changed, 81 insertions(+)
 create mode 100644 lld/test/wasm/lto/libcall-archive-bitcode.ll

diff --git a/lld/test/wasm/lto/libcall-archive-bitcode.ll 
b/lld/test/wasm/lto/libcall-archive-bitcode.ll
new file mode 100644
index 0000000000000..3b640efc1091f
--- /dev/null
+++ b/lld/test/wasm/lto/libcall-archive-bitcode.ll
@@ -0,0 +1,56 @@
+; REQUIRES: x86
+
+; RUN: rm -rf %t && split-file %s %t && cd %t
+; RUN: llvm-as main.ll -o main.o
+; RUN: llvm-as puts.ll -o puts.o
+; RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown printf.s -o 
printf.o
+; RUN: llvm-ar rcs libc.a puts.o printf.o
+
+;; Ensure that no printf->puts translation occurs during LTO because puts is in
+;; bitcode, but was not brought into the link. This would fail the link by
+;; extracting bitcode after LTO.
+; RUN: wasm-ld -o out.wasm main.o libc.a
+; RUN: obj2yaml out.wasm | FileCheck %s
+
+;; Test the same behavior with lazy objects.
+; RUN: wasm-ld -o out-lazy.wasm main.o --start-lib puts.o --end-lib printf.o
+; RUN: obj2yaml out-lazy.wasm | FileCheck %s
+
+;; Test that translation DOES occur when puts is extracted and brought into 
the link.
+; RUN: wasm-ld -o out-extracted.wasm main.o puts.o printf.o
+; RUN: obj2yaml out-extracted.wasm | FileCheck %s --check-prefix=EXTRACTED
+
+; CHECK-NOT: Name: puts
+; CHECK:     Name: printf
+
+; EXTRACTED: Name: puts
+; EXTRACTED-NOT: Name: printf
+
+;--- puts.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"
+
+define i32 @puts(ptr nocapture readonly %0) noinline {
+  call void asm sideeffect "", ""()
+  ret i32 0
+}
+
+;--- printf.s
+.globl printf
+printf:
+  .functype printf (i32, i32) -> (i32)
+  i32.const 0
+  end_function
+
+;--- main.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"
+
+@str = constant [5 x i8] c"foo\0A\00"
+
+define i32 @_start() {
+  %call = call i32 (ptr, ...) @printf(ptr @str)
+  ret i32 0
+}
+
+declare i32 @printf(ptr, ...)
\ No newline at end of file
diff --git a/lld/wasm/LTO.cpp b/lld/wasm/LTO.cpp
index 668cdf21ea3ed..2cc1a774a254c 100644
--- a/lld/wasm/LTO.cpp
+++ b/lld/wasm/LTO.cpp
@@ -184,6 +184,10 @@ static void thinLTOCreateEmptyIndexFiles() {
 
 // Merge all the bitcode files we have seen, codegen the result
 // and return the resulting objects.
+void BitcodeCompiler::setBitcodeLibFuncs(ArrayRef<StringRef> bitcodeLibFuncs) {
+  ltoObj->setBitcodeLibFuncs(bitcodeLibFuncs);
+}
+
 SmallVector<InputFile *, 0> BitcodeCompiler::compile() {
   unsigned maxTasks = ltoObj->getMaxTasks();
   buf.resize(maxTasks);
diff --git a/lld/wasm/LTO.h b/lld/wasm/LTO.h
index 21b1d59024663..6fa110d6099bf 100644
--- a/lld/wasm/LTO.h
+++ b/lld/wasm/LTO.h
@@ -46,6 +46,7 @@ class BitcodeCompiler {
 
   void add(BitcodeFile &f);
   SmallVector<InputFile *, 0> compile();
+  void setBitcodeLibFuncs(ArrayRef<StringRef> bitcodeLibFuncs);
 
 private:
   std::unique_ptr<llvm::lto::LTO> ltoObj;
diff --git a/lld/wasm/SymbolTable.cpp b/lld/wasm/SymbolTable.cpp
index 9bd93f317c3c5..9ee91e57a4f4d 100644
--- a/lld/wasm/SymbolTable.cpp
+++ b/lld/wasm/SymbolTable.cpp
@@ -82,8 +82,28 @@ void SymbolTable::compileBitcodeFiles() {
   // Prevent further LTO objects being included
   BitcodeFile::doneLTO = true;
 
+  llvm::Triple tt;
+  llvm::BumpPtrAllocator alloc;
+  llvm::StringSaver saver(alloc);
+  SmallVector<StringRef> bitcodeLibFuncs;
+
+  if (!ctx.bitcodeFiles.empty()) {
+    tt = llvm::Triple(ctx.bitcodeFiles.front()->obj->getTargetTriple());
+    for (StringRef libFunc : llvm::lto::LTO::getLibFuncSymbols(tt, saver)) {
+      if (Symbol *sym = find(libFunc)) {
+        if (auto *lazy = dyn_cast<LazySymbol>(sym)) {
+          if (isa<BitcodeFile>(lazy->getFile()))
+            bitcodeLibFuncs.push_back(libFunc);
+        }
+      }
+    }
+  }
+
   // Compile bitcode files and replace bitcode symbols.
   lto.reset(new BitcodeCompiler);
+  if (!ctx.bitcodeFiles.empty())
+    lto->setBitcodeLibFuncs(bitcodeLibFuncs);
+
   for (BitcodeFile *f : ctx.bitcodeFiles)
     lto->add(*f);
 

>From f94a930fa6679ae1c73930f7e09b54a2f4312abc Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Thu, 12 Mar 2026 16:10:00 -0700
Subject: [PATCH 21/22] Remove vestigial libcall info from COFF InputFiles

---
 lld/COFF/InputFiles.cpp | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/lld/COFF/InputFiles.cpp b/lld/COFF/InputFiles.cpp
index 3041e1ce6b445..d415955b6093b 100644
--- a/lld/COFF/InputFiles.cpp
+++ b/lld/COFF/InputFiles.cpp
@@ -25,7 +25,6 @@
 #include "llvm/DebugInfo/PDB/Native/NativeSession.h"
 #include "llvm/DebugInfo/PDB/Native/PDBFile.h"
 #include "llvm/IR/Mangler.h"
-#include "llvm/IR/RuntimeLibcalls.h"
 #include "llvm/LTO/LTO.h"
 #include "llvm/Object/Binary.h"
 #include "llvm/Object/COFF.h"
@@ -1396,10 +1395,6 @@ void BitcodeFile::parse() {
     // FIXME: Check nodeduplicate
     comdat[i] =
         symtab.addComdat(this, saver.save(obj->getComdatTable()[i].first));
-  Triple tt(obj->getTargetTriple());
-  TargetLibraryInfoImpl tlii(tt);
-  TargetLibraryInfo tli(tlii);
-  RTLIB::RuntimeLibcallsInfo libcalls(tt);
   for (const lto::InputFile::Symbol &objSym : obj->symbols()) {
     StringRef symName = saver.save(objSym.getName());
     int comdatIndex = objSym.getComdatIndex();

>From 7e9ceb0ce0db342763f93a1c7806eb073dead4cf Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Thu, 12 Mar 2026 16:19:38 -0700
Subject: [PATCH 22/22] Add FIXMEs for DTLTO

---
 clang/lib/CodeGen/BackendUtil.cpp | 2 ++
 llvm/lib/LTO/LTOBackend.cpp       | 1 +
 2 files changed, 3 insertions(+)

diff --git a/clang/lib/CodeGen/BackendUtil.cpp 
b/clang/lib/CodeGen/BackendUtil.cpp
index cfd12d6778d1a..e50f8a5845efd 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -1410,6 +1410,8 @@ runThinLTOBackend(CompilerInstance &CI, 
ModuleSummaryIndex *CombinedIndex,
 
   // FIXME: Both ExecuteAction and thinBackend set up optimization remarks for
   // the same context.
+  // FIXME: This does not yet set the list of bitcode libfuncs that it isn't
+  // safe to call. This precludes bitcode libc in DTLTO.
   finalizeLLVMOptimizationRemarks(M->getContext());
   if (Error E = thinBackend(
           Conf, -1, AddStream, *M, *CombinedIndex, ImportList,
diff --git a/llvm/lib/LTO/LTOBackend.cpp b/llvm/lib/LTO/LTOBackend.cpp
index 0b0257163dcd2..83f0a1de35040 100644
--- a/llvm/lib/LTO/LTOBackend.cpp
+++ b/llvm/lib/LTO/LTOBackend.cpp
@@ -307,6 +307,7 @@ static void runNewPMPasses(const Config &Conf, Module &Mod, 
TargetMachine *TM,
   // that might have been present in the current LTO unit, but are not, have
   // lost their only opportunity to be defined, and calls must not be emitted 
to
   // them.
+  // FIXME: BitcodeLibFuncs isn't yet set for DTLTO.
   TargetLibraryInfo TLI(*TLII);
   for (unsigned I = 0, E = static_cast<unsigned>(LibFunc::NumLibFuncs); I != E;
        ++I) {

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to