diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h --- a/lld/ELF/Config.h +++ b/lld/ELF/Config.h @@ -134,6 +134,7 @@ llvm::StringRef soName; llvm::StringRef sysroot; llvm::StringRef thinLTOCacheDir; + llvm::StringRef thinLTOIndex; llvm::StringRef thinLTOIndexOnlyArg; llvm::StringRef whyExtract; StringRef zBtiReport = "none"; @@ -365,6 +366,9 @@ // this means to map the primary and thread stacks as PROT_MTE. Note: This is // not supported on Android 11 & 12. bool androidMemtagStack; + + // If an input file equals a key, redirect it to the value. + llvm::DenseMap remappingFile; }; // The only instance of Configuration struct. diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -1017,6 +1017,28 @@ return arg == "none" || arg == "warning" || arg == "error"; } +// A remapping file contains a tab-separated pair: from, to. Distributed ThinLTO +// uses the file to redirect an input bitcode file to a backend compiled +// ELF object file . We make this option generic potentially for other use +// cases. +static void readRemappingFile(StringRef filename) { + Optional buffer = readFile(filename); + if (!buffer) + return; + SmallVector fields; + size_t lineno = 0; + for (StringRef line : args::getLines(*buffer)) { + fields.clear(); + line.split(fields, '\t'); + ++lineno; + if (fields.size() != 2) { + error(filename + ":" + Twine(lineno) + ": parse error"); + return; + } + config->remappingFile[fields[0]] = fields[1]; + } +} + // Initializes Config members by the command line options. static void readConfigs(opt::InputArgList &args) { errorHandler().verbose = args.hasArg(OPT_verbose); @@ -1186,10 +1208,13 @@ parseCachePruningPolicy(args.getLastArgValue(OPT_thinlto_cache_policy)), "--thinlto-cache-policy: invalid cache policy"); config->thinLTOEmitImportsFiles = args.hasArg(OPT_thinlto_emit_imports_files); - config->thinLTOEmitIndexFiles = args.hasArg(OPT_thinlto_emit_index_files) || + config->thinLTOIndex = args.getLastArgValue(OPT_thinlto_index); + config->thinLTOEmitIndexFiles = config->thinLTOIndex.size() || + args.hasArg(OPT_thinlto_emit_index_files) || args.hasArg(OPT_thinlto_index_only) || args.hasArg(OPT_thinlto_index_only_eq); - config->thinLTOIndexOnly = args.hasArg(OPT_thinlto_index_only) || + config->thinLTOIndexOnly = config->thinLTOIndex.size() || + args.hasArg(OPT_thinlto_index_only) || args.hasArg(OPT_thinlto_index_only_eq); config->thinLTOIndexOnlyArg = args.getLastArgValue(OPT_thinlto_index_only_eq); config->thinLTOObjectSuffixReplace = @@ -1261,6 +1286,9 @@ config->optEL = true; } + if (const auto *arg = args.getLastArg(OPT_remapping_file)) + readRemappingFile(arg->getValue()); + for (opt::Arg *arg : args.filtered(OPT_shuffle_sections)) { constexpr StringRef errPrefix = "--shuffle-sections=: "; std::pair kv = StringRef(arg->getValue()).split('='); @@ -2590,7 +2618,7 @@ // Skip the normal linked output if some LTO options are specified. // - // For --thinlto-index-only, index file creation is performed in + // For --thinlto-index{,-only}, index file creation is performed in // compileBitcodeFiles, so we are done afterwards. --plugin-opt=emit-llvm and // --plugin-opt=emit-asm create output files in bitcode or assembly code, // respectively. When only certain thinLTO modules are specified for diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp --- a/lld/ELF/InputFiles.cpp +++ b/lld/ELF/InputFiles.cpp @@ -105,6 +105,17 @@ if (!config->chroot.empty() && path.startswith("/")) path = saver().save(config->chroot + path); + auto it = config->remappingFile.find(path); + if (it != config->remappingFile.end()) { + path = it->second; + // --thinlto-index= uses /dev/null to indicate a non-extracted bitcode file + // which should be ignored. Change the path to NUL on Windows. +#ifdef _WIN32 + if (path == "/dev/null") + path = "NUL"; +#endif + } + log(path); config->dependencyFiles.insert(llvm::CachedHashString(path)); diff --git a/lld/ELF/LTO.cpp b/lld/ELF/LTO.cpp --- a/lld/ELF/LTO.cpp +++ b/lld/ELF/LTO.cpp @@ -208,6 +208,22 @@ ltoObj = std::make_unique(createConfig(), backend, config->ltoPartitions); + // Write a remapping file for the final native link. Non-lazy bitcode files + // are mapped to native object files. If lazy bitcode files is a minimized + // bitcode, it cannot participate the final link. Print /dev/null to ignore + // it. + if (!config->thinLTOIndex.empty()) { + if (auto os = openFile(config->thinLTOIndex)) { + for (BitcodeFile *file : ctx->bitcodeFiles) + *os << file->getName() << '\t' + << getThinLTOOutputFile(replaceThinLTOSuffix(file->getName())) + << '\n'; + for (BitcodeFile *file : ctx->lazyBitcodeFiles) + if (file->lazy) + *os << file->getName() << "\t/dev/null\n"; + } + } + // Initialize usedStartStop. if (ctx->bitcodeFiles.empty()) return; diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td --- a/lld/ELF/Options.td +++ b/lld/ELF/Options.td @@ -352,6 +352,10 @@ "Enable target-specific relaxations if supported (default)", "Disable target-specific relaxations">; +def remapping_file: JJ<"remapping-file=">, + HelpText<"Each line contains (from,to) separated by a tab. An input file matching is redirected to ">, + MetaVarName<"">; + defm reproduce: EEq<"reproduce", "Write tar file containing inputs and command to reproduce link">; @@ -601,6 +605,7 @@ defm thinlto_cache_policy: EEq<"thinlto-cache-policy", "Pruning policy for the ThinLTO cache">; def thinlto_emit_imports_files: FF<"thinlto-emit-imports-files">; def thinlto_emit_index_files: FF<"thinlto-emit-index-files">; +def thinlto_index: JJ<"thinlto-index=">; def thinlto_index_only: FF<"thinlto-index-only">; def thinlto_index_only_eq: JJ<"thinlto-index-only=">; def thinlto_jobs: JJ<"thinlto-jobs=">, diff --git a/lld/docs/ReleaseNotes.rst b/lld/docs/ReleaseNotes.rst --- a/lld/docs/ReleaseNotes.rst +++ b/lld/docs/ReleaseNotes.rst @@ -31,6 +31,9 @@ * ``--no-fortran-common`` (pre 12.0.0 behavior) is now the default. * ``FORCE_LLD_DIAGNOSTICS_CRASH`` environment variable is now available to force LLD to crash. (`D128195 `_) +* Experimental ``--thinlto-index=`` and ``--remapping-file=`` are added for distributed ThinLTO. + They address some symbol resolution issues with the old ``--thinlto-index-only=``. + (`D130229 `_) Breaking changes ---------------- diff --git a/lld/test/ELF/lto/thinlto-index-file.ll b/lld/test/ELF/lto/thinlto-index-file.ll --- a/lld/test/ELF/lto/thinlto-index-file.ll +++ b/lld/test/ELF/lto/thinlto-index-file.ll @@ -1,4 +1,7 @@ ; REQUIRES: x86 +;; Test --thinlto-index-only= for distributed ThinLTO. +;; This option is discouraged in favor of --thinlto-index=. + ; RUN: rm -rf %t && mkdir %t && cd %t ; RUN: opt -module-summary %s -o 1.o ; RUN: opt -module-summary %p/Inputs/thinlto.ll -o 2.o diff --git a/lld/test/ELF/lto/thinlto-index.ll b/lld/test/ELF/lto/thinlto-index.ll new file mode 100644 --- /dev/null +++ b/lld/test/ELF/lto/thinlto-index.ll @@ -0,0 +1,100 @@ +; REQUIRES: x86 +;; Test --thinlto-index= for distributed ThinLTO. + +; RUN: rm -rf %t && split-file %s %t && mkdir %t/out && cd %t +; RUN: llvm-mc -filetype=obj -triple=x86_64 main.s -o main.o +; RUN: llvm-mc -filetype=obj -triple=x86_64 c.s -o c.o +; RUN: opt -module-summary a.ll -o out/a.o +; RUN: opt -module-summary bc.ll -o out/bc.o +; RUN: opt -module-summary %p/Inputs/thinlto_empty.ll -o out/empty.indexing.o +; RUN: cp out/a.o out/a.indexing.o +; RUN: cp out/bc.o out/bc.indexing.o +; RUN: cp out/empty.indexing.o out/empty1.indexing.o + +; RUN: ld.lld --thinlto-index=1.map --thinlto-emit-imports-files --thinlto-prefix-replace='out;lto' \ +; RUN: --thinlto-object-suffix-replace='.indexing.o;.o' main.o out/a.indexing.o \ +; RUN: --start-lib out/bc.indexing.o out/empty.indexing.o --end-lib out/empty1.indexing.o --start-lib c.o --end-lib -o 1 +; RUN: FileCheck --input-file=1.map %s --implicit-check-not={{.}} --match-full-lines --strict-whitespace + +;; No entry for empty.indexing.o which is not extracted. empty1.indexing.o is present, +;; otherwise the final link may try in-process ThinLTO backend compilation. +; CHECK:out/a.indexing.o lto/a.o +; CHECK-NEXT:out/bc.indexing.o lto/bc.o +; CHECK-NEXT:out/empty1.indexing.o lto/empty1.o +; CHECK-NEXT:out/empty.indexing.o /dev/null + +;; Nevertheless, empty.o.{imports,thinlto.bc} exist to meet the build system requirement. +; RUN: ls lto/a.o.imports lto/bc.o.imports lto/empty.o.imports lto/empty1.o.imports + +; RUN: llvm-bcanalyzer -dump lto/a.o.thinlto.bc | FileCheck %s --check-prefix=BACKENDA +; RUN: llvm-bcanalyzer -dump lto/bc.o.thinlto.bc | FileCheck %s --check-prefix=BACKENDB +; RUN: llvm-bcanalyzer -dump lto/empty.o.thinlto.bc | FileCheck %s --check-prefix=BACKENDE +; RUN: llvm-bcanalyzer -dump lto/empty1.o.thinlto.bc | FileCheck %s --check-prefix=BACKENDE1 + +; BACKENDA: + +; BACKENDB: + +; BACKENDE: + +; BACKENDE1: + +;; Thin archives can be used as well. +; RUN: llvm-ar rcT thin.a out/bc.indexing.o out/empty.indexing.o +; RUN: ld.lld --thinlto-index=2.map --thinlto-prefix-replace='out;lto' \ +; RUN: --thinlto-object-suffix-replace='.indexing.o;.o' main.o \ +; RUN: out/a.indexing.o thin.a out/empty1.indexing.o -o 2 +; RUN: FileCheck --input-file=2.map %s --implicit-check-not={{.}} --match-full-lines --strict-whitespace + +;; For regular archives, the filename may be insufficient to locate the archive and the particular member. +; RUN: llvm-ar rc bc.a out/bc.indexing.o out/empty.indexing.o +; RUN: ld.lld --thinlto-index=3.map --thinlto-prefix-replace='out;lto' \ +; RUN: --thinlto-object-suffix-replace='.indexing.o;.o' main.o \ +; RUN: out/a.indexing.o bc.a out/empty1.indexing.o -o 3 +; RUN: FileCheck --input-file=3.map %s --check-prefix=ARCHIVE --implicit-check-not={{.}} --match-full-lines --strict-whitespace + +; ARCHIVE:out/a.indexing.o lto/a.o +; ARCHIVE-NEXT:bc.indexing.o bc.o +; ARCHIVE-NEXT:out/empty1.indexing.o lto/empty1.o +; ARCHIVE-NEXT:empty.indexing.o /dev/null + +;--- a.ll +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" + +declare void @b() +declare void @c() + +define void @a() { + call void () @b() + call void () @c() + ret void +} + +;--- bc.ll +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 @b() { + ret void +} +define void @c() { + ret void +} + +;--- main.s +.globl _start +_start: + call a + +;--- c.s +.globl c +c: diff --git a/lld/test/ELF/lto/thinlto-single-module.ll b/lld/test/ELF/lto/thinlto-single-module.ll --- a/lld/test/ELF/lto/thinlto-single-module.ll +++ b/lld/test/ELF/lto/thinlto-single-module.ll @@ -48,9 +48,20 @@ ; RUN: ls | FileCheck --implicit-check-not='thin.{{.*}}.thinlto.bc' /dev/null ; RUN: FileCheck %s --check-prefix=IDX < single5.idx ; RUN: count 1 < single5.idx +; RUN: rm main.o.thinlto.bc ; IDX: main.o +; RUN: ld.lld main.o thin.a --thinlto-single-module=main.o --thinlto-index=single5.map +; RUN: ls main.o.thinlto.bc +; RUN: ls | FileCheck --implicit-check-not='thin.{{.*}}.thinlto.bc' /dev/null +; RUN: FileCheck --input-file=single5.map %s --check-prefix=REMAP --implicit-check-not={{.}} + +;; Currently the --thinlto-index= file is not affected by --thinlto-single-module. +; REMAP: main.o main.o +; REMAP-NEXT: thin1.o thin1.o +; REMAP-NEXT: thin2.o thin2.o + ;; Check temporary output generated for main.o only. ; RUN: ld.lld main.o thin.a --thinlto-single-module=main.o --save-temps ; RUN: ls main.o.0.preopt.bc diff --git a/lld/test/ELF/remapping-file.test b/lld/test/ELF/remapping-file.test new file mode 100644 --- /dev/null +++ b/lld/test/ELF/remapping-file.test @@ -0,0 +1,57 @@ +# REQUIRES: x86 +## --remapping-file redirects an input file to another file. + +# RUN: rm -rf %t && split-file %s %t && cd %t +# RUN: llvm-mc -filetype=obj -triple=x86_64 a.s -o a.o +# RUN: llvm-as b.ll -o b.o +# RUN: llvm-mc -filetype=obj -triple=x86_64 c.s -o c.o && llvm-ar rc c.a c.o +# RUN: llvm-mc -filetype=obj -triple=x86_64 d.s -o d.o && ld.lld -shared -soname=d d.o -o d.so +# RUN: ld.lld --remapping-file=1.map --reproduce=repro.tar aa.o bb.bc cc.a dd.so empty -o 1 +# RUN: tar tf repro.tar | FileCheck %s --check-prefix=REPRO + +# REPRO: 1.map +# REPRO-NEXT: a.o +# REPRO-NEXT: b.o +# REPRO-NEXT: c.a +# REPRO-NEXT: d.so + +# RUN: not ld.lld --remapping-file=err.map --reproduce=repro.tar aa.o bb.bc -o /dev/null 2>&1 | FileCheck %s --check-prefix=ERR + +# ERR: error: err.map:2: parse error +# ERR-NEXT: error: cannot open bb.bc: {{.*}} + +#--- a.s +.globl _start +_start: + call b + call c + call d + +#--- b.ll +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 @b() { + ret void +} + +#--- c.s +.globl c +c: + +#--- d.s +.globl d +d: + +#--- 1.map +aa.o a.o +bb.bc b.o +cc.a c.a +dd.so d.so +## --thinlto-index= uses /dev/null to indicate an unextracted bitcode file which should be ignored. +empty /dev/null + +#--- err.map +aa.o a.o +bb.bc +cc.a