diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h --- a/lld/ELF/Config.h +++ b/lld/ELF/Config.h @@ -181,6 +181,7 @@ llvm::SmallVector thinLTOModulesToCompile; llvm::SmallVector undefined; llvm::SmallVector dynamicList; + llvm::SmallVector ltoExportSymbolList; llvm::SmallVector buildIdVector; llvm::SmallVector mllvmOpts; llvm::MapVector, @@ -224,6 +225,7 @@ bool ltoPGOWarnMismatch; bool ltoDebugPassManager; bool ltoEmitAsm; + bool ltoExportSymbols; bool ltoUniqueBasicBlockSectionNames; bool ltoWholeProgramVisibility; bool mergeArmExidx; diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -380,6 +380,9 @@ error("-r and --export-dynamic may not be used together"); } + if (config->ltoExportSymbols && !config->relocatable) + error("--lto-export-symbol-list may not be used without -r"); + if (config->executeOnly) { if (config->emachine != EM_AARCH64) error("--execute-only is only supported on AArch64 targets"); @@ -1519,6 +1522,11 @@ } else { error(Twine("cannot find version script ") + arg->getValue()); } + + config->ltoExportSymbols = args.hasArg(OPT_lto_export_symbol_list); + for (auto *arg : args.filtered(OPT_lto_export_symbol_list)) + if (std::optional buffer = readFile(arg->getValue())) + readLTOExportSymbolList(*buffer); } // Some Config members do not directly correspond to any particular @@ -2653,6 +2661,11 @@ symtab.scanVersionScript(); } + if (config->relocatable) { + llvm::TimeTraceScope timeScope("Process LTO export symbols"); + symtab.scanLTOExportList(); + } + // Skip the normal linked output if some LTO options are specified. // // For --thinlto-index-only, index file creation is performed in diff --git a/lld/ELF/DriverUtils.cpp b/lld/ELF/DriverUtils.cpp --- a/lld/ELF/DriverUtils.cpp +++ b/lld/ELF/DriverUtils.cpp @@ -192,6 +192,7 @@ case OPT_export_dynamic_symbol_list: case OPT_just_symbols: case OPT_library_path: + case OPT_lto_export_symbol_list: case OPT_retain_symbols_file: case OPT_rpath: case OPT_script: diff --git a/lld/ELF/LTO.cpp b/lld/ELF/LTO.cpp --- a/lld/ELF/LTO.cpp +++ b/lld/ELF/LTO.cpp @@ -249,21 +249,29 @@ r.Prevailing = !objSym.isUndefined() && sym->file == &f; // We ask LTO to preserve following global symbols: - // 1) All symbols when doing relocatable link, so that them can be used - // for doing final link. + // 1) All symbols when doing relocatable link, so that they can be used + // for doing final link, unless + // a) the symbol is in --lto-export-symbol-list, and + // b) the symbol is executable. // 2) Symbols that are used in regular objects. // 3) C named sections if we have corresponding __start_/__stop_ symbol. // 4) Symbols that are defined in bitcode files and used for dynamic // linking. // 5) Symbols that will be referenced after linker wrapping is performed. - r.VisibleToRegularObj = config->relocatable || sym->isUsedInRegularObj || - sym->referencedAfterWrap || - (r.Prevailing && sym->includeInDynsym()) || - usedStartStop.count(objSym.getSectionName()); + bool useExports = config->ltoExportSymbols && !sym->isUsedInRegularObj && + objSym.isExecutable(); + uint8_t binding = sym->computeBinding(useExports); + r.VisibleToRegularObj = + (config->relocatable && !useExports) || sym->isUsedInRegularObj || + sym->referencedAfterWrap || + (r.Prevailing && + (useExports ? binding != STB_LOCAL : sym->includeInDynsym())) || + usedStartStop.count(objSym.getSectionName()); + // Identify symbols exported dynamically, and that therefore could be // referenced by a shared library not visible to the linker. r.ExportDynamic = - sym->computeBinding() != STB_LOCAL && + binding != STB_LOCAL && (config->exportDynamic || sym->exportDynamic || sym->inDynamicList); const auto *dr = dyn_cast(sym); r.FinalDefinitionInLinkageUnit = diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td --- a/lld/ELF/Options.td +++ b/lld/ELF/Options.td @@ -554,6 +554,9 @@ HelpText<"Debug new pass manager">; def lto_emit_asm: FF<"lto-emit-asm">, HelpText<"Emit assembly code">; +defm lto_export_symbol_list : EEq<"lto-export-symbol-list", + "Specify a list of symbols that are marked visible to regular objects in a relocatable LTO link">, + MetaVarName<"file">; def lto_newpm_passes: JJ<"lto-newpm-passes=">, HelpText<"Passes to run during LTO">; def lto_O: JJ<"lto-O">, MetaVarName<"">, diff --git a/lld/ELF/ScriptParser.h b/lld/ELF/ScriptParser.h --- a/lld/ELF/ScriptParser.h +++ b/lld/ELF/ScriptParser.h @@ -23,6 +23,8 @@ void readDynamicList(MemoryBufferRef mb); +void readLTOExportSymbolList(MemoryBufferRef mb); + // Parses the defsym expression. void readDefsym(StringRef name, MemoryBufferRef mb); diff --git a/lld/ELF/ScriptParser.cpp b/lld/ELF/ScriptParser.cpp --- a/lld/ELF/ScriptParser.cpp +++ b/lld/ELF/ScriptParser.cpp @@ -63,6 +63,7 @@ void readLinkerScript(); void readVersionScript(); void readDynamicList(); + void readLTOExportSymbolList(); void readDefsym(StringRef name); private: @@ -203,6 +204,26 @@ config->dynamicList.push_back(v); } +void ScriptParser::readLTOExportSymbolList() { + expect("{"); + SmallVector locals; + SmallVector globals; + std::tie(locals, globals) = readSymbols(); + expect(";"); + + if (!atEOF()) { + setError("EOF expected, but got " + next()); + return; + } + if (!locals.empty()) { + setError("\"local:\" scope not supported in --lto-export-symbol-list"); + return; + } + + for (SymbolVersion v : globals) + config->ltoExportSymbolList.push_back(v); +} + void ScriptParser::readVersionScript() { readVersionScriptCommand(); if (!atEOF()) @@ -1781,6 +1802,12 @@ ScriptParser(mb).readDynamicList(); } +void elf::readLTOExportSymbolList(MemoryBufferRef mb) { + llvm::TimeTraceScope timeScope("Read LTO export symbol list", + mb.getBufferIdentifier()); + ScriptParser(mb).readLTOExportSymbolList(); +} + void elf::readDefsym(StringRef name, MemoryBufferRef mb) { llvm::TimeTraceScope timeScope("Read defsym input", name); ScriptParser(mb).readDefsym(name); diff --git a/lld/ELF/SymbolTable.h b/lld/ELF/SymbolTable.h --- a/lld/ELF/SymbolTable.h +++ b/lld/ELF/SymbolTable.h @@ -48,6 +48,8 @@ void scanVersionScript(); + void scanLTOExportList(); + Symbol *find(StringRef name); void handleDynamicList(); diff --git a/lld/ELF/SymbolTable.cpp b/lld/ELF/SymbolTable.cpp --- a/lld/ELF/SymbolTable.cpp +++ b/lld/ELF/SymbolTable.cpp @@ -332,3 +332,16 @@ // --dynamic-list. handleDynamicList(); } + +void SymbolTable::scanLTOExportList() { + SmallVector syms; + for (SymbolVersion &ver : config->ltoExportSymbolList) { + if (ver.hasWildcard) + syms = findAllByVersion(ver, /*includeNonDefault=*/true); + else + syms = findByVersion(ver); + + for (Symbol *sym : syms) + sym->ltoExportSymbol = true; + } +} diff --git a/lld/ELF/Symbols.h b/lld/ELF/Symbols.h --- a/lld/ELF/Symbols.h +++ b/lld/ELF/Symbols.h @@ -145,6 +145,9 @@ // exported into .dynsym. uint8_t inDynamicList : 1; + // True if the symbol is in the --lto-export-symbol-list file. + uint8_t ltoExportSymbol : 1; + // Used to track if there has been at least one undefined reference to the // symbol. For Undefined and SharedSymbol, the binding may change to STB_WEAK // if the first undefined reference from a non-shared object is weak. @@ -169,7 +172,7 @@ } bool includeInDynsym() const; - uint8_t computeBinding() const; + uint8_t computeBinding(bool useExports = false) const; bool isGlobal() const { return binding == llvm::ELF::STB_GLOBAL; } bool isWeak() const { return binding == llvm::ELF::STB_WEAK; } diff --git a/lld/ELF/Symbols.cpp b/lld/ELF/Symbols.cpp --- a/lld/ELF/Symbols.cpp +++ b/lld/ELF/Symbols.cpp @@ -269,7 +269,10 @@ } } -uint8_t Symbol::computeBinding() const { +uint8_t Symbol::computeBinding(bool useExports /*=false*/) const { + if (useExports && config->ltoExportSymbols && binding == STB_GLOBAL && + !ltoExportSymbol) + return STB_LOCAL; auto v = visibility(); if ((v != STV_DEFAULT && v != STV_PROTECTED) || versionId == VER_NDX_LOCAL) return STB_LOCAL; diff --git a/lld/test/ELF/driver.test b/lld/test/ELF/driver.test --- a/lld/test/ELF/driver.test +++ b/lld/test/ELF/driver.test @@ -71,6 +71,11 @@ # RUN: not ld.lld -r -export-dynamic %t -o /dev/null 2>&1 | FileCheck -check-prefix=ERR12 %s # ERR12: -r and --export-dynamic may not be used together +## Attempt to use --lto-export-symbol-list without -r +# RUN: echo "{ };" > %t.list +# RUN: not ld.lld --lto-export-symbol-list %t.list %t -o /dev/null 2>&1 | FileCheck -check-prefix=ERR13 %s +# ERR13: --lto-export-symbol-list may not be used without -r + .globl _start _start: nop diff --git a/lld/test/ELF/lto/lto-export-symbol-list-locals.s b/lld/test/ELF/lto/lto-export-symbol-list-locals.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/lto/lto-export-symbol-list-locals.s @@ -0,0 +1,7 @@ +# REQUIRES: x86 + +# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %s -o %t.o +# RUN: echo "{ local: *; };" > %t.list +# RUN: not ld.lld --lto-export-symbol-list %t.list -r %t.o -o /dev/null 2>&1 | FileCheck %s + +# CHECK: error: {{.*}}:1: "local:" scope not supported in --lto-export-symbol-list diff --git a/lld/test/ELF/lto/lto-export-symbol-list-unexpected-end.s b/lld/test/ELF/lto/lto-export-symbol-list-unexpected-end.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/lto/lto-export-symbol-list-unexpected-end.s @@ -0,0 +1,7 @@ +# REQUIRES: x86 + +# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %s -o %t.o +# RUN: echo "{ }; foo;" > %t.list +# RUN: not ld.lld --lto-export-symbol-list %t.list -r %t.o -o /dev/null 2>&1 | FileCheck %s + +# CHECK: error: {{.*}}:1: EOF expected, but got foo diff --git a/lld/test/ELF/lto/lto-export-symbol-list.ll b/lld/test/ELF/lto/lto-export-symbol-list.ll new file mode 100644 --- /dev/null +++ b/lld/test/ELF/lto/lto-export-symbol-list.ll @@ -0,0 +1,77 @@ +; REQUIRES: x86 +; RUN: llvm-as %s -o %t1.o + +; RUN: ld.lld %t1.o -r -o %t2.o +; RUN: llvm-readobj --symbols %t2.o | FileCheck %s --check-prefixes=CHECK,NOLIST + +; RUN: echo "{ f2; };" > %t.list +; RUN: ld.lld %t1.o -r --lto-export-symbol-list %t.list -o %t +; RUN: llvm-readobj --symbols %t | FileCheck %s --check-prefixes=CHECK,LIST + +;; f3 is referenced, but not in --lto-export-symbol-list. +; LIST: Name: f3 +; LIST-NEXT: Value: +; LIST-NEXT: Size: +; LIST-NEXT: Binding: Local +; LIST-NEXT: Type: Function +; LIST-NEXT: Other: +; LIST-NEXT: Section: .text +; LIST-NEXT: } + +;; f1 is not used and not in --export-symbol-list. +; LIST-NOT: Name: f1 +; NOLIST: Name: f1 +; NOLIST-NEXT: Value: +; NOLIST-NEXT: Size: +; NOLIST-NEXT: Binding: Global +; NOLIST-NEXT: Type: Function +; NOLIST-NEXT: Other: +; NOLIST-NEXT: Section: .text +; NOLIST-NEXT: } + +;; f2 must always be exported. +; CHECK: Name: f2 +; CHECK-NEXT: Value: +; CHECK-NEXT: Size: +; CHECK-NEXT: Binding: Global +; CHECK-NEXT: Type: Function +; CHECK-NEXT: Other: +; CHECK-NEXT: Section: .text +; CHECK-NEXT: } + +;; f3 without --lto-export-symbol-list must be exported. +; NOLIST: Name: f3 +; NOLIST-NEXT: Value: +; NOLIST-NEXT: Size: +; NOLIST-NEXT: Binding: Global +; NOLIST-NEXT: Type: Function +; NOLIST-NEXT: Other: +; NOLIST-NEXT: Section: .text +; NOLIST-NEXT: } + +;; p is not in an executable section, so it must not be affected. +; CHECK: Name: p +; CHECK-NEXT: Value: +; CHECK-NEXT: Size: +; CHECK-NEXT: Binding: Global +; CHECK-NEXT: Type: Object +; CHECK-NEXT: Other: +; CHECK-NEXT: Section: .data +; CHECK-NEXT: } + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +define void @f1() { + ret void +} + +define void @f2() { + ret void +} + +define void @f3() { + ret void +} + +@p = global ptr @f3, align 8