https://github.com/mysterymath updated https://github.com/llvm/llvm-project/pull/164916
>From 44c3ca2828ce68ca8a05e06aed0a1909a45cd32f 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 4b4eaadcf1527..7ed7cece9d2d8 100644 --- a/clang/lib/CodeGen/BackendUtil.cpp +++ b/clang/lib/CodeGen/BackendUtil.cpp @@ -1410,11 +1410,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 fac8fa2db04d6..4d283ddd6e3c4 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 c516b9070bf477bcdc32cc4b1b573b468a40d7d0 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 936c87df5da10c69cebea576f29597c8100f647b 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 4d283ddd6e3c4..db28f180557bb 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 eb4ed2bccfa75267a7c5240771585eae8b013685 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 db28f180557bb..c2304b7677f2a 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 3f8277fc35a24e637319d9b5845b3ac968a26f5e 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 c2304b7677f2a..476ccc9433e3a 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 1bea9ff4c498722f65c475b50838519f908f9c5f 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 fb7f0c8d1f8c5c73e7af81fda6fa12364434286e 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 da7fdf5d37f584e8e05e982234614f97ffd5fcc0 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 f140e65f33519ad250f56353b98e8f6ca2a3c48d 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 6e75c8fa968c81f77518dd8cbfdcd4f79556127c 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 1ce9fceb040cbb267f1156a00f6293d764508720 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 13035b6834ced3b5ed780dc2afc969a8da832e6e 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 e51dbccee20ca7bd55a28223614677caf268c998 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 221d338938b01e7d089de0a1e77e10b11147c622 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 53756408cd73f82e66b00805ee8393566fff3956 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 3b8c9af9f993720cac4d5aeb5a2d62155812ea26 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 853bb38c338fa756059ffbb1db0b47765c9fd3f5 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 d80373b32635f3856ff56ff4c4a2d4fd4c57eec3 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 476ccc9433e3a..b536d895bf657 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 155f6a01062a077303c12e65f88b302f3dab6468 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 3b1ffb6514c6c647819d1744eaa89cf4bae3ee8f 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 07ff66e6798eb60a79779c00999ca2ba4adb1631 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 c0baeb9cac0fb6752928e83bc39151ea2065d8a3 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 7ed7cece9d2d8..a5517a09b0489 100644 --- a/clang/lib/CodeGen/BackendUtil.cpp +++ b/clang/lib/CodeGen/BackendUtil.cpp @@ -1409,6 +1409,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
