[DTLTO] Make temporary file handling consistent (#176807)

DTLTO emits temporary files to allow distribution of archive member
inputs.

It also emits temporary files from the ThinLTO backend, such as the
index files needed for each distributed ThinLTO backend compilation.

This change brings archive member temporary files into line with those
produced by the ThinLTO backend. They are now emitted in the same
location, warnings are emitted if they cannot be deleted, and they are
cleaned up on abnormal exit (e.g. Ctrl-C). All temporary files are
preserved when --save-temps is specified.

The existing signal-handling test has been extended to cover the full
set of DTLTO temporary files, and a new test has been added to exercise
temporary file handling in normal operation. Additionally, a minimal
test has been added to show the COFF behaviour.

SIE Internal tracker: TOOLCHAIN-21022
This commit is contained in:
Ben Dunbobbin 2026-01-26 17:55:49 +00:00 committed by GitHub
parent 29fd868691
commit 1d74db7e8d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 365 additions and 68 deletions

View File

@ -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: <archive><member><member offset>.<task>.<pid>.<task>.<pid>.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; }

View File

@ -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)

View File

@ -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: <archive>(<member> at <offset>).<task>.<pid>.<task>.<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': {{.*}}
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; }

View File

@ -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: <archive>(<member> at <offset>).<task>.<pid>.<task>.<pid>.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; }

View File

@ -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: <archive>(<member> at <offset>).<task>.<pid>.<task>.<pid>.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)

View File

@ -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 <output_dir> <action> <command...>
Run <command>, 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 <action>, and then creates the
"send-signal2" file to allow the link to continue.
Actions:
kill Send an interrupt to cause <command> to terminate after the
ThinLTO backend compilations complete.
lock (Windows only) Hold open handles to DTLTO temporary files in
<output_dir> 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 <output_dir> 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())

View File

@ -142,8 +142,10 @@ BitcodeCompiler::BitcodeCompiler(COFFLinkerContext &c) : ctx(c) {
ltoObj = std::make_unique<lto::LTO>(createConfig(), backend,
ctx.config.ltoPartitions);
else
ltoObj = std::make_unique<lto::DTLTO>(createConfig(), backend,
ctx.config.ltoPartitions);
ltoObj = std::make_unique<lto::DTLTO>(
createConfig(), backend, ctx.config.ltoPartitions,
llvm::lto::LTO::LTOKind::LTOK_Default, ctx.config.outputFile,
!ctx.config.saveTempsArgs.empty());
}
BitcodeCompiler::~BitcodeCompiler() = default;

View File

@ -205,9 +205,10 @@ BitcodeCompiler::BitcodeCompiler(Ctx &ctx) : ctx(ctx) {
ctx.arg.ltoPartitions,
ltoModes[ctx.arg.ltoKind]);
else
ltoObj = std::make_unique<lto::DTLTO>(createConfig(ctx), backend,
ctx.arg.ltoPartitions,
ltoModes[ctx.arg.ltoKind]);
ltoObj = std::make_unique<lto::DTLTO>(
createConfig(ctx), backend, ctx.arg.ltoPartitions,
ltoModes[ctx.arg.ltoKind], ctx.arg.outputFile,
!ctx.arg.saveTempsArgs.empty());
// Initialize usedStartStop.
if (ctx.bitcodeFiles.empty())
return;

View File

@ -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<std::shared_ptr<InputFile>>
@ -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<bool> isThinArchive(const StringRef ArchivePath);

View File

@ -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<lto::InputFile> 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();
}