[clang] [lld] [llvm] [WIP] LTO with linker scripts, end-to-end. (PR #204282)
Sudharsan Veeravalli via cfe-commits
cfe-commits at lists.llvm.org
Tue Jul 14 22:20:52 PDT 2026
https://github.com/svs-quic updated https://github.com/llvm/llvm-project/pull/204282
>From c923c1d67a70a4c3ca051161ff71854ffc7cbeac Mon Sep 17 00:00:00 2001
From: Eli Friedman <efriedma at qti.qualcomm.com>
Date: Tue, 16 Jun 2026 14:43:36 -0700
Subject: [PATCH 1/3] [WIP] LTO with linker scripts, end-to-end.
"LTO with linker scripts" is a set of features that makes LTO interact
in a more predictable way with linker scripts: the semantics of linker
script rules are much closer to non-LTO semantics.
This includes all changes, end-to-end, required to enable LTO with
linker scripts. All the changes are guarded under flags: a clang flag
-flto-linker-scripts, and an lld flag --lto-linker-scripts.
Posting as one large patch so the whole design can be easily reviewed;
I'll split into patches as appropriate once the approach is approved.
RFC with more details will be posted to Discourse.
---
clang/include/clang/Basic/CodeGenOptions.def | 3 +
clang/include/clang/Options/Options.td | 7 +
clang/lib/CodeGen/BackendUtil.cpp | 6 +
clang/lib/Driver/ToolChains/Clang.cpp | 3 +
clang/test/CodeGen/lto-linker-scripts.c | 4 +
lld/ELF/Config.h | 1 +
lld/ELF/Driver.cpp | 2 +
lld/ELF/InputFiles.cpp | 12 +-
lld/ELF/LTO.cpp | 9 ++
lld/ELF/LinkerScript.cpp | 58 ++++++-
lld/ELF/LinkerScript.h | 9 ++
lld/ELF/Options.td | 5 +
.../CheckWrapSymbolResolution.test | 67 ++++++++
.../lto-with-linker-scripts/LTOAsmAlias.test | 41 +++++
.../LTOKeepSymbol.test | 63 ++++++++
.../LTOPreemptSymbol.test | 53 +++++++
.../lto-with-linker-scripts/LTOSaveTemps.test | 63 ++++++++
.../lto/lto-with-linker-scripts/LTOUsed.test | 19 +++
.../LocalsLinkerScriptSections.test | 45 ++++++
.../LocalsSameNameTwoFiles.test | 46 ++++++
.../NamespaceOverride.test | 72 +++++++++
.../lto-with-linker-scripts/NoCrossRefs.test | 107 +++++++++++++
.../OutputSection.test | 22 +++
.../SectionsWithNoSyms.test | 47 ++++++
.../ThinLTOCaching.test | 110 +++++++++++++
.../ThinLTOSameFileNameArchive.test | 63 ++++++++
.../ThinLTOWithLS.test | 65 ++++++++
.../lto/lto-with-linker-scripts/ltokeep.test | 44 ++++++
.../ltosectionattribute.test | 46 ++++++
.../llvm/CodeGen/AssignSectionsToGlobals.h | 26 ++++
llvm/include/llvm/LTO/LTO.h | 9 +-
llvm/include/llvm/Object/IRSymtab.h | 2 +
llvm/include/llvm/Object/SymbolicFile.h | 2 +
llvm/lib/Analysis/InlineCost.cpp | 23 +++
llvm/lib/CodeGen/AssignSectionsToGlobals.cpp | 145 ++++++++++++++++++
llvm/lib/CodeGen/CMakeLists.txt | 1 +
llvm/lib/LTO/LTO.cpp | 53 ++++++-
llvm/lib/Object/IRSymtab.cpp | 2 +
llvm/lib/Object/ModuleSymbolTable.cpp | 2 +-
39 files changed, 1343 insertions(+), 14 deletions(-)
create mode 100644 clang/test/CodeGen/lto-linker-scripts.c
create mode 100644 lld/test/ELF/lto/lto-with-linker-scripts/CheckWrapSymbolResolution.test
create mode 100644 lld/test/ELF/lto/lto-with-linker-scripts/LTOAsmAlias.test
create mode 100644 lld/test/ELF/lto/lto-with-linker-scripts/LTOKeepSymbol.test
create mode 100644 lld/test/ELF/lto/lto-with-linker-scripts/LTOPreemptSymbol.test
create mode 100644 lld/test/ELF/lto/lto-with-linker-scripts/LTOSaveTemps.test
create mode 100644 lld/test/ELF/lto/lto-with-linker-scripts/LTOUsed.test
create mode 100644 lld/test/ELF/lto/lto-with-linker-scripts/LocalsLinkerScriptSections.test
create mode 100644 lld/test/ELF/lto/lto-with-linker-scripts/LocalsSameNameTwoFiles.test
create mode 100644 lld/test/ELF/lto/lto-with-linker-scripts/NamespaceOverride.test
create mode 100644 lld/test/ELF/lto/lto-with-linker-scripts/NoCrossRefs.test
create mode 100644 lld/test/ELF/lto/lto-with-linker-scripts/OutputSection.test
create mode 100644 lld/test/ELF/lto/lto-with-linker-scripts/SectionsWithNoSyms.test
create mode 100644 lld/test/ELF/lto/lto-with-linker-scripts/ThinLTOCaching.test
create mode 100644 lld/test/ELF/lto/lto-with-linker-scripts/ThinLTOSameFileNameArchive.test
create mode 100644 lld/test/ELF/lto/lto-with-linker-scripts/ThinLTOWithLS.test
create mode 100644 lld/test/ELF/lto/lto-with-linker-scripts/ltokeep.test
create mode 100644 lld/test/ELF/lto/lto-with-linker-scripts/ltosectionattribute.test
create mode 100644 llvm/include/llvm/CodeGen/AssignSectionsToGlobals.h
create mode 100644 llvm/lib/CodeGen/AssignSectionsToGlobals.cpp
diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def
index aa36de6edecbf..e13d73df2bc85 100644
--- a/clang/include/clang/Basic/CodeGenOptions.def
+++ b/clang/include/clang/Basic/CodeGenOptions.def
@@ -532,6 +532,9 @@ ENUM_CODEGENOPT(WinControlFlowGuardMechanism, ControlFlowGuardMechanism,
/// Adds attributes that prevent outlining (`-mno-outline`)
CODEGENOPT(DisableOutlining, 1, 0, Benign)
+/// Emit section annotations in bitcode files for LTO.
+CODEGENOPT(LTOLinkerScripts, 1, 0, Benign)
+
/// FIXME: Make DebugOptions its own top-level .def file.
#include "DebugOptions.def"
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index 753e3ac1b74a5..9c4630f002bf3 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -9754,3 +9754,10 @@ def wasm_opt : Flag<["--"], "wasm-opt">,
Group<m_Group>,
HelpText<"Enable the wasm-opt optimizer (default)">,
MarshallingInfoNegativeFlag<LangOpts<"NoWasmOpt">>;
+
+defm lto_linker_scripts : BoolFOption<"lto-linker-scripts",
+ CodeGenOpts<"LTOLinkerScripts">, DefaultFalse,
+ PosFlag<SetTrue, [], [ClangOption, CC1Option],
+ "Emit bitcode annotations to preserve linker script semantics "
+ "during LTO.">,
+ NegFlag<SetFalse, [], [ClangOption]>>;
diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp
index a46a25c4492f2..fdf30609c0b5f 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -25,6 +25,7 @@
#include "llvm/Bitcode/BitcodeReader.h"
#include "llvm/Bitcode/BitcodeWriter.h"
#include "llvm/Bitcode/BitcodeWriterPass.h"
+#include "llvm/CodeGen/AssignSectionsToGlobals.h"
#include "llvm/CodeGen/TargetSubtargetInfo.h"
#include "llvm/Config/llvm-config.h"
#include "llvm/Frontend/Driver/CodeGenOptions.h"
@@ -1142,6 +1143,11 @@ void EmitAssemblyHelper::RunOptimizationPipeline(
if (!actionRequiresCodeGen(Action) && CodeGenOpts.VerifyModule)
MPM.addPass(VerifierPass());
+ if ((Action == Backend_EmitBC || Action == Backend_EmitLL) &&
+ CodeGenOpts.LTOLinkerScripts && TM) {
+ MPM.addPass(AssignSectionsToGlobalsPass(TM.get()));
+ }
+
if (Action == Backend_EmitBC || Action == Backend_EmitLL ||
CodeGenOpts.FatLTO) {
if (CodeGenOpts.PrepareForThinLTO && !CodeGenOpts.DisableLLVMPasses) {
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 2b415e60d5331..40f349fabe7d0 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -6431,6 +6431,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
Args.AddLastArg(CmdArgs, options::OPT_fconvergent_functions,
options::OPT_fno_convergent_functions);
+ Args.addOptInFlag(CmdArgs, options::OPT_flto_linker_scripts,
+ options::OPT_fno_lto_linker_scripts);
+
// NVPTX doesn't support PGO or coverage
if (!Triple.isNVPTX())
addPGOAndCoverageFlags(TC, C, JA, Output, Args, SanitizeArgs, CmdArgs);
diff --git a/clang/test/CodeGen/lto-linker-scripts.c b/clang/test/CodeGen/lto-linker-scripts.c
new file mode 100644
index 0000000000000..52ded692d071e
--- /dev/null
+++ b/clang/test/CodeGen/lto-linker-scripts.c
@@ -0,0 +1,4 @@
+// RUN: %clang_cc1 -flto-linker-scripts -triple aarch64-linux-gnu -emit-llvm < %s | FileCheck %s
+// REQUIRES: aarch64-registered-target
+// CHECK: @x = global i32 0, section ".bss"
+int x;
diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h
index 485f5bf657d3e..e7617737894e2 100644
--- a/lld/ELF/Config.h
+++ b/lld/ELF/Config.h
@@ -362,6 +362,7 @@ struct Config {
bool ltoPGOWarnMismatch;
bool ltoDebugPassManager;
bool ltoEmitAsm;
+ bool ltoLinkerScripts;
bool ltoUniqueBasicBlockSectionNames;
bool ltoValidateAllVtablesHaveTypeInfos;
bool ltoWholeProgramVisibility;
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index 214cb5678f4d8..38a33e0bbdd80 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -1470,6 +1470,8 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &args) {
OPT_no_lto_pgo_warn_mismatch, true);
ctx.arg.ltoDebugPassManager = args.hasArg(OPT_lto_debug_pass_manager);
ctx.arg.ltoEmitAsm = args.hasArg(OPT_lto_emit_asm);
+ ctx.arg.ltoLinkerScripts =
+ args.hasFlag(OPT_lto_linker_scripts, OPT_no_lto_linker_scripts, false);
ctx.arg.ltoNewPmPasses = args.getLastArgValue(OPT_lto_newpm_passes);
ctx.arg.ltoWholeProgramVisibility =
args.hasFlag(OPT_lto_whole_program_visibility,
diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp
index 87ea8e890626e..3e8497957290e 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -1830,7 +1830,7 @@ BitcodeFile::BitcodeFile(Ctx &ctx, MemoryBufferRef mb, StringRef archiveName,
MemoryBufferRef mbref(mb.getBuffer(), name);
- obj = CHECK2(lto::InputFile::create(mbref), this);
+ obj = CHECK2(lto::InputFile::create(mbref, ctx.arg.ltoLinkerScripts), this);
obj->setArchivePathAndName(archiveName, mb.getBufferIdentifier());
Triple t(obj->getTargetTriple());
@@ -1854,7 +1854,13 @@ static uint8_t mapVisibility(GlobalValue::VisibilityTypes gvVisibility) {
static void createBitcodeSymbol(Ctx &ctx, Symbol *&sym,
const lto::InputFile::Symbol &objSym,
BitcodeFile &f) {
- uint8_t binding = objSym.isWeak() ? STB_WEAK : STB_GLOBAL;
+ uint8_t binding;
+ if (!objSym.isGlobal())
+ binding = STB_LOCAL;
+ else if (objSym.isWeak())
+ binding = STB_WEAK;
+ else
+ binding = STB_GLOBAL;
uint8_t type = objSym.isTLS() ? STT_TLS : STT_NOTYPE;
uint8_t visibility = mapVisibility(objSym.getVisibility());
@@ -1938,7 +1944,7 @@ void BitcodeFile::postParse() {
for (auto [i, irSym] : llvm::enumerate(obj->symbols())) {
const Symbol &sym = *symbols[i];
if (sym.file == this || !sym.isDefined() || irSym.isUndefined() ||
- irSym.isCommon() || irSym.isWeak())
+ !irSym.isGlobal() || irSym.isCommon() || irSym.isWeak())
continue;
int c = irSym.getComdatIndex();
if (c != -1 && !keptComdats[c])
diff --git a/lld/ELF/LTO.cpp b/lld/ELF/LTO.cpp
index 27be8859a3f65..89cf17716cd1c 100644
--- a/lld/ELF/LTO.cpp
+++ b/lld/ELF/LTO.cpp
@@ -9,6 +9,7 @@
#include "LTO.h"
#include "Config.h"
#include "InputFiles.h"
+#include "LinkerScript.h"
#include "SymbolTable.h"
#include "Symbols.h"
#include "lld/Common/ErrorHandler.h"
@@ -243,6 +244,8 @@ void BitcodeCompiler::add(BitcodeFile &f) {
ArrayRef<Symbol *> syms = f.getSymbols();
ArrayRef<lto::InputFile::Symbol> objSyms = obj.symbols();
+ if (ctx.arg.ltoLinkerScripts && ctx.script)
+ ctx.script->ltoInputFileMapping[obj.getName()] = &f;
std::vector<lto::SymbolResolution> resols(syms.size());
// Provide a resolution to the LTO API for each symbol.
@@ -294,6 +297,12 @@ void BitcodeCompiler::add(BitcodeFile &f) {
// (with --wrap) symbols because otherwise LTO would inline them while
// their values are still not final.
r.LinkerRedefined = sym->scriptDefined;
+
+ StringRef inputSection = objSym.getSectionName();
+ if (!inputSection.empty() && ctx.script && ctx.arg.ltoLinkerScripts) {
+ r.VisibleToRegularObj |= ctx.script->shouldKeep(inputSection, &f);
+ r.OutputSectionName = ctx.script->mapLTOSectionName(inputSection, &f);
+ }
}
checkError(ctx.e, ltoObj->add(std::move(f.obj), resols));
}
diff --git a/lld/ELF/LinkerScript.cpp b/lld/ELF/LinkerScript.cpp
index b9ed964b8c720..d36565ca22e5a 100644
--- a/lld/ELF/LinkerScript.cpp
+++ b/lld/ELF/LinkerScript.cpp
@@ -421,6 +421,15 @@ void LinkerScript::assignSymbol(SymbolAssignment *cmd, bool inSec) {
cmd->sym->type = v.type;
}
+bool LinkerScript::matchesFile(const InputSectionDescription *desc,
+ InputSectionBase *sec) const {
+ if (StringRef filename = sec->name.split("^^").second; !filename.empty()) {
+ if (const InputFile *file = ltoInputFileMapping.lookup(filename))
+ return desc->matchesFile(*file);
+ }
+ return desc->matchesFile(*sec->file);
+}
+
bool InputSectionDescription::matchesFile(const InputFile &file) const {
if (filePat.isTrivialMatchAll())
return true;
@@ -439,6 +448,15 @@ bool InputSectionDescription::matchesFile(const InputFile &file) const {
return matchesFileCache->second;
}
+bool LinkerScript::excludesFile(const SectionPattern *pat,
+ InputSectionBase *sec) const {
+ if (StringRef filename = sec->name.split("^^").second; !filename.empty()) {
+ if (const InputFile *file = ltoInputFileMapping.lookup(filename))
+ return pat->excludesFile(*file);
+ }
+ return pat->excludesFile(*sec->file);
+}
+
bool SectionPattern::excludesFile(const InputFile &file) const {
if (excludedFilePat.empty())
return false;
@@ -461,6 +479,20 @@ bool LinkerScript::shouldKeep(InputSectionBase *s) {
return false;
}
+bool LinkerScript::shouldKeep(StringRef name, InputFile *file) {
+ for (InputSectionDescription *id : keptSections) {
+ if (id->matchesFile(*file)) {
+ for (SectionPattern &p : id->sectionPatterns) {
+ if (p.sectionPat.match(name)) {
+ // FIXME: Also check flags? Need to synthesize.
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
// A helper function for the SORT() command.
static bool matchConstraints(ArrayRef<InputSectionBase *> sections,
ConstraintKind kind) {
@@ -528,6 +560,28 @@ static void sortInputSections(Ctx &ctx, MutableArrayRef<InputSectionBase *> vec,
sortSections(vec, outer);
}
+StringRef LinkerScript::mapLTOSectionName(StringRef inputSection,
+ InputFile *file) {
+ for (auto *cmd : sectionCommands) {
+ if (auto *os = dyn_cast<OutputDesc>(cmd)) {
+ for (auto *innercmd : os->osec.commands) {
+ if (auto *isd = dyn_cast<InputSectionDescription>(innercmd)) {
+ for (const SectionPattern &pat : isd->sectionPatterns) {
+ if (!pat.sectionPat.match(inputSection))
+ continue;
+
+ if (!isd->matchesFile(*file) || pat.excludesFile(*file))
+ continue;
+
+ return os->osec.name;
+ }
+ }
+ }
+ }
+ }
+ return "";
+}
+
// Compute and remember which sections the InputSectionDescription matches.
SmallVector<InputSectionBase *, 0>
LinkerScript::computeInputSections(const InputSectionDescription *cmd,
@@ -581,10 +635,10 @@ LinkerScript::computeInputSections(const InputSectionDescription *cmd,
continue;
// Check the name early to improve performance in the common case.
- if (!pat.sectionPat.match(sec->name))
+ if (!pat.sectionPat.match(sec->name.split("^^").first))
continue;
- if (!cmd->matchesFile(*sec->file) || pat.excludesFile(*sec->file) ||
+ if (!matchesFile(cmd, sec) || excludesFile(&pat, sec) ||
!flagsMatch(sec))
continue;
diff --git a/lld/ELF/LinkerScript.h b/lld/ELF/LinkerScript.h
index 80c4f564afabc..ad4f93f1a7fe8 100644
--- a/lld/ELF/LinkerScript.h
+++ b/lld/ELF/LinkerScript.h
@@ -377,6 +377,7 @@ class LinkerScript final {
bool needsInterpSection();
bool shouldKeep(InputSectionBase *s);
+ bool shouldKeep(StringRef name, InputFile *file);
std::pair<const OutputSection *, const Defined *> assignAddresses();
bool spillSections();
void erasePotentialSpillSections();
@@ -411,6 +412,12 @@ class LinkerScript final {
// undefined reference.
bool shouldAddProvideSym(StringRef symName);
+ StringRef mapLTOSectionName(StringRef inputSection, InputFile *file);
+
+ bool matchesFile(const InputSectionDescription *desc,
+ InputSectionBase *sec) const;
+ bool excludesFile(const SectionPattern *pat, InputSectionBase *sec) const;
+
// SECTIONS command list.
SmallVector<SectionCommand *, 0> sectionCommands;
@@ -469,6 +476,8 @@ class LinkerScript final {
// section descriptions. Multiple references allow for sections to spill from
// one output section to another.
llvm::DenseMap<llvm::CachedHashStringRef, SectionClassDesc *> sectionClasses;
+
+ llvm::StringMap<InputFile *> ltoInputFileMapping;
};
} // end namespace lld::elf
diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td
index 64c42eb49607d..0efb11f875486 100644
--- a/lld/ELF/Options.td
+++ b/lld/ELF/Options.td
@@ -737,6 +737,11 @@ defm thinlto_remote_compiler_arg: EEq<"thinlto-remote-compiler-arg",
defm fat_lto_objects: BB<"fat-lto-objects",
"Use the .llvm.lto section, which contains LLVM bitcode, in fat LTO object files to perform LTO.",
"Ignore the .llvm.lto section in relocatable object files (default).">;
+defm lto_linker_scripts
+ : BB<"lto-linker-scripts",
+ "Make LTO respect section mappings specified in linker scripts",
+ "Ignore section mappings specified in linker scripts during LTO "
+ "(default)">;
def: J<"plugin-opt=O">, Alias<lto_O>, HelpText<"Alias for --lto-O">;
def: F<"plugin-opt=debug-pass-manager">,
diff --git a/lld/test/ELF/lto/lto-with-linker-scripts/CheckWrapSymbolResolution.test b/lld/test/ELF/lto/lto-with-linker-scripts/CheckWrapSymbolResolution.test
new file mode 100644
index 0000000000000..4ed61882af5df
--- /dev/null
+++ b/lld/test/ELF/lto/lto-with-linker-scripts/CheckWrapSymbolResolution.test
@@ -0,0 +1,67 @@
+# REQUIRES: aarch64
+# RUN: rm -rf %t && split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple=aarch64-unknown-linux-gnu %t/1.s -o %t/1.o
+# RUN: llvm-as %t/2.ll -o %t/2.o
+# RUN: llvm-as %t/3.ll -o %t/3.o
+# RUN: llvm-as %t/w.ll -o %t/w.o
+# RUN: ld.lld -m aarch64elf --lto-linker-scripts %t/1.o %t/w.o %t/2.o %t/3.o \
+# RUN: -T %t/script.t --wrap foo -t -o %t/out
+# RUN: llvm-objdump -Cx %t/out | FileCheck %s
+
+# CHECK: f000000 {{.*}} F .wrap {{.*}} __wrap_foo
+
+#--- 1.s
+.text
+.section .text.main,"ax", at progbits
+.globl main
+.type main, at function
+main:
+ bl foo
+ ret
+.size main, .-main
+
+#--- 2.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+declare i32 @foo()
+
+define i32 @bar() section ".text.bar" {
+ %v = call i32 @foo()
+ ret i32 %v
+}
+
+#--- 3.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+declare i32 @foo()
+
+define i32 @b1() section ".text.b1" {
+ %v = call i32 @foo()
+ ret i32 %v
+}
+
+#--- w.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+define i32 @__wrap_foo() section ".text.__wrap_foo" {
+ ret i32 1
+}
+
+#--- script.t
+SECTIONS {
+ .near : {
+ *(.text.main)
+ *(.text.foo)
+ *(.text.bar)
+ *(.text.b1)
+ *(.text.b2)
+ }
+
+ . = 0xF000000;
+ .wrap : {
+ *(.text.__wrap_foo)
+ }
+}
diff --git a/lld/test/ELF/lto/lto-with-linker-scripts/LTOAsmAlias.test b/lld/test/ELF/lto/lto-with-linker-scripts/LTOAsmAlias.test
new file mode 100644
index 0000000000000..14a556b05af42
--- /dev/null
+++ b/lld/test/ELF/lto/lto-with-linker-scripts/LTOAsmAlias.test
@@ -0,0 +1,41 @@
+# REQUIRES: aarch64
+# RUN: rm -rf %t && split-file %s %t
+# RUN: llvm-as %t/1.ll -o %t/1.o
+# RUN: llvm-mc -filetype=obj -triple=aarch64-unknown-linux-gnu %t/2.s -o %t/2.o
+# RUN: ld.lld -m aarch64elf --lto-linker-scripts --gc-sections -e main -T %t/script.t \
+# RUN: %t/1.o %t/2.o -o %t/out
+# RUN: llvm-readelf -S -W %t/out | FileCheck --check-prefix=SECTIONS %s
+# RUN: llvm-readelf -s -W %t/out | FileCheck %s
+
+# SECTIONS: foo
+# SECTIONS-NOT: ] .text
+
+# CHECK-DAG: FUNC GLOBAL DEFAULT {{.*}} foo
+# CHECK-DAG: FUNC GLOBAL DEFAULT {{.*}} alias_foo
+# CHECK-DAG: FUNC GLOBAL DEFAULT {{.*}} main
+
+#--- 1.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+module asm ".global alias_foo"
+module asm ".set alias_foo, foo"
+
+define i32 @foo() section ".text.foo" {
+ ret i32 0
+}
+
+#--- 2.s
+.text
+.section .text.main,"ax", at progbits
+.globl main
+.type main, at function
+main:
+ bl alias_foo
+ ret
+.size main, .-main
+
+#--- script.t
+SECTIONS {
+foo : { *(.text.*) }
+}
diff --git a/lld/test/ELF/lto/lto-with-linker-scripts/LTOKeepSymbol.test b/lld/test/ELF/lto/lto-with-linker-scripts/LTOKeepSymbol.test
new file mode 100644
index 0000000000000..159a1b7478117
--- /dev/null
+++ b/lld/test/ELF/lto/lto-with-linker-scripts/LTOKeepSymbol.test
@@ -0,0 +1,63 @@
+# REQUIRES: aarch64
+
+# RUN: rm -rf %t && split-file %s %t
+# RUN: llvm-as %t/1.ll -o %t/1.o
+# RUN: ld.lld -m aarch64elf --lto-linker-scripts %t/1.o -plugin-opt=-function-sections \
+# RUN: -Map=%t/map.txt -T %t/script.t -o %t/out
+# RUN: FileCheck %s --check-prefix=KEEP < %t/map.txt
+
+# KEEP: .text.baz1
+# KEEP: .text.baz2
+# KEEP: .text.baz3
+# KEEP: .text.baz
+# KEEP: .rodata.sys_call_table
+
+#--- 1.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+ at sys_call_table = constant [2 x ptr] [ptr @baz, ptr @baz], section ".rodata.sys_call_table"
+
+define hidden i32 @foo() section ".text.foo" {
+ ret i32 0
+}
+
+define weak i32 @bar() section ".text.bar" {
+ ret i32 0
+}
+
+define void @baz1() section ".text.baz1" {
+ call void @baz2()
+ ret void
+}
+
+define void @baz2() section ".text.baz2" {
+ call void @baz3()
+ ret void
+}
+
+define void @baz3() section ".text.baz3" {
+ %a = call i32 @foo()
+ %b = call i32 @baz()
+ %c = add i32 %a, %b
+ %d = icmp eq i32 %c, -1
+ br i1 %d, label %hit, label %done
+
+hit:
+ call void asm sideeffect "", ""()
+ br label %done
+
+done:
+ ret void
+}
+
+define i32 @baz() section ".text.baz" {
+ ret i32 0
+}
+
+#--- script.t
+SECTIONS {
+ .baz : { KEEP(*(.text.baz*)) }
+ .keeprodata : { KEEP(*(.rodata*)) }
+ .mytext : { *(.text*) }
+}
diff --git a/lld/test/ELF/lto/lto-with-linker-scripts/LTOPreemptSymbol.test b/lld/test/ELF/lto/lto-with-linker-scripts/LTOPreemptSymbol.test
new file mode 100644
index 0000000000000..fa3d2ae2aa70e
--- /dev/null
+++ b/lld/test/ELF/lto/lto-with-linker-scripts/LTOPreemptSymbol.test
@@ -0,0 +1,53 @@
+# REQUIRES: aarch64
+# RUN: rm -rf %t && split-file %s %t
+# RUN: llvm-as %t/1.ll -o %t/1.o
+# RUN: llvm-mc -filetype=obj -triple=aarch64-unknown-linux-gnu %t/2.s -o %t/2.o
+# RUN: ld.lld -m aarch64elf --lto-linker-scripts %t/1.o %t/2.o -Map=%t/map.txt -T %t/script.t -o %t/out
+# RUN: FileCheck %s < %t/map.txt
+
+# CHECK: .tcm
+# CHECK-NEXT: 1.o
+# CHECK-NEXT: $x
+# CHECK-NEXT: bar
+# CHECK: .text
+# CHECK-NEXT: 2.o:(.text)
+# CHECK-NEXT: 2.o:(.text.foo)
+# CHECK-NEXT: $x
+# CHECK-NEXT: foo
+
+#--- 1.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+define weak i32 @foo() section ".tcm_static" {
+ ret i32 0
+}
+
+define weak i32 @bar() section ".tcm_static" {
+ ret i32 0
+}
+
+#--- 2.s
+.text
+.section .text.foo,"ax", at progbits
+.globl foo
+.type foo, at function
+foo:
+ mov x0, xzr
+ ret
+.size foo, .-foo
+
+.section .text.main,"ax", at progbits
+.globl main
+.type main, at function
+main:
+ bl foo
+ bl bar
+ ret
+.size main, .-main
+
+#--- script.t
+SECTIONS {
+ .tcm : { KEEP (*(.tcm_static)) }
+ .text : { *(.text*) }
+}
diff --git a/lld/test/ELF/lto/lto-with-linker-scripts/LTOSaveTemps.test b/lld/test/ELF/lto/lto-with-linker-scripts/LTOSaveTemps.test
new file mode 100644
index 0000000000000..48c99ae282cf0
--- /dev/null
+++ b/lld/test/ELF/lto/lto-with-linker-scripts/LTOSaveTemps.test
@@ -0,0 +1,63 @@
+# REQUIRES: aarch64
+# RUN: rm -rf %t && split-file %s %t
+# RUN: opt -module-summary %t/1.ll -o %t/1.1.o
+# RUN: opt -module-summary %t/2.ll -o %t/1.2.o
+# RUN: llvm-mc -filetype=obj -triple=aarch64-unknown-linux-gnu %t/3.s -o %t/1.3.o
+# RUN: ld.lld -m aarch64elf --lto-linker-scripts --gc-sections -save-temps -e main \
+# RUN: -T %t/script.t %t/1.1.o %t/1.2.o %t/1.3.o -o %t/1.out
+# RUN: llvm-readelf -S -W %t/1.out | FileCheck --check-prefix=SECTIONS %s
+# RUN: llvm-as %t/1.ll -o %t/2.1.o
+# RUN: llvm-as %t/2.ll -o %t/2.2.o
+# RUN: ld.lld -m aarch64elf --lto-linker-scripts --gc-sections -save-temps -e main \
+# RUN: -T %t/script.t %t/2.1.o %t/2.2.o %t/1.3.o -o %t/2.out
+# RUN: llvm-readelf -S -W %t/2.out | FileCheck --check-prefix=REGSECTIONS %s
+
+# With thin LTO, IPSCCP propagates the constant return value of otherfun2, so .special gets GC'd.
+# SECTIONS: .alltext
+# SECTIONS-NOT: ] .text
+
+# With regular LTO, IPSCCP propagates the constant return value of otherfun2, so .special gets GC'd.
+# REGSECTIONS: .alltext
+# REGSECTIONS-NOT: ] .text
+
+#--- 1.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+declare i32 @otherfun()
+declare i32 @otherfun2()
+
+define i32 @myfun() section ".text.myfun" {
+ %a = call i32 @otherfun()
+ %b = call i32 @otherfun2()
+ %c = add i32 %a, %b
+ ret i32 %c
+}
+
+#--- 2.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+define i32 @otherfun() section ".text.otherfun" {
+ ret i32 23
+}
+
+define i32 @otherfun2() section ".text.otherfun2" {
+ ret i32 42
+}
+
+#--- 3.s
+.text
+.section .text.main,"ax", at progbits
+.globl main
+.type main, at function
+main:
+ bl myfun
+ ret
+.size main, .-main
+
+#--- script.t
+SECTIONS {
+ .special : { *.2.o(.text.otherfun2) }
+ .alltext : { *(.text.*) }
+}
diff --git a/lld/test/ELF/lto/lto-with-linker-scripts/LTOUsed.test b/lld/test/ELF/lto/lto-with-linker-scripts/LTOUsed.test
new file mode 100644
index 0000000000000..cb81aadcc9b3f
--- /dev/null
+++ b/lld/test/ELF/lto/lto-with-linker-scripts/LTOUsed.test
@@ -0,0 +1,19 @@
+# REQUIRES: aarch64
+# RUN: rm -rf %t && split-file %s %t
+# RUN: llvm-as %t/used.ll -o %t/used.o
+# RUN: ld.lld -m aarch64elf --lto-linker-scripts %t/used.o -e foo -o %t/out
+# RUN: llvm-readelf -s -W %t/out | FileCheck %s
+
+# CHECK: val
+
+#--- used.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+ at val = global i32 2, section ".data.val", align 4
+ at llvm.used = appending global [1 x ptr] [ptr @val], section "llvm.metadata"
+
+define i32 @foo() section ".text.foo" {
+ %v = load volatile i32, ptr @val
+ ret i32 %v
+}
diff --git a/lld/test/ELF/lto/lto-with-linker-scripts/LocalsLinkerScriptSections.test b/lld/test/ELF/lto/lto-with-linker-scripts/LocalsLinkerScriptSections.test
new file mode 100644
index 0000000000000..4c534256f81f6
--- /dev/null
+++ b/lld/test/ELF/lto/lto-with-linker-scripts/LocalsLinkerScriptSections.test
@@ -0,0 +1,45 @@
+# REQUIRES: aarch64
+# RUN: rm -rf %t && split-file %s %t
+# RUN: llvm-as %t/1.ll -o %t/1.o
+# RUN: ld.lld -m aarch64elf --lto-linker-scripts -e main -T %t/script.t \
+# RUN: -plugin-opt=O0 -plugin-opt=-data-sections -plugin-opt=-function-sections %t/1.o -o %t/out
+# RUN: llvm-readelf -S %t/out | FileCheck %s --check-prefix=SEC
+
+# SEC: .myfoo PROGBITS
+# SEC: .mybar PROGBITS
+# SEC: .mybaz PROGBITS
+
+#--- 1.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+define internal i32 @foo(i32 %x) section ".text.foo" {
+ %m = mul i32 %x, 10
+ ret i32 %m
+}
+
+define i32 @bar(i32 %y) section ".text.bar" {
+ %m = mul i32 %y, 10
+ %v = call i32 @foo(i32 %m)
+ ret i32 %v
+}
+
+define i32 @baz(i32 %z) section ".text.baz" {
+ %m = mul i32 %z, 20
+ %v = call i32 @foo(i32 %m)
+ ret i32 %v
+}
+
+define i32 @main(i32 %argc) section ".text.main" {
+ %a = call i32 @bar(i32 %argc)
+ %b = call i32 @baz(i32 %argc)
+ %c = add i32 %a, %b
+ ret i32 %c
+}
+
+#--- script.t
+SECTIONS {
+ .myfoo : { *(.text.foo)}
+ .mybar : { *1.o(.text.bar)}
+ .mybaz : { *1.o(.text.baz)}
+}
diff --git a/lld/test/ELF/lto/lto-with-linker-scripts/LocalsSameNameTwoFiles.test b/lld/test/ELF/lto/lto-with-linker-scripts/LocalsSameNameTwoFiles.test
new file mode 100644
index 0000000000000..8ec4fa6557a6d
--- /dev/null
+++ b/lld/test/ELF/lto/lto-with-linker-scripts/LocalsSameNameTwoFiles.test
@@ -0,0 +1,46 @@
+# REQUIRES: aarch64
+
+# RUN: rm -rf %t && split-file %s %t
+# RUN: llvm-as %t/1.ll -o %t/1.o
+# RUN: llvm-as %t/2.ll -o %t/2.o
+# RUN: ld.lld -m aarch64elf --lto-linker-scripts -T %t/script.t -plugin-opt=O0 \
+# RUN: -plugin-opt=-data-sections -plugin-opt=-function-sections %t/1.o %t/2.o -o %t/out
+# RUN: llvm-readelf -S -W %t/out | FileCheck %s --check-prefix=SEC
+
+# SEC: .mydata1 PROGBITS
+# SEC: .mydata2 PROGBITS
+
+#--- 1.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+ at bar = internal global i32 20, section ".data.bar"
+
+declare i32 @baz()
+
+define i32 @foo() section ".text.foo" {
+ %a = load i32, ptr @bar
+ %b = call i32 @baz()
+ %c = add i32 %a, %b
+ ret i32 %c
+}
+
+#--- 2.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+ at bar = internal global i64 30, section ".data.bar"
+
+define i32 @baz() section ".text.baz" {
+ %a = load i64, ptr @bar
+ %b = trunc i64 %a to i32
+ ret i32 %b
+}
+
+#--- script.t
+SECTIONS {
+ .text.foo : { *1.o(.text.foo) }
+ .text.baz : { *1.o(.text.baz) }
+ .mydata1 : { *1.o(.data.bar) }
+ .mydata2 : { *2.o(.data.bar) }
+}
diff --git a/lld/test/ELF/lto/lto-with-linker-scripts/NamespaceOverride.test b/lld/test/ELF/lto/lto-with-linker-scripts/NamespaceOverride.test
new file mode 100644
index 0000000000000..13cf727dd70f4
--- /dev/null
+++ b/lld/test/ELF/lto/lto-with-linker-scripts/NamespaceOverride.test
@@ -0,0 +1,72 @@
+# REQUIRES: aarch64
+
+# RUN: rm -rf %t && split-file %s %t
+# RUN: llvm-as %t/A.ll -o %t/A.o
+# RUN: llvm-mc -filetype=obj -triple=aarch64-unknown-linux-gnu %t/B.s -o %t/B.o
+# RUN: ld.lld -m aarch64elf --lto-linker-scripts -Map=%t/map.txt -T %t/script.t %t/A.o %t/B.o -o %t/out
+# RUN: FileCheck %s --check-prefix=MAP < %t/map.txt
+
+# MAP: .foo
+# MAP: __foo_A
+# MAP: .text.Afun1
+# MAP: .text.Afun2
+# MAP: __foo_B
+# MAP: .text.Bfun1
+# MAP: .text.Bfun2
+# MAP: .text.main
+# MAP: __foo_end
+
+#--- A.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+declare ptr @Bfun2(i32)
+
+define internal ptr @Afun1(i32 %x) noinline section ".text.Afun1" {
+ %p = call ptr @Bfun2(i32 0)
+ ret ptr %p
+}
+
+define ptr @Afun2(i32 %y) section ".text.Afun2" {
+ %p = call ptr @Afun1(i32 0)
+ ret ptr %p
+}
+
+#--- B.s
+.text
+.section .text.Bfun1,"ax", at progbits
+.type Bfun1, at function
+Bfun1:
+ mov x0, xzr
+ ret
+.size Bfun1, .-Bfun1
+
+.section .text.Bfun2,"ax", at progbits
+.globl Bfun2
+.type Bfun2, at function
+Bfun2:
+ b Bfun1
+.size Bfun2, .-Bfun2
+
+.section .text.main,"ax", at progbits
+.globl main
+.type main, at function
+main:
+ mov x0, xzr
+ bl Afun2
+ mov x0, xzr
+ bl Bfun2
+ mov x0, xzr
+ ret
+.size main, .-main
+
+#--- script.t
+SECTIONS {
+ .foo : {
+ __foo_A = .;
+ *A.o(.text.*)
+ __foo_B = .;
+ *B.o(.text.*)
+ __foo_end = .;
+ }
+}
diff --git a/lld/test/ELF/lto/lto-with-linker-scripts/NoCrossRefs.test b/lld/test/ELF/lto/lto-with-linker-scripts/NoCrossRefs.test
new file mode 100644
index 0000000000000..118700ffe44e8
--- /dev/null
+++ b/lld/test/ELF/lto/lto-with-linker-scripts/NoCrossRefs.test
@@ -0,0 +1,107 @@
+# REQUIRES: aarch64
+
+# RUN: rm -rf %t && split-file %s %t
+# RUN: llvm-as %t/1.ll -o %t/1.o
+# RUN: mkdir -p %t/archive
+# RUN: llvm-as %t/2.ll -o %t/archive/2.o
+# RUN: llvm-ar cr %t/lib1.a %t/archive/2.o
+# RUN: not ld.lld -m aarch64elf %t/1.o -T %t/script.t %t/lib1.a -e main \
+# RUN: -plugin-opt=-function-sections -o %t/1.out --error-limit 0 2>&1 | \
+# RUN: FileCheck %s --check-prefix=REGULAR
+# RUN: ld.lld -m aarch64elf %t/1.o -T %t/script.gc %t/lib1.a --gc-sections -o %t/2.out --entry=main
+# RUN: not ld.lld -m aarch64elf %t/1.o -T %t/script.dbg %t/lib1.a -e main -o %t/3.out 2>&1 | \
+# RUN: FileCheck %s --check-prefix=DBG
+
+# Test normal cross-reference prohibition between commons and sections.
+# REGULAR: .text.main{{.*}}: prohibited cross reference from '.text' to '.text.baz' in '.baz'
+
+# Test a cross reference between debug and text. The script keeps .debug_info as orphan,
+# which also checks orphan accounting for NOCROSSREFS.
+# DBG: .debug_info{{.*}}: prohibited cross reference from '.debug_info' to '.text.main' in '.text'
+
+#--- 1.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+ at common = common global i32 0, align 4
+ at another_common = external global [100 x i32], align 16
+ at z = external global i32, align 4
+ at x = global ptr @z, section ".data.x", align 8
+
+define i32 @baz() noinline optnone section ".text.baz" !dbg !8 {
+entry:
+ %v = load i32, ptr @z, align 4, !dbg !10
+ ret i32 %v, !dbg !11
+}
+
+define i32 @main() noinline optnone section ".text.main" !dbg !12 {
+entry:
+ store i32 1, ptr @common, align 4, !dbg !13
+ %p = getelementptr inbounds [100 x i32], ptr @another_common, i64 0, i64 0, !dbg !14
+ store i32 1, ptr %p, align 4, !dbg !14
+ %a = load i32, ptr @common, align 4, !dbg !15
+ %b = load i32, ptr %p, align 4, !dbg !16
+ %c = add i32 %a, %b, !dbg !17
+ %d = call i32 @baz(), !dbg !18
+ %e = add i32 %c, %d, !dbg !19
+ ret i32 %e, !dbg !20
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!4, !5}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !DIFile(filename: "1.c", directory: "/")
+!2 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!3 = !DISubroutineType(types: !{!2})
+!4 = !{i32 2, !"Dwarf Version", i32 4}
+!5 = !{i32 2, !"Debug Info Version", i32 3}
+!8 = distinct !DISubprogram(name: "baz", scope: !1, file: !1, line: 6, type: !3, scopeLine: 6, spFlags: DISPFlagDefinition, unit: !0)
+!10 = !DILocation(line: 6, column: 24, scope: !8)
+!11 = !DILocation(line: 6, column: 17, scope: !8)
+!12 = distinct !DISubprogram(name: "main", scope: !1, file: !1, line: 8, type: !3, scopeLine: 8, spFlags: DISPFlagDefinition, unit: !0)
+!13 = !DILocation(line: 9, column: 3, scope: !12)
+!14 = !DILocation(line: 10, column: 3, scope: !12)
+!15 = !DILocation(line: 11, column: 10, scope: !12)
+!16 = !DILocation(line: 11, column: 19, scope: !12)
+!17 = !DILocation(line: 11, column: 17, scope: !12)
+!18 = !DILocation(line: 11, column: 36, scope: !12)
+!19 = !DILocation(line: 11, column: 28, scope: !12)
+!20 = !DILocation(line: 11, column: 3, scope: !12)
+
+#--- 2.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+ at z = global i32 1, section ".data.z", align 4
+ at another_common = global [100 x i32] zeroinitializer, section ".sdata.another_common", align 16
+
+#--- script.t
+NOCROSSREFS( .baz .text .bss .data .xsection .zsection)
+SECTIONS {
+ .zsection : { *(.data.z) }
+ .xsection : { *(.data.x) }
+ .baz : { *(.text.baz) }
+ .text : { *(.text.*) }
+ .bss : { *(.sdata*) }
+}
+
+#--- script.gc
+NOCROSSREFS(.text .xsection)
+SECTIONS {
+ .zsection : { *(.data.z) }
+ .xsection : { *(.data.x) }
+ .baz : { *(.text.baz) }
+ .text : { *(.text.*) }
+ .bss : { *(.sdata*) }
+}
+
+#--- script.dbg
+NOCROSSREFS(.text .debug_info)
+SECTIONS {
+ .zsection : { *(.data.z) }
+ .xsection : { *(.data.x) }
+ .baz : { *(.text.baz) }
+ .text : { *(.text.*) }
+ .bss : { *(.sdata*) }
+}
diff --git a/lld/test/ELF/lto/lto-with-linker-scripts/OutputSection.test b/lld/test/ELF/lto/lto-with-linker-scripts/OutputSection.test
new file mode 100644
index 0000000000000..201ee1e4130d6
--- /dev/null
+++ b/lld/test/ELF/lto/lto-with-linker-scripts/OutputSection.test
@@ -0,0 +1,22 @@
+# REQUIRES: aarch64
+# RUN: rm -rf %t && split-file %s %t
+# RUN: llvm-as %t/foo.ll -o %t/foo.foo.o
+# RUN: ld.lld -m aarch64elf --lto-linker-scripts -T %t/script.t %t/foo.foo.o -e foo \
+# RUN: -plugin-opt=-function-sections -o %t/out
+# RUN: llvm-readelf -S -s -W %t/out | FileCheck %s --check-prefix=ELF
+
+# ELF: [ [[S:[0-9]+]]] .myfoo PROGBITS
+# ELF: FUNC GLOBAL DEFAULT [[S]] foo
+
+#--- foo.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+define i32 @foo() section ".text.foo" {
+ ret i32 0
+}
+
+#--- script.t
+SECTIONS {
+ .myfoo : { *.foo.o(.text.foo) }
+}
diff --git a/lld/test/ELF/lto/lto-with-linker-scripts/SectionsWithNoSyms.test b/lld/test/ELF/lto/lto-with-linker-scripts/SectionsWithNoSyms.test
new file mode 100644
index 0000000000000..4a765fe2e8569
--- /dev/null
+++ b/lld/test/ELF/lto/lto-with-linker-scripts/SectionsWithNoSyms.test
@@ -0,0 +1,47 @@
+# REQUIRES: aarch64
+
+# RUN: rm -rf %t && split-file %s %t
+# RUN: llvm-as %t/1.ll -o %t/1.o
+# RUN: ld.lld -m aarch64elf --lto-linker-scripts -e main -T %t/script.t -plugin-opt=O0 \
+# RUN: -plugin-opt=-data-sections -plugin-opt=-function-sections %t/1.o -o %t/out
+# RUN: llvm-readelf -S %t/out | FileCheck %s --check-prefix=SEC
+
+# SEC: .myrodata PROGBITS{{.*}}000004
+
+#--- 1.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+ at .str = private unnamed_addr constant [4 x i8] c"100\00", section ".rodata.str"
+ at s = global ptr @.str, section ".data.s", align 8
+
+define i32 @bar() section ".text.bar" {
+ %p = load ptr, ptr @s, align 8
+ %c0 = load i8, ptr %p, align 1
+ %p1 = getelementptr inbounds i8, ptr %p, i64 1
+ %c1 = load i8, ptr %p1, align 1
+ %i0 = sext i8 %c0 to i32
+ %i1 = sext i8 %c1 to i32
+ %sum = add i32 %i0, %i1
+ ret i32 %sum
+}
+
+define i32 @main(i32 %a) section ".text.main" {
+ %f = ptrtoint ptr @bar to i64
+ %f32 = trunc i64 %f to i32
+ %p = load ptr, ptr @s, align 8
+ %p3 = getelementptr inbounds i8, ptr %p, i64 3
+ %c3 = load i8, ptr %p3, align 1
+ %i3 = sext i8 %c3 to i32
+ %sum = add i32 %f32, %i3
+ ret i32 %sum
+}
+
+#--- script.t
+SECTIONS {
+ .text.main : { *1.o(.text.main) }
+ .text.bar : { *1.o(.text.bar) }
+ .myrodata : { *1.o(.rodata*) }
+ .mydata : { *1.o(.data.s) }
+ .unrecognized : { *1.o(*) }
+}
diff --git a/lld/test/ELF/lto/lto-with-linker-scripts/ThinLTOCaching.test b/lld/test/ELF/lto/lto-with-linker-scripts/ThinLTOCaching.test
new file mode 100644
index 0000000000000..4b6e6c5a3fbb2
--- /dev/null
+++ b/lld/test/ELF/lto/lto-with-linker-scripts/ThinLTOCaching.test
@@ -0,0 +1,110 @@
+# REQUIRES: aarch64
+# RUN: rm -rf %t && split-file %s %t
+# RUN: opt -module-hash -module-summary %t/1.ll -o %t/1.1.o
+# RUN: opt -module-hash -module-summary %t/2.ll -o %t/1.2.o
+# RUN: llvm-mc -filetype=obj -triple=aarch64-unknown-linux-gnu %t/3.s -o %t/1.3.o
+# RUN: cp -f %t/script.t %t/1.script.t
+# RUN: rm -Rf %t/1.cache && mkdir %t/1.cache
+# RUN: ld.lld -m aarch64elf --lto-linker-scripts --gc-sections --thinlto-cache-dir=%t/1.cache \
+# RUN: -plugin-opt=-function-sections -e main -T %t/1.script.t %t/1.1.o %t/1.2.o %t/1.3.o -o %t/1.out
+# RUN: llvm-readelf -S -W %t/1.out | FileCheck --check-prefix=SECTIONS %s
+# RUN: ls %t/1.cache | FileCheck --check-prefix=LLVMCACHE-TWO --check-prefix=LLVMCACHE %s
+# RUN: ld.lld -m aarch64elf --lto-linker-scripts --gc-sections --thinlto-cache-dir=%t/1.cache \
+# RUN: -plugin-opt=-function-sections -e main -T %t/1.script.t %t/1.1.o %t/1.2.o %t/1.3.o -o %t/1.out
+# RUN: ls %t/1.cache | FileCheck --check-prefix=LLVMCACHE-TWO --check-prefix=LLVMCACHE %s
+# RUN: llvm-readelf -S -W %t/1.out | FileCheck --check-prefix=SECTIONS %s
+# RUN: cp -f %t/script2.t %t/1.script.t
+# RUN: ld.lld -m aarch64elf --lto-linker-scripts --gc-sections --thinlto-cache-dir=%t/1.cache \
+# RUN: -plugin-opt=-function-sections -e main -T %t/1.script.t %t/1.1.o %t/1.2.o %t/1.3.o -o %t/1.out
+# RUN: llvm-readelf -S -W %t/1.out | FileCheck --check-prefix=SECTIONS %s
+# RUN: ls %t/1.cache | FileCheck --check-prefix=LLVMCACHE-TWO --check-prefix=LLVMCACHE %s
+# RUN: cp -f %t/script2a.t %t/1.script.t
+# RUN: ld.lld -m aarch64elf --lto-linker-scripts --gc-sections --thinlto-cache-dir=%t/1.cache \
+# RUN: -plugin-opt=-function-sections -e main -T %t/1.script.t %t/1.1.o %t/1.2.o %t/1.3.o -o %t/1.out
+# RUN: llvm-readelf -S -W %t/1.out | FileCheck --check-prefix=SECTIONS2 %s
+# RUN: ls %t/1.cache | FileCheck --check-prefix=LLVMCACHE-TWO --check-prefix=LLVMCACHE %s
+# RUN: llvm-ar cr %t/1.lib.a %t/1.3.o
+# RUN: llvm-ar cr %t/1.lib.a %t/1.2.o
+# RUN: llvm-ar cr %t/1.lib.a %t/1.1.o
+# RUN: cp -f %t/script3.t %t/1.script.t
+# RUN: rm -Rf %t/1.cache && mkdir %t/1.cache
+# RUN: ld.lld -m aarch64elf --lto-linker-scripts --gc-sections --thinlto-cache-dir=%t/1.cache \
+# RUN: -plugin-opt=-function-sections -e main -T %t/1.script.t %t/1.lib.a -o %t/1.out
+# RUN: ls %t/1.cache | FileCheck --check-prefix=LLVMCACHE-TWO --check-prefix=LLVMCACHE %s
+# RUN: rm %t/1.lib.a
+# RUN: llvm-ar cr %t/1.lib.a %t/1.1.o
+# RUN: llvm-ar cr %t/1.lib.a %t/1.2.o
+# RUN: llvm-ar cr %t/1.lib.a %t/1.3.o
+# RUN: ld.lld -m aarch64elf --lto-linker-scripts --gc-sections --thinlto-cache-dir=%t/1.cache \
+# RUN: -e main -T %t/1.script.t %t/1.lib.a -o %t/1.out
+# RUN: ls %t/1.cache | FileCheck --check-prefix=LLVMCACHE-TWO --check-prefix=LLVMCACHE %s
+
+# SECTIONS: .alltext
+# SECTIONS-NOT: ] .text
+
+# SECTIONS2-NOT: .special
+
+# LLVMCACHE-TWO-COUNT-2: llvmcache-[[X:[[:xdigit:]]+]]
+# LLVMCACHE-NOT: llvmcache-[[X:[[:xdigit:]]+]]
+
+#--- 1.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+declare i32 @otherfun()
+declare i32 @otherfun2()
+
+define i32 @myfun() section ".text.myfun" {
+ %a = call i32 @otherfun()
+ %b = call i32 @otherfun2()
+ %c = add i32 %a, %b
+ ret i32 %c
+}
+
+#--- 2.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+define i32 @otherfun() section ".text.otherfun" {
+ ret i32 23
+}
+
+define i32 @otherfun2() section ".text.otherfun2" {
+ ret i32 42
+}
+
+#--- 3.s
+.text
+.section .text.main,"ax", at progbits
+.globl main
+.type main, at function
+main:
+ bl myfun
+ ret
+.size main, .-main
+
+#--- script.t
+SECTIONS {
+ .special : { *.2.o(.text.otherfun2) }
+ .alltext : { *(.text.*) }
+}
+
+#--- script2.t
+/* Identical to script.t except for this comment */
+SECTIONS {
+ .special : { *.2.o(.text.otherfun2) }
+ .alltext : { *(.text.*) }
+}
+
+#--- script2a.t
+SECTIONS {
+ .special : { *.2.o(.text.nomatch) }
+ .alltext : { *(.text.*) }
+}
+
+#--- script3.t
+/* Identical to script.t except for this comment */
+SECTIONS {
+ .special : { *.lib.a:(.text.otherfun2) }
+ .alltext : { *(.text.*) }
+}
diff --git a/lld/test/ELF/lto/lto-with-linker-scripts/ThinLTOSameFileNameArchive.test b/lld/test/ELF/lto/lto-with-linker-scripts/ThinLTOSameFileNameArchive.test
new file mode 100644
index 0000000000000..055cf468513c7
--- /dev/null
+++ b/lld/test/ELF/lto/lto-with-linker-scripts/ThinLTOSameFileNameArchive.test
@@ -0,0 +1,63 @@
+# REQUIRES: aarch64
+# RUN: rm -rf %t && split-file %s %t
+# RUN: mkdir -p %t/1/tmp
+# RUN: mkdir -p %t/2/tmp
+# RUN: opt -module-hash -module-summary %t/a/1.ll -o %t/1/tmp/1.o
+# RUN: opt -module-hash -module-summary %t/b/1.ll -o %t/2/tmp/1.o
+# RUN: llvm-ar cr %t/2.a %t/1/tmp/1.o %t/2/tmp/1.o
+# RUN: llvm-mc -filetype=obj -triple=aarch64-unknown-linux-gnu %t/3.s -o %t/1.3.o
+# RUN: ld.lld -m aarch64elf --lto-linker-scripts --gc-sections -plugin-opt=-function-sections \
+# RUN: -plugin-opt=-data-sections -e main -T %t/script.t %t/1.3.o %t/2.a -o %t/1.out
+# RUN: llvm-readelf -S -W %t/1.out | FileCheck --check-prefix=SECTIONS %s
+# RUN: llvm-readelf -s -W %t/1.out | FileCheck %s
+
+# SECTIONS: .alltext
+# SECTIONS-NOT: .text
+
+# CHECK-NOT: FUNC
+# CHECK-DAG: FUNC GLOBAL DEFAULT {{[0-9]+}} main
+# CHECK-DAG: FUNC GLOBAL DEFAULT {{[0-9]+}} myfun
+# CHECK-NOT: FUNC
+
+#--- a/1.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+declare i32 @otherfun()
+declare i32 @otherfun2()
+
+define i32 @myfun() section ".text.myfun" {
+ %a = call i32 @otherfun()
+ %b = call i32 @otherfun2()
+ %c = add i32 %a, %b
+ ret i32 %c
+}
+
+#--- b/1.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+define i32 @otherfun() section ".text.otherfun" {
+ ret i32 23
+}
+
+define i32 @otherfun2() section ".text.otherfun2" {
+ ret i32 42
+}
+
+#--- 3.s
+.text
+.section .text.main,"ax", at progbits
+.globl main
+.type main, at function
+main:
+ bl myfun
+ ret
+.size main, .-main
+
+#--- script.t
+SECTIONS {
+ .special : { *(.text.otherfun2) }
+ .alltext : { *(.text.*) }
+ .ARM.exidx : { *(.ARM.exidx*) }
+}
diff --git a/lld/test/ELF/lto/lto-with-linker-scripts/ThinLTOWithLS.test b/lld/test/ELF/lto/lto-with-linker-scripts/ThinLTOWithLS.test
new file mode 100644
index 0000000000000..06f762b14b08b
--- /dev/null
+++ b/lld/test/ELF/lto/lto-with-linker-scripts/ThinLTOWithLS.test
@@ -0,0 +1,65 @@
+# REQUIRES: aarch64
+# RUN: rm -rf %t && split-file %s %t
+# RUN: opt -module-hash -module-summary %t/1.ll -o %t/1.1.o
+# RUN: opt -module-hash -module-summary %t/2.ll -o %t/1.2.o
+# RUN: llvm-mc -filetype=obj -triple=aarch64-unknown-linux-gnu %t/3.s -o %t/1.3.o
+# RUN: ld.lld -m aarch64elf --lto-linker-scripts --gc-sections -print-gc-sections \
+# RUN: -plugin-opt=-function-sections -plugin-opt=-data-sections --save-temps -e main \
+# RUN: -T %t/script.t %t/1.1.o %t/1.2.o %t/1.3.o -o %t/1.out 2>&1 | \
+# RUN: FileCheck --check-prefix=GARBAGE %s
+# RUN: llvm-readelf -S -W %t/1.out | FileCheck --check-prefix=SECTIONS %s
+# RUN: llvm-readelf -s -W %t/1.out | FileCheck %s
+
+# GARBAGE-DAG: removing unused section {{.*}}(.text.otherfun2)
+# GARBAGE-DAG: removing unused section {{.*}}(.text.otherfun)
+
+# SECTIONS: .alltext
+# SECTIONS-NOT: .text
+
+# CHECK-NOT: FUNC
+# CHECK: FUNC GLOBAL DEFAULT {{[0-9]+}} myfun
+# CHECK: FUNC GLOBAL DEFAULT {{[0-9]+}} main
+# CHECK-NOT: FUNC
+
+#--- 1.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+declare i32 @otherfun()
+declare i32 @otherfun2()
+
+define i32 @myfun() section ".text.myfun" {
+ %a = call i32 @otherfun()
+ %b = call i32 @otherfun2()
+ %c = add i32 %a, %b
+ ret i32 %c
+}
+
+#--- 2.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+define i32 @otherfun() section ".text.otherfun" {
+ ret i32 23
+}
+
+define i32 @otherfun2() section ".text.otherfun2" {
+ ret i32 42
+}
+
+#--- 3.s
+.text
+.section .text.main,"ax", at progbits
+.globl main
+.type main, at function
+main:
+ bl myfun
+ ret
+.size main, .-main
+
+#--- script.t
+SECTIONS {
+ .special : { *.2.o(.text.otherfun2) }
+ .alltext : { *(.text.*) }
+ .ARM.exidx : { *(.ARM.exidx*) }
+}
diff --git a/lld/test/ELF/lto/lto-with-linker-scripts/ltokeep.test b/lld/test/ELF/lto/lto-with-linker-scripts/ltokeep.test
new file mode 100644
index 0000000000000..d2127a57364f3
--- /dev/null
+++ b/lld/test/ELF/lto/lto-with-linker-scripts/ltokeep.test
@@ -0,0 +1,44 @@
+# REQUIRES: aarch64
+
+# RUN: rm -rf %t && split-file %s %t
+# RUN: llvm-as %t/foo.ll -o %t/foo.o
+# RUN: llvm-mc -filetype=obj -triple=aarch64-unknown-linux-gnu %t/main.s -o %t/main.o
+# RUN: ld.lld -m aarch64elf --lto-linker-scripts %t/foo.o %t/main.o -T %t/script.t -o %t/out
+# RUN: llvm-readelf -S %t/out | FileCheck %s --check-prefix=SECTIONS
+
+# SECTIONS: .text.foo
+
+#--- foo.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+declare i32 @bar()
+
+define i32 @foo() section ".text.foo" {
+ %v = call i32 @bar()
+ ret i32 %v
+}
+
+#--- main.s
+.text
+.section .text.main,"ax", at progbits
+.globl main
+.type main, at function
+main:
+ mov x0, xzr
+ ret
+.size main, .-main
+
+.section .text.bar,"ax", at progbits
+.globl bar
+.type bar, at function
+bar:
+ mov x0, xzr
+ ret
+.size bar, .-bar
+
+#--- script.t
+SECTIONS {
+ .text.foo : { KEEP(*(.text.foo)) }
+ .text.others : { *(.text.*) }
+}
diff --git a/lld/test/ELF/lto/lto-with-linker-scripts/ltosectionattribute.test b/lld/test/ELF/lto/lto-with-linker-scripts/ltosectionattribute.test
new file mode 100644
index 0000000000000..93d0a7c8506c2
--- /dev/null
+++ b/lld/test/ELF/lto/lto-with-linker-scripts/ltosectionattribute.test
@@ -0,0 +1,46 @@
+# REQUIRES: aarch64
+# RUN: rm -rf %t && split-file %s %t
+# RUN: llvm-as %t/1.ll -o %t/1.o
+# RUN: llvm-as %t/2.ll -o %t/2.o
+# RUN: llvm-mc -filetype=obj -triple=aarch64-unknown-linux-gnu %t/3.s -o %t/3.o
+# RUN: ld.lld -m aarch64elf --lto-linker-scripts %t/1.o %t/2.o %t/3.o -T %t/script.t -o %t/out
+# RUN: llvm-readelf -S -W %t/out | FileCheck %s
+
+# CHECK: .myfoo
+# CHECK: .mybar
+
+#--- 1.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+define i32 @foo() section ".foo" {
+ ret i32 0
+}
+
+#--- 2.ll
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+define i32 @bar() section ".foo" {
+ ret i32 0
+}
+
+#--- 3.s
+.text
+.section .text,"ax", at progbits
+.globl main
+.type main, at function
+main:
+ bl foo
+ mov x0, xzr
+ bl bar
+ mov x0, xzr
+ ret
+.size main, .-main
+
+#--- script.t
+SECTIONS {
+.myfoo : { *1.o*(.foo) }
+.mybar : { *2.o*(.foo) }
+.main : { *(.text) }
+}
diff --git a/llvm/include/llvm/CodeGen/AssignSectionsToGlobals.h b/llvm/include/llvm/CodeGen/AssignSectionsToGlobals.h
new file mode 100644
index 0000000000000..dcd44a848143e
--- /dev/null
+++ b/llvm/include/llvm/CodeGen/AssignSectionsToGlobals.h
@@ -0,0 +1,26 @@
+//==- include/llvm/CodeGen/AssignSectionsToGlobals.h -------------*- C++ -*-==//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+
+#ifndef LLVM_CODEGEN_ASSIGNSECTIONSTOGLOBALS_H
+#define LLVM_CODEGEN_ASSIGNSECTIONSTOGLOBALS_H
+
+#include "llvm/IR/PassManager.h"
+#include "llvm/Target/TargetMachine.h"
+
+namespace llvm {
+
+class AssignSectionsToGlobalsPass
+ : public PassInfoMixin<AssignSectionsToGlobalsPass> {
+ TargetMachine *TM;
+
+public:
+ AssignSectionsToGlobalsPass(TargetMachine *tm) : TM(tm) {}
+ PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM);
+};
+} // end namespace llvm
+
+#endif // LLVM_CODEGEN_ASSIGNSECTIONSTOGLOBALS_H
diff --git a/llvm/include/llvm/LTO/LTO.h b/llvm/include/llvm/LTO/LTO.h
index aba2661e81c47..47ecf4727fe1a 100644
--- a/llvm/include/llvm/LTO/LTO.h
+++ b/llvm/include/llvm/LTO/LTO.h
@@ -140,6 +140,7 @@ class InputFile {
// written out to a new standalone file.
bool SerializeForDistribution = false;
bool IsThinLTO = false;
+ bool IncludeLocalSymbols = false;
StringRef ArchivePath;
StringRef MemberName;
@@ -148,7 +149,7 @@ class InputFile {
/// Create an InputFile.
LLVM_ABI static Expected<std::unique_ptr<InputFile>>
- create(MemoryBufferRef Object);
+ create(MemoryBufferRef Object, bool IncludeLocalSymbols = false);
/// The purpose of this struct is to only expose the symbol information that
/// an LTO client should need in order to do symbol resolution.
@@ -713,6 +714,12 @@ struct SymbolResolution {
/// Linker redefined version of the symbol which appeared in -wrap or -defsym
/// linker option.
unsigned LinkerRedefined : 1;
+
+ /// When linking with a linker script, the output section where this symbol
+ /// will be placed. Certain optimizations must be suppressed between globals
+ /// with a different output section, to ensure the variable or function is
+ /// actually emitted into the correct section.
+ StringRef OutputSectionName;
};
} // namespace lto
diff --git a/llvm/include/llvm/Object/IRSymtab.h b/llvm/include/llvm/Object/IRSymtab.h
index 0b7d6e0734e82..8eff8773a80c8 100644
--- a/llvm/include/llvm/Object/IRSymtab.h
+++ b/llvm/include/llvm/Object/IRSymtab.h
@@ -115,6 +115,7 @@ struct Symbol {
FB_format_specific,
FB_unnamed_addr,
FB_executable,
+ FB_private,
};
};
@@ -213,6 +214,7 @@ struct Symbol {
bool isFormatSpecific() const { return (Flags >> S::FB_format_specific) & 1; }
bool isUnnamedAddr() const { return (Flags >> S::FB_unnamed_addr) & 1; }
bool isExecutable() const { return (Flags >> S::FB_executable) & 1; }
+ bool isPrivate() const { return (Flags >> S::FB_private) & 1; }
uint64_t getCommonSize() const {
assert(isCommon());
diff --git a/llvm/include/llvm/Object/SymbolicFile.h b/llvm/include/llvm/Object/SymbolicFile.h
index da59eec7700bd..8f46af3fbdcdb 100644
--- a/llvm/include/llvm/Object/SymbolicFile.h
+++ b/llvm/include/llvm/Object/SymbolicFile.h
@@ -122,6 +122,8 @@ class BasicSymbolRef {
SF_Const = 1U << 10, // Symbol value is constant
SF_Executable = 1U << 11, // Symbol points to an executable section
// (IR only)
+ SF_Private = 1U << 12, // Symbol points to a private "symbol"
+ // (IR only)
};
BasicSymbolRef() = default;
diff --git a/llvm/lib/Analysis/InlineCost.cpp b/llvm/lib/Analysis/InlineCost.cpp
index d975a93e9b1fd..fd0e5c8537c9a 100644
--- a/llvm/lib/Analysis/InlineCost.cpp
+++ b/llvm/lib/Analysis/InlineCost.cpp
@@ -184,6 +184,10 @@ static cl::opt<bool> InlineAllViableCalls(
"inline-all-viable-calls", cl::Hidden, cl::init(false),
cl::desc("Inline all viable calls, even if they exceed the inlining "
"threshold"));
+static cl::opt<bool> InlineOnlyWithinSection(
+ "inline-only-within-section", cl::init(true), cl::Hidden,
+ cl::desc("Only inline functions into call sites within the same section."));
+
namespace llvm {
std::optional<int> getStringFnAttrAsInt(const Attribute &Attr) {
if (Attr.isValid()) {
@@ -3079,6 +3083,19 @@ void InlineCostCallAnalyzer::print(raw_ostream &OS) {
LLVM_DUMP_METHOD void InlineCostCallAnalyzer::dump() { print(dbgs()); }
#endif
+/// Test that two functions have matching output sections.
+static bool functionsHaveMatchingOutputSections(Function *F1, Function *F2) {
+ Attribute F1Sec = F1->getFnAttribute("linker_output_section");
+ Attribute F2Sec = F2->getFnAttribute("linker_output_section");
+ if (F1Sec.isValid() != F2Sec.isValid())
+ return false; // Differing output sections.
+
+ if (F1Sec.isValid())
+ return F1Sec.getValueAsString() == F2Sec.getValueAsString();
+
+ return true; // No output sections specified.
+}
+
/// Test that there are no attribute conflicts between Caller and Callee
/// that prevent inlining.
static bool functionsHaveCompatibleAttributes(
@@ -3226,6 +3243,12 @@ std::optional<InlineResult> llvm::getAttributeBasedInliningDecision(
return InlineResult::failure(IsViable.getFailureReason());
}
+ // With a linker script, only inline functions into call sites within the same
+ // output section (unless callee has always-inline attribute).
+ if (InlineOnlyWithinSection &&
+ !functionsHaveMatchingOutputSections(Call.getCaller(), Callee))
+ return InlineResult::failure("caller and callee in different sections");
+
// Never inline functions with conflicting attributes (unless callee has
// always-inline attribute).
Function *Caller = Call.getCaller();
diff --git a/llvm/lib/CodeGen/AssignSectionsToGlobals.cpp b/llvm/lib/CodeGen/AssignSectionsToGlobals.cpp
new file mode 100644
index 0000000000000..608c2c701e093
--- /dev/null
+++ b/llvm/lib/CodeGen/AssignSectionsToGlobals.cpp
@@ -0,0 +1,145 @@
+//===- AssignSectionsToGlobals.cpp ----------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// For each global which doesn't have a section, set it's section to the
+// default as computed by the backend. For LTO with a linker script, this is
+// used by the linker before LTO runs to compute the output sections for each
+// global.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/CodeGen/AssignSectionsToGlobals.h"
+#include "llvm/Bitcode/BitcodeWriterPass.h"
+#include "llvm/CodeGen/AsmPrinter.h"
+#include "llvm/CodeGen/MachineModuleInfo.h"
+#include "llvm/CodeGen/Passes.h"
+#include "llvm/CodeGen/TargetInstrInfo.h"
+#include "llvm/CodeGen/TargetLowering.h"
+#include "llvm/CodeGen/TargetPassConfig.h"
+#include "llvm/CodeGen/TargetSubtargetInfo.h"
+#include "llvm/IR/LegacyPassManager.h"
+#include "llvm/IR/Metadata.h"
+#include "llvm/IR/Module.h"
+#include "llvm/InitializePasses.h"
+#include "llvm/MC/MCAsmInfo.h"
+#include "llvm/MC/MCContext.h"
+#include "llvm/MC/MCDirectives.h"
+#include "llvm/MC/MCInstrInfo.h"
+#include "llvm/MC/MCObjectFileInfo.h"
+#include "llvm/MC/MCParser/MCAsmParser.h"
+#include "llvm/MC/MCParser/MCTargetAsmParser.h"
+#include "llvm/MC/MCRegisterInfo.h"
+#include "llvm/MC/MCSectionELF.h"
+#include "llvm/MC/MCStreamer.h"
+#include "llvm/MC/MCSubtargetInfo.h"
+#include "llvm/MC/MCSymbol.h"
+#include "llvm/MC/MCTargetOptions.h"
+#include "llvm/MC/TargetRegistry.h"
+#include "llvm/Pass.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/SourceMgr.h"
+#include "llvm/Target/TargetLoweringObjectFile.h"
+#include "llvm/Target/TargetMachine.h"
+#include "llvm/Transforms/Utils/ModuleUtils.h"
+
+#define DEBUG_TYPE "assign-sections-globals"
+
+using namespace llvm;
+
+namespace {
+
+class AssignSections {
+public:
+ bool run(Module &M, TargetMachine *TM);
+
+private:
+ TargetMachine *TM;
+ Module *M;
+
+ void collectGlobalObjectSections(
+ SmallVectorImpl<std::pair<StringRef, StringRef>> &GOSectionPairs);
+ void collectAsmAliasSections(
+ const Triple &TT, const Target &T, MCContext &MCCtx,
+ SmallVectorImpl<std::pair<StringRef, StringRef>> &GOSectionPairs);
+ StringRef getDefaultSectionNameForGlobal(const GlobalObject &GO);
+};
+
+} // namespace
+
+StringRef
+AssignSections::getDefaultSectionNameForGlobal(const GlobalObject &GO) {
+ TargetLoweringObjectFile &TLOF = *TM->getObjFileLowering();
+
+ SectionKind GVKind = TLOF.getKindForGlobal(&GO, *TM);
+
+ // Section selection might depend on module flags like SmallDataLimit.
+ TLOF.getModuleMetadata(*M);
+
+ auto Section = static_cast<const MCSectionELF *>(
+ TLOF.SectionForGlobal(&GO, GVKind, *TM));
+
+ if (Section)
+ return Section->getName();
+ else
+ return "";
+}
+
+bool AssignSections::run(Module &Mod, TargetMachine *TMIn) {
+ M = &Mod;
+ TM = TMIn;
+
+ if (!TM)
+ return false;
+
+ if (Mod.alias_empty() && Mod.global_empty() && Mod.empty())
+ return false;
+
+ // We currently only support ELF for LTO with linker scripts.
+ const Triple TT(Mod.getTargetTriple());
+ if (!TT.isOSBinFormatELF())
+ return false;
+
+ // Initialize the target so we can query sections.
+ const Target &T = TM->getTarget();
+ assert(T.hasMCAsmParser());
+ std::unique_ptr<MCRegisterInfo> MRI(T.createMCRegInfo(TT));
+ if (!MRI)
+ return false;
+ llvm::MCTargetOptions MCOptions;
+ std::unique_ptr<MCAsmInfo> MAI(T.createMCAsmInfo(*MRI, TT, MCOptions));
+ if (!MAI)
+ return false;
+ std::unique_ptr<llvm::MCSubtargetInfo> STI(
+ T.createMCSubtargetInfo(TT, "", ""));
+ MCObjectFileInfo MOFI;
+ MCContext MCCtx(Triple(TT), *MAI, *MRI, *STI);
+ MCCtx.setObjectFileInfo(&MOFI);
+ MOFI.initMCObjectFileInfo(MCCtx, /*PIC*/ false);
+ TM->getObjFileLowering()->Initialize(MCCtx, *TM);
+
+ // Iterate over objects, and assign sections
+ for (GlobalObject &GO : M->global_objects()) {
+ if (GO.isDeclarationForLinker() || GO.hasSection() ||
+ GO.hasCommonLinkage() || GO.getName().starts_with("llvm."))
+ continue;
+
+ GO.setSection(getDefaultSectionNameForGlobal(GO));
+ }
+
+ return true;
+}
+
+PreservedAnalyses AssignSectionsToGlobalsPass::run(Module &M,
+ ModuleAnalysisManager &MAM) {
+ AssignSections O;
+
+ if (O.run(M, TM))
+ return PreservedAnalyses::none();
+
+ return PreservedAnalyses::all();
+}
diff --git a/llvm/lib/CodeGen/CMakeLists.txt b/llvm/lib/CodeGen/CMakeLists.txt
index c572128b023c1..69a692a9508bf 100644
--- a/llvm/lib/CodeGen/CMakeLists.txt
+++ b/llvm/lib/CodeGen/CMakeLists.txt
@@ -27,6 +27,7 @@ add_llvm_component_library(LLVMCodeGen
AllocationOrder.cpp
Analysis.cpp
AssignmentTrackingAnalysis.cpp
+ AssignSectionsToGlobals.cpp
AtomicExpandPass.cpp
BasicTargetTransformInfo.cpp
BranchFolding.cpp
diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp
index 95faf3484c456..d6d40d88d5f16 100644
--- a/llvm/lib/LTO/LTO.cpp
+++ b/llvm/lib/LTO/LTO.cpp
@@ -621,13 +621,15 @@ void llvm::thinLTOInternalizeAndPromoteInIndex(
// Requires a destructor for std::vector<InputModule>.
InputFile::~InputFile() = default;
-Expected<std::unique_ptr<InputFile>> InputFile::create(MemoryBufferRef Object) {
+Expected<std::unique_ptr<InputFile>>
+InputFile::create(MemoryBufferRef Object, bool IncludeLocalSymbols) {
std::unique_ptr<InputFile> File(new InputFile);
Expected<IRSymtabFile> FOrErr = readIRSymtab(Object);
if (!FOrErr)
return FOrErr.takeError();
+ File->IncludeLocalSymbols = IncludeLocalSymbols;
File->TargetTriple = FOrErr->TheReader.getTargetTriple();
File->SourceFileName = FOrErr->TheReader.getSourceFileName();
File->COFFLinkerOpts = FOrErr->TheReader.getCOFFLinkerOpts();
@@ -639,11 +641,17 @@ Expected<std::unique_ptr<InputFile>> InputFile::create(MemoryBufferRef Object) {
for (unsigned I = 0; I != FOrErr->Mods.size(); ++I) {
size_t Begin = File->Symbols.size();
for (const irsymtab::Reader::SymbolRef &Sym :
- FOrErr->TheReader.module_symbols(I))
+ FOrErr->TheReader.module_symbols(I)) {
// Skip symbols that are irrelevant to LTO. Note that this condition needs
// to match the one in Skip() in LTO::addRegularLTO().
- if (Sym.isGlobal() && !Sym.isFormatSpecific())
- File->Symbols.push_back(Sym);
+ if (IncludeLocalSymbols) {
+ if (!Sym.isFormatSpecific() || Sym.isPrivate())
+ File->Symbols.push_back(Sym);
+ } else {
+ if (Sym.isGlobal() && !Sym.isFormatSpecific())
+ File->Symbols.push_back(Sym);
+ }
+ }
File->ModuleSymIndices.push_back({Begin, File->Symbols.size()});
}
@@ -1025,9 +1033,15 @@ LTO::addRegularLTO(InputFile &Input, ArrayRef<SymbolResolution> InputRes,
auto Skip = [&]() {
while (MsymI != MsymE) {
auto Flags = SymTab.getSymbolFlags(*MsymI);
- if ((Flags & object::BasicSymbolRef::SF_Global) &&
- !(Flags & object::BasicSymbolRef::SF_FormatSpecific))
- return;
+ if (Input.IncludeLocalSymbols) {
+ if (!(Flags & object::BasicSymbolRef::SF_FormatSpecific) ||
+ (Flags & object::BasicSymbolRef::SF_Private))
+ return;
+ } else {
+ if ((Flags & object::BasicSymbolRef::SF_Global) &&
+ !(Flags & object::BasicSymbolRef::SF_FormatSpecific))
+ return;
+ }
++MsymI;
}
};
@@ -1081,6 +1095,31 @@ LTO::addRegularLTO(InputFile &Input, ArrayRef<SymbolResolution> InputRes,
GV->setDLLStorageClass(GlobalValue::DLLStorageClassTypes::
DefaultStorageClass);
}
+
+ // Set the output section based on the linker script.
+ if (!R.OutputSectionName.empty()) {
+ if (auto *GVar = dyn_cast<GlobalVariable>(GV)) {
+ // First, remove the old attribute if present
+ if (GVar->hasAttribute("linker_output_section")) {
+ AttrBuilder Attrs(GVar->getParent()->getContext(),
+ GVar->getAttributes());
+ Attrs.removeAttribute("linker_output_section");
+ GVar->setAttributes(AttributeSet::get(GVar->getContext(), Attrs));
+ }
+ GVar->addAttribute("linker_output_section", R.OutputSectionName);
+ if (GVar->hasSection())
+ GVar->setSection(
+ (GVar->getSection() + "^^" + M.getModuleIdentifier()).str());
+ } else if (auto *F = dyn_cast<Function>(GV)) {
+ // First, remove the old attribute if present
+ if (F->hasFnAttribute("linker_output_section"))
+ F->removeFnAttr("linker_output_section");
+ F->addFnAttr("linker_output_section", R.OutputSectionName);
+ if (F->hasSection())
+ F->setSection(
+ (F->getSection() + "^^" + M.getModuleIdentifier()).str());
+ }
+ }
} else if (auto *AS =
dyn_cast_if_present<ModuleSymbolTable::AsmSymbol *>(Msym)) {
// Collect non-prevailing symbols.
diff --git a/llvm/lib/Object/IRSymtab.cpp b/llvm/lib/Object/IRSymtab.cpp
index 0f404be9bd4a8..24698c3be6a60 100644
--- a/llvm/lib/Object/IRSymtab.cpp
+++ b/llvm/lib/Object/IRSymtab.cpp
@@ -246,6 +246,8 @@ Error Builder::addSymbol(const ModuleSymbolTable &Msymtab,
Sym.Flags |= 1 << storage::Symbol::FB_format_specific;
if (Flags & object::BasicSymbolRef::SF_Executable)
Sym.Flags |= 1 << storage::Symbol::FB_executable;
+ if (Flags & object::BasicSymbolRef::SF_Private)
+ Sym.Flags |= 1 << storage::Symbol::FB_private;
Sym.ComdatIndex = -1;
auto *GV = dyn_cast_if_present<GlobalValue *>(Msym);
diff --git a/llvm/lib/Object/ModuleSymbolTable.cpp b/llvm/lib/Object/ModuleSymbolTable.cpp
index 1da5fa9c10a0b..e1aa7ad72f147 100644
--- a/llvm/lib/Object/ModuleSymbolTable.cpp
+++ b/llvm/lib/Object/ModuleSymbolTable.cpp
@@ -232,7 +232,7 @@ uint32_t ModuleSymbolTable::getSymbolFlags(Symbol S) const {
if (isa<GlobalAlias>(GV))
Res |= BasicSymbolRef::SF_Indirect;
if (GV->hasPrivateLinkage())
- Res |= BasicSymbolRef::SF_FormatSpecific;
+ Res |= BasicSymbolRef::SF_FormatSpecific | BasicSymbolRef::SF_Private;
if (!GV->hasLocalLinkage())
Res |= BasicSymbolRef::SF_Global;
if (GV->hasCommonLinkage())
>From a3442ebd55f4fcec55e6c652524c3c9a18a00c5a Mon Sep 17 00:00:00 2001
From: Eli Friedman <efriedma at qti.qualcomm.com>
Date: Wed, 17 Jun 2026 16:42:53 -0700
Subject: [PATCH 2/3] Guard "^^" handling in lld with ltoLinkerScripts.
---
lld/ELF/LinkerScript.cpp | 33 +++++++++++++--------------------
lld/ELF/LinkerScript.h | 4 ----
2 files changed, 13 insertions(+), 24 deletions(-)
diff --git a/lld/ELF/LinkerScript.cpp b/lld/ELF/LinkerScript.cpp
index d36565ca22e5a..f9b0bcda04216 100644
--- a/lld/ELF/LinkerScript.cpp
+++ b/lld/ELF/LinkerScript.cpp
@@ -421,15 +421,6 @@ void LinkerScript::assignSymbol(SymbolAssignment *cmd, bool inSec) {
cmd->sym->type = v.type;
}
-bool LinkerScript::matchesFile(const InputSectionDescription *desc,
- InputSectionBase *sec) const {
- if (StringRef filename = sec->name.split("^^").second; !filename.empty()) {
- if (const InputFile *file = ltoInputFileMapping.lookup(filename))
- return desc->matchesFile(*file);
- }
- return desc->matchesFile(*sec->file);
-}
-
bool InputSectionDescription::matchesFile(const InputFile &file) const {
if (filePat.isTrivialMatchAll())
return true;
@@ -448,15 +439,6 @@ bool InputSectionDescription::matchesFile(const InputFile &file) const {
return matchesFileCache->second;
}
-bool LinkerScript::excludesFile(const SectionPattern *pat,
- InputSectionBase *sec) const {
- if (StringRef filename = sec->name.split("^^").second; !filename.empty()) {
- if (const InputFile *file = ltoInputFileMapping.lookup(filename))
- return pat->excludesFile(*file);
- }
- return pat->excludesFile(*sec->file);
-}
-
bool SectionPattern::excludesFile(const InputFile &file) const {
if (excludedFilePat.empty())
return false;
@@ -634,11 +616,22 @@ LinkerScript::computeInputSections(const InputSectionDescription *cmd,
cast<InputSection>(sec)->getRelocatedSection())
continue;
+ StringRef sectionName = sec->name;
+ const InputFile *sectionFile = sec->file;
+ if (ctx.arg.ltoLinkerScripts) {
+ auto splitSectionName = sec->name.split("^^");
+ if (StringRef filename = splitSectionName.second; !filename.empty()) {
+ if (const InputFile *file = ltoInputFileMapping.lookup(filename)) {
+ sectionName = splitSectionName.first;
+ sectionFile = file;
+ }
+ }
+ }
// Check the name early to improve performance in the common case.
- if (!pat.sectionPat.match(sec->name.split("^^").first))
+ if (!pat.sectionPat.match(sectionName))
continue;
- if (!matchesFile(cmd, sec) || excludesFile(&pat, sec) ||
+ if (!cmd->matchesFile(*sectionFile) || pat.excludesFile(*sectionFile) ||
!flagsMatch(sec))
continue;
diff --git a/lld/ELF/LinkerScript.h b/lld/ELF/LinkerScript.h
index ad4f93f1a7fe8..6db8a88569c7d 100644
--- a/lld/ELF/LinkerScript.h
+++ b/lld/ELF/LinkerScript.h
@@ -414,10 +414,6 @@ class LinkerScript final {
StringRef mapLTOSectionName(StringRef inputSection, InputFile *file);
- bool matchesFile(const InputSectionDescription *desc,
- InputSectionBase *sec) const;
- bool excludesFile(const SectionPattern *pat, InputSectionBase *sec) const;
-
// SECTIONS command list.
SmallVector<SectionCommand *, 0> sectionCommands;
>From 50e4cb09b218577990edca5e2dd08013b6d14403 Mon Sep 17 00:00:00 2001
From: Eli Friedman <efriedma at qti.qualcomm.com>
Date: Wed, 17 Jun 2026 16:45:39 -0700
Subject: [PATCH 3/3] Fix ABI annotation.
---
llvm/include/llvm/CodeGen/AssignSectionsToGlobals.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/include/llvm/CodeGen/AssignSectionsToGlobals.h b/llvm/include/llvm/CodeGen/AssignSectionsToGlobals.h
index dcd44a848143e..4648390ad984e 100644
--- a/llvm/include/llvm/CodeGen/AssignSectionsToGlobals.h
+++ b/llvm/include/llvm/CodeGen/AssignSectionsToGlobals.h
@@ -19,7 +19,7 @@ class AssignSectionsToGlobalsPass
public:
AssignSectionsToGlobalsPass(TargetMachine *tm) : TM(tm) {}
- PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM);
+ LLVM_ABI PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM);
};
} // end namespace llvm
More information about the cfe-commits
mailing list