| //===- 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/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> | |
| #include <system_error> | |
| #include <utility> | |
| #include <vector> | |
| using namespace llvm; | |
| // Experimentation showed that serial deletion is most efficient, hence | |
| // a single thread. | |
| lto::DTLTO::BackgroundDeletion::BackgroundDeletion() | |
| : DefaultThreadPool(hardware_concurrency(1)) {} | |
| lto::DTLTO::BackgroundDeletion::~BackgroundDeletion() { waitForTasks(); } | |
| void lto::DTLTO::BackgroundDeletion::waitForTasks() { | |
| wait(); | |
| for (const std::string &Warning : Warnings) | |
| errs() << "warning: could not remove the file " << Warning << "\n"; | |
| Warnings.clear(); | |
| } | |
| void lto::DTLTO::BackgroundDeletion::removeFiles( | |
| std::vector<std::string> &&Files, const Config &Conf) { | |
| if (Files.empty()) | |
| return; | |
| async([this, Files = std::move(Files), TTE = Conf.TimeTraceEnabled, | |
| TTG = Conf.TimeTraceGranularity] { | |
| if (LLVM_ENABLE_THREADS && TTE) | |
| timeTraceProfilerInitialize(TTG, "Remove DTLTO temporary files"); | |
| { | |
| TimeTraceScope TimeScope("Remove DTLTO temporary files"); | |
| for (const std::string &Path : Files) { | |
| std::error_code EC = sys::fs::remove(Path, true); | |
| if (!EC || | |
| EC == std::make_error_code(std::errc::no_such_file_or_directory)) | |
| continue; | |
| Warnings.emplace_back("'" + Path + "': " + EC.message()); | |
| } | |
| } | |
| if (LLVM_ENABLE_THREADS && TTE) | |
| timeTraceProfilerFinishThread(); | |
| }); | |
| } | |
| void lto::DTLTO::waitForCleanup() { BackgroundDeleter.waitForTasks(); } | |
| // Remove temporary files created to enable distribution. | |
| void lto::DTLTO::cleanup() { | |
| if (SaveTemps) | |
| return; | |
| BackgroundDeleter.removeFiles(std::move(CleanupList), Conf); | |
| } | |
| // 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 = extractLTOInputs()) | |
| 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(); | |
| TimeTraceScope TimeScope("Check cache for DTLTO", J.SummaryIndexPath); | |
| 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) { | |
| InputModuleIDsToExtract.insert(J.ModuleID); | |
| for (StringRef ImportPath : J.ImportsFilesList) | |
| InputModuleIDsToExtract.insert(ImportPath); | |
| 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.PTO.LoopInterchange) | |
| Ops.push_back("-floop-interchange"); | |
| 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; | |
| InputModuleIDsToExtract.clear(); | |
| 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())); | |
| auto &CacheStream = *(CachedFileStreamOrErr->get()); | |
| // This object file will be renamed into cache entry file. The file | |
| // memory buffer will be added to lld list of object files. | |
| if (Error Err = CacheStream.commit(std::move(ObjFileMbOrErr.get()))) | |
| 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(); | |
| } |