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.AlwaysEmitRegularLTOObj = !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.AlwaysEmitRegularLTOObj = !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 +; Ensure ld emits empty combined module if specific obj-path. +; 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 + +; Ensure ld does not emit empty combined module in default. +; 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 @@ -1,13 +1,13 @@ ; REQUIRES: x86 ; RUN: llvm-as %s -o %t.o +; RUN: rm -f %t2.* ; RUN: echo "foo = 1;" > %t.script ; RUN: ld.lld %t.o -o %t2 --script %t.script -save-temps +; Combined module is not empty, but it will be empty after optimization. +; Ensure ld still emits empty combined obj in this case. ; 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 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 +; Ensure ld emits empty combined module if specific obj-path. +; 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 + +; Ensure ld does not emit empty combined module in default. +; 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,11 @@ /// link. bool HasWholeProgramVisibility = false; + /// Always emit a Regular LTO object even when it is empty because no Regular + /// LTO modules were linked. This option is useful for some build system which + /// want to know a priori all possible output files. + bool AlwaysEmitRegularLTOObj = 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/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 @@ -331,6 +331,7 @@ std::vector Keep; }; std::vector ModsWithSummaries; + bool EmptyCombinedModule = true; } RegularLTO; struct ThinLTOState { 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 @@ -603,6 +603,7 @@ if (LTOInfo->IsThinLTO) return addThinLTO(BM, ModSyms, ResI, ResE); + RegularLTO.EmptyCombinedModule = false; Expected ModOrErr = addRegularLTO(BM, ModSyms, ResI, ResE); if (!ModOrErr) @@ -1026,10 +1027,13 @@ !Conf.PostInternalizeModuleHook(0, *RegularLTO.CombinedModule)) return Error::success(); } - if (Error Err = - backend(Conf, AddStream, RegularLTO.ParallelCodeGenParallelismLevel, - std::move(RegularLTO.CombinedModule), ThinLTO.CombinedIndex)) - return Err; + + if (!RegularLTO.EmptyCombinedModule || Conf.AlwaysEmitRegularLTOObj) { + if (Error Err = backend( + Conf, AddStream, RegularLTO.ParallelCodeGenParallelismLevel, + std::move(RegularLTO.CombinedModule), ThinLTO.CombinedIndex)) + return Err; + } return finalizeOptimizationRemarks(std::move(*DiagFileOrErr)); } 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 @@ -1,11 +1,11 @@ ; RUN: opt -module-summary -o %t.bc %s -; RUN: rm -f %t2.0 -; RUN: llvm-lto2 run %t.bc -r %t.bc,foo,pl -o %t2 -thinlto-distributed-indexes -; RUN: llvm-readobj -h %t2.0 | FileCheck %s -; RUN: llvm-nm %t2.0 2>&1 | count 0 - -; CHECK: Format: elf64-x86-64 +; RUN: rm -f %t2.* +; RUN: llvm-lto2 run %t.bc -r %t.bc,foo,pl -o %t2 -thinlto-distributed-indexes -save-temps +; Ensure lto does not emit empty combined module. +; RUN: test ! -e %t2.0 +; Ensure empty combined module has only 2 temp files. +; RUN: ls %t2.0.*.bc | count 2 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" 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 does not emit empty combined module in default. +; 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 emits empty combined 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.AlwaysEmitRegularLTOObj = !options::obj_path.empty(); if (options::thinlto_index_only) { std::string OldPrefix, NewPrefix;