diff --git a/cross-project-tests/dtlto/link-savetemps.test b/cross-project-tests/dtlto/link-savetemps.test new file mode 100644 index 000000000000..8bf0bd6b08ae --- /dev/null +++ b/cross-project-tests/dtlto/link-savetemps.test @@ -0,0 +1,44 @@ +REQUIRES: lld-link + +# Test that DTLTO temporary files are "best-effort" cleaned up unless +# --save-temps is specified. We use archives in this test as the handling for +# archives requires a superset of the temporary files used for object inputs. + +RUN: rm -rf %t && split-file %s %t && cd %t + +RUN: %clang --target=x86_64-pc-windows-msvc -O2 t1.c -flto=thin -c + +RUN: lld-link /lib /out:t.lib t1.o + +DEFINE: %{tdir} = dummy-to-make-lit-work +DEFINE: %{dtlto} = mkdir %{tdir} && \ +DEFINE: lld-link /subsystem:console /machine:x64 /out:%{tdir}/my.exe \ +DEFINE: /wholearchive:t.lib \ +DEFINE: -thinlto-distributor:%python \ +DEFINE: -thinlto-distributor-arg:%llvm_src_root/utils/dtlto/local.py \ +DEFINE: -thinlto-remote-compiler:%clang + +# Check that temporary files are removed normally. +REDEFINE: %{tdir} = empty +RUN: %{dtlto} +RUN: ls %{tdir} | sort | FileCheck %s --check-prefixes=BOOKEND,ELF + +# Check that --save-temps preserves temporary files. +REDEFINE: %{tdir} = savetemps +RUN: %{dtlto} /lldsavetemps +RUN: ls %{tdir} | sort | FileCheck %s --check-prefixes=BOOKEND,TEMPS,ELF,OTHER + +# No files are expected before. +BOOKEND-NOT: {{.}} +TEMPS: {{^}}my.[[#PID:]].dist-file.json{{$}} +ELF: {{^}}my.exe{{$}} +OTHER: {{^}}my.exe.resolution.txt{{$}} +# Filename composition: .....native.o. +TEMPS: {{^}}t.libt1.o[[#T1_OFFSET:]].1.[[#%X,HEXPID:]].1.[[#PID]].native.o{{$}} +TEMPS: {{^}}t.libt1.o[[#T1_OFFSET]].1.[[#%X,HEXPID]].1.[[#PID]].native.o.thinlto.bc{{$}} +TEMPS: {{^}}t.libt1.o[[#T1_OFFSET]].1.[[#%X,HEXPID]].o{{$}} +# No files are expected after. +BOOKEND-NOT: {{.}} + +#--- t1.c +__attribute__((retain)) int mainCRTStartup() { return 0; } diff --git a/cross-project-tests/dtlto/local_codegen_and_wait.py b/cross-project-tests/dtlto/local_codegen_and_wait.py new file mode 100644 index 000000000000..ead5ff03b6b6 --- /dev/null +++ b/cross-project-tests/dtlto/local_codegen_and_wait.py @@ -0,0 +1,20 @@ +""" +This simple distributor performs code generation locally, creates the +"send-signal1" file, and then waits for the "send-signal2" file to appear +before exiting. It is intended to be used in tandem with test_temps.py. +Please see test_temps.py for more information. +""" + +import json, subprocess, sys, time, os, pathlib + +# Load the DTLTO information from the input JSON file. +data = json.loads(pathlib.Path(sys.argv[-1]).read_bytes()) + +# Iterate over the jobs and execute the codegen tool. +for job in data["jobs"]: + subprocess.check_call(data["common"]["args"] + job["args"]) + +pathlib.Path("send-signal1").touch() + +while not os.path.exists("send-signal2"): + time.sleep(0.05) diff --git a/cross-project-tests/dtlto/savetemps-lock.test b/cross-project-tests/dtlto/savetemps-lock.test new file mode 100644 index 000000000000..822744b1dfc4 --- /dev/null +++ b/cross-project-tests/dtlto/savetemps-lock.test @@ -0,0 +1,44 @@ +# This test relies on locking files which is difficult to do in a way the keeps +# a test robust on Linux, so it is restricted to Windows. +REQUIRES: ld.lld,system-windows + +# Test that a warning is emitted for each DTLTO temporary file that cannot be +# removed. This test uses archives because archive handling exercises a superset +# of the temporary files used for object inputs. +# +# This scenario is logically related to the cases in savetemps.test; however, it +# is placed here to maintain coverage, as this behavior can only be tested +# effectively on Windows. + +RUN: rm -rf %t && split-file %s %t && cd %t + +RUN: %clang --target=x86_64-linux-gnu -O2 t1.c t2.c -flto=thin -c + +RUN: llvm-ar rcs t.a t1.o t2.o + +# Check that a warning is reported for each temporary file that cannot be +# removed. Note that the use of the name "locked" for the output directory +# triggers special behaviour in test_temps.py. +RUN: mkdir locked && %python %S/test_temps.py locked lock \ +RUN: %clang --target=x86_64-linux-gnu -nostdlib -O2 -flto=thin \ +RUN: -fuse-ld=lld -Wl,--whole-archive t.a -o locked/t.elf -shared \ +RUN: -fthinlto-distributor=%python \ +RUN: -Xthinlto-distributor=%S/local_codegen_and_wait.py 2>&1 \ +RUN: | FileCheck %s --implicit-check-not=warning + +# Sanity check for the expected test_temps.py behaviour. +CHECK-DAG: Lock any files in the output directory. +CHECK-DAG: warning: could not remove the file 'locked{{/|\\}}t.[[#PID:]].dist-file.json': {{.*}} +# Filename composition: ( at ).....native.o. +CHECK-DAG: warning: could not remove the file 'locked{{/|\\}}t.a(t1.o at [[#T1_OFFSET:]]).1.[[#%X,HEXPID:]].1.[[#PID]].native.o': {{.*}} +CHECK-DAG: warning: could not remove the file 'locked{{/|\\}}t.a(t1.o at [[#T1_OFFSET]]).1.[[#%X,HEXPID]].1.[[#PID]].native.o.thinlto.bc': {{.*}} +CHECK-DAG: warning: could not remove the file 'locked{{/|\\}}t.a(t2.o at [[#T2_OFFSET:]]).2.[[#%X,HEXPID]].2.[[#PID]].native.o': {{.*}} +CHECK-DAG: warning: could not remove the file 'locked{{/|\\}}t.a(t2.o at [[#T2_OFFSET]]).2.[[#%X,HEXPID]].2.[[#PID]].native.o.thinlto.bc': {{.*}} +CHECK-DAG: warning: could not remove temporary DTLTO input file 'locked{{/|\\}}t.a(t1.o at [[#T1_OFFSET]]).1.[[#%X,HEXPID]].o': {{.*}} +CHECK-DAG: warning: could not remove temporary DTLTO input file 'locked{{/|\\}}t.a(t2.o at [[#T2_OFFSET]]).2.[[#%X,HEXPID]].o': {{.*}} + +#--- t1.c +__attribute__((retain)) int t1(int x) { return x; } + +#--- t2.c +__attribute__((retain)) int t2(int x) { return x; } diff --git a/cross-project-tests/dtlto/savetemps.test b/cross-project-tests/dtlto/savetemps.test new file mode 100644 index 000000000000..f8615a00ad66 --- /dev/null +++ b/cross-project-tests/dtlto/savetemps.test @@ -0,0 +1,70 @@ +REQUIRES: ld.lld + +# Test that DTLTO temporary files are "best-effort" cleaned up unless +# --save-temps is specified. We use archives in this test as the handling for +# archives requires a superset of the temporary files used for object inputs. + +RUN: rm -rf %t && split-file %s %t && cd %t + +RUN: %clang --target=x86_64-linux-gnu -O2 t1.c t2.c -flto=thin -c + +RUN: llvm-ar rcs t.a t1.o t2.o + +DEFINE: %{tdir} = dummy-to-make-lit-work +DEFINE: %{action} = dummy-to-make-lit-work +DEFINE: %{test-temps-dtlto} = \ +DEFINE: mkdir %{tdir} && %python %S/test_temps.py %{tdir} %{action} \ +DEFINE: %clang --target=x86_64-linux-gnu -nostdlib -O2 -flto=thin \ +DEFINE: -fuse-ld=lld -Wl,--whole-archive t.a -o %{tdir}/t.elf -shared \ +DEFINE: -fthinlto-distributor=%python \ +DEFINE: -Xthinlto-distributor=%S/local_codegen_and_wait.py + +# Check that all temporary files are removed in normal operation. +REDEFINE: %{tdir} = empty +REDEFINE: %{action} = none +RUN: %{test-temps-dtlto} +RUN: ls %{tdir} | sort | FileCheck %s --check-prefixes=BOOKEND,ELF + +# Check that --save-temps preserves temporary files. +REDEFINE: %{tdir} = savetemps +REDEFINE: %{action} = none +RUN: %{test-temps-dtlto} -Wl,--save-temps +RUN: ls %{tdir} | sort | FileCheck %s --check-prefixes=BOOKEND,TEMPS,INDEX,ELF + +# Check that --thinlto-emit-index-files preserves the index files. +REDEFINE: %{tdir} = index +REDEFINE: %{action} = none +RUN: %{test-temps-dtlto} -Wl,--thinlto-emit-index-files +RUN: ls %{tdir} | sort | FileCheck %s --check-prefixes=BOOKEND,INDEX,ELF + +# No files are expected before. +BOOKEND-NOT: {{.}} +TEMPS: {{^}}t.[[#PID:]].dist-file.json{{$}} +# Filename composition: ( at ).....native.o. +TEMPS: {{^}}t.a(t1.o at [[#T1_OFFSET:]]).1.[[#%X,HEXPID:]].1.[[#PID]].native.o{{$}} +INDEX: {{^}}t.a(t1.o at [[#T1_OFFSET:]]).1.[[#%X,HEXPID:]].1.[[#PID:]].native.o.thinlto.bc{{$}} +TEMPS: {{^}}t.a(t1.o at [[#T1_OFFSET]]).1.[[#%X,HEXPID]].o{{$}} +TEMPS: {{^}}t.a(t2.o at [[#T2_OFFSET:]]).2.[[#%X,HEXPID]].2.[[#PID]].native.o{{$}} +INDEX: {{^}}t.a(t2.o at [[#T2_OFFSET:]]).2.[[#%X,HEXPID]].2.[[#PID]].native.o.thinlto.bc{{$}} +TEMPS: {{^}}t.a(t2.o at [[#T2_OFFSET]]).2.[[#%X,HEXPID]].o{{$}} +ELF: {{^}}t.elf{{$}} +TEMPS: {{^}}t.elf.resolution.txt{{$}} +# No files are expected after. +BOOKEND-NOT: {{.}} + +# Check that no warnings are produced if temporary files are missing. Note that +# the use of the name "removed" for the output directory triggers special +# behaviour in test_temps.py. +REDEFINE: %{tdir} = removed +REDEFINE: %{action} = remove +RUN: %{test-temps-dtlto} 2>&1 | FileCheck %s --check-prefix=NOWARN --allow-empty +RUN: ls %{tdir} | sort | FileCheck %s --check-prefixes=BOOKEND,ELF +# Sanity check for the expected test_temps.py behaviour. +NOWARN: Remove non-essential files in the output directory. +NOWARN-NOT: warning + +#--- t1.c +__attribute__((retain)) int t1(int x) { return x; } + +#--- t2.c +__attribute__((retain)) int t2(int x) { return x; } diff --git a/cross-project-tests/dtlto/signal.test b/cross-project-tests/dtlto/signal.test index 666964dd94e0..ba08b2bc1066 100644 --- a/cross-project-tests/dtlto/signal.test +++ b/cross-project-tests/dtlto/signal.test @@ -1,19 +1,22 @@ REQUIRES: ld.lld # Test that if a link is terminated by a signal (or the equivalent on -# Windows), e.g. CTRL-C, DTLTO temporary files are cleaned up. +# Windows), e.g. CTRL-C, DTLTO temporary files are cleaned up. We use +# archives in this test as the handling for archives requires a superset +# of the temporary files used for object inputs. RUN: rm -rf %t && split-file %s %t && cd %t RUN: %clang --target=x86_64-linux-gnu -O2 t1.c t2.c -flto=thin -c +RUN: llvm-ar rcs t.a t1.o t2.o + DEFINE: %{tdir} = dummy-to-make-lit-work -DEFINE: %{kill-dtlto} = rm -f send-signal && mkdir %{tdir} && \ -DEFINE: %python killer.py \ -DEFINE: %clang --target=x86_64-linux-gnu -nostdlib -O2 -flto=thin \ -DEFINE: -fuse-ld=lld -Wl,--whole-archive t1.o t2.o -o %{tdir}/t.elf -shared \ -DEFINE: -fthinlto-distributor=%python \ -DEFINE: -Xthinlto-distributor=local_codegen_and_wait.py +DEFINE: %{kill-dtlto} = mkdir %{tdir} && %python %S/test_temps.py %{tdir} kill \ +DEFINE: %clang --target=x86_64-linux-gnu -nostdlib -O2 -flto=thin \ +DEFINE: -fuse-ld=lld -Wl,--whole-archive t.a -o %{tdir}/t.elf -shared \ +DEFINE: -fthinlto-distributor=%python \ +DEFINE: -Xthinlto-distributor=%S/local_codegen_and_wait.py # Check that all temporary files are removed if the process is aborted. REDEFINE: %{tdir} = empty @@ -25,7 +28,7 @@ EMPTY-NOT: {{.}} # Check that --save-temps preserves temporary files if the process is aborted. REDEFINE: %{tdir} = savetemps RUN: %{kill-dtlto} -Wl,--save-temps -RUN: ls %{tdir} | sort | FileCheck %s --check-prefixes=BOOKEND,TEMPS,INDEX +RUN: ls %{tdir} | sort | FileCheck %s --check-prefixes=BOOKEND,TEMPS,INDEX,OTHER # Check that --thinlto-emit-index-files preserves the index files if the process # is aborted. @@ -36,11 +39,14 @@ RUN: ls %{tdir} | sort | FileCheck %s --check-prefixes=BOOKEND,INDEX # No files are expected before. BOOKEND-NOT: {{.}} TEMPS: {{^}}t.[[#PID:]].dist-file.json{{$}} -TEMPS: {{^}}t.elf.{{.+$}} -TEMPS: {{^}}t1.1.[[#PID]].native.o{{$}} -INDEX: {{^}}t1.1.[[#PID:]].native.o.thinlto.bc{{$}} -TEMPS: {{^}}t2.2.[[#PID]].native.o{{$}} -INDEX: {{^}}t2.2.[[#PID]].native.o.thinlto.bc{{$}} +# Filename composition: ( at ).....native.o. +TEMPS: {{^}}t.a(t1.o at [[#T1_OFFSET:]]).1.[[#%X,HEXPID:]].1.[[#PID]].native.o{{$}} +INDEX: {{^}}t.a(t1.o at [[#T1_OFFSET:]]).1.[[#%X,HEXPID:]].1.[[#PID:]].native.o.thinlto.bc{{$}} +TEMPS: {{^}}t.a(t1.o at [[#T1_OFFSET]]).1.[[#%X,HEXPID]].o{{$}} +TEMPS: {{^}}t.a(t2.o at [[#T2_OFFSET:]]).2.[[#%X,HEXPID]].2.[[#PID]].native.o{{$}} +INDEX: {{^}}t.a(t2.o at [[#T2_OFFSET:]]).2.[[#%X,HEXPID]].2.[[#PID]].native.o.thinlto.bc{{$}} +TEMPS: {{^}}t.a(t2.o at [[#T2_OFFSET]]).2.[[#%X,HEXPID]].o{{$}} +OTHER: {{^}}t.elf.resolution.txt{{$}} # No files are expected after. BOOKEND-NOT: {{.}} @@ -49,45 +55,3 @@ __attribute__((retain)) int t1(int x) { return x; } #--- t2.c __attribute__((retain)) int t2(int x) { return x; } - -#--- local_codegen_and_wait.py -"""Perform codegen locally, create "send-signal" file and wait.""" -from pathlib import Path -import json, subprocess, sys, time - -# Load the DTLTO information from the input JSON file. -data = json.loads(Path(sys.argv[-1]).read_bytes()) - -# Iterate over the jobs and execute the codegen tool. -for job in data["jobs"]: - subprocess.check_call(data["common"]["args"] + job["args"]) -Path("send-signal").touch() -while True: - time.sleep(1) - -#--- killer.py -"""Run command, wait for "send-signal" file to exist, and then send a -termination signal.""" -import os, sys, time, signal, subprocess - -if os.name == "nt": - # CREATE_NEW_PROCESS_GROUP is used so that p.send_signal(CTRL_BREAK_EVENT) - # does not get sent to the LIT processes that are running the test. - kwargs = {"creationflags": subprocess.CREATE_NEW_PROCESS_GROUP} -else: - # Makes the child a process-group leader so os.killpg(p.pid, SIGINT) works. - kwargs = {"start_new_session": True} - -p = subprocess.Popen(sys.argv[1:], **kwargs) - -while not os.path.exists("send-signal"): - time.sleep(0.05) - -if os.name == "nt": - # Note that CTRL_C_EVENT does not appear to work for clang. - p.send_signal(signal.CTRL_BREAK_EVENT) -else: - os.killpg(p.pid, signal.SIGINT) -p.wait() - -sys.exit(0 if p.returncode != 0 else 1) diff --git a/cross-project-tests/dtlto/test_temps.py b/cross-project-tests/dtlto/test_temps.py new file mode 100644 index 000000000000..ecf1a3882b2b --- /dev/null +++ b/cross-project-tests/dtlto/test_temps.py @@ -0,0 +1,128 @@ +""" +This script works in tandem with local_codegen_and_wait.py. By coordinating +via the "send-signal*" files, the scripts ensure that the requested action is +performed after all DTLTO backend compilations have completed but before +DTLTO itself finishes. At this point, DTLTO temporary files have been +created but have not yet been cleaned up. + +Usage: + %python test_temps.py + +Run , which must be a ThinLTO link invocation that uses +local_codegen_and_wait.py as the ThinLTO distributor. The script waits for +the "send-signal1" file to appear, performs , and then creates the +"send-signal2" file to allow the link to continue. + +Actions: + kill Send an interrupt to cause to terminate after the + ThinLTO backend compilations complete. + lock (Windows only) Hold open handles to DTLTO temporary files in + to prevent their deletion after the ThinLTO backend + compilations complete. This action is not supported on Linux, as + there is no reliable mechanism to prevent file deletion that is + guaranteed to be released when the script exits (AFAICT). + remove Delete non-essential files in after the ThinLTO + backend compilations complete. +""" + +import os +import signal +import subprocess +import sys +import time +from pathlib import Path + +IS_WIN = os.name == "nt" +SIGNAL1 = Path("send-signal1") +SIGNAL2 = Path("send-signal2") + +if IS_WIN: + import ctypes + from ctypes import wintypes + + CreateFileW = ctypes.WinDLL("kernel32", use_last_error=True).CreateFileW + CreateFileW.argtypes = [ + wintypes.LPCWSTR, # lpFileName + wintypes.DWORD, # dwDesiredAccess + wintypes.DWORD, # dwShareMode + wintypes.LPVOID, # lpSecurityAttributes + wintypes.DWORD, # dwCreationDisposition + wintypes.DWORD, # dwFlagsAndAttributes + wintypes.HANDLE, # hTemplateFile + ] + CreateFileW.restype = wintypes.HANDLE + + +def lock_no_delete_share(path): + # Windows-specific: deny FILE_SHARE_DELETE by omitting it from dwShareMode. + h = CreateFileW( + path, + 0x80000000, # GENERIC_READ + 0x00000003, # FILE_SHARE_READ/WRITE (no FILE_SHARE_DELETE) + None, # lpSecurityAttributes + 3, # OPEN_EXISTING + 0, # dwFlagsAndAttributes + None, # hTemplateFile + ) + if h == wintypes.HANDLE(-1).value: + err = ctypes.get_last_error() + raise OSError(err, f"CreateFileW failed ({err}) for: {path}") + return h + + +output_dir = Path(sys.argv[1]) +action = sys.argv[2] + +# "lock" is Windows-only; fail early if invoked elsewhere. +if action == "lock" and not IS_WIN: + print("error: action 'lock' is only supported on Windows", file=sys.stderr) + sys.exit(1) + +# Remove any pre-existing signal files from previous script runs. +SIGNAL1.unlink(missing_ok=True) +SIGNAL2.unlink(missing_ok=True) + +kwargs = {} +if action == "kill": + if IS_WIN: + # CREATE_NEW_PROCESS_GROUP is used so that p.send_signal(CTRL_BREAK_EVENT) + # does not get sent to the LIT processes that are running the test. + kwargs = {"creationflags": subprocess.CREATE_NEW_PROCESS_GROUP} + else: + # Makes the child a process-group leader so os.killpg(p.pid, SIGINT) works. + kwargs = {"start_new_session": True} + +p = subprocess.Popen(sys.argv[3:], **kwargs) + +while not SIGNAL1.exists() and p.poll() is None: + time.sleep(0.05) +if p.poll() is not None: + sys.exit(1) + +if action == "kill": + if IS_WIN: + # Note that CTRL_C_EVENT does not appear to work for clang. + p.send_signal(signal.CTRL_BREAK_EVENT) + else: + os.killpg(p.pid, signal.SIGINT) + +if action == "lock": + print("Lock any files in the output directory.") + for f in output_dir.iterdir(): + if f.is_file(): + lock_no_delete_share(str(f)) + +if action == "remove": + print("Remove non-essential files in the output directory.") + for f in output_dir.iterdir(): + if f.is_file() and not f.name.endswith("native.o"): + f.unlink() + +SIGNAL2.touch() + +if action == "kill": + # Expect termination: succeed if the child fails, fail if it exits cleanly. + p.wait() + sys.exit(0 if p.returncode != 0 else 1) + +sys.exit(p.wait()) diff --git a/lld/COFF/LTO.cpp b/lld/COFF/LTO.cpp index e621bb263d2c..d55f95493a85 100644 --- a/lld/COFF/LTO.cpp +++ b/lld/COFF/LTO.cpp @@ -142,8 +142,10 @@ BitcodeCompiler::BitcodeCompiler(COFFLinkerContext &c) : ctx(c) { ltoObj = std::make_unique(createConfig(), backend, ctx.config.ltoPartitions); else - ltoObj = std::make_unique(createConfig(), backend, - ctx.config.ltoPartitions); + ltoObj = std::make_unique( + createConfig(), backend, ctx.config.ltoPartitions, + llvm::lto::LTO::LTOKind::LTOK_Default, ctx.config.outputFile, + !ctx.config.saveTempsArgs.empty()); } BitcodeCompiler::~BitcodeCompiler() = default; diff --git a/lld/ELF/LTO.cpp b/lld/ELF/LTO.cpp index fa39444408ee..6f916a501a26 100644 --- a/lld/ELF/LTO.cpp +++ b/lld/ELF/LTO.cpp @@ -205,9 +205,10 @@ BitcodeCompiler::BitcodeCompiler(Ctx &ctx) : ctx(ctx) { ctx.arg.ltoPartitions, ltoModes[ctx.arg.ltoKind]); else - ltoObj = std::make_unique(createConfig(ctx), backend, - ctx.arg.ltoPartitions, - ltoModes[ctx.arg.ltoKind]); + ltoObj = std::make_unique( + createConfig(ctx), backend, ctx.arg.ltoPartitions, + ltoModes[ctx.arg.ltoKind], ctx.arg.outputFile, + !ctx.arg.saveTempsArgs.empty()); // Initialize usedStartStop. if (ctx.bitcodeFiles.empty()) return; diff --git a/llvm/include/llvm/DTLTO/DTLTO.h b/llvm/include/llvm/DTLTO/DTLTO.h index 14f1f5fd00e3..02b098a68aec 100644 --- a/llvm/include/llvm/DTLTO/DTLTO.h +++ b/llvm/include/llvm/DTLTO/DTLTO.h @@ -19,9 +19,14 @@ class DTLTO : public LTO { using Base = LTO; public: - // Inherit constructors. - using Base::Base; - ~DTLTO() override = default; + LLVM_ABI DTLTO(Config Conf, ThinBackend Backend, + unsigned ParallelCodeGenParallelismLevel, LTOKind LTOMode, + StringRef LinkerOutputFile, bool SaveTemps) + : Base(std::move(Conf), Backend, ParallelCodeGenParallelismLevel, + LTOMode), + LinkerOutputFile(LinkerOutputFile), SaveTemps(SaveTemps) { + assert(!LinkerOutputFile.empty() && "expected a valid linker output file"); + } // Add an input file and prepare it for distribution. LLVM_ABI Expected> @@ -37,6 +42,12 @@ private: BumpPtrAllocator PtrAlloc; StringSaver Saver{PtrAlloc}; + /// The output file to which this LTO invocation will contribute. + StringRef LinkerOutputFile; + + /// Controls preservation of any created temporary files. + bool SaveTemps; + // Determines if a file at the given path is a thin archive file. Expected isThinArchive(const StringRef ArchivePath); diff --git a/llvm/lib/DTLTO/DTLTO.cpp b/llvm/lib/DTLTO/DTLTO.cpp index b9a5cd3a062e..4d8f8ba0fc4a 100644 --- a/llvm/lib/DTLTO/DTLTO.cpp +++ b/llvm/lib/DTLTO/DTLTO.cpp @@ -25,6 +25,7 @@ #include "llvm/Support/MemoryBufferRef.h" #include "llvm/Support/Path.h" #include "llvm/Support/Process.h" +#include "llvm/Support/Signals.h" #include "llvm/Support/TimeProfiler.h" #include "llvm/Support/raw_ostream.h" @@ -158,7 +159,9 @@ lto::DTLTO::addInput(std::unique_ptr InputPtr) { std::string PID = utohexstr(sys::Process::getProcessId()); std::string Seq = std::to_string(InputFiles.size()); - NewModuleId = {sys::path::filename(ModuleId), ".", Seq, ".", PID, ".o"}; + NewModuleId = sys::path::parent_path(LinkerOutputFile); + sys::path::append(NewModuleId, sys::path::filename(ModuleId) + "." + Seq + + "." + PID + ".o"); } // Update the module identifier and save it. @@ -174,6 +177,9 @@ Error lto::DTLTO::saveInputArchiveMember(lto::InputFile *Input) { StringRef ModuleId = Input->getName(); if (Input->isMemberOfArchive()) { TimeTraceScope TimeScope("Save input archive member for DTLTO", ModuleId); + // Cleanup this file on abnormal process exit. + if (!SaveTemps) + llvm::sys::RemoveFileOnSignal(ModuleId); MemoryBufferRef MemoryBufferRef = Input->getFileBuffer(); if (Error EC = saveBuffer(MemoryBufferRef.getBuffer(), ModuleId)) return EC; @@ -207,11 +213,18 @@ llvm::Error lto::DTLTO::handleArchiveInputs() { // Remove temporary archive member files created to enable distribution. void lto::DTLTO::cleanup() { - { + if (!SaveTemps) { TimeTraceScope TimeScope("Remove temporary inputs for DTLTO"); - for (auto &Input : InputFiles) - if (Input->isMemberOfArchive()) - sys::fs::remove(Input->getName(), /*IgnoreNonExisting=*/true); + for (auto &Input : InputFiles) { + if (!Input->isMemberOfArchive()) + continue; + std::error_code EC = + sys::fs::remove(Input->getName(), /*IgnoreNonExisting=*/true); + if (EC && + EC != std::make_error_code(std::errc::no_such_file_or_directory)) + errs() << "warning: could not remove temporary DTLTO input file '" + << Input->getName() << "': " << EC.message() << "\n"; + } } Base::cleanup(); }