diff --git a/lld/COFF/LTO.cpp b/lld/COFF/LTO.cpp --- a/lld/COFF/LTO.cpp +++ b/lld/COFF/LTO.cpp @@ -81,6 +81,7 @@ c.CPU = getCPUStr(); c.MAttrs = getMAttrs(); c.CGOptLevel = args::getCGOptLevel(config->ltoo); + c.EmitEmptyObj = !config->ltoObjPath.empty(); if (config->saveTemps) checkError(c.addSaveTemps(std::string(config->outputFile) + ".", diff --git a/lld/ELF/LTO.cpp b/lld/ELF/LTO.cpp --- a/lld/ELF/LTO.cpp +++ b/lld/ELF/LTO.cpp @@ -138,6 +138,7 @@ c.DwoDir = std::string(config->dwoDir); c.HasWholeProgramVisibility = config->ltoWholeProgramVisibility; + c.EmitEmptyObj = !config->ltoObjPath.empty(); c.TimeTraceEnabled = config->timeTraceEnabled; c.TimeTraceGranularity = config->timeTraceGranularity; @@ -327,7 +328,8 @@ } if (config->saveTemps) { - saveBuffer(buf[0], config->outputFile + ".lto.o"); + if (!buf[0].empty()) + saveBuffer(buf[0], config->outputFile + ".lto.o"); for (unsigned i = 1; i != maxTasks; ++i) saveBuffer(buf[i], config->outputFile + Twine(i) + ".lto.o"); } diff --git a/lld/test/COFF/lto-obj-path.ll b/lld/test/COFF/lto-obj-path.ll --- a/lld/test/COFF/lto-obj-path.ll +++ b/lld/test/COFF/lto-obj-path.ll @@ -11,6 +11,20 @@ ; RUN: llvm-nm %t4.obj 2>&1 | FileCheck %s -check-prefix=SYMBOLS ; RUN: llvm-nm %t4.obj 2>&1 | count 1 +; Test to ensure lto-obj-path will emit empty module +; RUN: rm -fr %T/objpath +; RUN: mkdir %T/objpath +; RUN: lld-link /out:%T/objpath/a.exe -lto-obj-path:%t4.obj \ +; RUN: -entry:main %t1.obj %t2.obj -lldsavetemps +; RUN: ls %T/objpath/a.exe.lto.* | count 3 + +; Test to ensure without lto-obj-path will not emit empty module +; RUN: rm -fr %T/objpath +; RUN: mkdir %T/objpath +; RUN: lld-link /out:%T/objpath/a.exe \ +; RUN: -entry:main %t1.obj %t2.obj -lldsavetemps +; RUN: ls %T/objpath/a.exe.lto.* | count 2 + ; CHECK: Format: COFF-x86-64 ; SYMBOLS: @feat.00 diff --git a/lld/test/COFF/pdb-thinlto.ll b/lld/test/COFF/pdb-thinlto.ll --- a/lld/test/COFF/pdb-thinlto.ll +++ b/lld/test/COFF/pdb-thinlto.ll @@ -29,10 +29,8 @@ ; CHECK: Modules ; CHECK: ============================================================ -; CHECK: Mod 0000 | `{{.*}}main.exe.lto.obj`: -; CHECK: Obj: `{{.*}}main.exe.lto.obj`: -; CHECK: Mod 0001 | `{{.*}}main.exe.lto.1.obj`: +; CHECK: Mod 0000 | `{{.*}}main.exe.lto.1.obj`: ; CHECK: Obj: `{{.*}}main.exe.lto.1.obj`: -; CHECK: Mod 0002 | `{{.*}}main.exe.lto.2.obj`: +; CHECK: Mod 0001 | `{{.*}}main.exe.lto.2.obj`: ; CHECK: Obj: `{{.*}}main.exe.lto.2.obj`: -; CHECK: Mod 0003 | `* Linker *`: +; CHECK: Mod 0002 | `* Linker *`: diff --git a/lld/test/ELF/lto/linker-script-symbols-assign.ll b/lld/test/ELF/lto/linker-script-symbols-assign.ll --- a/lld/test/ELF/lto/linker-script-symbols-assign.ll +++ b/lld/test/ELF/lto/linker-script-symbols-assign.ll @@ -3,12 +3,8 @@ ; RUN: echo "foo = 1;" > %t.script ; RUN: ld.lld %t.o -o %t2 --script %t.script -save-temps -; RUN: llvm-nm %t2.lto.o | count 0 - -; CHECK-NOT: bar -; CHECK-NOT: foo - ; RUN: llvm-readobj --symbols %t2 | FileCheck %s --check-prefix=VAL + ; VAL: Symbol { ; VAL: Name: foo ; VAL-NEXT: Value: 0x1 diff --git a/lld/test/ELF/lto/thinlto-obj-path.ll b/lld/test/ELF/lto/thinlto-obj-path.ll --- a/lld/test/ELF/lto/thinlto-obj-path.ll +++ b/lld/test/ELF/lto/thinlto-obj-path.ll @@ -14,6 +14,19 @@ ; RUN: ld.lld -thinlto-index-only -lto-obj-path=%t4.o -shared %t1.o %t2.o -o /dev/null ; RUN: llvm-readobj -h %t4.o | FileCheck %s +; Test to ensure lto-obj-path will emit empty module +; RUN: rm -fr %T/objpath +; RUN: mkdir %T/objpath +; RUN: ld.lld --plugin-opt=obj-path=%t4.o -shared %t1.o %t2.o -o %T/objpath/a.out -save-temps +; RUN: ls %T/objpath/a.out*.lto.* | count 3 + +; Test to ensure without lto-obj-path will not emit empty module +; RUN: rm -fr %T/objpath +; RUN: mkdir %T/objpath +; RUN: ld.lld %t1.o %t2.o -o %T/objpath/a.out -save-temps +; RUN: ls %T/objpath/a.out*.lto.* | count 2 + + ; CHECK: Format: elf64-x86-64 target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" 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 @@ -66,6 +66,10 @@ /// link. bool HasWholeProgramVisibility = false; + /// Allow emit object from empty module. This option is usedful for some + /// build system which want to know a priori all possible output files. + bool EmitEmptyObj = false; + /// If this field is set, the set of passes run in the middle-end optimizer /// will be the one specified by the string. Only works with the new pass /// manager as the old one doesn't have this ability. diff --git a/llvm/lib/LTO/LTOBackend.cpp b/llvm/lib/LTO/LTOBackend.cpp --- a/llvm/lib/LTO/LTOBackend.cpp +++ b/llvm/lib/LTO/LTOBackend.cpp @@ -362,10 +362,13 @@ /*EmbedMarker*/ false, /*CmdArgs*/ nullptr); } -void codegen(const Config &Conf, TargetMachine *TM, AddStreamFn AddStream, - unsigned Task, Module &Mod) { +static void codegen(const Config &Conf, TargetMachine *TM, + AddStreamFn AddStream, unsigned Task, Module &Mod, + bool HookOnly = false) { if (Conf.PreCodeGenModuleHook && !Conf.PreCodeGenModuleHook(Task, Mod)) return; + if (HookOnly) + return; EmitBitcodeSection(Mod, Conf); @@ -493,7 +496,12 @@ } if (ParallelCodeGenParallelismLevel == 1) { - codegen(C, TM.get(), AddStream, 0, *Mod); + bool HookOnly = false; + if (Mod->empty() && Mod->global_empty() && Mod->alias_empty() && + Mod->ifunc_empty() && Mod->debug_compile_units().empty() && + Mod->getModuleInlineAsm() == "" && !C.EmitEmptyObj) + HookOnly = true; + codegen(C, TM.get(), AddStream, 0, *Mod, HookOnly); } else { splitCodeGen(C, TM.get(), AddStream, ParallelCodeGenParallelismLevel, std::move(Mod)); diff --git a/llvm/test/ThinLTO/X86/Inputs/empty-object.ll b/llvm/test/ThinLTO/X86/Inputs/empty-object.ll new file mode 100644 --- /dev/null +++ b/llvm/test/ThinLTO/X86/Inputs/empty-object.ll @@ -0,0 +1,7 @@ + +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 @foo() { + ret void +} diff --git a/llvm/test/ThinLTO/X86/empty-module.ll b/llvm/test/ThinLTO/X86/empty-module.ll --- a/llvm/test/ThinLTO/X86/empty-module.ll +++ b/llvm/test/ThinLTO/X86/empty-module.ll @@ -5,6 +5,10 @@ ; RUN: llvm-readobj -h %t2.0 | FileCheck %s ; RUN: llvm-nm %t2.0 2>&1 | count 0 +; RUN: rm -f %t3.0 %t3.1 +; RUN: llvm-lto2 run %t.bc -r %t.bc,foo,pl -o %t3 +; RUN: ls %t3.* | count 1 + ; CHECK: Format: elf64-x86-64 target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" diff --git a/llvm/test/ThinLTO/X86/empty-object.ll b/llvm/test/ThinLTO/X86/empty-object.ll new file mode 100644 --- /dev/null +++ b/llvm/test/ThinLTO/X86/empty-object.ll @@ -0,0 +1,26 @@ +; Test to ensure lto2 will not emit empty module +; RUN: rm -f %t.1 %t.2 +; RUN: opt -module-summary %s -o %t1.bc +; RUN: opt -module-summary %S/Inputs/empty-object.ll -o %t2.bc +; RUN: llvm-lto2 run %t1.bc %t2.bc -o %t -r=%t1.bc,bar,px -r=%t1.bc,foo, -r=%t2.bc,foo,px -save-temps +; RUN: ls %t.[0-9] | count 2 + +; Test to ensure emit empty module with -thinlto-distributed-indexes option +; RUN: rm -f %t3 +; RUN: llvm-lto2 run %t1.bc %t2.bc -o %t3 -r=%t1.bc,bar,px -r=%t1.bc,foo, -r=%t2.bc,foo,px -save-temps -thinlto-distributed-indexes +; RUN: ls %t3.[0-9] | count 1 +; RUN: llvm-nm %t3.0 + +; CHECK-NOT: bar +; CHECK-NOT: foo + + +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 @foo() + +define void @bar() { + call void @foo() + ret void +} diff --git a/llvm/test/tools/gold/X86/thinlto.ll b/llvm/test/tools/gold/X86/thinlto.ll --- a/llvm/test/tools/gold/X86/thinlto.ll +++ b/llvm/test/tools/gold/X86/thinlto.ll @@ -32,6 +32,7 @@ ; Ensure gold generates an index as well as a binary with save-temps in ThinLTO mode. ; First force single-threaded mode +; RUN: rm -f %t4* ; RUN: %gold -plugin %llvmshlibdir/LLVMgold%shlibext \ ; RUN: -m elf_x86_64 \ ; RUN: --plugin-opt=save-temps \ @@ -40,6 +41,8 @@ ; RUN: -shared %t.o %t2.o -o %t4 ; RUN: llvm-bcanalyzer -dump %t4.index.bc | FileCheck %s --check-prefix=COMBINED ; RUN: llvm-nm %t4 | FileCheck %s --check-prefix=NM +; Ensure ld will not generate empty module without obj-path +; RUN: ls %t4.o* | count 2 ; Check with --no-map-whole-files ; RUN: %gold -plugin %llvmshlibdir/LLVMgold%shlibext \ @@ -72,6 +75,8 @@ ; RUN: -shared %t.o %t2.o -o %t4 ; RUN: llvm-nm %t5.o1 | FileCheck %s --check-prefix=NM2 ; RUN: llvm-nm %t5.o2 | FileCheck %s --check-prefix=NM2 +; Ensure ld will generate empty module if specific obj-path +; RUN: ls %t5.o* | count 3 ; Test to ensure that thinlto-index-only with obj-path creates the file. ; RUN: rm -f %t5.o %t5.o1 diff --git a/llvm/tools/gold/gold-plugin.cpp b/llvm/tools/gold/gold-plugin.cpp --- a/llvm/tools/gold/gold-plugin.cpp +++ b/llvm/tools/gold/gold-plugin.cpp @@ -871,6 +871,7 @@ Conf.OptLevel = options::OptLevel; Conf.PTO.LoopVectorization = options::OptLevel > 1; Conf.PTO.SLPVectorization = options::OptLevel > 1; + Conf.EmitEmptyObj = !options::obj_path.empty(); if (options::thinlto_index_only) { std::string OldPrefix, NewPrefix; @@ -1054,26 +1055,22 @@ bool SaveTemps = !Filename.empty(); size_t MaxTasks = Lto->getMaxTasks(); - std::vector, bool>> Files(MaxTasks); - - auto AddStream = - [&](size_t Task) -> std::unique_ptr { - Files[Task].second = !SaveTemps; - int FD = getOutputFileName(Filename, /* TempOutFile */ !SaveTemps, - Files[Task].first, Task); - return std::make_unique( - std::make_unique(FD, true)); - }; - - auto AddBuffer = [&](size_t Task, std::unique_ptr MB) { - *AddStream(Task)->OS << MB->getBuffer(); - }; + std::vector> Buf(MaxTasks); + std::vector> CacheFiles(MaxTasks); NativeObjectCache Cache; if (!options::cache_dir.empty()) - Cache = check(localCache(options::cache_dir, AddBuffer)); - - check(Lto->run(AddStream, Cache)); + Cache = check(localCache( + options::cache_dir, [&](size_t Task, std::unique_ptr MB) { + CacheFiles[Task] = std::move(MB); + })); + + check(Lto->run( + [&](size_t Task) { + return std::make_unique( + std::make_unique(Buf[Task])); + }, + Cache)); // Write empty output files that may be expected by the distributed build // system. @@ -1084,6 +1081,21 @@ OldPrefix, NewPrefix, /* SkipModule */ false); + std::vector, bool>> Files(MaxTasks); + for (unsigned TaskID = 0; TaskID != MaxTasks; ++TaskID) { + if (CacheFiles[TaskID] || !Buf[TaskID].empty()) { + SmallString<128> FilePath; + int FD = getOutputFileName(Filename, !SaveTemps, FilePath, TaskID); + raw_fd_ostream Out(FD, true, true); + if (CacheFiles[TaskID]) { + Out << CacheFiles[TaskID]->getBuffer(); + } else { + Out << Buf[TaskID]; + } + Files.push_back({FilePath, !SaveTemps}); + } + } + return Files; } diff --git a/llvm/tools/llvm-lto2/llvm-lto2.cpp b/llvm/tools/llvm-lto2/llvm-lto2.cpp --- a/llvm/tools/llvm-lto2/llvm-lto2.cpp +++ b/llvm/tools/llvm-lto2/llvm-lto2.cpp @@ -286,6 +286,7 @@ Conf.StatsFile = StatsFile; Conf.PTO.LoopVectorization = Conf.OptLevel > 1; Conf.PTO.SLPVectorization = Conf.OptLevel > 1; + Conf.EmitEmptyObj = ThinLTODistributedIndexes; ThinBackend Backend; if (ThinLTODistributedIndexes)