[lld] [llvm] [LTO] Move section names from symbol table to module summary. (PR #210856)

Eli Friedman via llvm-commits llvm-commits at lists.llvm.org
Wed Jul 22 12:57:08 PDT 2026


https://github.com/efriedma-quic updated https://github.com/llvm/llvm-project/pull/210856

>From 9a1b9b1e93eea27134a481ed7f4c4a849bf48b17 Mon Sep 17 00:00:00 2001
From: Eli Friedman <efriedma at qti.qualcomm.com>
Date: Tue, 7 Jul 2026 18:31:35 -0700
Subject: [PATCH 1/2] [LTO] Move section names from symbol table to module
 summary.

Section names don't logically belong in the symbol table; they're a
property of the data, not the symbol itself. Move the section name
information from the symbol table into the ThinLTO summary.

As discussed on
https://discourse.llvm.org/t/rfc-lto-lld-lto-with-linker-scripts-implemented/91089
.  Splitting this part off to make it easier to review; it basically
stands on its own.

The OutputSectionName is currently unused, but will be used as part of
the full implementation of LTO with linker scripts.
---
 lld/ELF/LTO.cpp                             | 10 ++-
 lld/test/ELF/lto/section-name.ll            |  5 ++
 llvm/include/llvm/AsmParser/LLParser.h      | 10 +--
 llvm/include/llvm/AsmParser/LLToken.h       |  5 ++
 llvm/include/llvm/Bitcode/LLVMBitCodes.h    |  1 +
 llvm/include/llvm/IR/ModuleSummaryIndex.h   | 18 +++++
 llvm/include/llvm/LTO/LTO.h                 | 40 +++++++----
 llvm/include/llvm/Object/IRSymtab.h         | 13 +---
 llvm/lib/Analysis/ModuleSummaryAnalysis.cpp |  9 +++
 llvm/lib/AsmParser/LLLexer.cpp              |  4 ++
 llvm/lib/AsmParser/LLParser.cpp             | 77 +++++++++++++++++++--
 llvm/lib/Bitcode/Reader/BitcodeReader.cpp   | 15 ++++
 llvm/lib/Bitcode/Writer/BitcodeWriter.cpp   | 22 ++++++
 llvm/lib/IR/AsmWriter.cpp                   |  6 ++
 llvm/lib/LTO/LTO.cpp                        | 64 +++++++++++++++--
 llvm/lib/Object/IRSymtab.cpp                |  4 --
 llvm/test/Assembler/thinlto-sectioninfo.ll  | 24 +++++++
 llvm/tools/gold/gold-plugin.cpp             | 18 ++---
 llvm/tools/llvm-lto2/llvm-lto2.cpp          |  3 -
 19 files changed, 286 insertions(+), 62 deletions(-)
 create mode 100644 llvm/test/Assembler/thinlto-sectioninfo.ll

diff --git a/lld/ELF/LTO.cpp b/lld/ELF/LTO.cpp
index e40575bffec62..791410785a810 100644
--- a/lld/ELF/LTO.cpp
+++ b/lld/ELF/LTO.cpp
@@ -265,8 +265,7 @@ void BitcodeCompiler::add(BitcodeFile &f) {
     // 5) Symbols that will be referenced after linker wrapping is performed.
     r.VisibleToRegularObj = ctx.arg.relocatable || sym->isUsedInRegularObj ||
                             sym->referencedAfterWrap ||
-                            (r.Prevailing && sym->isExported) ||
-                            usedStartStop.contains(objSym.getSectionName());
+                            (r.Prevailing && sym->isExported);
     // Identify symbols exported dynamically, and that therefore could be
     // referenced by a shared library not visible to the linker.
     r.ExportDynamic = sym->computeBinding(ctx) != STB_LOCAL &&
@@ -292,7 +291,12 @@ void BitcodeCompiler::add(BitcodeFile &f) {
     // their values are still not final.
     r.LinkerRedefined = sym->scriptDefined;
   }
-  checkError(ctx.e, ltoObj->add(std::move(f.obj), resols));
+  auto resolver = [this](StringRef sectionName) {
+    lto::SectionResolution r;
+    r.Keep = usedStartStop.contains(sectionName);
+    return r;
+  };
+  checkError(ctx.e, ltoObj->add(std::move(f.obj), resols, std::move(resolver)));
 }
 
 // If LazyObjFile has not been added to link, emit empty index files.
diff --git a/lld/test/ELF/lto/section-name.ll b/lld/test/ELF/lto/section-name.ll
index a91537d5004b7..6149f8c9b2d0f 100644
--- a/lld/test/ELF/lto/section-name.ll
+++ b/lld/test/ELF/lto/section-name.ll
@@ -4,6 +4,11 @@
 ; RUN: llvm-readelf -S %t.so | FileCheck %s
 ; RUN: ld.lld %t.o -o %t.so -shared --gc-sections -z nostart-stop-gc
 ; RUN: llvm-readelf -S %t.so | FileCheck --check-prefix=GC %s
+; RUN: opt -module-summary %s -o %t.o
+; RUN: ld.lld %t.o -o %t.so -shared
+; RUN: llvm-readelf -S %t.so | FileCheck %s
+; RUN: ld.lld %t.o -o %t.so -shared --gc-sections -z nostart-stop-gc
+; RUN: llvm-readelf -S %t.so | FileCheck --check-prefix=GC %s
 
 target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
 target triple = "x86_64-unknown-linux-gnu"
diff --git a/llvm/include/llvm/AsmParser/LLParser.h b/llvm/include/llvm/AsmParser/LLParser.h
index 788b56cb78f08..23e4c4eb30171 100644
--- a/llvm/include/llvm/AsmParser/LLParser.h
+++ b/llvm/include/llvm/AsmParser/LLParser.h
@@ -426,9 +426,11 @@ namespace llvm {
     bool parseSummaryIndexFlags();
     bool parseBlockCount();
     bool parseGVEntry(unsigned ID);
-    bool parseFunctionSummary(std::string Name, GlobalValue::GUID, unsigned ID);
-    bool parseVariableSummary(std::string Name, GlobalValue::GUID, unsigned ID);
-    bool parseAliasSummary(std::string Name, GlobalValue::GUID, unsigned ID);
+    bool parseFunctionSummary(std::string Name, GlobalValue::GUID &,
+                              unsigned ID);
+    bool parseVariableSummary(std::string Name, GlobalValue::GUID &,
+                              unsigned ID);
+    bool parseAliasSummary(std::string Name, GlobalValue::GUID &, unsigned ID);
     bool parseGVFlags(GlobalValueSummary::GVFlags &GVFlags);
     bool parseGVarFlags(GlobalVarSummary::GVarFlags &GVarFlags);
     bool parseOptionalFFlags(FunctionSummary::FFlags &FFlags);
@@ -469,7 +471,7 @@ namespace llvm {
         std::map<std::vector<uint64_t>, WholeProgramDevirtResolution::ByArg>
             &ResByArg);
     bool parseArgs(std::vector<uint64_t> &Args);
-    bool addGlobalValueToIndex(std::string Name, GlobalValue::GUID,
+    bool addGlobalValueToIndex(std::string Name, GlobalValue::GUID &,
                                GlobalValue::LinkageTypes Linkage, unsigned ID,
                                std::unique_ptr<GlobalValueSummary> Summary,
                                LocTy Loc);
diff --git a/llvm/include/llvm/AsmParser/LLToken.h b/llvm/include/llvm/AsmParser/LLToken.h
index d2766a05ce9ba..0a2a13c633287 100644
--- a/llvm/include/llvm/AsmParser/LLToken.h
+++ b/llvm/include/llvm/AsmParser/LLToken.h
@@ -482,6 +482,11 @@ enum Kind {
   kw_versions,
   kw_memProf,
   kw_notcold,
+  // sectionInfo keywords.
+  kw_sectionInfo,
+  kw_sectionName,
+  kw_outputSectionName,
+  kw_keep,
 
   // GV's with __attribute__((no_sanitize("address"))), or things in
   // -fsanitize-ignorelist when built with ASan.
diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
index 358f9a65a80af..c18c1582028fc 100644
--- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h
+++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
@@ -348,6 +348,7 @@ enum GlobalValueSummarySymtabCodes {
   //  nummib x alloc type,
   //  numver x version]
   FS_COMBINED_ALLOC_INFO_NO_CONTEXT = 33,
+  FS_SECTION_INFO = 34,
 };
 
 enum MetadataCodes {
diff --git a/llvm/include/llvm/IR/ModuleSummaryIndex.h b/llvm/include/llvm/IR/ModuleSummaryIndex.h
index f4c5e9b40796b..2c21b47a14988 100644
--- a/llvm/include/llvm/IR/ModuleSummaryIndex.h
+++ b/llvm/include/llvm/IR/ModuleSummaryIndex.h
@@ -17,6 +17,7 @@
 
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/MapVector.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SetVector.h"
 #include "llvm/ADT/SmallPtrSet.h"
@@ -1597,6 +1598,16 @@ class ModuleSummaryIndex {
   CfiFunctionIndex CfiFunctionDefs;
   CfiFunctionIndex CfiFunctionDecls;
 
+public:
+  struct SectionInfo {
+    StringRef SectionName;
+    StringRef OutputSectionName;
+    bool Keep = false;
+  };
+
+private:
+  MapVector<GlobalValue::GUID, SectionInfo> SectionInfos;
+
   // Used in cases where we want to record the name of a global, but
   // don't have the string owned elsewhere (e.g. the Strtab on a module).
   BumpPtrAllocator Alloc;
@@ -1695,6 +1706,13 @@ class ModuleSummaryIndex {
     StackIds.shrink_to_fit();
   }
 
+  const MapVector<GlobalValue::GUID, SectionInfo> &sectionInfos() const {
+    return SectionInfos;
+  }
+  MapVector<GlobalValue::GUID, SectionInfo> &sectionInfos() {
+    return SectionInfos;
+  }
+
   /// Convenience function for doing a DFS on a ValueInfo. Marks the function in
   /// the FunctionHasParent map.
   static void discoverNodes(ValueInfo V,
diff --git a/llvm/include/llvm/LTO/LTO.h b/llvm/include/llvm/LTO/LTO.h
index 32aa671183890..828b0e1cc972b 100644
--- a/llvm/include/llvm/LTO/LTO.h
+++ b/llvm/include/llvm/LTO/LTO.h
@@ -22,6 +22,7 @@
 
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/MapVector.h"
+#include "llvm/ADT/STLFunctionalExtras.h"
 #include "llvm/Bitcode/BitcodeReader.h"
 #include "llvm/IR/ModuleSummaryIndex.h"
 #include "llvm/LTO/Config.h"
@@ -110,6 +111,13 @@ LLVM_ABI std::vector<int> generateModulesOrdering(ArrayRef<BitcodeModule *> R);
 class LTO;
 struct SymbolResolution;
 
+struct SectionResolution {
+  bool Keep = false;
+};
+
+using SectionResolverFn =
+    llvm::function_ref<SectionResolution(StringRef SectionName)>;
+
 /// An input file. This is a symbol table wrapper that only exposes the
 /// information that an LTO client should need in order to do symbol resolution.
 class InputFile {
@@ -159,22 +167,21 @@ class InputFile {
   public:
     Symbol(const irsymtab::Symbol &S) : irsymtab::Symbol(S) {}
 
-    using irsymtab::Symbol::isUndefined;
-    using irsymtab::Symbol::isCommon;
-    using irsymtab::Symbol::isWeak;
-    using irsymtab::Symbol::isIndirect;
-    using irsymtab::Symbol::getName;
-    using irsymtab::Symbol::getIRName;
-    using irsymtab::Symbol::getVisibility;
     using irsymtab::Symbol::canBeOmittedFromSymbolTable;
-    using irsymtab::Symbol::isTLS;
+    using irsymtab::Symbol::getCOFFWeakExternalFallback;
     using irsymtab::Symbol::getComdatIndex;
-    using irsymtab::Symbol::getCommonSize;
     using irsymtab::Symbol::getCommonAlignment;
-    using irsymtab::Symbol::getCOFFWeakExternalFallback;
-    using irsymtab::Symbol::getSectionName;
+    using irsymtab::Symbol::getCommonSize;
+    using irsymtab::Symbol::getIRName;
+    using irsymtab::Symbol::getName;
+    using irsymtab::Symbol::getVisibility;
+    using irsymtab::Symbol::isCommon;
     using irsymtab::Symbol::isExecutable;
+    using irsymtab::Symbol::isIndirect;
+    using irsymtab::Symbol::isTLS;
+    using irsymtab::Symbol::isUndefined;
     using irsymtab::Symbol::isUsed;
+    using irsymtab::Symbol::isWeak;
 
     // Returns whether this symbol is a library call that LTO code generation
     // may emit references to. Such symbols must be considered external, as
@@ -417,7 +424,8 @@ class LLVM_ABI LTO {
   /// Add an input file to the LTO link, using the provided symbol resolutions.
   /// The symbol resolutions must appear in the enumeration order given by
   /// InputFile::symbols().
-  Error add(std::unique_ptr<InputFile> Obj, ArrayRef<SymbolResolution> Res);
+  Error add(std::unique_ptr<InputFile> Obj, ArrayRef<SymbolResolution> Res,
+            SectionResolverFn SectionResolver = {});
 
   /// Set the list of functions implemented in bitcode that were not extracted
   /// from an archive. Such functions may not be referenced, as they have
@@ -613,18 +621,20 @@ class LLVM_ABI LTO {
   // to the resolutions for the remaining modules in the InputFile.
   Expected<ArrayRef<SymbolResolution>>
   addModule(InputFile &Input, ArrayRef<SymbolResolution> InputRes,
-            unsigned ModI, ArrayRef<SymbolResolution> Res);
+            unsigned ModI, ArrayRef<SymbolResolution> Res,
+            SectionResolverFn SectionResolver);
 
   Expected<std::pair<RegularLTOState::AddedModule, ArrayRef<SymbolResolution>>>
   addRegularLTO(InputFile &Input, ArrayRef<SymbolResolution> InputRes,
                 BitcodeModule BM, ArrayRef<InputFile::Symbol> Syms,
-                ArrayRef<SymbolResolution> Res);
+                ArrayRef<SymbolResolution> Res,
+                SectionResolverFn SectionResolver);
   Error linkRegularLTO(RegularLTOState::AddedModule Mod,
                        bool LivenessFromIndex);
 
   Expected<ArrayRef<SymbolResolution>>
   addThinLTO(BitcodeModule BM, ArrayRef<InputFile::Symbol> Syms,
-             ArrayRef<SymbolResolution> Res);
+             ArrayRef<SymbolResolution> Res, SectionResolverFn SectionResolver);
 
   Error runRegularLTO(AddStreamFn AddStream);
   Error runThinLTO(AddStreamFn AddStream, FileCache Cache,
diff --git a/llvm/include/llvm/Object/IRSymtab.h b/llvm/include/llvm/Object/IRSymtab.h
index f59ad293d61ae..5f3080d2a54f2 100644
--- a/llvm/include/llvm/Object/IRSymtab.h
+++ b/llvm/include/llvm/Object/IRSymtab.h
@@ -127,9 +127,6 @@ struct Uncommon {
   /// COFF-specific: the name of the symbol that a weak external resolves to
   /// if not defined.
   Str COFFWeakExternFallbackName;
-
-  /// Specified section name, if any.
-  Str SectionName;
 };
 
 
@@ -138,7 +135,7 @@ struct Header {
   /// when the format changes, but it does not need to be incremented if a
   /// change to LLVM would cause it to create a different symbol table.
   Word Version;
-  enum { kCurrentVersion = 4 };
+  enum { kCurrentVersion = 5 };
 
   /// The producer's version string (LLVM_VERSION_STRING " " LLVM_REVISION).
   /// Consumers should rebuild the symbol table from IR if the producer's
@@ -181,7 +178,6 @@ struct Symbol {
   uint64_t CommonSize;
   uint32_t CommonAlign;
   StringRef COFFWeakExternFallbackName;
-  StringRef SectionName;
 
   /// Returns the mangled symbol name.
   StringRef getName() const { return Name; }
@@ -232,8 +228,6 @@ struct Symbol {
     assert(isWeak() && isIndirect());
     return COFFWeakExternFallbackName;
   }
-
-  StringRef getSectionName() const { return SectionName; }
 };
 
 /// This class can be used to read a Symtab and Strtab produced by
@@ -333,10 +327,7 @@ class Reader::SymbolRef : public Symbol {
       CommonSize = UncI->CommonSize;
       CommonAlign = UncI->CommonAlign;
       COFFWeakExternFallbackName = R->str(UncI->COFFWeakExternFallbackName);
-      SectionName = R->str(UncI->SectionName);
-    } else
-      // Reset this field so it can be queried unconditionally for all symbols.
-      SectionName = "";
+    }
   }
 
 public:
diff --git a/llvm/lib/Analysis/ModuleSummaryAnalysis.cpp b/llvm/lib/Analysis/ModuleSummaryAnalysis.cpp
index 33caa268b1c6e..0626770681e65 100644
--- a/llvm/lib/Analysis/ModuleSummaryAnalysis.cpp
+++ b/llvm/lib/Analysis/ModuleSummaryAnalysis.cpp
@@ -1171,6 +1171,15 @@ ModuleSummaryIndex llvm::buildModuleSummaryIndex(
     Index.exportToDot(OSDot, {});
   }
 
+  for (const GlobalObject &GO : M.global_objects()) {
+    if (GO.isDeclaration() || GO.getName().starts_with("llvm."))
+      continue;
+    if (GO.hasSection()) {
+      auto &Entry = Index.sectionInfos()[GO.getGUID()];
+      Entry.SectionName = Index.saveString(GO.getSection());
+    }
+  }
+
   return Index;
 }
 
diff --git a/llvm/lib/AsmParser/LLLexer.cpp b/llvm/lib/AsmParser/LLLexer.cpp
index 069a180056488..e50e91e09310a 100644
--- a/llvm/lib/AsmParser/LLLexer.cpp
+++ b/llvm/lib/AsmParser/LLLexer.cpp
@@ -900,6 +900,10 @@ lltok::Kind LLLexer::LexIdentifier() {
   KEYWORD(versions);
   KEYWORD(memProf);
   KEYWORD(notcold);
+  KEYWORD(sectionInfo);
+  KEYWORD(sectionName);
+  KEYWORD(outputSectionName);
+  KEYWORD(keep);
 
 #undef KEYWORD
 
diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp
index c58b109b5ff9b..9ded64007ef58 100644
--- a/llvm/lib/AsmParser/LLParser.cpp
+++ b/llvm/lib/AsmParser/LLParser.cpp
@@ -10001,8 +10001,9 @@ static void resolveFwdRef(ValueInfo *Fwd, ValueInfo &Resolved) {
 /// Stores the given Name/GUID and associated summary into the Index.
 /// Also updates any forward references to the associated entry ID.
 bool LLParser::addGlobalValueToIndex(
-    std::string Name, GlobalValue::GUID GUID, GlobalValue::LinkageTypes Linkage,
-    unsigned ID, std::unique_ptr<GlobalValueSummary> Summary, LocTy Loc) {
+    std::string Name, GlobalValue::GUID &GUID,
+    GlobalValue::LinkageTypes Linkage, unsigned ID,
+    std::unique_ptr<GlobalValueSummary> Summary, LocTy Loc) {
   // First create the ValueInfo utilizing the Name or GUID.
   ValueInfo VI;
   if (GUID != 0) {
@@ -10172,8 +10173,70 @@ bool LLParser::parseGVEntry(unsigned ID) {
     }
   } while (EatIfPresent(lltok::comma));
 
-  if (parseToken(lltok::rparen, "expected ')' here") ||
-      parseToken(lltok::rparen, "expected ')' here"))
+  if (parseToken(lltok::rparen, "expected ')' here"))
+    return true;
+
+  if (EatIfPresent(lltok::comma)) {
+    if (parseToken(lltok::kw_sectionInfo, "expected 'sectioninfo' here") ||
+        parseToken(lltok::colon, "expected ':' here") ||
+        parseToken(lltok::lparen, "expected '(' here"))
+      return true;
+
+    ModuleSummaryIndex::SectionInfo Info;
+    do {
+      switch (Lex.getKind()) {
+      case lltok::kw_sectionName: {
+        Lex.Lex();
+        if (parseToken(lltok::colon, "expected ':' here"))
+          return true;
+        std::string SectionName;
+        if (parseStringConstant(SectionName))
+          return true;
+        Info.SectionName = Index->saveString(SectionName);
+        break;
+      }
+      case lltok::kw_outputSectionName: {
+        Lex.Lex();
+        if (parseToken(lltok::colon, "expected ':' here"))
+          return true;
+        std::string OutputSectionName;
+        if (parseStringConstant(OutputSectionName))
+          return true;
+        Info.OutputSectionName = Index->saveString(OutputSectionName);
+        break;
+      }
+      case lltok::kw_keep: {
+        Lex.Lex();
+        if (parseToken(lltok::colon, "expected ':' here"))
+          return true;
+        switch (Lex.getKind()) {
+        case lltok::kw_true:
+          Lex.Lex();
+          Info.Keep = true;
+          break;
+        case lltok::kw_false:
+          Lex.Lex();
+          Info.Keep = false;
+          break;
+        default:
+          return error(Lex.getLoc(), "expected 'true' or 'false'");
+        }
+        break;
+      }
+      default:
+        return error(Lex.getLoc(), "expected sectionInfo field");
+      }
+    } while (EatIfPresent(lltok::comma));
+
+    if (parseToken(lltok::rparen, "expected ')' here"))
+      return true;
+
+    if (!GUID)
+      return error(Lex.getLoc(), "missing GUID parsing sectionInfo");
+    Index->sectionInfos().insert_or_assign(GUID, Info);
+  }
+
+  if (parseToken(lltok::rparen, "expected ')' here"))
     return true;
 
   return false;
@@ -10184,7 +10247,7 @@ bool LLParser::parseGVEntry(unsigned ID) {
 ///         ',' 'insts' ':' UInt32 [',' OptionalFFlags]? [',' OptionalCalls]?
 ///         [',' OptionalTypeIdInfo]? [',' OptionalParamAccesses]?
 ///         [',' OptionalRefs]? ')'
-bool LLParser::parseFunctionSummary(std::string Name, GlobalValue::GUID GUID,
+bool LLParser::parseFunctionSummary(std::string Name, GlobalValue::GUID &GUID,
                                     unsigned ID) {
   LocTy Loc = Lex.getLoc();
   assert(Lex.getKind() == lltok::kw_function);
@@ -10272,7 +10335,7 @@ bool LLParser::parseFunctionSummary(std::string Name, GlobalValue::GUID GUID,
 /// VariableSummary
 ///   ::= 'variable' ':' '(' 'module' ':' ModuleReference ',' GVFlags
 ///         [',' OptionalRefs]? ')'
-bool LLParser::parseVariableSummary(std::string Name, GlobalValue::GUID GUID,
+bool LLParser::parseVariableSummary(std::string Name, GlobalValue::GUID &GUID,
                                     unsigned ID) {
   LocTy Loc = Lex.getLoc();
   assert(Lex.getKind() == lltok::kw_variable);
@@ -10331,7 +10394,7 @@ bool LLParser::parseVariableSummary(std::string Name, GlobalValue::GUID GUID,
 /// AliasSummary
 ///   ::= 'alias' ':' '(' 'module' ':' ModuleReference ',' GVFlags ','
 ///         'aliasee' ':' GVReference ')'
-bool LLParser::parseAliasSummary(std::string Name, GlobalValue::GUID GUID,
+bool LLParser::parseAliasSummary(std::string Name, GlobalValue::GUID &GUID,
                                  unsigned ID) {
   assert(Lex.getKind() == lltok::kw_alias);
   LocTy Loc = Lex.getLoc();
diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
index ac61ede6395af..7058d04a3d40a 100644
--- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
+++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
@@ -8325,6 +8325,21 @@ Error ModuleSummaryIndexBitcodeReader::parseEntireSummary(unsigned ID) {
       break;
     }
 
+    case bitc::FS_SECTION_INFO: {
+      if (Record.size() < 6)
+        return error("Invalid record");
+      uint64_t GUID = Record[0];
+      StringRef SectionName(Strtab.data() + Record[1], Record[2]);
+      StringRef OutputSectionName(Strtab.data() + Record[3], Record[4]);
+      bool Keep = Record[5] != 0;
+
+      auto &Entry = TheIndex.sectionInfos()[GUID];
+      Entry.SectionName = TheIndex.saveString(SectionName);
+      Entry.OutputSectionName = TheIndex.saveString(OutputSectionName);
+      Entry.Keep = Keep;
+      break;
+    }
+
     case bitc::FS_STACK_IDS: { // [n x stackid]
       // Save stack ids in the reader to consult when adding stack ids from the
       // lists in the stack node and alloc node entries.
diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
index 571336c217797..db1bec8adc19d 100644
--- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
+++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
@@ -5010,6 +5010,17 @@ void ModuleBitcodeWriterBase::writePerModuleGlobalValueSummary() {
     Stream.EmitRecord(bitc::FS_BLOCK_COUNT,
                       ArrayRef<uint64_t>{Index->getBlockCount()});
 
+  for (const auto &Entry : Index->sectionInfos()) {
+    NameVals.push_back(Entry.first); // GUID
+    NameVals.push_back(StrtabBuilder.add(Entry.second.SectionName));
+    NameVals.push_back(Entry.second.SectionName.size());
+    NameVals.push_back(StrtabBuilder.add(Entry.second.OutputSectionName));
+    NameVals.push_back(Entry.second.OutputSectionName.size());
+    NameVals.push_back(Entry.second.Keep);
+    Stream.EmitRecord(bitc::FS_SECTION_INFO, NameVals);
+    NameVals.clear();
+  }
+
   Stream.ExitBlock();
 }
 
@@ -5431,6 +5442,17 @@ void IndexBitcodeWriter::writeCombinedGlobalValueSummary() {
     Stream.EmitRecord(bitc::FS_BLOCK_COUNT,
                       ArrayRef<uint64_t>{Index.getBlockCount()});
 
+  for (const auto &Entry : Index.sectionInfos()) {
+    NameVals.push_back(Entry.first); // GUID
+    NameVals.push_back(StrtabBuilder.add(Entry.second.SectionName));
+    NameVals.push_back(Entry.second.SectionName.size());
+    NameVals.push_back(StrtabBuilder.add(Entry.second.OutputSectionName));
+    NameVals.push_back(Entry.second.OutputSectionName.size());
+    NameVals.push_back(Entry.second.Keep);
+    Stream.EmitRecord(bitc::FS_SECTION_INFO, NameVals);
+    NameVals.clear();
+  }
+
   Stream.ExitBlock();
 }
 
diff --git a/llvm/lib/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp
index cad4f17b0db91..6616d662287cd 100644
--- a/llvm/lib/IR/AsmWriter.cpp
+++ b/llvm/lib/IR/AsmWriter.cpp
@@ -3809,6 +3809,12 @@ void AssemblyWriter::printSummaryInfo(unsigned Slot, const ValueInfo &VI) {
     }
     Out << ")";
   }
+  if (auto I = TheIndex->sectionInfos().find(VI.getGUID());
+      I != TheIndex->sectionInfos().end()) {
+    Out << ", sectionInfo: (sectionName: \"" << I->second.SectionName
+        << "\", outputSectionName: \"" << I->second.OutputSectionName
+        << "\", keep: " << (I->second.Keep ? "true" : "false") << ")";
+  }
   Out << ")";
   if (VI.hasName() && !VI.name().empty())
     Out << " ; guid = " << VI.getGUID();
diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp
index c8c5b0880819a..9a93a84699cbb 100644
--- a/llvm/lib/LTO/LTO.cpp
+++ b/llvm/lib/LTO/LTO.cpp
@@ -61,6 +61,7 @@
 #include "llvm/Transforms/IPO/MemProfContextDisambiguation.h"
 #include "llvm/Transforms/IPO/WholeProgramDevirt.h"
 #include "llvm/Transforms/Utils/FunctionImportUtils.h"
+#include "llvm/Transforms/Utils/ModuleUtils.h"
 #include "llvm/Transforms/Utils/SplitModule.h"
 
 #include <optional>
@@ -827,7 +828,8 @@ static void writeToResolutionFile(raw_ostream &OS, InputFile *Input,
 }
 
 Error LTO::add(std::unique_ptr<InputFile> InputPtr,
-               ArrayRef<SymbolResolution> Res) {
+               ArrayRef<SymbolResolution> Res,
+               SectionResolverFn SectionResolver) {
   llvm::TimeTraceScope timeScope("LTO add input", InputPtr->getName());
   assert(!CalledGetMaxTasks);
 
@@ -849,7 +851,8 @@ Error LTO::add(std::unique_ptr<InputFile> InputPtr,
 
   ArrayRef<SymbolResolution> InputRes = Res;
   for (unsigned I = 0; I != Input->Mods.size(); ++I) {
-    if (auto Err = addModule(*Input, InputRes, I, Res).moveInto(Res))
+    if (auto Err =
+            addModule(*Input, InputRes, I, Res, SectionResolver).moveInto(Res))
       return Err;
   }
 
@@ -865,7 +868,8 @@ void LTO::setBitcodeLibFuncs(ArrayRef<StringRef> BitcodeLibFuncs) {
 
 Expected<ArrayRef<SymbolResolution>>
 LTO::addModule(InputFile &Input, ArrayRef<SymbolResolution> InputRes,
-               unsigned ModI, ArrayRef<SymbolResolution> Res) {
+               unsigned ModI, ArrayRef<SymbolResolution> Res,
+               SectionResolverFn SectionResolver) {
   llvm::TimeTraceScope timeScope("LTO add module", Input.getName());
   Expected<BitcodeLTOInfo> LTOInfo = Input.Mods[ModI].getLTOInfo();
   if (!LTOInfo)
@@ -904,10 +908,11 @@ LTO::addModule(InputFile &Input, ArrayRef<SymbolResolution> InputRes,
                        LTOInfo->HasSummary, Triple(Input.getTargetTriple()));
 
   if (IsThinLTO)
-    return addThinLTO(BM, ModSyms, Res);
+    return addThinLTO(BM, ModSyms, Res, SectionResolver);
 
   RegularLTO.EmptyCombinedModule = false;
-  auto ModOrErr = addRegularLTO(Input, InputRes, BM, ModSyms, Res);
+  auto ModOrErr =
+      addRegularLTO(Input, InputRes, BM, ModSyms, Res, SectionResolver);
   if (!ModOrErr)
     return ModOrErr.takeError();
   Res = ModOrErr->second;
@@ -964,7 +969,8 @@ Expected<
     std::pair<LTO::RegularLTOState::AddedModule, ArrayRef<SymbolResolution>>>
 LTO::addRegularLTO(InputFile &Input, ArrayRef<SymbolResolution> InputRes,
                    BitcodeModule BM, ArrayRef<InputFile::Symbol> Syms,
-                   ArrayRef<SymbolResolution> Res) {
+                   ArrayRef<SymbolResolution> Res,
+                   SectionResolverFn SectionResolver) {
   llvm::TimeTraceScope timeScope("LTO add regular LTO");
   RegularLTOState::AddedModule Mod;
   Expected<std::unique_ptr<Module>> MOrErr =
@@ -978,6 +984,21 @@ LTO::addRegularLTO(InputFile &Input, ArrayRef<SymbolResolution> InputRes,
   if (Error Err = M.materializeMetadata())
     return std::move(Err);
 
+  if (SectionResolver) {
+    SmallVector<GlobalValue *, 4> Keep;
+    for (GlobalObject &GO : M.global_objects()) {
+      if (GO.isDeclaration() || GO.getName().starts_with("llvm."))
+        continue;
+      if (GO.hasSection()) {
+        auto Res = SectionResolver(GO.getSection());
+        if (Res.Keep)
+          Keep.push_back(&GO);
+      }
+    }
+    if (!Keep.empty())
+      appendToCompilerUsed(M, Keep);
+  }
+
   if (LTOMode == LTOK_UnifiedRegular) {
     // cfi.functions metadata is intended to be used with ThinLTO and may
     // trigger invalid IR transformations if they are present when doing regular
@@ -1181,7 +1202,8 @@ Error LTO::linkRegularLTO(RegularLTOState::AddedModule Mod,
 // Add a ThinLTO module to the link.
 Expected<ArrayRef<SymbolResolution>>
 LTO::addThinLTO(BitcodeModule BM, ArrayRef<InputFile::Symbol> Syms,
-                ArrayRef<SymbolResolution> Res) {
+                ArrayRef<SymbolResolution> Res,
+                SectionResolverFn SectionResolver) {
   llvm::TimeTraceScope timeScope("LTO add thin LTO");
   const auto BMID = BM.getModuleIdentifier();
   ArrayRef<SymbolResolution> ResTmp = Res;
@@ -1209,6 +1231,23 @@ LTO::addThinLTO(BitcodeModule BM, ArrayRef<InputFile::Symbol> Syms,
             }
           }))
     return Err;
+
+  if (SectionResolver) {
+    for (auto &Entry : ThinLTO.CombinedIndex.sectionInfos()) {
+      GlobalValue::GUID GUID = Entry.first;
+      ValueInfo VI = ThinLTO.CombinedIndex.getValueInfo(GUID);
+      if (VI) {
+        for (const auto &Summary : VI.getSummaryList()) {
+          if (Summary->modulePath() == BMID) {
+            auto Res = SectionResolver(Entry.second.SectionName);
+            Entry.second.Keep = Res.Keep;
+            break;
+          }
+        }
+      }
+    }
+  }
+
   LLVM_DEBUG(dbgs() << "Module " << BMID << "\n");
 
   for (const InputFile::Symbol &Sym : Syms) {
@@ -1339,6 +1378,13 @@ Error LTO::run(AddStreamFn AddStream, FileCache Cache) {
     if (Res.second.VisibleOutsideSummary && Res.second.Prevailing)
       GUIDPreservedSymbols.insert(GUID);
 
+    auto It = ThinLTO.CombinedIndex.sectionInfos().find(GUID);
+    if (It != ThinLTO.CombinedIndex.sectionInfos().end() && It->second.Keep &&
+        Res.second.Prevailing) {
+      Res.second.Partition = GlobalResolution::External;
+      Res.second.VisibleOutsideSummary = true;
+    }
+
     if (Res.second.ExportDynamic)
       DynamicExportSymbols.insert(GUID);
 
@@ -1346,6 +1392,10 @@ Error LTO::run(AddStreamFn AddStream, FileCache Cache) {
         Res.second.Prevailing ? PrevailingType::Yes : PrevailingType::No;
   }
 
+  for (const auto &Entry : ThinLTO.CombinedIndex.sectionInfos())
+    if (Entry.second.Keep)
+      GUIDPreservedSymbols.insert(Entry.first);
+
   auto isPrevailing = [&](GlobalValue::GUID G) {
     auto It = GUIDPrevailingResolutions.find(G);
     if (It == GUIDPrevailingResolutions.end())
diff --git a/llvm/lib/Object/IRSymtab.cpp b/llvm/lib/Object/IRSymtab.cpp
index 0f404be9bd4a8..b56ae10dcf7fb 100644
--- a/llvm/lib/Object/IRSymtab.cpp
+++ b/llvm/lib/Object/IRSymtab.cpp
@@ -220,7 +220,6 @@ Error Builder::addSymbol(const ModuleSymbolTable &Msymtab,
     Unc = &Uncommons.back();
     *Unc = {};
     setStr(Unc->COFFWeakExternFallbackName, "");
-    setStr(Unc->SectionName, "");
     return *Unc;
   };
 
@@ -311,9 +310,6 @@ Error Builder::addSymbol(const ModuleSymbolTable &Msymtab,
     }
   }
 
-  if (!GO->getSection().empty())
-    setStr(Uncommon().SectionName, Saver.save(GO->getSection()));
-
   return Error::success();
 }
 
diff --git a/llvm/test/Assembler/thinlto-sectioninfo.ll b/llvm/test/Assembler/thinlto-sectioninfo.ll
new file mode 100644
index 0000000000000..0457ba72718f0
--- /dev/null
+++ b/llvm/test/Assembler/thinlto-sectioninfo.ll
@@ -0,0 +1,24 @@
+; Test sectionInfo parsing
+; RUN: llvm-as %s -o - | llvm-dis -o - | FileCheck %s
+
+ at a = internal global i32 0, section ".foodata", align 4, !guid !0
+ at b = dso_local global ptr @a, section ".foodata", align 8, !guid !1
+
+define dso_local void @g() section ".footext" !guid !8 {
+entry:
+  ret void
+}
+
+!0 = !{i64 -4514776715853495485}
+!1 = !{i64 -1427730249719747694}
+!8 = !{i64 -5300342847281564238}
+
+^0 = module: (path: "[Regular LTO]", hash: (0, 0, 0, 0, 0))
+^1 = gv: (name: "g", summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 1, canAutoHide: 0, importType: definition, noRenameOnPromotion: 0), insts: 1, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 1, alwaysInline: 0, noUnwind: 1, mayThrow: 0, hasUnknownCall: 0, mustBeUnreachable: 0))), sectionInfo: (sectionName: ".footext", outputSectionName: "", keep: false)) ; guid = 13146401226427987378
+^2 = gv: (name: "a", summaries: (variable: (module: ^0, flags: (linkage: internal, visibility: default, notEligibleToImport: 1, live: 0, dsoLocal: 1, canAutoHide: 0, importType: definition, noRenameOnPromotion: 0), varFlags: (readonly: 1, writeonly: 1, constant: 0))), sectionInfo: (sectionName: ".foodata", outputSectionName: "", keep: false)) ; guid = 13931967357856056131
+^3 = gv: (name: "b", summaries: (variable: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 1, live: 0, dsoLocal: 1, canAutoHide: 0, importType: definition, noRenameOnPromotion: 0), varFlags: (readonly: 1, writeonly: 1, constant: 0), refs: (^2))), sectionInfo: (sectionName: ".foodata", outputSectionName: "", keep: false)) ; guid = 17019013823989803922
+^4 = blockcount: 0
+
+; CHECK: sectionInfo: (sectionName: ".footext", outputSectionName: "", keep: false)
+; CHECK: sectionInfo: (sectionName: ".foodata", outputSectionName: "", keep: false)
+; CHECK: sectionInfo: (sectionName: ".foodata", outputSectionName: "", keep: false)
diff --git a/llvm/tools/gold/gold-plugin.cpp b/llvm/tools/gold/gold-plugin.cpp
index 98aafd635651d..d1e97129ee02e 100644
--- a/llvm/tools/gold/gold-plugin.cpp
+++ b/llvm/tools/gold/gold-plugin.cpp
@@ -799,13 +799,6 @@ static void addModule(LTO &Lto, claimed_file &F, const void *View,
       break;
     }
 
-    // If the symbol has a C identifier section name, we need to mark
-    // it as visible to a regular object so that LTO will keep it around
-    // to ensure the linker generates special __start_<secname> and
-    // __stop_<secname> symbols which may be used elsewhere.
-    if (isValidCIdentifier(InpSym.getSectionName()))
-      R.VisibleToRegularObj = true;
-
     if (Resolution != LDPR_RESOLVED_DYN && Resolution != LDPR_UNDEF &&
         (IsExecutable || !Res.DefaultVisibility))
       R.FinalDefinitionInLinkageUnit = true;
@@ -819,7 +812,16 @@ static void addModule(LTO &Lto, claimed_file &F, const void *View,
     freeSymName(Sym);
   }
 
-  check(Lto.add(std::move(Input), Resols),
+  auto resolver = [this, &f](StringRef sectionName) {
+    // If the symbol has a C identifier section name, we need to mark
+    // it as visible to a regular object so that LTO will keep it around
+    // to ensure the linker generates special __start_<secname> and
+    // __stop_<secname> symbols which may be used elsewhere.
+    lto::SectionResolution r;
+    r.Keep = isValidCIdentifier(sectionName);
+    return r;
+  };
+  check(Lto.add(std::move(Input), Resols, resolver),
         std::string("Failed to link module ") + F.name);
 }
 
diff --git a/llvm/tools/llvm-lto2/llvm-lto2.cpp b/llvm/tools/llvm-lto2/llvm-lto2.cpp
index 4bcf04df89ac9..a37ba2ad16acd 100644
--- a/llvm/tools/llvm-lto2/llvm-lto2.cpp
+++ b/llvm/tools/llvm-lto2/llvm-lto2.cpp
@@ -633,9 +633,6 @@ static int dumpSymtab(int argc, char **argv) {
       if (TT.isOSBinFormatCOFF() && Sym.isWeak() && Sym.isIndirect())
         outs() << "         fallback " << Sym.getCOFFWeakExternalFallback()
                << '\n';
-
-      if (!Sym.getSectionName().empty())
-        outs() << "         section " << Sym.getSectionName() << "\n";
     }
 
     outs() << '\n';

>From 19739ce58a9a0ff81d55b8fa40ef7cade2f8705f Mon Sep 17 00:00:00 2001
From: Eli Friedman <efriedma at qti.qualcomm.com>
Date: Wed, 22 Jul 2026 12:56:47 -0700
Subject: [PATCH 2/2] Fix gold plugin.

---
 llvm/tools/gold/gold-plugin.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/llvm/tools/gold/gold-plugin.cpp b/llvm/tools/gold/gold-plugin.cpp
index d1e97129ee02e..3b9fdd11e26a7 100644
--- a/llvm/tools/gold/gold-plugin.cpp
+++ b/llvm/tools/gold/gold-plugin.cpp
@@ -757,7 +757,6 @@ static void addModule(LTO &Lto, claimed_file &F, const void *View,
   assert(InputFileSyms.size() == F.syms.size());
   std::vector<SymbolResolution> Resols(F.syms.size());
   for (ld_plugin_symbol &Sym : F.syms) {
-    const InputFile::Symbol &InpSym = InputFileSyms[SymNum];
     SymbolResolution &R = Resols[SymNum++];
 
     ld_plugin_symbol_resolution Resolution =
@@ -812,7 +811,7 @@ static void addModule(LTO &Lto, claimed_file &F, const void *View,
     freeSymName(Sym);
   }
 
-  auto resolver = [this, &f](StringRef sectionName) {
+  auto resolver = [](StringRef sectionName) {
     // If the symbol has a C identifier section name, we need to mark
     // it as visible to a regular object so that LTO will keep it around
     // to ensure the linker generates special __start_<secname> and



More information about the llvm-commits mailing list