diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h --- a/lld/ELF/Config.h +++ b/lld/ELF/Config.h @@ -125,6 +125,7 @@ std::vector filterList; std::vector searchPaths; std::vector symbolOrderingFile; + std::vector thinLTOModulesToCompile; std::vector undefined; std::vector dynamicList; std::vector buildIdVector; diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -986,6 +986,8 @@ getOldNewOptions(args, OPT_thinlto_object_suffix_replace_eq); config->thinLTOPrefixReplace = getOldNewOptions(args, OPT_thinlto_prefix_replace_eq); + config->thinLTOModulesToCompile = + args::getStrings(args, OPT_thinlto_single_module_eq); config->timeTraceEnabled = args.hasArg(OPT_time_trace); config->timeTraceGranularity = args::getInteger(args, OPT_time_trace_granularity, 500); @@ -1955,7 +1957,10 @@ // Likewise, --plugin-opt=emit-llvm and --plugin-opt=emit-asm are the // options to create output files in bitcode or assembly code // repsectively. No object files are generated. - if (config->thinLTOIndexOnly || config->emitLLVM || config->ltoEmitAsm) + // Also bail out here when only certain thinLTO modules are specified for + // compilation. The intermediate object file are the expected output. + if (config->thinLTOIndexOnly || config->emitLLVM || config->ltoEmitAsm || + !config->thinLTOModulesToCompile.empty()) return; // Apply symbol renames for -wrap. diff --git a/lld/ELF/LTO.cpp b/lld/ELF/LTO.cpp --- a/lld/ELF/LTO.cpp +++ b/lld/ELF/LTO.cpp @@ -139,6 +139,9 @@ c.HasWholeProgramVisibility = config->ltoWholeProgramVisibility; c.AlwaysEmitRegularLTOObj = !config->ltoObjPath.empty(); + for (auto &name : config->thinLTOModulesToCompile) + c.ThinLTOModulesToCompile.emplace_back(name); + c.TimeTraceEnabled = config->timeTraceEnabled; c.TimeTraceGranularity = config->timeTraceGranularity; diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td --- a/lld/ELF/Options.td +++ b/lld/ELF/Options.td @@ -553,6 +553,8 @@ HelpText<"Number of ThinLTO jobs. Default to --threads=">; def thinlto_object_suffix_replace_eq: JJ<"thinlto-object-suffix-replace=">; def thinlto_prefix_replace_eq: JJ<"thinlto-prefix-replace=">; +def thinlto_single_module_eq: JJ<"thinlto-single-module=">, + HelpText<"Specific a single module to compile in ThinLTO mode, for debugging only">; def: J<"plugin-opt=O">, Alias, HelpText<"Alias for --lto-O">; def: F<"plugin-opt=debug-pass-manager">, diff --git a/lld/test/ELF/lto/thinlto-single-module.ll b/lld/test/ELF/lto/thinlto-single-module.ll new file mode 100644 --- /dev/null +++ b/lld/test/ELF/lto/thinlto-single-module.ll @@ -0,0 +1,69 @@ +; REQUIRES: x86 +; RUN: rm -fr %t && mkdir %t && cd %t +; RUN: opt -thinlto-bc -o main.o %s +; RUN: opt -thinlto-bc -o thin1.o %S/Inputs/thin1.ll +; RUN: opt -thinlto-bc -o thin2.o %S/Inputs/thin2.ll +; RUN: llvm-ar qcT thin.a thin1.o thin2.o + +; --thinlto-single-module=main.o should result in only main.o compiled, of which +; the object code is saved in single1.o1. Note that single1.o is always the dummy +; output, aka ld-temp.o. There should be no more object files generated. +; RUN: ld.lld main.o thin.a --thinlto-single-module=main.o --lto-obj-path=single1.o +; RUN: llvm-readelf -S -s single1.o | FileCheck %s -check-prefix=DEFAULT +; RUN: llvm-readelf -S -s single1.o1 | FileCheck %s -check-prefix=MAIN +; RUN: not ls single1.o2 +; +; --thinlto-single-module=thin.a should result in only thin1.o and thin2.o compiled. +; RUN: ld.lld main.o thin.a --thinlto-single-module=thin.a --lto-obj-path=single2.o +; RUN: llvm-readelf -S -s single2.o | FileCheck %s -check-prefix=DEFAULT +; RUN: llvm-readelf -S -s single2.o1 | FileCheck %s -check-prefix=FOO +; RUN: llvm-readelf -S -s single2.o2 | FileCheck %s -check-prefix=BLAH +; RUN: not ls single1.o3 +; +; --thinlto-single-module=thin1.o should result in only thin1.o compiled. +; RUN: ld.lld main.o thin.a --thinlto-single-module=thin1.o --lto-obj-path=single3.o +; RUN: llvm-readelf -S -s single3.o | FileCheck %s -check-prefix=DEFAULT +; RUN: llvm-readelf -S -s single3.o1 | FileCheck %s -check-prefix=FOO +; RUN: not ls single3.o2 +; +; Multiple --thinlto-single-module uses should result in a combination of inputs compiled. +; RUN: ld.lld main.o thin.a --thinlto-single-module=main.o --thinlto-single-module=thin2.o --lto-obj-path=single4.o +; RUN: llvm-readelf -S -s single4.o | FileCheck %s -check-prefix=DEFAULT +; RUN: llvm-readelf -S -s single4.o1 | FileCheck %s -check-prefix=MAIN +; RUN: llvm-readelf -S -s single4.o2 | FileCheck %s -check-prefix=BLAH +; RUN: not ls single4.o3 + +; Check only thin1.o is in the result thin index file. +; RUN: ld.lld main.o thin.a --thinlto-single-module=thin1.o --thinlto-index-only=single5.idx +; RUN: FileCheck %s -check-prefix=IDX < single5.idx + +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-scei-ps4" + +declare i32 @blah(i32 %meh) +declare i32 @foo(i32 %goo) + +define i32 @_start() { + call i32 @foo(i32 0) + call i32 @blah(i32 0) + ret i32 0 +} + +; DEFAULT: Value Size Type Bind Vis Ndx Name +; DEFAULT: 0000000000000000 0 FILE LOCAL DEFAULT ABS ld-temp.o + +; MAIN: Value Size Type Bind Vis Ndx Name +; MAIN: 0000000000000000 0 FILE LOCAL DEFAULT ABS thinlto-single-module.ll +; MAIN-NEXT: 0000000000000000 3 FUNC GLOBAL DEFAULT 3 _start + +; FOO: Value Size Type Bind Vis Ndx Name +; FOO: 0000000000000000 0 FILE LOCAL DEFAULT ABS thin1.ll +; FOO-NEXT: 0000000000000000 6 FUNC GLOBAL DEFAULT 3 foo + +; BLAH: Value Size Type Bind Vis Ndx Name +; BLAH: 0000000000000000 0 FILE LOCAL DEFAULT ABS thin2.ll +; BLAH-NEXT: 0000000000000000 4 FUNC GLOBAL DEFAULT 3 blah + +; IDX: thin.a(thin1.o at [[#]]) +; IDX-NOT: thin.a(thin2.o at [[#]]) +; IDX-NOT: main.o diff --git a/llvm/include/llvm/LTO/Config.h b/llvm/include/llvm/LTO/Config.h --- a/llvm/include/llvm/LTO/Config.h +++ b/llvm/include/llvm/LTO/Config.h @@ -130,6 +130,9 @@ /// Statistics output file path. std::string StatsFile; + /// Specific thinLTO modules to compile. + std::vector ThinLTOModulesToCompile; + /// Time trace enabled. bool TimeTraceEnabled = false; diff --git a/llvm/include/llvm/LTO/LTO.h b/llvm/include/llvm/LTO/LTO.h --- a/llvm/include/llvm/LTO/LTO.h +++ b/llvm/include/llvm/LTO/LTO.h @@ -17,6 +17,7 @@ #include "llvm/ADT/MapVector.h" #include "llvm/ADT/StringMap.h" +#include "llvm/Bitcode/BitcodeReader.h" #include "llvm/IR/ModuleSummaryIndex.h" #include "llvm/LTO/Config.h" #include "llvm/Object/IRSymtab.h" @@ -26,7 +27,6 @@ namespace llvm { -class BitcodeModule; class Error; class IRMover; class LLVMContext; @@ -330,12 +330,18 @@ bool EmptyCombinedModule = true; } RegularLTO; + using ModuleMapType = MapVector; + struct ThinLTOState { ThinLTOState(ThinBackend Backend); ThinBackend Backend; ModuleSummaryIndex CombinedIndex; - MapVector ModuleMap; + // The full set of bitcode modules in input order. + ModuleMapType ModuleMap; + // The bitcode modules to compile, if specified by + // --thinlto-single-module=. + Optional ModulesToCompile; DenseMap PrevailingModuleForGUID; } ThinLTO; diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp --- a/llvm/lib/LTO/LTO.cpp +++ b/llvm/lib/LTO/LTO.cpp @@ -849,12 +849,28 @@ "Expected at most one ThinLTO module per bitcode file", inconvertibleErrorCode()); + if (!Conf.ThinLTOModulesToCompile.empty()) { + if (!ThinLTO.ModulesToCompile) + ThinLTO.ModulesToCompile = ModuleMapType(); + // This is a fuzzy name matching where only modules with name containing the + // specified switch values are going to be compiled. + for (auto &Name : Conf.ThinLTOModulesToCompile) { + if (BM.getModuleIdentifier().contains(Name)) { + ThinLTO.ModulesToCompile->insert({BM.getModuleIdentifier(), BM}); + llvm::errs() << "[ThinLTO] Selecting " << BM.getModuleIdentifier() + << " to compile\n"; + } + } + } + return Error::success(); } unsigned LTO::getMaxTasks() const { CalledGetMaxTasks = true; - return RegularLTO.ParallelCodeGenParallelismLevel + ThinLTO.ModuleMap.size(); + auto ModuleCount = ThinLTO.ModulesToCompile ? ThinLTO.ModulesToCompile->size() + : ThinLTO.ModuleMap.size(); + return RegularLTO.ParallelCodeGenParallelismLevel + ModuleCount; } // If only some of the modules were split, we cannot correctly handle @@ -1291,6 +1307,11 @@ if (ThinLTO.ModuleMap.empty()) return Error::success(); + if (ThinLTO.ModulesToCompile && ThinLTO.ModulesToCompile->empty()) { + llvm::errs() << "warning: [ThinLTO] No module compiled\n"; + return Error::success(); + } + if (Conf.CombinedIndexHook && !Conf.CombinedIndexHook(ThinLTO.CombinedIndex, GUIDPreservedSymbols)) return Error::success(); @@ -1395,10 +1416,13 @@ ThinLTO.Backend(Conf, ThinLTO.CombinedIndex, ModuleToDefinedGVSummaries, AddStream, Cache); + auto &ModuleMap = + ThinLTO.ModulesToCompile ? *ThinLTO.ModulesToCompile : ThinLTO.ModuleMap; + // Tasks 0 through ParallelCodeGenParallelismLevel-1 are reserved for combined // module and parallel code generation partitions. unsigned Task = RegularLTO.ParallelCodeGenParallelismLevel; - for (auto &Mod : ThinLTO.ModuleMap) { + for (auto &Mod : ModuleMap) { if (Error E = BackendProc->start(Task, Mod.second, ImportLists[Mod.first], ExportLists[Mod.first], ResolvedODR[Mod.first], ThinLTO.ModuleMap))