blob: d1917eb8d954a932eb9c5ca62298ef6ffaf7bb7c [file] [edit]
//===- DTLTO.cpp - Integrated Distributed ThinLTO implementation ----------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// \file
// This file implements support functions for Integrated Distributed ThinLTO,
// focusing on preparing complilation jobs for distribution.
//
//===----------------------------------------------------------------------===//
#include "llvm/DTLTO/DTLTO.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/LTO/LTO.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MemoryBufferRef.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Process.h"
#include "llvm/Support/TimeProfiler.h"
#include "llvm/Support/raw_ostream.h"
#include <string>
using namespace llvm;
// Remove temporary files created to enable distribution.
void lto::DTLTO::cleanup() {
if (!SaveTemps) {
// Remove one file, report error if any.
auto removeFile = [](StringRef FileName) -> void {
std::error_code EC = sys::fs::remove(FileName, true);
if (EC &&
EC != std::make_error_code(std::errc::no_such_file_or_directory))
errs() << "warning: could not remove the file '" << FileName
<< "': " << EC.message() << "\n";
};
TimeTraceScope JobScope("Remove DTLTO temporary files");
for (const auto &Name : CleanupList)
removeFile(Name);
// Clean the CleanupList for safety.
CleanupList.clear();
}
}
// Runs the DTLTO thin link phase, producing per-module summary indices,
// import lists, and cache keys for distribution.
Error lto::DTLTO::performThinLink() {
size_t NumTasks = getMaxTasks();
SummaryIndexFiles.resize(NumTasks);
ImportsFilesList.resize(NumTasks);
CacheKeysList.resize(NumTasks);
lto::Config &Cfg = getConfig();
Cfg.GetSummaryIndexOutputStream =
[&](size_t task) -> std::unique_ptr<raw_svector_ostream> {
return std::make_unique<raw_svector_ostream>(SummaryIndexFiles[task]);
};
Cfg.GetCacheKeyOutputString = [&](size_t task) -> std::string & {
return CacheKeysList[task];
};
Cfg.GetImportsListOutputArray =
[&](size_t task) -> std::vector<std::string> & {
return ImportsFilesList[task];
};
return Base::run(AddStreamFunc, {});
}
// Runs the DTLTO pipeline.
LLVM_ABI Error lto::DTLTO::run(AddStreamFn AddStream, FileCache CacheParam) {
scope_exit CleanUp([this]() { cleanup(); });
AddStreamFunc = AddStream;
Cache = std::move(CacheParam);
Conf.Dtlto = 1;
UID = itostr(sys::Process::getProcessId());
if (Error Err = performThinLink())
return Err;
ThinLTOTaskOffset = RegularLTO.ParallelCodeGenParallelismLevel;
DistributorParams.TargetTriple = RegularLTO.CombinedModule->getTargetTriple();
if (Error Err = prepareDtltoJobs())
return Err;
if (Error Err = serializeLTOInputs())
return Err;
if (Error Err = performCodegen())
return Err;
if (Error Err = addObjectFilesToLink())
return Err;
return Error::success();
}
// Probes the LTO cache for a compiled native object for the given job.
Error lto::DTLTO::checkCacheHit(Job &J) {
if (!Cache.isValid())
return Error::success();
auto CacheAddStreamExp = Cache(J.Task, J.CacheKey, J.ModuleID);
if (Error Err = CacheAddStreamExp.takeError())
return Err;
AddStreamFn &CacheAddStream = *CacheAddStreamExp;
// If CacheAddStream is null, we have a cache hit and at this point
// object file is already passed back to the linker.
if (!CacheAddStream) {
J.Cached = true; // Cache hit, mark the job as cached.
CachedJobs.fetch_add(1);
} else {
// If CacheAddStream is not null, we have a cache miss and we need to
// run the backend for codegen. Save cache 'add stream'
// function for a later use.
J.CacheAddStream = std::move(CacheAddStream);
}
return Error::success();
}
// Prepares a single DTLTO backend compilation job for a ThinLTO module.
Error lto::DTLTO::prepareDtltoJob(StringRef ModulePath, unsigned Task) {
assert(Task >= ThinLTOTaskOffset && Task - ThinLTOTaskOffset < Jobs.size() &&
"Task index out of range for Jobs");
assert(Task < SummaryIndexFiles.size() && "Task index out of range");
SString ObjFilePath =
sys::path::parent_path(DistributorParams.LinkerOutputFile);
sys::path::append(ObjFilePath, sys::path::stem(ModulePath) + "." +
itostr(Task) + "." + UID + ".native.o");
SString SummaryIndexPathStr = ObjFilePath;
SummaryIndexPathStr += ".thinlto.bc";
SString ImportsPathStr = ModulePath;
ImportsPathStr += ".imports";
Job &J = Jobs[Task - ThinLTOTaskOffset];
J = {Task,
ModulePath,
Saver.save(ObjFilePath.str()),
Saver.save(SummaryIndexPathStr.str()),
Saver.save(ImportsPathStr.str()),
ImportsFilesList[Task],
CacheKeysList[Task],
nullptr,
false};
if (Error Err = checkCacheHit(J))
return Err;
if (!J.Cached) {
TimeTraceScope JobScope("Emit individual index for DTLTO",
J.SummaryIndexPath);
if (Error Err = save(SummaryIndexFiles[Task], J.SummaryIndexPath))
return Err;
}
if (OnIndexWriteCb)
OnIndexWriteCb(J.SummaryIndexPath.str());
if (ShouldEmitImportFiles)
if (Error Err = save(join(J.ImportsFilesList, "\n"), J.ImportsPath))
return Err;
if (!SaveTemps) {
if (!J.Cached)
addToCleanup(J.NativeObjectPath.str());
if (!ShouldEmitIndexFiles)
addToCleanup(J.SummaryIndexPath.str());
if (!ShouldEmitImportFiles)
addToCleanup(J.ImportsPath.str());
}
return Error::success();
}
// Derive a set of Clang options that will be shared/common for all DTLTO
// backend compilations.
void lto::DTLTO::buildCommonRemoteCompilerOptions() {
const lto::Config &C = getConfig();
auto &Ops = DistributorParams.CodegenOptions;
Ops.push_back(Saver.save("-O" + Twine(C.OptLevel)));
if (C.Options.EmitAddrsig)
Ops.push_back("-faddrsig");
if (C.Options.FunctionSections)
Ops.push_back("-ffunction-sections");
if (C.Options.DataSections)
Ops.push_back("-fdata-sections");
if (C.RelocModel == Reloc::PIC_)
// Clang doesn't have -fpic for all triples.
if (!DistributorParams.TargetTriple.isOSBinFormatCOFF())
Ops.push_back("-fpic");
// Turn on/off warnings about profile cfg mismatch (default on)
// --lto-pgo-warn-mismatch.
if (!C.PGOWarnMismatch) {
Ops.push_back("-mllvm");
Ops.push_back("-no-pgo-warn-mismatch");
}
// Enable sample-based profile guided optimizations.
// Sample profile file path --lto-sample-profile=<value>.
if (!C.SampleProfile.empty()) {
Ops.push_back(Saver.save("-fprofile-sample-use=" + Twine(C.SampleProfile)));
DistributorParams.CommonInputs.insert(C.SampleProfile);
}
// We don't know which of options will be used by Clang.
Ops.push_back("-Wno-unused-command-line-argument");
// Forward any supplied options.
if (!DistributorParams.RemoteCompilerArgs.empty())
for (auto &a : DistributorParams.RemoteCompilerArgs)
Ops.push_back(a);
}
// Initializes DTLTO state and prepares a job for each ThinLTO module.
Error lto::DTLTO::prepareDtltoJobs() {
auto &ModuleMap =
ThinLTO.ModulesToCompile ? *ThinLTO.ModulesToCompile : ThinLTO.ModuleMap;
if (ModuleMap.empty())
return Error::success();
Jobs.resize(ModuleMap.size());
for (auto [I, Mod] : enumerate(ModuleMap))
if (Error E = prepareDtltoJob(Mod.first, ThinLTOTaskOffset + I))
return E;
return Error::success();
}
// Runs the DTLTO code generation phase. Must be invoked after thinLink().
Error lto::DTLTO::performCodegen() {
if (Jobs.empty())
return Error::success();
// Build common remote compiler options.
buildCommonRemoteCompilerOptions();
DistributionDriver Distributor(DistributorParams, Jobs, SaveTemps,
[&](StringRef S) { addToCleanup(S); });
if (CachedJobs.load() < Jobs.size()) {
if (Error E = Distributor())
return E;
}
return Error::success();
}
// Adds compiled object files to the link for each non-cached job.
Error lto::DTLTO::addObjectFilesToLink() {
TimeTraceScope FilesScope("Add DTLTO files to the link");
for (auto &Job : Jobs) {
if (!Job.CacheKey.empty() && Job.Cached) {
assert(Cache.isValid());
continue;
}
// Load the native object from a file into a memory buffer
// and store its contents in the output buffer.
auto ObjFileMbOrErr =
MemoryBuffer::getFile(Job.NativeObjectPath, /*IsText=*/false,
/*RequiresNullTerminator=*/false);
if (std::error_code EC = ObjFileMbOrErr.getError())
return make_error<StringError>(
BCError + "cannot open native object file: " + Job.NativeObjectPath +
": " + EC.message(),
inconvertibleErrorCode());
MemoryBufferRef ObjFileMbRef = ObjFileMbOrErr->get()->getMemBufferRef();
if (Cache.isValid()) {
// Cache hits are taken care of earlier. At this point, we could only
// have cache misses.
assert(Job.CacheAddStream);
// Obtain a file stream for a storing a cache entry.
auto CachedFileStreamOrErr = Job.CacheAddStream(Job.Task, Job.ModuleID);
if (!CachedFileStreamOrErr)
return joinErrors(
CachedFileStreamOrErr.takeError(),
createStringError(inconvertibleErrorCode(),
"Cannot get a cache file stream: %s",
Job.NativeObjectPath.data()));
// Store a file buffer into the cache stream.
auto &CacheStream = *(CachedFileStreamOrErr->get());
*(CacheStream.OS) << ObjFileMbRef.getBuffer();
if (Error Err = CacheStream.commit())
return Err;
} else {
if (AddBuffer) {
AddBuffer(Job.Task, Job.ModuleID, std::move(ObjFileMbOrErr.get()));
} else {
auto StreamOrErr = AddStreamFunc(Job.Task, Job.ModuleID);
if (Error Err = StreamOrErr.takeError())
return Err;
auto &Stream = *StreamOrErr->get();
*Stream.OS << ObjFileMbRef.getBuffer();
if (Error Err = Stream.commit())
return Err;
}
}
}
return Error::success();
}