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 +; 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,11 @@ /// link. bool HasWholeProgramVisibility = false; + /// Allow emit Regular LTO object from empty module. This option is usedful + /// 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/include/llvm/LTO/LTOBackend.h b/llvm/include/llvm/LTO/LTOBackend.h --- a/llvm/include/llvm/LTO/LTOBackend.h +++ b/llvm/include/llvm/LTO/LTOBackend.h @@ -37,7 +37,8 @@ /// regular LTO phase of ThinLTO, which may need to access the combined index. Error backend(const Config &C, AddStreamFn AddStream, unsigned ParallelCodeGenParallelismLevel, - std::unique_ptr M, ModuleSummaryIndex &CombinedIndex); + std::unique_ptr M, ModuleSummaryIndex &CombinedIndex, + bool EmptyCombinedModule); /// Runs a ThinLTO backend. Error thinBackend(const Config &C, unsigned Task, AddStreamFn AddStream, 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) @@ -1028,7 +1029,8 @@ } if (Error Err = backend(Conf, AddStream, RegularLTO.ParallelCodeGenParallelismLevel, - std::move(RegularLTO.CombinedModule), ThinLTO.CombinedIndex)) + std::move(RegularLTO.CombinedModule), ThinLTO.CombinedIndex, + RegularLTO.EmptyCombinedModule)) return Err; return finalizeOptimizationRemarks(std::move(*DiagFileOrErr)); 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 @@ -479,7 +479,8 @@ Error lto::backend(const Config &C, AddStreamFn AddStream, unsigned ParallelCodeGenParallelismLevel, std::unique_ptr Mod, - ModuleSummaryIndex &CombinedIndex) { + ModuleSummaryIndex &CombinedIndex, + bool EmptyCombinedModule) { Expected TOrErr = initAndLookupTarget(C, *Mod); if (!TOrErr) return TOrErr.takeError(); @@ -493,7 +494,8 @@ } if (ParallelCodeGenParallelismLevel == 1) { - codegen(C, TM.get(), AddStream, 0, *Mod); + if (!EmptyCombinedModule || C.AlwaysEmitRegularLTOObj) + codegen(C, TM.get(), AddStream, 0, *Mod); } 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.AlwaysEmitRegularLTOObj = !options::obj_path.empty(); if (options::thinlto_index_only) { std::string OldPrefix, NewPrefix; 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.AlwaysEmitRegularLTOObj = ThinLTODistributedIndexes; ThinBackend Backend; if (ThinLTODistributedIndexes)