Index: include/llvm/ADT/StringExtras.h =================================================================== --- include/llvm/ADT/StringExtras.h +++ include/llvm/ADT/StringExtras.h @@ -59,6 +59,22 @@ return std::string(BufPtr, std::end(Buffer)); } +// Convert buffer \p Input to its hexadecimal representation. +// The returned string is double the size of \p Input. +static inline std::string toHex(StringRef Input) { + static const char *const LUT = "0123456789ABCDEF"; + size_t Length = Input.size(); + + std::string Output; + Output.reserve(2 * Length); + for (size_t i = 0; i < Length; ++i) { + const unsigned char c = Input[i]; + Output.push_back(LUT[c >> 4]); + Output.push_back(LUT[c & 15]); + } + return Output; +} + static inline std::string utostr(uint64_t X, bool isNeg = false) { char Buffer[21]; char *BufPtr = std::end(Buffer); Index: include/llvm/Support/SHA1.h =================================================================== --- include/llvm/Support/SHA1.h +++ include/llvm/Support/SHA1.h @@ -34,6 +34,11 @@ /// Digest more data. void update(ArrayRef Data); + /// Digest more data. + void update(StringRef Str) { + update(ArrayRef((uint8_t *)Str.data(), Str.size())); + } + /// Return a reference to the current raw 160-bits SHA1 for the digested data /// since the last call to init(). This call will add data to the internal /// state and as such is not suited for getting an intermediate result Index: lib/LTO/ThinLTOCodeGenerator.cpp =================================================================== --- lib/LTO/ThinLTOCodeGenerator.cpp +++ lib/LTO/ThinLTOCodeGenerator.cpp @@ -32,6 +32,10 @@ #include "llvm/MC/SubtargetFeature.h" #include "llvm/Object/ModuleSummaryIndexObjectFile.h" #include "llvm/Support/Debug.h" +#include "llvm/Support/CachePruning.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/SHA1.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/TargetRegistry.h" #include "llvm/Support/ThreadPool.h" @@ -192,7 +196,7 @@ /// Fixup linkage, see ResolveODR() above. void fixupODR( Module &TheModule, - const DenseMap &ResolvedODR) { + const std::map &ResolvedODR) { // Process functions and global now for (auto &GV : TheModule) { auto NewLinkage = ResolvedODR.find(GV.getGUID()); @@ -306,11 +310,102 @@ return make_unique(std::move(OutputBuffer)); } +/// Manage caching for a single Module. +class ModuleCacheEntry { + SmallString<128> EntryPath; + +public: + // Create a cache entry. This compute a unique hash for the Module considering + // the current list of export/import, and offer an interface to query to + // access the content in the cache. + ModuleCacheEntry( + StringRef CachePath, const ModuleSummaryIndex &Index, StringRef ModuleID, + const FunctionImporter::ImportMapTy &ImportList, + const FunctionImporter::ExportSetTy &ExportList, + const std::map &ResolvedODR, + const std::map &DefinedFunctions, + const DenseSet &PreservedSymbols) { + if (CachePath.empty()) + return; + + // Compute the unique hash for this entry + // This is based on the current compiler version, the module itself, the + // export list, the hash for every single module in the import list, the + // list of ResolvedODR for the module, and the list of preserved symbols. + + SHA1 Hasher; + + // Start with the compiler revision + Hasher.update(LLVM_VERSION_STRING); + Hasher.update(LLVM_REVISION); + + + // Include the hash for the current module + auto ModHash = Index.getModuleHash(ModuleID); + Hasher.update(ArrayRef((uint8_t *)&ModHash[0], sizeof(ModHash))); + for (auto F : ExportList) + // The export list can impact the internalization, be conservative here + Hasher.update(ArrayRef((uint8_t *)&F, sizeof(F))); + + // Include the hash for every module we import functions from + for (auto &Entry : ImportList) { + auto ModHash = Index.getModuleHash(Entry.first()); + Hasher.update(ArrayRef((uint8_t *)&ModHash[0], sizeof(ModHash))); + } + + // Include the hash for the resolved ODR. + for (auto &Entry : ResolvedODR) { + Hasher.update(ArrayRef((uint8_t *)&Entry.first, + sizeof(GlobalValue::GUID))); + Hasher.update(ArrayRef((uint8_t *)&Entry.second, + sizeof(GlobalValue::LinkageTypes))); + } + + // Include the hash for the preserved symbols. + for (auto &Entry : PreservedSymbols) { + if (DefinedFunctions.count(Entry)) + Hasher.update( + ArrayRef((uint8_t *)&Entry, sizeof(GlobalValue::GUID))); + } + + sys::path::append(EntryPath, CachePath, toHex(Hasher.result())); + } + + // Try loading the buffer for this cache entry. + ErrorOr> tryLoadingBuffer() { + if (EntryPath.empty()) + return std::error_code(); + return MemoryBuffer::getFile(EntryPath); + } + + // Cache the Produced object file + void write(MemoryBufferRef OutputBuffer) { + if (EntryPath.empty()) + return; + + // Write to a temporary to avoid race condition + SmallString<128> TempFilename; + int TempFD; + std::error_code EC = + sys::fs::createTemporaryFile("Thin", "tmp.o", TempFD, TempFilename); + if (EC) { + errs() << "Error: " << EC.message() << "\n"; + report_fatal_error("ThinLTO: Can't get a temporary file"); + } + { + raw_fd_ostream OS(TempFD, /* ShouldClose */ true); + OS << OutputBuffer.getBuffer(); + } + // Rename to final destination (hopefully race condition won't matter here) + sys::fs::rename(TempFilename, EntryPath); + } +}; + static std::unique_ptr ProcessThinLTOModule( Module &TheModule, const ModuleSummaryIndex &Index, StringMap &ModuleMap, TargetMachine &TM, const FunctionImporter::ImportMapTy &ImportList, - DenseMap &ResolvedODR, + std::map &ResolvedODR, ThinLTOCodeGenerator::CachingOptions CacheOptions, bool DisableCodeGen, StringRef SaveTempsDir, unsigned count) { @@ -459,7 +554,7 @@ // Resolve the LinkOnceODR, trying to turn them into "available_externally" // where possible. // This is a compile-time optimization. - DenseMap ResolvedODR; + std::map ResolvedODR; ResolveODR(Index, ModuleToDefinedGVSummaries[ModuleIdentifier], ModuleIdentifier, ResolvedODR); fixupODR(TheModule, ResolvedODR); @@ -564,19 +659,43 @@ ComputeCrossModuleImport(*Index, ModuleToDefinedGVSummaries, ImportLists, ExportLists); + // Convert the preserved symbols set from string to GUID, this is needed for + // computing the caching. + DenseSet GUIDPreservedSymbols(PreservedSymbols.size()); + for (auto &Entry : PreservedSymbols) + GUIDPreservedSymbols.insert(GlobalValue::getGUID(Entry.first())); + // Parallel optimizer + codegen { ThreadPool Pool(ThreadCount); int count = 0; for (auto &ModuleBuffer : Modules) { Pool.async([&](int count) { - LLVMContext Context; - Context.setDiscardValueNames(LTODiscardValueNames); auto ModuleIdentifier = ModuleBuffer.getBufferIdentifier(); + auto &DefinedFunctions = ModuleToDefinedGVSummaries[ModuleIdentifier]; + + // Resolve ODR, this has to be done early because it impacts the caching + std::map ResolvedODR; + ResolveODR(*Index, DefinedFunctions, ModuleIdentifier, ResolvedODR); + + // The module may be cached, this helps handling it. + ModuleCacheEntry CacheEntry( + CacheOptions.Path, *Index, ModuleBuffer.getBufferIdentifier(), + ImportLists[ModuleBuffer.getBufferIdentifier()], + ExportLists[ModuleBuffer.getBufferIdentifier()], ResolvedODR, + DefinedFunctions, GUIDPreservedSymbols); + + { + auto ErrOrBuffer = CacheEntry.tryLoadingBuffer(); + if (ErrOrBuffer) { + // Cache Hit! + ProducedBinaries[count] = std::move(ErrOrBuffer.get()); + return; + } + } - DenseMap ResolvedODR; - ResolveODR(*Index, ModuleToDefinedGVSummaries[ModuleIdentifier], - ModuleIdentifier, ResolvedODR); + LLVMContext Context; + Context.setDiscardValueNames(LTODiscardValueNames); // Parse module now auto TheModule = loadModuleFromBuffer(ModuleBuffer, Context, false); @@ -587,14 +706,23 @@ } auto &ImportList = ImportLists[ModuleIdentifier]; - ProducedBinaries[count] = ProcessThinLTOModule( + auto OutputBuffer = ProcessThinLTOModule( *TheModule, *Index, ModuleMap, *TMBuilder.create(), ImportList, ResolvedODR, CacheOptions, DisableCodeGen, SaveTempsDir, count); + + CacheEntry.write(*OutputBuffer); + ProducedBinaries[count] = std::move(OutputBuffer); }, count); count++; } } + CachePruning(CacheOptions.Path) + .setPruningInterval(CacheOptions.PruningInterval) + .setEntryExpiration(CacheOptions.Expiration) + .setMaxSize(CacheOptions.MaxPercentageOfAvailableSpace) + .prune(); + // If statistics were requested, print them out now. if (llvm::AreStatisticsEnabled()) llvm::PrintStatistics();