[clang] [lld] [llvm] Machine outline fix for LTO + linker scripts (PR #230063)
Garvit Gupta via cfe-commits
cfe-commits at lists.llvm.org
Thu Oct 8 04:21:06 PDT 2026
https://github.com/quic-garvgupt created https://github.com/llvm/llvm-project/pull/230063
This fix would be needed once #204282 is landed
>From 140f3cc1d52074ca73a1bc0ddbf8f257d281536d 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/8] 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 7e54e75752f39..0b206fcdb5568 100644
--- a/clang/include/clang/Basic/CodeGenOptions.def
+++ b/clang/include/clang/Basic/CodeGenOptions.def
@@ -539,6 +539,9 @@ CODEGENOPT(DisableOutlining, 1, 0, Benign)
/// Controlls the emission of function name in PPA1 on z/OS.
CODEGENOPT(ZOSPPA1Name, 1, 1, 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 b354a475346df..cf575336c7613 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -10039,3 +10039,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 e95552b7e9e06..9e36d35b61dac 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -26,6 +26,7 @@
#include "llvm/Bitcode/BitcodeWriter.h"
#include "llvm/Bitcode/BitcodeWriterPass.h"
#include "llvm/CodeGen/MachineModuleInfo.h"
+#include "llvm/CodeGen/AssignSectionsToGlobals.h"
#include "llvm/CodeGen/TargetSubtargetInfo.h"
#include "llvm/Config/llvm-config.h"
#include "llvm/Frontend/Driver/CodeGenOptions.h"
@@ -1158,6 +1159,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 d2e22920aa432..62e3ad28fb0cb 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -6594,6 +6594,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 54e0aa58591ad..c06b2ba85bf43 100644
--- a/lld/ELF/Config.h
+++ b/lld/ELF/Config.h
@@ -375,6 +375,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 0393410b35d4d..c123bc87c8c50 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -1478,6 +1478,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 23649d6200719..dcb5b9ea56a5d 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -1833,7 +1833,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());
@@ -1857,7 +1857,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());
@@ -1941,7 +1947,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 a11681133373d..59d52bb70f1b1 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"
@@ -245,6 +246,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.
@@ -296,6 +299,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 64fb717e17d9d..91c00b3f01922 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 cea66f99b99bb..6882df591d095 100644
--- a/llvm/include/llvm/LTO/LTO.h
+++ b/llvm/include/llvm/LTO/LTO.h
@@ -141,6 +141,7 @@ class InputFile {
// standalone file. Effectively, extracted from its container.
bool ExtractForDistribution = false;
bool IsThinLTO = false;
+ bool IncludeLocalSymbols = false;
StringRef ArchivePath;
StringRef MemberName;
@@ -149,7 +150,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.
@@ -709,6 +710,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 f59ad293d61ae..48a18ce766007 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,
};
};
@@ -215,6 +216,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 6cd84376d8a98..a21a4807ab900 100644
--- a/llvm/lib/Analysis/InlineCost.cpp
+++ b/llvm/lib/Analysis/InlineCost.cpp
@@ -183,6 +183,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()) {
@@ -3077,6 +3081,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(
@@ -3218,6 +3235,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).
if (!functionsHaveCompatibleAttributes(Caller, Callee, GetTLI))
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 c8c5b0880819a..e13d22da1b9dd 100644
--- a/llvm/lib/LTO/LTO.cpp
+++ b/llvm/lib/LTO/LTO.cpp
@@ -627,13 +627,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();
@@ -645,11 +647,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()});
}
@@ -1032,9 +1040,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;
}
};
@@ -1088,6 +1102,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 44f717ec12323..a44445e7e5ade 100644
--- a/llvm/lib/Object/ModuleSymbolTable.cpp
+++ b/llvm/lib/Object/ModuleSymbolTable.cpp
@@ -234,7 +234,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 d04af7a20c18c66b517924b084f4f1a8d94a50ff 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/8] 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 91c00b3f01922..aa8ce55d3c839 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 d222a60e1a19c24093803b044345beb0f7669e7e 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/8] 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
>From b8d26e0c24c13b2f793f99dc956b0f217e173c88 Mon Sep 17 00:00:00 2001
From: Eli Friedman <efriedma at qti.qualcomm.com>
Date: Wed, 22 Jul 2026 14:09:50 -0700
Subject: [PATCH 4/8] Don't resolve local symbols in the global symbol table.
---
lld/ELF/InputFiles.cpp | 12 ++++++++++++
llvm/lib/LTO/LTO.cpp | 2 ++
2 files changed, 14 insertions(+)
diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp
index dcb5b9ea56a5d..1d4fa983b7cb0 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -1867,6 +1867,16 @@ static void createBitcodeSymbol(Ctx &ctx, Symbol *&sym,
uint8_t type = objSym.isTLS() ? STT_TLS : STT_NOTYPE;
uint8_t visibility = mapVisibility(objSym.getVisibility());
+ if (!objSym.isGlobal()) {
+ // Local symbols aren't inserted in the symbol table.
+ assert(!sym);
+ objSym.Name = ctx.uniqueSaver.save(objSym.getName());
+ sym = reinterpret_cast<Symbol *>(makeThreadLocalN<SymbolUnion>(1));
+ new (sym) Defined(ctx, &f, StringRef(), binding, visibility, type, 0, 0,
+ nullptr);
+ return;
+ }
+
if (!sym) {
// Symbols can be duplicated in bitcode files because of '#include' and
// linkonce_odr. Use uniqueSaver to save symbol names for de-duplication.
@@ -1929,6 +1939,8 @@ void BitcodeFile::parseLazy() {
numSymbols = obj->symbols().size();
symbols = std::make_unique<Symbol *[]>(numSymbols);
for (auto [i, irSym] : llvm::enumerate(obj->symbols())) {
+ if (!irSym.isGlobal())
+ continue;
// Symbols can be duplicated in bitcode files because of '#include' and
// linkonce_odr. Use uniqueSaver to save symbol names for de-duplication.
// Update objSym.Name to reference (via StringRef) the string saver's copy;
diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp
index e13d22da1b9dd..fd16b63a401bd 100644
--- a/llvm/lib/LTO/LTO.cpp
+++ b/llvm/lib/LTO/LTO.cpp
@@ -739,6 +739,8 @@ void LTO::addModuleToGlobalRes(ArrayRef<InputFile::Symbol> Syms,
assert(ResI != ResE);
SymbolResolution Res = *ResI++;
+ if (!Sym.isGlobal())
+ continue;
StringRef SymbolName = Sym.getName();
// Keep copies of symbols if the client of LTO says so.
if (GlobalResolutionSymbolSaver && !GlobalResolutions->contains(SymbolName))
>From 050ac7becc888c66eb471ed16d698818dfadc6a2 Mon Sep 17 00:00:00 2001
From: Eli Friedman <efriedma at qti.qualcomm.com>
Date: Wed, 22 Jul 2026 16:44:02 -0700
Subject: [PATCH 5/8] Fix formatting
---
clang/lib/CodeGen/BackendUtil.cpp | 2 +-
lld/ELF/InputFiles.cpp | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp
index 9e36d35b61dac..5b0c413ac673d 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -25,8 +25,8 @@
#include "llvm/Bitcode/BitcodeReader.h"
#include "llvm/Bitcode/BitcodeWriter.h"
#include "llvm/Bitcode/BitcodeWriterPass.h"
-#include "llvm/CodeGen/MachineModuleInfo.h"
#include "llvm/CodeGen/AssignSectionsToGlobals.h"
+#include "llvm/CodeGen/MachineModuleInfo.h"
#include "llvm/CodeGen/TargetSubtargetInfo.h"
#include "llvm/Config/llvm-config.h"
#include "llvm/Frontend/Driver/CodeGenOptions.h"
diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp
index 1d4fa983b7cb0..d8d7fb2482476 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -1872,8 +1872,8 @@ static void createBitcodeSymbol(Ctx &ctx, Symbol *&sym,
assert(!sym);
objSym.Name = ctx.uniqueSaver.save(objSym.getName());
sym = reinterpret_cast<Symbol *>(makeThreadLocalN<SymbolUnion>(1));
- new (sym) Defined(ctx, &f, StringRef(), binding, visibility, type, 0, 0,
- nullptr);
+ new (sym)
+ Defined(ctx, &f, StringRef(), binding, visibility, type, 0, 0, nullptr);
return;
}
>From 68e9f66c0d9c77eccb3cd85467f37ed1a8a94d23 Mon Sep 17 00:00:00 2001
From: Eli Friedman <efriedma at qti.qualcomm.com>
Date: Thu, 23 Jul 2026 18:27:39 -0700
Subject: [PATCH 6/8] New test.
---
.../LTOAsmEmptySymbol.test | 51 +++++++++++++++++++
1 file changed, 51 insertions(+)
create mode 100644 lld/test/ELF/lto/lto-with-linker-scripts/LTOAsmEmptySymbol.test
diff --git a/lld/test/ELF/lto/lto-with-linker-scripts/LTOAsmEmptySymbol.test b/lld/test/ELF/lto/lto-with-linker-scripts/LTOAsmEmptySymbol.test
new file mode 100644
index 0000000000000..39f8613b81594
--- /dev/null
+++ b/lld/test/ELF/lto/lto-with-linker-scripts/LTOAsmEmptySymbol.test
@@ -0,0 +1,51 @@
+# 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 --gc-sections -e main -T %t/script.t \
+# RUN: %t/1.o %t/2.o %t/3.o -o %t/out
+# RUN: llvm-readelf -s -W %t/out | FileCheck %s
+
+# CHECK: FUNC GLOBAL DEFAULT {{.*}} 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"
+
+module asm ".xword ."
+
+define i32 @foo(i32 %x) nounwind section ".text.foo" {
+ %r = add i32 %x, 2
+ ret i32 %r
+}
+
+#--- 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"
+
+module asm ".xword ."
+module asm ".weak foo"
+module asm ".equ foo,bar"
+
+ at llvm.compiler.used = appending global [1 x ptr] [ptr @bar], section "llvm.metadata"
+
+define internal i32 @bar(i32 %x) nounwind section ".text.bar" {
+ %r = add i32 %x, 1
+ ret i32 %r
+}
+
+#--- 3.s
+.text
+.section .text.main,"ax", at progbits
+.globl main
+.type main, at function
+main:
+ .xword foo
+ ret
+.size main, .-main
+
+#--- script.t
+SECTIONS {
+ .text : { *(.text.*) }
+}
>From ef79d285c8a69f3c223231a33d6d659c528914a2 Mon Sep 17 00:00:00 2001
From: Eli Friedman <efriedma at qti.qualcomm.com>
Date: Tue, 1 Sep 2026 14:43:26 -0700
Subject: [PATCH 7/8] Disable section markings for globals with type markings.
LTO by default emits type metadata on all vtables when LTO is enabled.
This is true even when none of the options which would actually
consume the metadata are enabled. We can't control the section of such
globals: LowerTypeTests rewrites globals. So don't try.
We might want to expose a clang option to control this.
---
llvm/lib/CodeGen/AssignSectionsToGlobals.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/llvm/lib/CodeGen/AssignSectionsToGlobals.cpp b/llvm/lib/CodeGen/AssignSectionsToGlobals.cpp
index 608c2c701e093..32402387aa7e6 100644
--- a/llvm/lib/CodeGen/AssignSectionsToGlobals.cpp
+++ b/llvm/lib/CodeGen/AssignSectionsToGlobals.cpp
@@ -125,7 +125,8 @@ bool AssignSections::run(Module &Mod, TargetMachine *TMIn) {
// Iterate over objects, and assign sections
for (GlobalObject &GO : M->global_objects()) {
if (GO.isDeclarationForLinker() || GO.hasSection() ||
- GO.hasCommonLinkage() || GO.getName().starts_with("llvm."))
+ GO.hasCommonLinkage() || GO.getName().starts_with("llvm.") ||
+ GO.hasMetadata(LLVMContext::MD_type))
continue;
GO.setSection(getDefaultSectionNameForGlobal(GO));
>From 514f35ffe75a0a522a96d0b5e8f87af9e9491c53 Mon Sep 17 00:00:00 2001
From: Garvit Gupta <garvgupt at qti.qualcomm.com>
Date: Thu, 8 Oct 2026 04:18:38 -0700
Subject: [PATCH 8/8] [Machine Outliner][RISCV] Support sections with LTO
linker scripts
Allow section-aware targets to outline repeated sequences from named input sections. Partition candidates by the linker_output_section attribute when present, falling back to input section otherwise, and give the generated outlined function its parents' section and output-section attribute.
This lets RISCV preserve linker-script placement while outlining LTO code whose distinct input sections map to the same output section.
Signed-off-by: Garvit Gupta <garvgupt at qti.qualcomm.com>
---
.../machine-outliner-output-section.test | 95 ++++++++++
llvm/include/llvm/CodeGen/MachineOutliner.h | 8 +-
llvm/include/llvm/CodeGen/TargetInstrInfo.h | 10 ++
llvm/lib/CodeGen/MachineOutliner.cpp | 140 +++++++++++++--
llvm/lib/Target/AArch64/AArch64InstrInfo.cpp | 14 +-
llvm/lib/Target/ARM/ARMBaseInstrInfo.cpp | 14 +-
llvm/lib/Target/RISCV/RISCVInstrInfo.cpp | 9 +-
llvm/lib/Target/RISCV/RISCVInstrInfo.h | 2 +
.../machine-outliner-multiple-sections.ll | 169 ++++++++++++++++++
.../RISCV/machine-outliner-output-section.ll | 101 +++++++++++
.../machine-outliner-section-attribute.ll | 68 +++++++
.../RISCV/machine-outliner-section-debug.ll | 16 ++
.../RISCV/machineoutliner-pcrel-lo.mir | 36 ++--
.../RISCV/machine-outliner-section-rv32.ll | 117 ++++++++++++
.../RISCV/machine-outliner-section-rv64.ll | 117 ++++++++++++
15 files changed, 849 insertions(+), 67 deletions(-)
create mode 100644 lld/test/ELF/lto/lto-with-linker-scripts/machine-outliner-output-section.test
create mode 100644 llvm/test/CodeGen/RISCV/machine-outliner-multiple-sections.ll
create mode 100644 llvm/test/CodeGen/RISCV/machine-outliner-output-section.ll
create mode 100644 llvm/test/CodeGen/RISCV/machine-outliner-section-attribute.ll
create mode 100644 llvm/test/CodeGen/RISCV/machine-outliner-section-debug.ll
create mode 100644 llvm/test/LTO/RISCV/machine-outliner-section-rv32.ll
create mode 100644 llvm/test/LTO/RISCV/machine-outliner-section-rv64.ll
diff --git a/lld/test/ELF/lto/lto-with-linker-scripts/machine-outliner-output-section.test b/lld/test/ELF/lto/lto-with-linker-scripts/machine-outliner-output-section.test
new file mode 100644
index 0000000000000..447d8565b1ce4
--- /dev/null
+++ b/lld/test/ELF/lto/lto-with-linker-scripts/machine-outliner-output-section.test
@@ -0,0 +1,95 @@
+# Check that the machine outliner groups candidates by the linker script output
+# section during LTO. hot_0 and hot_1 are in different input sections but the
+# script maps both to .out_hot, so they are outlined together and the outlined
+# function is placed in that output section. cold_0 is mapped to .out_cold and
+# must not join them.
+
+# REQUIRES: riscv
+# RUN: rm -rf %t && split-file %s %t
+# RUN: llvm-as %t/1.ll -o %t/1.o
+# RUN: llvm-mc -filetype=obj -triple=riscv32 %t/2.s -o %t/2.o
+# RUN: ld.lld -m elf32lriscv --lto-linker-scripts -e main \
+# RUN: -mllvm -enable-machine-outliner=always \
+# RUN: -T %t/script.t %t/1.o %t/2.o -o %t/out
+# RUN: llvm-readelf -S -s -W %t/out | FileCheck %s
+
+# A single outlined function is created for the two candidates that share the
+# .out_hot output section, and it is placed in that section.
+# CHECK: [ [[HOT:[0-9]+]]] .out_hot PROGBITS
+# CHECK: [ [[COLD:[0-9]+]]] .out_cold PROGBITS
+
+# CHECK-DAG: FUNC LOCAL DEFAULT [[HOT]] OUTLINED_FUNCTION_0
+# CHECK-DAG: FUNC GLOBAL DEFAULT [[HOT]] hot_0
+# CHECK-DAG: FUNC GLOBAL DEFAULT [[HOT]] hot_1
+
+# The candidate mapped to a different output section is not grouped with them,
+# so no second outlined function exists.
+# CHECK-DAG: FUNC GLOBAL DEFAULT [[COLD]] cold_0
+# CHECK-NOT: OUTLINED_FUNCTION_1
+
+#--- 1.ll
+target datalayout = "e-m:e-p:32:32-i64:64-n32-S128"
+target triple = "riscv32-unknown-linux-gnu"
+
+define i32 @hot_0(i32 %a, i32 %b) noinline nounwind section ".text.hot_0" {
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 1
+ ret i32 %result
+}
+
+define i32 @hot_1(i32 %a, i32 %b) noinline nounwind section ".text.hot_1" {
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 2
+ ret i32 %result
+}
+
+define i32 @cold_0(i32 %a, i32 %b) noinline nounwind section ".text.cold_0" {
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 3
+ ret i32 %result
+}
+
+#--- 2.s
+.text
+.globl main
+.type main, at function
+main:
+ addi sp, sp, -16
+ sw ra, 12(sp)
+ li a0, 1
+ li a1, 2
+ call hot_0
+ li a0, 1
+ li a1, 2
+ call hot_1
+ li a0, 1
+ li a1, 2
+ call cold_0
+ li a0, 0
+ lw ra, 12(sp)
+ addi sp, sp, 16
+ ret
+.size main, .-main
+
+#--- script.t
+SECTIONS {
+ . = 0x10000;
+ .out_hot : { *(.text.hot_*) }
+ .out_cold : { *(.text.cold_*) }
+ .text : { *(.text*) }
+}
diff --git a/llvm/include/llvm/CodeGen/MachineOutliner.h b/llvm/include/llvm/CodeGen/MachineOutliner.h
index 2f38017f10274..453a7f6254706 100644
--- a/llvm/include/llvm/CodeGen/MachineOutliner.h
+++ b/llvm/include/llvm/CodeGen/MachineOutliner.h
@@ -107,10 +107,6 @@ struct Candidate {
}
public:
- /// The index of this \p Candidate's \p OutlinedFunction in the list of
- /// \p OutlinedFunctions.
- unsigned FunctionIdx = 0;
-
/// Identifier denoting the instructions to emit to call an outlined function
/// from this point. Defined by the target.
unsigned CallConstructionID = 0;
@@ -201,9 +197,9 @@ struct Candidate {
Candidate(unsigned StartIdx, unsigned Len,
MachineBasicBlock::iterator &FirstInst,
MachineBasicBlock::iterator &LastInst, MachineBasicBlock *MBB,
- unsigned FunctionIdx, unsigned Flags)
+ unsigned Flags)
: StartIdx(StartIdx), Len(Len), FirstInst(FirstInst), LastInst(LastInst),
- MBB(MBB), FunctionIdx(FunctionIdx), Flags(Flags) {}
+ MBB(MBB), Flags(Flags) {}
Candidate() = delete;
/// Used to ensure that \p Candidates are outlined in an order that
diff --git a/llvm/include/llvm/CodeGen/TargetInstrInfo.h b/llvm/include/llvm/CodeGen/TargetInstrInfo.h
index 4749d06501cb2..e3d81ddb4c95a 100644
--- a/llvm/include/llvm/CodeGen/TargetInstrInfo.h
+++ b/llvm/include/llvm/CodeGen/TargetInstrInfo.h
@@ -2225,6 +2225,16 @@ class LLVM_ABI TargetInstrInfo : public MCInstrInfo {
virtual void mergeOutliningCandidateAttributes(
Function &F, std::vector<outliner::Candidate> &Candidates) const;
+ /// Return true if the target supports outlining candidates from multiple
+ /// input sections in one pass. When enabled, candidates are partitioned by
+ /// input section before target-specific outlining checks are performed, and
+ /// each outlined function is placed in the input section of its parents.
+ ///
+ /// A target that enables this must not independently reject functions with
+ /// a section marking in isFunctionSafeToOutlineFrom(), otherwise such
+ /// functions never reach the outliner and this hook has no effect.
+ virtual bool supportsSectionAwareOutlining() const { return false; }
+
protected:
/// Target-dependent implementation for getOutliningTypeImpl.
virtual outliner::InstrType
diff --git a/llvm/lib/CodeGen/MachineOutliner.cpp b/llvm/lib/CodeGen/MachineOutliner.cpp
index 9c92c7bf2369b..45e3da0e53b34 100644
--- a/llvm/lib/CodeGen/MachineOutliner.cpp
+++ b/llvm/lib/CodeGen/MachineOutliner.cpp
@@ -479,6 +479,13 @@ struct MachineOutliner : public ModulePass {
unsigned StringLen, std::vector<Candidate> &CandidatesForRepeatedSeq,
OutlinedFunction &OF);
+ /// Run the outlining cost model for a group of candidates and append the
+ /// resulting outlined function if it is profitable.
+ void createOutlinedFunctionIfBeneficial(
+ std::vector<Candidate> &Candidates, unsigned StringLen,
+ unsigned MinRepeats, const TargetInstrInfo &TII,
+ std::vector<std::unique_ptr<OutlinedFunction>> &FunctionList);
+
/// Remark output explaining that a function was outlined.
void emitOutlinedFunctionRemark(OutlinedFunction &OF);
@@ -723,8 +730,7 @@ void MachineOutliner::findGlobalCandidates(
auto Length = ME.EndIdx - ME.StartIdx + 1;
MachineBasicBlock *MBB = StartIt->getParent();
CandidatesForRepeatedSeq.emplace_back(ME.StartIdx, Length, StartIt, EndIt,
- MBB, FunctionList.size(),
- MBBFlagsMap[MBB]);
+ MBB, MBBFlagsMap[MBB]);
const TargetInstrInfo *TII =
MBB->getParent()->getSubtarget().getInstrInfo();
unsigned MinRepeats = 1;
@@ -740,6 +746,64 @@ void MachineOutliner::findGlobalCandidates(
}
}
+static StringRef getCandidateInputSection(const Candidate &C) {
+ const Function &F = C.getMF()->getFunction();
+ return F.hasSection() ? F.getSection() : StringRef();
+}
+
+/// Returns the value of the "linker_output_section" attribute if it exists,
+/// otherwise returns the input section of the candidate's parent function.
+static StringRef getCandidatePlacementSection(const Candidate &C) {
+ const Function &F = C.getMF()->getFunction();
+ Attribute OutputSection = F.getFnAttribute("linker_output_section");
+ if (OutputSection.isValid())
+ return OutputSection.getValueAsString();
+ return getCandidateInputSection(C);
+}
+
+static SmallVector<std::vector<Candidate>>
+partitionCandidatesByPlacementSection(std::vector<Candidate> &Candidates) {
+ // Each candidate belongs to exactly one MachineFunction, so the partitions
+ // are disjoint and can be outlined independently.
+ SmallVector<std::vector<Candidate>> Partitions;
+ SmallVector<StringRef> Sections;
+ for (Candidate &C : Candidates) {
+ StringRef Section = getCandidatePlacementSection(C);
+ auto It = llvm::find(Sections, Section);
+ if (It == Sections.end()) {
+ Sections.push_back(Section);
+ Partitions.emplace_back();
+ It = Sections.end() - 1;
+ }
+ Partitions[std::distance(Sections.begin(), It)].push_back(std::move(C));
+ }
+ return Partitions;
+}
+
+void MachineOutliner::createOutlinedFunctionIfBeneficial(
+ std::vector<Candidate> &Candidates, unsigned StringLen, unsigned MinRepeats,
+ const TargetInstrInfo &TII,
+ std::vector<std::unique_ptr<OutlinedFunction>> &FunctionList) {
+ if (Candidates.size() < MinRepeats)
+ return;
+
+ std::optional<std::unique_ptr<OutlinedFunction>> OF =
+ TII.getOutliningCandidateInfo(*MMI, Candidates, MinRepeats);
+
+ // If we deleted too many candidates, then there's nothing worth outlining.
+ // FIXME: This should take target-specified instruction sizes into account.
+ if (!OF.has_value() || OF.value()->Candidates.size() < MinRepeats)
+ return;
+
+ // Is it better to outline this candidate than not?
+ if (OF.value()->getBenefit() < OutlinerBenefitThreshold) {
+ emitNotOutliningCheaperRemark(StringLen, Candidates, *OF.value());
+ return;
+ }
+
+ FunctionList.emplace_back(std::move(OF.value()));
+}
+
void MachineOutliner::findCandidates(
InstructionMapper &Mapper,
std::vector<std::unique_ptr<OutlinedFunction>> &FunctionList) {
@@ -809,8 +873,7 @@ void MachineOutliner::findCandidates(
MachineBasicBlock::iterator EndIt = Mapper.InstrList[EndIdx];
MachineBasicBlock *MBB = StartIt->getParent();
CandidatesForRepeatedSeq.emplace_back(StartIdx, StringLen, StartIt, EndIt,
- MBB, FunctionList.size(),
- Mapper.MBBFlagsMap[MBB]);
+ MBB, Mapper.MBBFlagsMap[MBB]);
}
#ifndef NDEBUG
LLVM_DEBUG(dbgs() << " Candidates discarded: " << NumDiscarded
@@ -830,23 +893,40 @@ void MachineOutliner::findCandidates(
const TargetInstrInfo *TII =
CandidatesForRepeatedSeq[0].getMF()->getSubtarget().getInstrInfo();
- std::optional<std::unique_ptr<OutlinedFunction>> OF =
- TII->getOutliningCandidateInfo(*MMI, CandidatesForRepeatedSeq,
- MinRepeats);
-
- // If we deleted too many candidates, then there's nothing worth outlining.
- // FIXME: This should take target-specified instruction sizes into account.
- if (!OF.has_value() || OF.value()->Candidates.size() < MinRepeats)
- continue;
-
- // Is it better to outline this candidate than not?
- if (OF.value()->getBenefit() < OutlinerBenefitThreshold) {
- emitNotOutliningCheaperRemark(StringLen, CandidatesForRepeatedSeq,
- *OF.value());
+ // Targets that do not opt in to section aware outlining never map
+ // functions with a section marking, so every candidate would land in a
+ // single partition. Skip partitioning entirely to avoid the allocation.
+ if (!TII->supportsSectionAwareOutlining()) {
+ createOutlinedFunctionIfBeneficial(CandidatesForRepeatedSeq, StringLen,
+ MinRepeats, *TII, FunctionList);
continue;
}
- FunctionList.emplace_back(std::move(OF.value()));
+#ifndef NDEBUG
+ LLVM_DEBUG(
+ dbgs()
+ << "*** Section Aware Outlining is enabled for the target *** \n");
+#endif
+
+ SmallVector<std::vector<Candidate>> Partitions =
+ partitionCandidatesByPlacementSection(CandidatesForRepeatedSeq);
+#ifndef NDEBUG
+ LLVM_DEBUG(dbgs() << " Input section partitions: " << Partitions.size()
+ << "\n");
+#endif
+
+ for (std::vector<Candidate> &Partition : Partitions) {
+#ifndef NDEBUG
+ LLVM_DEBUG({
+ StringRef Section = getCandidatePlacementSection(Partition.front());
+ dbgs() << " .. section '"
+ << (Section.empty() ? StringRef("<none>") : Section)
+ << "': " << Partition.size() << " candidates\n";
+ });
+#endif
+ createOutlinedFunctionIfBeneficial(Partition, StringLen, MinRepeats, *TII,
+ FunctionList);
+ }
}
}
@@ -916,6 +996,30 @@ MachineFunction *MachineOutliner::createOutlinedFunction(
TII.mergeOutliningCandidateAttributes(*F, OF.Candidates);
+ // Place the outlined function in the first candidate's section. Candidates
+ // were partitioned so that they all share one placement section, so any of
+ // them is representative; note they may still differ in input section when
+ // several input sections map to one linker script output section.
+ if (TII.supportsSectionAwareOutlining()) {
+ const Function &ParentFn = FirstCand.getMF()->getFunction();
+ if (ParentFn.hasSection()) {
+ F->setSection(ParentFn.getSection());
+#ifndef NDEBUG
+ LLVM_DEBUG(dbgs() << " INHERITED SECTION: " << ParentFn.getSection()
+ << "\n");
+#endif
+ }
+
+ Attribute OutputSection = ParentFn.getFnAttribute("linker_output_section");
+ if (OutputSection.isValid()) {
+ F->addFnAttr("linker_output_section", OutputSection.getValueAsString());
+#ifndef NDEBUG
+ LLVM_DEBUG(dbgs() << " INHERITED OUTPUT SECTION: "
+ << OutputSection.getValueAsString() << "\n");
+#endif
+ }
+ }
+
// Set uwtable, so we generate eh_frame.
UWTableKind UW = std::accumulate(
OF.Candidates.cbegin(), OF.Candidates.cend(), UWTableKind::None,
diff --git a/llvm/lib/Target/AArch64/AArch64InstrInfo.cpp b/llvm/lib/Target/AArch64/AArch64InstrInfo.cpp
index 9e128651598f3..640f4f1179f76 100644
--- a/llvm/lib/Target/AArch64/AArch64InstrInfo.cpp
+++ b/llvm/lib/Target/AArch64/AArch64InstrInfo.cpp
@@ -10885,13 +10885,6 @@ bool AArch64InstrInfo::isFunctionSafeToOutlineFrom(
if (!OutlineFromLinkOnceODRs && F.hasLinkOnceODRLinkage())
return false;
- // Don't outline from functions with section markings; the program could
- // expect that all the code is in the named section.
- // FIXME: Allow outlining from multiple functions with the same section
- // marking.
- if (F.hasSection())
- return false;
-
// Outlining from functions with redzones is unsafe since the outliner may
// modify the stack. Check if hasRedZone is true or unknown; if yes, don't
// outline from it.
@@ -10910,6 +10903,13 @@ bool AArch64InstrInfo::isFunctionSafeToOutlineFrom(
if (MF.getTarget().getMCAsmInfo().usesWindowsCFI())
return false;
+ // Allow outlining from functions with section markings if the target can
+ // place the outlined function in the same section. Otherwise, the outlined
+ // function may be placed in a different section, which can break assumptions
+ // about the section layout.
+ if (F.hasSection())
+ return supportsSectionAwareOutlining();
+
// It's safe to outline from MF.
return true;
}
diff --git a/llvm/lib/Target/ARM/ARMBaseInstrInfo.cpp b/llvm/lib/Target/ARM/ARMBaseInstrInfo.cpp
index a922024b032db..d4d163c4f94d1 100644
--- a/llvm/lib/Target/ARM/ARMBaseInstrInfo.cpp
+++ b/llvm/lib/Target/ARM/ARMBaseInstrInfo.cpp
@@ -6045,17 +6045,17 @@ bool ARMBaseInstrInfo::isFunctionSafeToOutlineFrom(
if (!OutlineFromLinkOnceODRs && F.hasLinkOnceODRLinkage())
return false;
- // Don't outline from functions with section markings; the program could
- // expect that all the code is in the named section.
- // FIXME: Allow outlining from multiple functions with the same section
- // marking.
- if (F.hasSection())
- return false;
-
// FIXME: Thumb1 outlining is not handled
if (MF.getInfo<ARMFunctionInfo>()->isThumb1OnlyFunction())
return false;
+ // Allow outlining from functions with section markings if the target can
+ // place the outlined function in the same section. Otherwise, the outlined
+ // function may be placed in a different section, which can break assumptions
+ // about the section layout.
+ if (F.hasSection())
+ return supportsSectionAwareOutlining();
+
// It's safe to outline from MF.
return true;
}
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
index df93401d98067..4798a8cecfe53 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
@@ -3630,6 +3630,7 @@ RISCVInstrInfo::getSerializableDirectMachineOperandTargetFlags() const {
};
return ArrayRef(TargetFlags);
}
+
bool RISCVInstrInfo::isFunctionSafeToOutlineFrom(
MachineFunction &MF, bool OutlineFromLinkOnceODRs) const {
const Function &F = MF.getFunction();
@@ -3638,10 +3639,12 @@ bool RISCVInstrInfo::isFunctionSafeToOutlineFrom(
if (!OutlineFromLinkOnceODRs && F.hasLinkOnceODRLinkage())
return false;
- // Don't outline from functions with section markings; the program could
- // expect that all the code is in the named section.
+ // Allow outlining from functions with section markings if the target can
+ // place the outlined function in the same section. Otherwise, the outlined
+ // function may be placed in a different section, which can break assumptions
+ // about the section layout.
if (F.hasSection())
- return false;
+ return supportsSectionAwareOutlining();
// It's safe to outline from MF.
return true;
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfo.h b/llvm/lib/Target/RISCV/RISCVInstrInfo.h
index c75335ba7d145..0f6983c08f0cb 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfo.h
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfo.h
@@ -233,6 +233,8 @@ class RISCVInstrInfo : public RISCVGenInstrInfo {
bool shouldOutlineFromFunctionByDefault(MachineFunction &MF) const override;
+ bool supportsSectionAwareOutlining() const override { return true; }
+
// Return true if the candidate should be discarded from outlining.
bool analyzeCandidate(outliner::Candidate &C) const;
// Calculate target-specific information for a set of outlining candidates.
diff --git a/llvm/test/CodeGen/RISCV/machine-outliner-multiple-sections.ll b/llvm/test/CodeGen/RISCV/machine-outliner-multiple-sections.ll
new file mode 100644
index 0000000000000..1652897f03da4
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/machine-outliner-multiple-sections.ll
@@ -0,0 +1,169 @@
+; NOTE: This test is derived from the following C source. It exercises one
+; repeated sequence across unsectioned functions and three explicit sections.
+;
+; All four input-section buckets are outlined in one pass.
+;
+; #define BODY(N) do { a ^= b; b += a; a = (a << 3) | (a >> 29); \
+; a ^= b; return a + N; } while (0)
+; #define SEC(N, V) __attribute__((noinline, section(".sec_shared"))) \
+; unsigned N(unsigned a, unsigned b) { BODY(V); }
+; #define SEC2(S, N, V) __attribute__((noinline, section(S))) \
+; unsigned N(unsigned a, unsigned b) { BODY(V); }
+; #define PLAIN(N, V) __attribute__((noinline)) \
+; unsigned N(unsigned a, unsigned b) { BODY(V); }
+; SEC(shared_0, 1) SEC(shared_1, 2) SEC(shared_2, 3)
+; PLAIN(plain_0, 4) PLAIN(plain_1, 5) PLAIN(plain_2, 6)
+; SEC2(".sec_a", a_0, 7) SEC2(".sec_a", a_1, 8)
+; SEC2(".sec_b", b_0, 9) SEC2(".sec_b", b_1, 10)
+
+; RUN: llc -mtriple=riscv32 -enable-machine-outliner=always -verify-machineinstrs < %s | FileCheck %s --check-prefixes=CHECK,NOFS
+; RUN: llc -mtriple=riscv32 -enable-machine-outliner=always --function-sections -verify-machineinstrs < %s | FileCheck %s --check-prefixes=CHECK,FS
+; RUN: llc -mtriple=riscv64 -enable-machine-outliner=always -verify-machineinstrs < %s | FileCheck %s --check-prefixes=CHECK,NOFS
+; RUN: llc -mtriple=riscv64 -enable-machine-outliner=always --function-sections -verify-machineinstrs < %s | FileCheck %s --check-prefixes=CHECK,FS
+
+; CHECK: .section .sec_shared,"ax", at progbits
+
+define i32 @shared_0(i32 %a, i32 %b) noinline nounwind section ".sec_shared" {
+; CHECK-LABEL: shared_0:
+; CHECK: call t0, OUTLINED_FUNCTION_[[SHARED:[0-9_]+]]
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 1
+ ret i32 %result
+}
+
+define i32 @shared_1(i32 %a, i32 %b) noinline nounwind section ".sec_shared" {
+; CHECK-LABEL: shared_1:
+; CHECK: call t0, OUTLINED_FUNCTION_[[SHARED]]
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 2
+ ret i32 %result
+}
+
+define i32 @shared_2(i32 %a, i32 %b) noinline nounwind section ".sec_shared" {
+; CHECK-LABEL: shared_2:
+; CHECK: call t0, OUTLINED_FUNCTION_[[SHARED]]
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 3
+ ret i32 %result
+}
+
+define i32 @plain_0(i32 %a, i32 %b) noinline nounwind {
+; NOFS: .text{{$}}
+; FS: .section .text.plain_0,"ax", at progbits
+; CHECK-LABEL: plain_0:
+; CHECK: call t0, OUTLINED_FUNCTION_[[PLAIN:[0-9_]+]]
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 4
+ ret i32 %result
+}
+
+define i32 @plain_1(i32 %a, i32 %b) noinline nounwind {
+; FS: .section .text.plain_1,"ax", at progbits
+; CHECK-LABEL: plain_1:
+; CHECK: call t0, OUTLINED_FUNCTION_[[PLAIN]]
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 5
+ ret i32 %result
+}
+
+define i32 @plain_2(i32 %a, i32 %b) noinline nounwind {
+; FS: .section .text.plain_2,"ax", at progbits
+; CHECK-LABEL: plain_2:
+; CHECK: call t0, OUTLINED_FUNCTION_[[PLAIN]]
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 6
+ ret i32 %result
+}
+
+define i32 @a_0(i32 %a, i32 %b) noinline nounwind section ".sec_a" {
+; CHECK-LABEL: a_0:
+; CHECK: call t0, OUTLINED_FUNCTION_[[A:[0-9_]+]]
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 7
+ ret i32 %result
+}
+
+define i32 @a_1(i32 %a, i32 %b) noinline nounwind section ".sec_a" {
+; CHECK-LABEL: a_1:
+; CHECK: call t0, OUTLINED_FUNCTION_[[A]]
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 8
+ ret i32 %result
+}
+
+define i32 @b_0(i32 %a, i32 %b) noinline nounwind section ".sec_b" {
+; CHECK-LABEL: b_0:
+; CHECK: call t0, OUTLINED_FUNCTION_[[B:[0-9_]+]]
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 9
+ ret i32 %result
+}
+
+define i32 @b_1(i32 %a, i32 %b) noinline nounwind section ".sec_b" {
+; CHECK-LABEL: b_1:
+; CHECK: call t0, OUTLINED_FUNCTION_[[B]]
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 10
+ ret i32 %result
+}
+
+; All four section buckets were outlined in the same pass.
+; CHECK: OUTLINED_FUNCTION_[[SHARED]]:
+; NOFS: .text{{$}}
+; NOFS: OUTLINED_FUNCTION_[[PLAIN]]:
+; FS: .section .text.OUTLINED_FUNCTION_[[PLAIN]],"ax", at progbits
+; FS: OUTLINED_FUNCTION_[[PLAIN]]:
+; CHECK: .section .sec_a,"ax", at progbits
+; CHECK: OUTLINED_FUNCTION_[[A]]:
+; CHECK: .section .sec_b,"ax", at progbits
+; CHECK: OUTLINED_FUNCTION_[[B]]:
diff --git a/llvm/test/CodeGen/RISCV/machine-outliner-output-section.ll b/llvm/test/CodeGen/RISCV/machine-outliner-output-section.ll
new file mode 100644
index 0000000000000..037daabd48802
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/machine-outliner-output-section.ll
@@ -0,0 +1,101 @@
+; NOTE: This test is derived from the following C source. With LTO + a linker
+; script the linker records the output section each function is mapped to as the
+; "linker_output_section" attribute.
+;
+; same_out_0/1 are in different input sections but map to one output section, so
+; they are outlined together. diff_out maps elsewhere and is not grouped with
+; them. no_attr_0/1 carry no attribute and fall back to their shared input
+; section.
+;
+; #define BODY(N) do { a ^= b; b += a; a = (a << 3) | (a >> 29); \
+; a ^= b; return a + N; } while (0)
+; #define SEC(S, N, V) __attribute__((noinline, section(S))) \
+; unsigned N(unsigned a, unsigned b) { BODY(V); }
+; SEC(".text.same_out_0", same_out_0, 1) SEC(".text.same_out_1", same_out_1, 2)
+; SEC(".text.diff_out", diff_out, 3)
+; SEC(".text.shared", no_attr_0, 4) SEC(".text.shared", no_attr_1, 5)
+
+; RUN: llc -mtriple=riscv32 -enable-machine-outliner=always -verify-machineinstrs < %s | FileCheck %s
+; RUN: llc -mtriple=riscv64 -enable-machine-outliner=always -verify-machineinstrs < %s | FileCheck %s
+
+; The two functions sharing an output section are outlined together even though
+; their input sections differ.
+define i32 @same_out_0(i32 %a, i32 %b) noinline nounwind #0 section ".text.same_out_0" {
+; CHECK: .section .text.same_out_0,"ax", at progbits
+; CHECK-LABEL: same_out_0:
+; CHECK: call t0, OUTLINED_FUNCTION_[[HOT:[0-9_]+]]
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 1
+ ret i32 %result
+}
+
+define i32 @same_out_1(i32 %a, i32 %b) noinline nounwind #0 section ".text.same_out_1" {
+; CHECK: .section .text.same_out_1,"ax", at progbits
+; CHECK-LABEL: same_out_1:
+; CHECK: call t0, OUTLINED_FUNCTION_[[HOT]]
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 2
+ ret i32 %result
+}
+
+; A different output section must not be grouped with the pair above.
+define i32 @diff_out(i32 %a, i32 %b) noinline nounwind #1 section ".text.diff_out" {
+; CHECK: .section .text.diff_out,"ax", at progbits
+; CHECK-LABEL: diff_out:
+; CHECK-NOT: call t0, OUTLINED_FUNCTION_
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 3
+ ret i32 %result
+}
+
+; No attribute: the input section is used, and these two share it.
+define i32 @no_attr_0(i32 %a, i32 %b) noinline nounwind section ".text.shared" {
+; CHECK: .section .text.shared,"ax", at progbits
+; CHECK-LABEL: no_attr_0:
+; CHECK: call t0, OUTLINED_FUNCTION_[[SHARED:[0-9_]+]]
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 4
+ ret i32 %result
+}
+
+define i32 @no_attr_1(i32 %a, i32 %b) noinline nounwind section ".text.shared" {
+; CHECK-LABEL: no_attr_1:
+; CHECK: call t0, OUTLINED_FUNCTION_[[SHARED]]
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 5
+ ret i32 %result
+}
+
+attributes #0 = { "linker_output_section"="out_hot" }
+attributes #1 = { "linker_output_section"="out_cold" }
+
+; Each outlined function is emitted into the input section of its candidates.
+; CHECK: .section .text.same_out_0,"ax", at progbits
+; CHECK: OUTLINED_FUNCTION_[[HOT]]:
+; CHECK: .section .text.shared,"ax", at progbits
+; CHECK: OUTLINED_FUNCTION_[[SHARED]]:
diff --git a/llvm/test/CodeGen/RISCV/machine-outliner-section-attribute.ll b/llvm/test/CodeGen/RISCV/machine-outliner-section-attribute.ll
new file mode 100644
index 0000000000000..52eb6f79791f2
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/machine-outliner-section-attribute.ll
@@ -0,0 +1,68 @@
+; NOTE: This test is derived from the following C source. It uses only
+; parameter arithmetic so the repeated sequence is self-contained.
+;
+; #define BODY(N) do { a ^= b; b += a; a = (a << 3) | (a >> 29); \
+; a ^= b; return a + N; } while (0)
+; __attribute__((noinline, section(".sec_shared")))
+; unsigned shared_0(unsigned a, unsigned b) { BODY(1); }
+; __attribute__((noinline, section(".sec_shared")))
+; unsigned shared_1(unsigned a, unsigned b) { BODY(2); }
+; __attribute__((noinline, section(".sec_shared")))
+; unsigned shared_2(unsigned a, unsigned b) { BODY(3); }
+
+; RUN: llc -mtriple=riscv32 -enable-machine-outliner=always -verify-machineinstrs < %s | FileCheck %s --check-prefixes=CHECK,RV32
+; RUN: llc -mtriple=riscv32 -enable-machine-outliner=always --function-sections -verify-machineinstrs < %s | FileCheck %s --check-prefixes=CHECK,RV32
+; RUN: llc -mtriple=riscv64 -enable-machine-outliner=always -verify-machineinstrs < %s | FileCheck %s --check-prefixes=CHECK,RV64
+; RUN: llc -mtriple=riscv64 -enable-machine-outliner=always --function-sections -verify-machineinstrs < %s | FileCheck %s --check-prefixes=CHECK,RV64
+
+; CHECK: .section .sec_shared,"ax", at progbits
+
+define i32 @shared_0(i32 %a, i32 %b) noinline nounwind section ".sec_shared" {
+; CHECK-LABEL: shared_0:
+; CHECK: call t0, OUTLINED_FUNCTION_0
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 1
+ ret i32 %result
+}
+
+define i32 @shared_1(i32 %a, i32 %b) noinline nounwind section ".sec_shared" {
+; CHECK-LABEL: shared_1:
+; CHECK: call t0, OUTLINED_FUNCTION_0
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 2
+ ret i32 %result
+}
+
+define i32 @shared_2(i32 %a, i32 %b) noinline nounwind section ".sec_shared" {
+; CHECK-LABEL: shared_2:
+; CHECK: call t0, OUTLINED_FUNCTION_0
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 3
+ ret i32 %result
+}
+
+; The outlined function inherits the input section of its parents.
+; CHECK: OUTLINED_FUNCTION_0:
+; CHECK: xor a0, a0, a1
+; RV32-NEXT: srli a2, a0, 29
+; RV64-NEXT: srliw a2, a0, 29
+; CHECK-NEXT: slli a3, a0, 3
+; CHECK-NEXT: add a0, a1, a0
+; CHECK-NEXT: or a2, a3, a2
+; CHECK-NEXT: xor a0, a2, a0
+; CHECK-NEXT: jr t0
diff --git a/llvm/test/CodeGen/RISCV/machine-outliner-section-debug.ll b/llvm/test/CodeGen/RISCV/machine-outliner-section-debug.ll
new file mode 100644
index 0000000000000..4b5e5ac0453f1
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/machine-outliner-section-debug.ll
@@ -0,0 +1,16 @@
+; Check debug output for section-aware machine outlining.
+;
+; REQUIRES: asserts
+; RUN: llc -mtriple=riscv32 -enable-machine-outliner=always \
+; RUN: -debug-only=machine-outliner \
+; RUN: %S/machine-outliner-multiple-sections.ll -o /dev/null 2>&1 | FileCheck %s
+
+; CHECK: *** Section Aware Outlining is enabled for the target ***
+; CHECK: Input section partitions: 4
+; CHECK: .. section '.sec_shared': 3 candidates
+; CHECK: .. section '<none>': 3 candidates
+; CHECK: .. section '.sec_a': 2 candidates
+; CHECK: .. section '.sec_b': 2 candidates
+; CHECK: INHERITED SECTION: .sec_shared
+; CHECK: INHERITED SECTION: .sec_a
+; CHECK: INHERITED SECTION: .sec_b
diff --git a/llvm/test/CodeGen/RISCV/machineoutliner-pcrel-lo.mir b/llvm/test/CodeGen/RISCV/machineoutliner-pcrel-lo.mir
index fd3630bcfad25..4dfe614b12db4 100644
--- a/llvm/test/CodeGen/RISCV/machineoutliner-pcrel-lo.mir
+++ b/llvm/test/CodeGen/RISCV/machineoutliner-pcrel-lo.mir
@@ -7,10 +7,12 @@
# RUN: | FileCheck -check-prefix=CHECK-FS %s
# RUN: llc -mtriple=riscv64 -x mir -run-pass=machine-outliner -simplify-mir --function-sections -verify-machineinstrs -outliner-leaf-descendants=false < %s \
# RUN: | FileCheck -check-prefix=CHECK-FS %s
+# CHECK: define internal void @OUTLINED_FUNCTION_2() unnamed_addr {{.*}} section ".abc"
+# CHECK-FS: define internal void @OUTLINED_FUNCTION_1() unnamed_addr {{.*}} section ".abc"
--- |
- ; Cannot outline instructions with pcrel-lo operands if function section
- ; enabled.
+ ; pcrel-lo operands stay in the source section when outlining could split
+ ; their pcrel-hi/pcrel-lo pair across sections.
@bar = dso_local local_unnamed_addr global i32 0, align 4
define i32 @foo(i32 %a, i32 %b) { ret i32 0 }
@@ -199,30 +201,21 @@ body: |
; CHECK: bb.0:
; CHECK-NEXT: liveins: $x10, $x11, $x13
; CHECK-NEXT: {{ $}}
- ; CHECK-NEXT: $x11 = ORI $x11, 1023
- ; CHECK-NEXT: $x12 = ADDI $x10, 17
- ; CHECK-NEXT: $x11 = AND $x12, $x11
- ; CHECK-NEXT: $x10 = SUB $x10, $x11
+ ; CHECK-NEXT: $x5 = PseudoCALLReg target-flags(riscv-call) @OUTLINED_FUNCTION_2, implicit-def $x5, implicit-def $x10, implicit-def $x11, implicit-def $x12, implicit $x10, implicit $x11
; CHECK-NEXT: $x11 = LW killed renamable $x13, target-flags(riscv-pcrel-lo) <mcsymbol .Lpcrel_hi1> :: (dereferenceable load (s32) from @bar)
; CHECK-NEXT: PseudoBR %bb.3
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: bb.1:
; CHECK-NEXT: liveins: $x10, $x11, $x13
; CHECK-NEXT: {{ $}}
- ; CHECK-NEXT: $x11 = ORI $x11, 1023
- ; CHECK-NEXT: $x12 = ADDI $x10, 17
- ; CHECK-NEXT: $x11 = AND $x12, $x11
- ; CHECK-NEXT: $x10 = SUB $x10, $x11
+ ; CHECK-NEXT: $x5 = PseudoCALLReg target-flags(riscv-call) @OUTLINED_FUNCTION_2, implicit-def $x5, implicit-def $x10, implicit-def $x11, implicit-def $x12, implicit $x10, implicit $x11
; CHECK-NEXT: $x11 = LW killed renamable $x13, target-flags(riscv-pcrel-lo) <mcsymbol .Lpcrel_hi1> :: (dereferenceable load (s32) from @bar)
; CHECK-NEXT: PseudoBR %bb.3
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: bb.2:
; CHECK-NEXT: liveins: $x10, $x11, $x13
; CHECK-NEXT: {{ $}}
- ; CHECK-NEXT: $x11 = ORI $x11, 1023
- ; CHECK-NEXT: $x12 = ADDI $x10, 17
- ; CHECK-NEXT: $x11 = AND $x12, $x11
- ; CHECK-NEXT: $x10 = SUB $x10, $x11
+ ; CHECK-NEXT: $x5 = PseudoCALLReg target-flags(riscv-call) @OUTLINED_FUNCTION_2, implicit-def $x5, implicit-def $x10, implicit-def $x11, implicit-def $x12, implicit $x10, implicit $x11
; CHECK-NEXT: $x11 = LW killed renamable $x13, target-flags(riscv-pcrel-lo) <mcsymbol .Lpcrel_hi1> :: (dereferenceable load (s32) from @bar)
; CHECK-NEXT: PseudoBR %bb.3
; CHECK-NEXT: {{ $}}
@@ -233,30 +226,21 @@ body: |
; CHECK-FS: bb.0:
; CHECK-FS-NEXT: liveins: $x10, $x11, $x13
; CHECK-FS-NEXT: {{ $}}
- ; CHECK-FS-NEXT: $x11 = ORI $x11, 1023
- ; CHECK-FS-NEXT: $x12 = ADDI $x10, 17
- ; CHECK-FS-NEXT: $x11 = AND $x12, $x11
- ; CHECK-FS-NEXT: $x10 = SUB $x10, $x11
+ ; CHECK-FS-NEXT: $x5 = PseudoCALLReg target-flags(riscv-call) @OUTLINED_FUNCTION_1, implicit-def $x5, implicit-def $x10, implicit-def $x11, implicit-def $x12, implicit $x10, implicit $x11
; CHECK-FS-NEXT: $x11 = LW killed renamable $x13, target-flags(riscv-pcrel-lo) <mcsymbol .Lpcrel_hi1> :: (dereferenceable load (s32) from @bar)
; CHECK-FS-NEXT: PseudoBR %bb.3
; CHECK-FS-NEXT: {{ $}}
; CHECK-FS-NEXT: bb.1:
; CHECK-FS-NEXT: liveins: $x10, $x11, $x13
; CHECK-FS-NEXT: {{ $}}
- ; CHECK-FS-NEXT: $x11 = ORI $x11, 1023
- ; CHECK-FS-NEXT: $x12 = ADDI $x10, 17
- ; CHECK-FS-NEXT: $x11 = AND $x12, $x11
- ; CHECK-FS-NEXT: $x10 = SUB $x10, $x11
+ ; CHECK-FS-NEXT: $x5 = PseudoCALLReg target-flags(riscv-call) @OUTLINED_FUNCTION_1, implicit-def $x5, implicit-def $x10, implicit-def $x11, implicit-def $x12, implicit $x10, implicit $x11
; CHECK-FS-NEXT: $x11 = LW killed renamable $x13, target-flags(riscv-pcrel-lo) <mcsymbol .Lpcrel_hi1> :: (dereferenceable load (s32) from @bar)
; CHECK-FS-NEXT: PseudoBR %bb.3
; CHECK-FS-NEXT: {{ $}}
; CHECK-FS-NEXT: bb.2:
; CHECK-FS-NEXT: liveins: $x10, $x11, $x13
; CHECK-FS-NEXT: {{ $}}
- ; CHECK-FS-NEXT: $x11 = ORI $x11, 1023
- ; CHECK-FS-NEXT: $x12 = ADDI $x10, 17
- ; CHECK-FS-NEXT: $x11 = AND $x12, $x11
- ; CHECK-FS-NEXT: $x10 = SUB $x10, $x11
+ ; CHECK-FS-NEXT: $x5 = PseudoCALLReg target-flags(riscv-call) @OUTLINED_FUNCTION_1, implicit-def $x5, implicit-def $x10, implicit-def $x11, implicit-def $x12, implicit $x10, implicit $x11
; CHECK-FS-NEXT: $x11 = LW killed renamable $x13, target-flags(riscv-pcrel-lo) <mcsymbol .Lpcrel_hi1> :: (dereferenceable load (s32) from @bar)
; CHECK-FS-NEXT: PseudoBR %bb.3
; CHECK-FS-NEXT: {{ $}}
diff --git a/llvm/test/LTO/RISCV/machine-outliner-section-rv32.ll b/llvm/test/LTO/RISCV/machine-outliner-section-rv32.ll
new file mode 100644
index 0000000000000..96e128fdd55a0
--- /dev/null
+++ b/llvm/test/LTO/RISCV/machine-outliner-section-rv32.ll
@@ -0,0 +1,117 @@
+; Check that the RISC-V machine outliner preserves an explicit input section
+; when it runs in the full LTO backend.
+;
+; RUN: llvm-as %s -o %t.bc
+; RUN: llvm-lto2 run -enable-machine-outliner=always -filetype=asm -o %t.s %t.bc \
+; RUN: -r=%t.bc,shared_0,px -r=%t.bc,shared_1,px -r=%t.bc,shared_2,px \
+; RUN: -r=%t.bc,plain_0,px -r=%t.bc,plain_1,px -r=%t.bc,plain_2,px
+; RUN: FileCheck %s --input-file %t.s.0 --check-prefixes=CHECK,NOFS
+;
+; RUN: llvm-lto2 run -enable-machine-outliner=always --function-sections -filetype=asm -o %t.s %t.bc \
+; RUN: -r=%t.bc,shared_0,px -r=%t.bc,shared_1,px -r=%t.bc,shared_2,px \
+; RUN: -r=%t.bc,plain_0,px -r=%t.bc,plain_1,px -r=%t.bc,plain_2,px
+; RUN: FileCheck %s --input-file %t.s.0 --check-prefixes=CHECK,FS
+
+target datalayout = "e-m:e-p:32:32-i64:64-n32-S128"
+target triple = "riscv32-unknown-linux-gnu"
+
+; CHECK: .section .sec_shared,"ax", at progbits
+
+define i32 @shared_0(i32 %a, i32 %b) noinline nounwind section ".sec_shared" {
+; CHECK-LABEL: shared_0:
+; CHECK: call t0, OUTLINED_FUNCTION_0
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 1
+ ret i32 %result
+}
+
+define i32 @shared_1(i32 %a, i32 %b) noinline nounwind section ".sec_shared" {
+; CHECK-LABEL: shared_1:
+; CHECK: call t0, OUTLINED_FUNCTION_0
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 2
+ ret i32 %result
+}
+
+define i32 @shared_2(i32 %a, i32 %b) noinline nounwind section ".sec_shared" {
+; CHECK-LABEL: shared_2:
+; CHECK: call t0, OUTLINED_FUNCTION_0
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 3
+ ret i32 %result
+}
+
+define i32 @plain_0(i32 %a, i32 %b) noinline nounwind {
+; NOFS: .text{{$}}
+; FS: .section .text.plain_0,"ax", at progbits
+; CHECK-LABEL: plain_0:
+; CHECK: call t0, OUTLINED_FUNCTION_1
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 4
+ ret i32 %result
+}
+
+define i32 @plain_1(i32 %a, i32 %b) noinline nounwind {
+; FS: .section .text.plain_1,"ax", at progbits
+; CHECK-LABEL: plain_1:
+; CHECK: call t0, OUTLINED_FUNCTION_1
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 5
+ ret i32 %result
+}
+
+define i32 @plain_2(i32 %a, i32 %b) noinline nounwind {
+; FS: .section .text.plain_2,"ax", at progbits
+; CHECK-LABEL: plain_2:
+; CHECK: call t0, OUTLINED_FUNCTION_1
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 6
+ ret i32 %result
+}
+
+; The outlined function for the explicitly sectioned parents inherits their
+; input section, while the one for the unsectioned parents is placed in .text,
+; or in its own synthesized section when --function-sections is enabled.
+; CHECK: .section .sec_shared,"ax", at progbits
+; CHECK: OUTLINED_FUNCTION_0:
+; CHECK: xor a0, a0, a1
+; CHECK-NEXT: srli a2, a0, 29
+; CHECK-NEXT: slli a3, a0, 3
+; CHECK-NEXT: add a0, a1, a0
+; CHECK-NEXT: or a2, a3, a2
+; CHECK-NEXT: xor a0, a2, a0
+; CHECK-NEXT: jr t0
+; NOFS: .text{{$}}
+; NOFS: OUTLINED_FUNCTION_1:
+; FS: .section .text.OUTLINED_FUNCTION_1,"ax", at progbits
+; FS: OUTLINED_FUNCTION_1:
diff --git a/llvm/test/LTO/RISCV/machine-outliner-section-rv64.ll b/llvm/test/LTO/RISCV/machine-outliner-section-rv64.ll
new file mode 100644
index 0000000000000..f850a2b3dcb56
--- /dev/null
+++ b/llvm/test/LTO/RISCV/machine-outliner-section-rv64.ll
@@ -0,0 +1,117 @@
+; Check that the RISC-V machine outliner preserves an explicit input section
+; when it runs in the full LTO backend.
+;
+; RUN: llvm-as %s -o %t.bc
+; RUN: llvm-lto2 run -enable-machine-outliner=always -filetype=asm -o %t.s %t.bc \
+; RUN: -r=%t.bc,shared_0,px -r=%t.bc,shared_1,px -r=%t.bc,shared_2,px \
+; RUN: -r=%t.bc,plain_0,px -r=%t.bc,plain_1,px -r=%t.bc,plain_2,px
+; RUN: FileCheck %s --input-file %t.s.0 --check-prefixes=CHECK,NOFS
+;
+; RUN: llvm-lto2 run -enable-machine-outliner=always --function-sections -filetype=asm -o %t.s %t.bc \
+; RUN: -r=%t.bc,shared_0,px -r=%t.bc,shared_1,px -r=%t.bc,shared_2,px \
+; RUN: -r=%t.bc,plain_0,px -r=%t.bc,plain_1,px -r=%t.bc,plain_2,px
+; RUN: FileCheck %s --input-file %t.s.0 --check-prefixes=CHECK,FS
+
+target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"
+target triple = "riscv64-unknown-linux-gnu"
+
+; CHECK: .section .sec_shared,"ax", at progbits
+
+define i32 @shared_0(i32 %a, i32 %b) noinline nounwind section ".sec_shared" {
+; CHECK-LABEL: shared_0:
+; CHECK: call t0, OUTLINED_FUNCTION_0
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 1
+ ret i32 %result
+}
+
+define i32 @shared_1(i32 %a, i32 %b) noinline nounwind section ".sec_shared" {
+; CHECK-LABEL: shared_1:
+; CHECK: call t0, OUTLINED_FUNCTION_0
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 2
+ ret i32 %result
+}
+
+define i32 @shared_2(i32 %a, i32 %b) noinline nounwind section ".sec_shared" {
+; CHECK-LABEL: shared_2:
+; CHECK: call t0, OUTLINED_FUNCTION_0
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 3
+ ret i32 %result
+}
+
+define i32 @plain_0(i32 %a, i32 %b) noinline nounwind {
+; NOFS: .text{{$}}
+; FS: .section .text.plain_0,"ax", at progbits
+; CHECK-LABEL: plain_0:
+; CHECK: call t0, OUTLINED_FUNCTION_1
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 4
+ ret i32 %result
+}
+
+define i32 @plain_1(i32 %a, i32 %b) noinline nounwind {
+; FS: .section .text.plain_1,"ax", at progbits
+; CHECK-LABEL: plain_1:
+; CHECK: call t0, OUTLINED_FUNCTION_1
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 5
+ ret i32 %result
+}
+
+define i32 @plain_2(i32 %a, i32 %b) noinline nounwind {
+; FS: .section .text.plain_2,"ax", at progbits
+; CHECK-LABEL: plain_2:
+; CHECK: call t0, OUTLINED_FUNCTION_1
+ %a0 = xor i32 %a, %b
+ %b0 = add i32 %b, %a0
+ %a1 = shl i32 %a0, 3
+ %a2 = lshr i32 %a0, 29
+ %a3 = or i32 %a1, %a2
+ %a4 = xor i32 %a3, %b0
+ %result = add i32 %a4, 6
+ ret i32 %result
+}
+
+; The outlined function for the explicitly sectioned parents inherits their
+; input section, while the one for the unsectioned parents is placed in .text,
+; or in its own synthesized section when --function-sections is enabled.
+; CHECK: .section .sec_shared,"ax", at progbits
+; CHECK: OUTLINED_FUNCTION_0:
+; CHECK: xor a0, a0, a1
+; CHECK-NEXT: srliw a2, a0, 29
+; CHECK-NEXT: slli a3, a0, 3
+; CHECK-NEXT: add a0, a1, a0
+; CHECK-NEXT: or a2, a3, a2
+; CHECK-NEXT: xor a0, a2, a0
+; CHECK-NEXT: jr t0
+; NOFS: .text{{$}}
+; NOFS: OUTLINED_FUNCTION_1:
+; FS: .section .text.OUTLINED_FUNCTION_1,"ax", at progbits
+; FS: OUTLINED_FUNCTION_1:
More information about the cfe-commits
mailing list