//===- 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 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 { return std::make_unique(SummaryIndexFiles[task]); }; Cfg.GetCacheKeyOutputString = [&](size_t task) -> std::string & { return CacheKeysList[task]; }; Cfg.GetImportsListOutputArray = [&](size_t task) -> std::vector & { 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=. 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( 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(); }