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