//===----------------- ModulesBuilder.cpp ------------------------*- C++-*-===// // // 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 // //===----------------------------------------------------------------------===// #include "ModulesBuilder.h" #include "Compiler.h" #include "SourceCode.h" #include "support/Logger.h" #include "clang/Frontend/FrontendAction.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Serialization/ASTReader.h" #include "clang/Serialization/ModuleCache.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/LockFileManager.h" #include "llvm/Support/Path.h" #include "llvm/Support/Process.h" #include #include namespace clang { namespace clangd { namespace { llvm::cl::opt DebugModulesBuilder( "debug-modules-builder", llvm::cl::desc("Don't remove clangd's built module files for debugging. " "Remember to remove them later after debugging."), llvm::cl::init(false)); llvm::cl::opt VersionedModuleFileGCThresholdSeconds( "modules-builder-versioned-gc-threshold-seconds", llvm::cl::desc("Delete versioned copy-on-read module files whose last " "access time is older than this many seconds."), llvm::cl::init(3 * 24 * 60 * 60)); //===----------------------------------------------------------------------===// // Persistent Module Cache Layout. // // clangd publishes prerequisite BMIs into a stable on-disk cache so later // builders can reuse them across sessions. Cache entries are grouped by a // readable module-unit source directory name plus a hash of the normalized // source path, and are further separated by a hash of the full compile // command, which keeps incompatible BMI variants apart. // // module-unit source // | // v // cache root // | // +-- - // | // +-- // | // +-- [-].pcm //===----------------------------------------------------------------------===// std::string hashStringForCache(llvm::StringRef Content) { return llvm::toHex(digest(Content)); } std::string normalizePathForCache(PathRef Path) { llvm::SmallString<256> Normalized(Path); llvm::sys::path::remove_dots(Normalized, /*remove_dot_dot=*/true); return maybeCaseFoldPath(Normalized); } /// Returns the root directory used for persistent module cache storage. /// Prefer a project-local cache so different clangd sessions working on the /// same source tree can reuse BMIs. Fall back to the user cache directory, and /// finally to a non-ephemeral temp directory when no better cache root exists. llvm::SmallString<256> getModuleCacheRoot(PathRef ModuleUnitFileName, const GlobalCompilationDatabase &CDB) { llvm::SmallString<256> Result; if (auto PI = CDB.getProjectInfo(ModuleUnitFileName); PI && !PI->SourceRoot.empty()) { Result = PI->SourceRoot; llvm::sys::path::append(Result, ".cache", "clangd", "modules"); return Result; } if (llvm::sys::path::cache_directory(Result)) { llvm::sys::path::append(Result, "clangd", "modules"); return Result; } llvm::sys::path::system_temp_directory(/*erasedOnReboot=*/false, Result); llvm::sys::path::append(Result, "clangd", "modules"); return Result; } /// Returns the directory holding source-scoped lock files for the persistent /// module cache. Placing locks beside the cache ensures all builders sharing /// the cache also synchronize through the same lock namespace. llvm::SmallString<256> getModuleCacheLocksDirectory(PathRef ModuleUnitFileName, const GlobalCompilationDatabase &CDB) { llvm::SmallString<256> Result = getModuleCacheRoot(ModuleUnitFileName, CDB); llvm::sys::path::append(Result, ".locks"); return Result; } std::string getModuleUnitSourcePathHash(PathRef ModuleUnitFileName) { return hashStringForCache(normalizePathForCache(ModuleUnitFileName)); } std::string getModuleUnitSourceDirectoryName(PathRef ModuleUnitFileName) { std::string Result = llvm::sys::path::filename(ModuleUnitFileName).str(); Result.push_back('-'); Result.append(getModuleUnitSourcePathHash(ModuleUnitFileName)); return Result; } std::string getCompileCommandStringHash(const tooling::CompileCommand &Cmd) { std::string SerializedCommand; SerializedCommand.reserve(Cmd.Directory.size() + Cmd.Filename.size() + Cmd.CommandLine.size() * 16); // The module-unit source path is already encoded in the parent cache // directory. Output is rewritten while staging the BMI, so hash only the // semantic compile command to keep the cache key stable across rebuilds. SerializedCommand.append(Cmd.Directory); SerializedCommand.push_back('\0'); for (const auto &Arg : Cmd.CommandLine) { SerializedCommand.append(Arg); SerializedCommand.push_back('\0'); } return hashStringForCache(SerializedCommand); } /// Returns the directory for a persistent BMI built from a specific module /// unit source and compile command. The directory name keeps a readable source /// basename alongside the source-hash, and the command-hash keeps incompatible /// command lines apart. llvm::SmallString<256> getModuleFilesDirectory(PathRef ModuleUnitFileName, const tooling::CompileCommand &Cmd, const GlobalCompilationDatabase &CDB) { llvm::SmallString<256> Result = getModuleCacheRoot(ModuleUnitFileName, CDB); llvm::sys::path::append(Result, getModuleUnitSourceDirectoryName(ModuleUnitFileName), getCompileCommandStringHash(Cmd)); return Result; } /// Returns the lock file path guarding publication of BMIs for a module unit /// source. Builders targeting the same source-hash serialize through this path. llvm::SmallString<256> getModuleSourceHashLockPath(PathRef ModuleUnitFileName, const GlobalCompilationDatabase &CDB) { llvm::SmallString<256> Result = getModuleCacheLocksDirectory(ModuleUnitFileName, CDB); llvm::sys::path::append(Result, getModuleUnitSourcePathHash(ModuleUnitFileName)); return Result; } /// Returns a unique temporary path used to stage a BMI before atomically /// publishing it to the stable cache path. llvm::SmallString<256> getTemporaryModuleFilePath(PathRef ModuleFilePath) { llvm::SmallString<256> ResultPattern(ModuleFilePath); ResultPattern.append(".tmp-%%-%%-%%-%%-%%-%%"); llvm::SmallString<256> Result; llvm::sys::fs::createUniquePath(ResultPattern, Result, /*MakeAbsolute=*/false); return Result; } std::string getModuleFileVersionTimestamp() { const auto Now = std::chrono::system_clock::now(); const auto Micros = std::chrono::duration_cast( Now.time_since_epoch()) % std::chrono::seconds(1); const std::time_t CalendarTime = std::chrono::system_clock::to_time_t(Now); std::tm LocalTime; #ifdef _WIN32 localtime_s(&LocalTime, &CalendarTime); #else localtime_r(&CalendarTime, &LocalTime); #endif return llvm::formatv("{0:04}{1:02}{2:02}-{3:02}{4:02}{5:02}-{6:06}", LocalTime.tm_year + 1900, LocalTime.tm_mon + 1, LocalTime.tm_mday, LocalTime.tm_hour, LocalTime.tm_min, LocalTime.tm_sec, Micros.count()) .str(); } llvm::SmallString<256> getCopyOnReadModuleFilePath(PathRef PublishedModuleFile) { llvm::SmallString<256> Result(PublishedModuleFile); llvm::sys::path::remove_filename(Result); llvm::sys::path::append( Result, llvm::formatv("{0}-{1}{2}", llvm::sys::path::stem(PublishedModuleFile), getModuleFileVersionTimestamp(), llvm::sys::path::extension(PublishedModuleFile)) .str()); return Result; } /// Ensures the lock anchor file exists before LockFileManager tries to acquire /// ownership, creating parent directories as needed. llvm::Error ensureLockAnchorFileExists(PathRef LockPath) { llvm::SmallString<256> LockParent(LockPath); llvm::sys::path::remove_filename(LockParent); if (std::error_code EC = llvm::sys::fs::create_directories(LockParent)) return llvm::createStringError(llvm::formatv( "Failed to create lock directory {0}: {1}", LockParent, EC.message())); int FD = -1; if (std::error_code EC = llvm::sys::fs::openFileForWrite( LockPath, FD, llvm::sys::fs::CD_OpenAlways)) return llvm::createStringError(llvm::formatv( "Failed to open lock file anchor {0}: {1}", LockPath, EC.message())); llvm::sys::Process::SafelyCloseFileDescriptor(FD); return llvm::Error::success(); } //===----------------------------------------------------------------------===// // Persistent Module Cache Locking. // // Builders targeting the same module-unit source share a source-hash lock. // This serializes in-place replacement of stale cache entries and final publish // of the stable BMI path, while still allowing unrelated module sources to be // built concurrently. // // builder A builder B // | | // +---- lock(source) ----->| // | | // | build/publish BMI | wait // | | // +---- unlock ----------->| // | reuse or rebuild //===----------------------------------------------------------------------===// /// Serializes publication and in-place replacement of persistent BMIs for a /// single module-unit source across multiple builders. class ScopedModuleSourceLock { public: static llvm::Expected acquire(PathRef ModuleUnitFileName, const GlobalCompilationDatabase &CDB) { constexpr auto LockWaitInterval = std::chrono::seconds(10); llvm::SmallString<256> LockPath = getModuleSourceHashLockPath(ModuleUnitFileName, CDB); if (llvm::Error Err = ensureLockAnchorFileExists(LockPath)) return std::move(Err); auto Waited = std::chrono::seconds::zero(); while (true) { auto Lock = std::make_unique(LockPath); auto TryLock = Lock->tryLock(); if (!TryLock) return TryLock.takeError(); if (*TryLock) return ScopedModuleSourceLock(std::move(Lock)); switch (Lock->waitForUnlockFor(LockWaitInterval)) { case llvm::WaitForUnlockResult::Success: case llvm::WaitForUnlockResult::OwnerDied: continue; case llvm::WaitForUnlockResult::Timeout: Waited += LockWaitInterval; log("Still waiting for module lock {0} after {1}s", LockPath, Waited.count()); continue; } llvm_unreachable("Unhandled lock wait result"); } } private: explicit ScopedModuleSourceLock(std::unique_ptr Lock) : Lock(std::move(Lock)) {} std::unique_ptr Lock; }; // Get the stable published module file path under \param ModuleFilesPrefix. std::string getModuleFilePath(llvm::StringRef ModuleName, PathRef ModuleFilesPrefix) { llvm::SmallString<256> ModuleFilePath(ModuleFilesPrefix); auto [PrimaryModuleName, PartitionName] = ModuleName.split(':'); llvm::sys::path::append(ModuleFilePath, PrimaryModuleName); if (!PartitionName.empty()) { ModuleFilePath.append("-"); ModuleFilePath.append(PartitionName); } ModuleFilePath.append(".pcm"); return std::string(ModuleFilePath); } std::string getPublishedModuleFilePath(llvm::StringRef ModuleName, PathRef ModuleFilesPrefix) { return getModuleFilePath(ModuleName, ModuleFilesPrefix); } // FailedPrerequisiteModules - stands for the PrerequisiteModules which has // errors happened during the building process. class FailedPrerequisiteModules : public PrerequisiteModules { public: ~FailedPrerequisiteModules() override = default; // We shouldn't adjust the compilation commands based on // FailedPrerequisiteModules. void adjustHeaderSearchOptions(HeaderSearchOptions &Options) const override {} // FailedPrerequisiteModules can never be reused. bool canReuse(const CompilerInvocation &CI, llvm::IntrusiveRefCntPtr) const override { return false; } }; /// Represents a reference to a module file (*.pcm). class ModuleFile { protected: ModuleFile(StringRef ModuleName, PathRef ModuleFilePath) : ModuleName(ModuleName.str()), ModuleFilePath(ModuleFilePath.str()) {} public: ModuleFile() = delete; ModuleFile(const ModuleFile &) = delete; ModuleFile operator=(const ModuleFile &) = delete; // The move constructor is needed for llvm::SmallVector. ModuleFile(ModuleFile &&Other) : ModuleName(std::move(Other.ModuleName)), ModuleFilePath(std::move(Other.ModuleFilePath)) { Other.ModuleName.clear(); Other.ModuleFilePath.clear(); } ModuleFile &operator=(ModuleFile &&Other) { if (this == &Other) return *this; this->~ModuleFile(); new (this) ModuleFile(std::move(Other)); return *this; } virtual ~ModuleFile() = default; StringRef getModuleName() const { return ModuleName; } StringRef getModuleFilePath() const { return ModuleFilePath; } protected: std::string ModuleName; std::string ModuleFilePath; }; /// Represents a prebuilt module file which is not owned by us. class PrebuiltModuleFile : public ModuleFile { private: // private class to make sure the class can only be constructed by member // functions. struct CtorTag {}; public: PrebuiltModuleFile(StringRef ModuleName, PathRef ModuleFilePath, CtorTag) : ModuleFile(ModuleName, ModuleFilePath) {} static std::shared_ptr make(StringRef ModuleName, PathRef ModuleFilePath) { return std::make_shared(ModuleName, ModuleFilePath, CtorTag{}); } }; //===----------------------------------------------------------------------===// // Module File Ownership and Reuse. // // PrebuiltModuleFile refers to BMIs supplied directly by the compile command. // BuiltModuleFile refers to BMIs produced by clangd and published into the // persistent cache. Object lifetime does not control filesystem lifetime for // BuiltModuleFile; cache files remain on disk for reuse across builders. The // versioned copies handed to clang for actual reads are owned by // CopyOnReadModuleFile and are deleted when the last reader releases them. // // Copy-on-read keeps the published BMI path stable for future builders while // avoiding in-place replacement races for active readers. clangd never hands // the stable cache entry directly to parsing code. Instead, once a published // BMI is known to be up to date, clangd copies it to a versioned sibling path // and gives that copy to readers. Rebuilding only mutates the stable cache // entry; existing readers keep their own immutable copy until the last // shared_ptr reference drops and the copy-on-read file is deleted. // // compile command ---------> PrebuiltModuleFile // // clangd build -> publish -> BuiltModuleFile ------------> stable cache path // (M.pcm) // | // +-> copy for read -> CopyOnReadModuleFile // (M-.pcm) // -> handed to clang readers // -> removed on last release // // later builder -----------> reuse stable cache path ----> copy for read //===----------------------------------------------------------------------===// /// Represents a module file built and published by clangd into its persistent /// cache. class BuiltModuleFile final : public ModuleFile { private: // private class to make sure the class can only be constructed by member // functions. struct CtorTag {}; public: BuiltModuleFile(StringRef ModuleName, PathRef ModuleFilePath, CtorTag) : ModuleFile(ModuleName, ModuleFilePath) {} static std::shared_ptr make(StringRef ModuleName, PathRef ModuleFilePath) { return std::make_shared(ModuleName, ModuleFilePath, CtorTag{}); } }; /// Represents a versioned copy of a published BMI handed to clangd readers. /// The copy is removed when the last reader releases it. class CopyOnReadModuleFile final : public ModuleFile { private: struct CtorTag {}; public: CopyOnReadModuleFile(StringRef ModuleName, PathRef ModuleFilePath, CtorTag) : ModuleFile(ModuleName, ModuleFilePath) {} ~CopyOnReadModuleFile() override { if (!ModuleFilePath.empty() && !DebugModulesBuilder) if (std::error_code EC = llvm::sys::fs::remove(ModuleFilePath)) vlog("Failed to remove copy-on-read module file {0}: {1}", ModuleFilePath, EC.message()); } static std::shared_ptr make(StringRef ModuleName, PathRef ModuleFilePath) { return std::make_shared(ModuleName, ModuleFilePath, CtorTag{}); } }; // ReusablePrerequisiteModules - stands for PrerequisiteModules for which all // the required modules are built successfully. All the module files // are owned by the modules builder. class ReusablePrerequisiteModules : public PrerequisiteModules { public: ReusablePrerequisiteModules() = default; ReusablePrerequisiteModules(const ReusablePrerequisiteModules &Other) = default; ReusablePrerequisiteModules & operator=(const ReusablePrerequisiteModules &) = default; ReusablePrerequisiteModules(ReusablePrerequisiteModules &&) = delete; ReusablePrerequisiteModules operator=(ReusablePrerequisiteModules &&) = delete; ~ReusablePrerequisiteModules() override = default; void adjustHeaderSearchOptions(HeaderSearchOptions &Options) const override { // Appending all built module files. for (const auto &RequiredModule : RequiredModules) Options.PrebuiltModuleFiles.insert_or_assign( RequiredModule->getModuleName().str(), RequiredModule->getModuleFilePath().str()); } std::string getAsString() const { std::string Result; llvm::raw_string_ostream OS(Result); for (const auto &MF : RequiredModules) { OS << "-fmodule-file=" << MF->getModuleName() << "=" << MF->getModuleFilePath() << " "; } return Result; } bool canReuse(const CompilerInvocation &CI, llvm::IntrusiveRefCntPtr) const override; bool isModuleUnitBuilt(llvm::StringRef ModuleName) const { return BuiltModuleNames.contains(ModuleName); } void addModuleFile(std::shared_ptr MF) { BuiltModuleNames.insert(MF->getModuleName()); RequiredModules.emplace_back(std::move(MF)); } private: llvm::SmallVector, 8> RequiredModules; // A helper class to speedup the query if a module is built. llvm::StringSet<> BuiltModuleNames; }; bool IsModuleFileUpToDate(PathRef ModuleFilePath, const PrerequisiteModules &RequisiteModules, llvm::IntrusiveRefCntPtr VFS) { HeaderSearchOptions HSOpts; RequisiteModules.adjustHeaderSearchOptions(HSOpts); HSOpts.ForceCheckCXX20ModulesInputFiles = true; HSOpts.ValidateASTInputFilesContent = true; clang::clangd::IgnoreDiagnostics IgnoreDiags; DiagnosticOptions DiagOpts; IntrusiveRefCntPtr Diags = CompilerInstance::createDiagnostics(*VFS, DiagOpts, &IgnoreDiags, /*ShouldOwnClient=*/false); LangOptions LangOpts; LangOpts.SkipODRCheckInGMF = true; FileManager FileMgr(FileSystemOptions(), VFS); SourceManager SourceMgr(*Diags, FileMgr); HeaderSearch HeaderInfo(HSOpts, SourceMgr, *Diags, LangOpts, /*Target=*/nullptr); PreprocessorOptions PPOpts; TrivialModuleLoader ModuleLoader; Preprocessor PP(PPOpts, *Diags, LangOpts, SourceMgr, HeaderInfo, ModuleLoader); std::shared_ptr ModCache = createCrossProcessModuleCache(); PCHContainerOperations PCHOperations; CodeGenOptions CodeGenOpts; ASTReader Reader( PP, *ModCache, /*ASTContext=*/nullptr, PCHOperations.getRawReader(), CodeGenOpts, {}, /*isysroot=*/"", /*DisableValidationKind=*/DisableValidationForModuleKind::None, /*AllowASTWithCompilerErrors=*/false, /*AllowConfigurationMismatch=*/false, /*ValidateSystemInputs=*/false, /*ForceValidateUserInputs=*/true, /*ValidateASTInputFilesContent=*/true); // We don't need any listener here. By default it will use a validator // listener. Reader.setListener(nullptr); // Use ARR_OutOfDate so that ReadAST returns OutOfDate instead of Failure // when input files are modified. This allows us to detect staleness // without treating it as a hard error. // ReadAST will validate all input files internally and return OutOfDate // if any file is modified. return Reader.ReadAST(ModuleFileName::makeExplicit(ModuleFilePath), serialization::MK_MainFile, SourceLocation(), ASTReader::ARR_OutOfDate) == ASTReader::Success; } bool IsModuleFilesUpToDate( llvm::SmallVector ModuleFilePaths, const PrerequisiteModules &RequisiteModules, llvm::IntrusiveRefCntPtr VFS) { return llvm::all_of( ModuleFilePaths, [&RequisiteModules, VFS](auto ModuleFilePath) { return IsModuleFileUpToDate(ModuleFilePath, RequisiteModules, VFS); }); } /// Builds a BMI into a temporary file and publishes it to `ModuleFilePath`. /// If another builder wins the publish race first, reports that through /// `PublishedExistingModuleFile` so the caller can validate and reuse it. llvm::Expected> buildModuleFile(llvm::StringRef ModuleName, PathRef ModuleUnitFileName, tooling::CompileCommand Cmd, PathRef ModuleFilePath, const ThreadsafeFS &TFS, const ReusablePrerequisiteModules &BuiltModuleFiles, bool &PublishedExistingModuleFile) { PublishedExistingModuleFile = false; llvm::SmallString<256> ModuleFilesPrefix(ModuleFilePath); llvm::sys::path::remove_filename(ModuleFilesPrefix); if (std::error_code EC = llvm::sys::fs::create_directories(ModuleFilesPrefix)) return llvm::createStringError( llvm::formatv("Failed to create module cache directory {0}: {1}", ModuleFilesPrefix, EC.message())); llvm::SmallString<256> TemporaryModuleFilePath = getTemporaryModuleFilePath(ModuleFilePath); auto RemoveTemporaryModuleFile = llvm::scope_exit([&] { if (!TemporaryModuleFilePath.empty() && !DebugModulesBuilder) llvm::sys::fs::remove(TemporaryModuleFilePath); }); (void)RemoveTemporaryModuleFile; Cmd.Output = TemporaryModuleFilePath.str().str(); ParseInputs Inputs; Inputs.TFS = &TFS; Inputs.CompileCommand = std::move(Cmd); IgnoreDiagnostics IgnoreDiags; auto CI = buildCompilerInvocation(Inputs, IgnoreDiags); if (!CI) return llvm::createStringError("Failed to build compiler invocation"); auto FS = Inputs.TFS->view(Inputs.CompileCommand.Directory); auto Buf = FS->getBufferForFile(Inputs.CompileCommand.Filename); if (!Buf) return llvm::createStringError("Failed to create buffer"); // In clang's driver, we will suppress the check for ODR violation in GMF. // See the implementation of RenderModulesOptions in Clang.cpp. CI->getLangOpts().SkipODRCheckInGMF = true; // Hash the contents of input files and store the hash value to the BMI files. // So that we can check if the files are still valid when we want to reuse the // BMI files. CI->getHeaderSearchOpts().ValidateASTInputFilesContent = true; BuiltModuleFiles.adjustHeaderSearchOptions(CI->getHeaderSearchOpts()); CI->getFrontendOpts().OutputFile = Inputs.CompileCommand.Output; auto Clang = prepareCompilerInstance(std::move(CI), /*Preamble=*/nullptr, std::move(*Buf), std::move(FS), IgnoreDiags); if (!Clang) return llvm::createStringError("Failed to prepare compiler instance"); GenerateReducedModuleInterfaceAction Action; Clang->ExecuteAction(Action); if (Clang->getDiagnostics().hasErrorOccurred()) { std::string Cmds; for (const auto &Arg : Inputs.CompileCommand.CommandLine) { if (!Cmds.empty()) Cmds += " "; Cmds += Arg; } clangd::vlog("Failed to compile {0} with command: {1}", ModuleUnitFileName, Cmds); std::string BuiltModuleFilesStr = BuiltModuleFiles.getAsString(); if (!BuiltModuleFilesStr.empty()) clangd::vlog("The actual used module files built by clangd is {0}", BuiltModuleFilesStr); return llvm::createStringError( llvm::formatv("Failed to compile {0}. Use '--log=verbose' to view " "detailed failure reasons. It is helpful to use " "'--debug-modules-builder' flag to keep the clangd's " "built module files to reproduce the failure for " "debugging. Remember to remove them after debugging.", ModuleUnitFileName)); } if (std::error_code EC = llvm::sys::fs::rename(TemporaryModuleFilePath, ModuleFilePath)) { if (!llvm::sys::fs::exists(ModuleFilePath)) return llvm::createStringError( llvm::formatv("Failed to publish module file {0}: {1}", ModuleFilePath, EC.message())); // Another builder already published the stable cache entry. Drop our // staged BMI and let the caller revalidate the published path. PublishedExistingModuleFile = true; } else { // Rename consumed the staging file into the stable cache path. Clear it so // the scope-exit cleanup does not try to remove the published BMI. TemporaryModuleFilePath.clear(); } return BuiltModuleFile::make(ModuleName, ModuleFilePath); } llvm::Expected> copyModuleFileForRead(llvm::StringRef ModuleName, PathRef PublishedModuleFilePath) { llvm::SmallString<256> VersionedModuleFilePath = getCopyOnReadModuleFilePath(PublishedModuleFilePath); if (std::error_code EC = llvm::sys::fs::copy_file(PublishedModuleFilePath, VersionedModuleFilePath)) return llvm::createStringError(llvm::formatv( "Failed to copy module file {0} to {1}: {2}", PublishedModuleFilePath, VersionedModuleFilePath, EC.message())); return CopyOnReadModuleFile::make(ModuleName, VersionedModuleFilePath); } bool ReusablePrerequisiteModules::canReuse( const CompilerInvocation &CI, llvm::IntrusiveRefCntPtr VFS) const { if (RequiredModules.empty()) return true; llvm::SmallVector BMIPaths; for (auto &MF : RequiredModules) BMIPaths.push_back(MF->getModuleFilePath()); return IsModuleFilesUpToDate(BMIPaths, *this, VFS); } //===----------------------------------------------------------------------===// // In-Memory Module File Cache. // // This cache deduplicates BMIs within a single builder instance. Its key // mirrors the persistent cache layout: module name, module-unit source, and // compile command hash. That prevents a builder from reusing a BMI built under // a different command line. // // (module name, // module-unit source, // command hash) // | // v // ModuleFileCache // | // +-- hit -> reuse in current builder // | // +-- miss -> probe persistent cache / rebuild //===----------------------------------------------------------------------===// /// In-memory cache for module files built by clangd. Entries are keyed by /// module name, module-unit source, and compile-command hash so persistent BMI /// variants do not collide. class ModuleFileCache { public: ModuleFileCache(const GlobalCompilationDatabase &CDB) : CDB(CDB) {} const GlobalCompilationDatabase &getCDB() const { return CDB; } std::shared_ptr getModule(StringRef ModuleName, PathRef ModuleUnitSource, llvm::StringRef CommandHash); void add(StringRef ModuleName, PathRef ModuleUnitSource, llvm::StringRef CommandHash, std::shared_ptr ModuleFile) { std::lock_guard Lock(ModuleFilesMutex); ModuleFiles[cacheKey(ModuleName, ModuleUnitSource, CommandHash)] = ModuleFile; } void remove(StringRef ModuleName, PathRef ModuleUnitSource, llvm::StringRef CommandHash); private: static std::string cacheKey(StringRef ModuleName, PathRef ModuleUnitSource, llvm::StringRef CommandHash) { std::string Key; Key.reserve(ModuleName.size() + ModuleUnitSource.size() + CommandHash.size() + 2); Key.append(ModuleName); Key.push_back('\0'); Key.append(maybeCaseFoldPath(ModuleUnitSource)); Key.push_back('\0'); Key.append(CommandHash); return Key; } const GlobalCompilationDatabase &CDB; llvm::StringMap> ModuleFiles; std::mutex ModuleFilesMutex; }; std::shared_ptr ModuleFileCache::getModule(StringRef ModuleName, PathRef ModuleUnitSource, llvm::StringRef CommandHash) { std::lock_guard Lock(ModuleFilesMutex); auto Iter = ModuleFiles.find(cacheKey(ModuleName, ModuleUnitSource, CommandHash)); if (Iter == ModuleFiles.end()) return nullptr; if (auto Res = Iter->second.lock()) return Res; ModuleFiles.erase(Iter); return nullptr; } void ModuleFileCache::remove(StringRef ModuleName, PathRef ModuleUnitSource, llvm::StringRef CommandHash) { std::lock_guard Lock(ModuleFilesMutex); ModuleFiles.erase(cacheKey(ModuleName, ModuleUnitSource, CommandHash)); } class ModuleNameToSourceCache { public: std::string getUniqueSourceForModuleName(llvm::StringRef ModuleName) { std::lock_guard Lock(CacheMutex); auto Iter = ModuleNameToUniqueSourceCache.find(ModuleName); if (Iter != ModuleNameToUniqueSourceCache.end()) return Iter->second; return ""; } void addUniqueEntry(llvm::StringRef ModuleName, PathRef Source) { std::lock_guard Lock(CacheMutex); ModuleNameToUniqueSourceCache[ModuleName] = Source.str(); } void eraseUniqueEntry(llvm::StringRef ModuleName) { std::lock_guard Lock(CacheMutex); ModuleNameToUniqueSourceCache.erase(ModuleName); } std::string getMultipleSourceForModuleName(llvm::StringRef ModuleName, PathRef RequiredSrcFile) { std::lock_guard Lock(CacheMutex); auto Outer = ModuleNameToMultipleSourceCache.find(ModuleName); if (Outer == ModuleNameToMultipleSourceCache.end()) return ""; auto Inner = Outer->second.find(maybeCaseFoldPath(RequiredSrcFile)); if (Inner == Outer->second.end()) return ""; return Inner->second; } void addMultipleEntry(llvm::StringRef ModuleName, PathRef RequiredSrcFile, PathRef Source) { std::lock_guard Lock(CacheMutex); ModuleNameToMultipleSourceCache[ModuleName] [maybeCaseFoldPath(RequiredSrcFile)] = Source.str(); } void eraseMultipleEntry(llvm::StringRef ModuleName, PathRef RequiredSrcFile) { std::lock_guard Lock(CacheMutex); auto Outer = ModuleNameToMultipleSourceCache.find(ModuleName); if (Outer == ModuleNameToMultipleSourceCache.end()) return; Outer->second.erase(maybeCaseFoldPath(RequiredSrcFile)); if (Outer->second.empty()) ModuleNameToMultipleSourceCache.erase(Outer); } private: std::mutex CacheMutex; llvm::StringMap ModuleNameToUniqueSourceCache; // Map from module name to a map from required source to module unit source // which declares the corresponding module name. // This looks inefficiency. We can only assume there won't too many duplicated // module names with different module units in a project. llvm::StringMap> ModuleNameToMultipleSourceCache; }; class CachingProjectModules : public ProjectModules { public: CachingProjectModules(std::unique_ptr MDB, ModuleNameToSourceCache &Cache) : MDB(std::move(MDB)), Cache(Cache) { assert(this->MDB && "CachingProjectModules should only be created with a " "valid underlying ProjectModules"); } std::vector getRequiredModules(PathRef File) override { return MDB->getRequiredModules(File); } std::string getModuleNameForSource(PathRef File) override { return MDB->getModuleNameForSource(File); } ModuleNameState getModuleNameState(llvm::StringRef ModuleName) override { return MDB->getModuleNameState(ModuleName); } std::string getSourceForModuleName(llvm::StringRef ModuleName, PathRef RequiredSrcFile) override { auto ModuleState = MDB->getModuleNameState(ModuleName); if (ModuleState == ModuleNameState::Multiple) { std::string CachedResult = Cache.getMultipleSourceForModuleName(ModuleName, RequiredSrcFile); // Verify Cached Result by seeing if the source declaring the same module // as we query. if (!CachedResult.empty()) { std::string ModuleNameOfCachedSource = MDB->getModuleNameForSource(CachedResult); if (ModuleNameOfCachedSource == ModuleName) return CachedResult; // Cached Result is invalid. Clear it. Cache.eraseMultipleEntry(ModuleName, RequiredSrcFile); } auto Result = MDB->getSourceForModuleName(ModuleName, RequiredSrcFile); if (!Result.empty()) Cache.addMultipleEntry(ModuleName, RequiredSrcFile, Result); return Result; } // For unknown module name state, assume it is unique. This may give user // higher usability. assert(ModuleState == ModuleNameState::Unique || ModuleState == ModuleNameState::Unknown); std::string CachedResult = Cache.getUniqueSourceForModuleName(ModuleName); // Verify Cached Result by seeing if the source declaring the same module // as we query. if (!CachedResult.empty()) { std::string ModuleNameOfCachedSource = MDB->getModuleNameForSource(CachedResult); if (ModuleNameOfCachedSource == ModuleName) return CachedResult; // Cached Result is invalid. Clear it. Cache.eraseUniqueEntry(ModuleName); } auto Result = MDB->getSourceForModuleName(ModuleName, RequiredSrcFile); if (!Result.empty()) Cache.addUniqueEntry(ModuleName, Result); return Result; } private: std::unique_ptr MDB; ModuleNameToSourceCache &Cache; }; /// Collect the directly and indirectly required module names for \param /// ModuleName in topological order. The \param ModuleName is guaranteed to /// be the last element in \param ModuleNames. llvm::SmallVector getAllRequiredModules(PathRef RequiredSource, CachingProjectModules &MDB, StringRef ModuleName) { llvm::SmallVector ModuleNames; llvm::StringSet<> ModuleNamesSet; auto VisitDeps = [&](StringRef ModuleName, auto Visitor) -> void { ModuleNamesSet.insert(ModuleName); for (StringRef RequiredModuleName : MDB.getRequiredModules( MDB.getSourceForModuleName(ModuleName, RequiredSource))) if (ModuleNamesSet.insert(RequiredModuleName).second) Visitor(RequiredModuleName, Visitor); ModuleNames.push_back(ModuleName.str()); }; VisitDeps(ModuleName, VisitDeps); return ModuleNames; } /// Collects cache roots to scan during constructor-time GC. /// Scans one cache root and returns all `.pcm` files under it. std::vector collectModuleFiles(PathRef CacheRoot) { std::vector Result; std::error_code EC; for (llvm::sys::fs::recursive_directory_iterator It(CacheRoot, EC), End; It != End && !EC; It.increment(EC)) { if (llvm::sys::path::extension(It->path()) != ".pcm") continue; Result.push_back(It->path()); } if (EC) log("Failed to scan module cache directory {0}: {1}", CacheRoot, EC.message()); return Result; } /// Performs one GC pass over a persistent module cache root. void garbageCollectModuleCache(PathRef CacheRoot) { for (const auto &ModuleFilePath : collectModuleFiles(CacheRoot)) { llvm::sys::fs::file_status Status; if (std::error_code EC = llvm::sys::fs::status(ModuleFilePath, Status)) { log("Failed to stat cached module file {0} for GC: {1}", ModuleFilePath, EC.message()); continue; } llvm::sys::TimePoint<> LastAccess = Status.getLastAccessedTime(); llvm::sys::TimePoint<> Now = std::chrono::system_clock::now(); if (LastAccess > Now) continue; auto Age = std::chrono::duration_cast(Now - LastAccess); auto Threshold = std::chrono::seconds(VersionedModuleFileGCThresholdSeconds); if (Age <= Threshold) continue; if (!llvm::sys::fs::exists(ModuleFilePath)) continue; constexpr llvm::StringLiteral Reason = "file older than GC threshold"; if (std::error_code EC = llvm::sys::fs::remove(ModuleFilePath)) { log("Failed to remove cached module file {0} ({1}): {2}", ModuleFilePath, Reason, EC.message()); continue; } log("Removed cached module file {0} ({1})", ModuleFilePath, Reason); } } } // namespace class ModulesBuilder::ModulesBuilderImpl { public: ModulesBuilderImpl(const GlobalCompilationDatabase &CDB) : Cache(CDB) {} ModuleNameToSourceCache &getProjectModulesCache() { return ProjectModulesCache; } const GlobalCompilationDatabase &getCDB() const { return Cache.getCDB(); } llvm::Error getOrBuildModuleFile(PathRef RequiredSource, StringRef ModuleName, const ThreadsafeFS &TFS, CachingProjectModules &MDB, ReusablePrerequisiteModules &BuiltModuleFiles); private: /// Try to get prebuilt module files from the compilation database. void getPrebuiltModuleFile(StringRef ModuleName, PathRef ModuleUnitFileName, const ThreadsafeFS &TFS, ReusablePrerequisiteModules &BuiltModuleFiles); /// Runs GC once for the cache root owning a project root. void garbageCollectModuleCacheForProjectRoot(PathRef ProjectRoot); ModuleFileCache Cache; ModuleNameToSourceCache ProjectModulesCache; std::mutex GarbageCollectedProjectRootsMutex; llvm::StringSet<> GarbageCollectedProjectRoots; }; void ModulesBuilder::ModulesBuilderImpl:: garbageCollectModuleCacheForProjectRoot(PathRef ProjectRoot) { if (ProjectRoot.empty()) return; std::string NormalizedProjectRoot = normalizePathForCache(ProjectRoot); { // If the project root lives in GarbageCollectedProjectRoots, it implies // we've already started GC on the cache root. std::lock_guard Lock(GarbageCollectedProjectRootsMutex); if (!GarbageCollectedProjectRoots.insert(NormalizedProjectRoot).second) return; } llvm::SmallString<256> CacheRoot(ProjectRoot); llvm::sys::path::append(CacheRoot, ".cache", "clangd", "modules"); log("Running GC pass for clangd built module files under {0} with age " "threshold {1} seconds (adjust with --modules-builder-versioned-gc-" "threshold-seconds)", CacheRoot, VersionedModuleFileGCThresholdSeconds); garbageCollectModuleCache(CacheRoot); log("Done running GC pass for clangd built module files under {0}", CacheRoot); } void ModulesBuilder::ModulesBuilderImpl::getPrebuiltModuleFile( StringRef ModuleName, PathRef ModuleUnitFileName, const ThreadsafeFS &TFS, ReusablePrerequisiteModules &BuiltModuleFiles) { auto Cmd = getCDB().getCompileCommand(ModuleUnitFileName); if (!Cmd) return; ParseInputs Inputs; Inputs.TFS = &TFS; Inputs.CompileCommand = std::move(*Cmd); IgnoreDiagnostics IgnoreDiags; auto CI = buildCompilerInvocation(Inputs, IgnoreDiags); if (!CI) return; // We don't need to check if the module files are in ModuleCache or adding // them to the module cache. As even if the module files are in the module // cache, we still need to validate them. And it looks not helpful to add them // to the module cache, since we may always try to get the prebuilt module // files before building the module files by ourselves. for (auto &[ModuleName, ModuleFilePath] : CI->getHeaderSearchOpts().PrebuiltModuleFiles) { if (BuiltModuleFiles.isModuleUnitBuilt(ModuleName)) continue; // Convert relative path to absolute path based on the compilation directory llvm::SmallString<256> AbsoluteModuleFilePath; if (llvm::sys::path::is_relative(ModuleFilePath)) { AbsoluteModuleFilePath = Inputs.CompileCommand.Directory; llvm::sys::path::append(AbsoluteModuleFilePath, ModuleFilePath); } else AbsoluteModuleFilePath = ModuleFilePath; if (IsModuleFileUpToDate(AbsoluteModuleFilePath, BuiltModuleFiles, TFS.view(std::nullopt))) { log("Reusing prebuilt module file {0} of module {1} for {2}", AbsoluteModuleFilePath, ModuleName, ModuleUnitFileName); BuiltModuleFiles.addModuleFile( PrebuiltModuleFile::make(ModuleName, AbsoluteModuleFilePath)); } } } llvm::Error ModulesBuilder::ModulesBuilderImpl::getOrBuildModuleFile( PathRef RequiredSource, StringRef ModuleName, const ThreadsafeFS &TFS, CachingProjectModules &MDB, ReusablePrerequisiteModules &BuiltModuleFiles) { if (BuiltModuleFiles.isModuleUnitBuilt(ModuleName)) return llvm::Error::success(); std::string ModuleUnitFileName = MDB.getSourceForModuleName(ModuleName, RequiredSource); /// It is possible that we're meeting third party modules (modules whose /// source are not in the project. e.g, the std module may be a third-party /// module for most project) or something wrong with the implementation of /// ProjectModules. /// FIXME: How should we treat third party modules here? If we want to ignore /// third party modules, we should return true instead of false here. /// Currently we simply bail out. if (ModuleUnitFileName.empty()) return llvm::createStringError( llvm::formatv("Don't get the module unit for module {0}", ModuleName)); /// Try to get prebuilt module files from the compilation database first. This /// helps to avoid building the module files that are already built by the /// compiler. getPrebuiltModuleFile(ModuleName, ModuleUnitFileName, TFS, BuiltModuleFiles); // Get Required modules in topological order. auto ReqModuleNames = getAllRequiredModules(RequiredSource, MDB, ModuleName); for (llvm::StringRef ReqModuleName : ReqModuleNames) { if (BuiltModuleFiles.isModuleUnitBuilt(ReqModuleName)) continue; std::string ReqFileName = MDB.getSourceForModuleName(ReqModuleName, RequiredSource); auto Cmd = getCDB().getCompileCommand(ReqFileName); if (!Cmd) return llvm::createStringError( llvm::formatv("No compile command for {0}", ReqFileName)); if (auto PI = getCDB().getProjectInfo(ReqFileName); PI && !PI->SourceRoot.empty()) garbageCollectModuleCacheForProjectRoot(PI->SourceRoot); const std::string CommandHash = getCompileCommandStringHash(*Cmd); const std::string PublishedModuleFilePath = getPublishedModuleFilePath( ReqModuleName, getModuleFilesDirectory(ReqFileName, *Cmd, getCDB())); // Keep the source-scoped lock while probing and validating cached BMIs so // stale-file replacement and final publication stay serialized. auto SourceLock = ScopedModuleSourceLock::acquire(ReqFileName, getCDB()); if (!SourceLock) return SourceLock.takeError(); std::shared_ptr Cached = Cache.getModule(ReqModuleName, ReqFileName, CommandHash); if (Cached) { if (IsModuleFileUpToDate(Cached->getModuleFilePath(), BuiltModuleFiles, TFS.view(std::nullopt))) { log("Reusing module {0} from {1}", ReqModuleName, Cached->getModuleFilePath()); BuiltModuleFiles.addModuleFile(std::move(Cached)); continue; } Cache.remove(ReqModuleName, ReqFileName, CommandHash); } if (llvm::sys::fs::exists(PublishedModuleFilePath)) { if (IsModuleFileUpToDate(PublishedModuleFilePath, BuiltModuleFiles, TFS.view(std::nullopt))) { log("Reusing persistent module {0} from {1}", ReqModuleName, PublishedModuleFilePath); auto Materialized = copyModuleFileForRead(ReqModuleName, PublishedModuleFilePath); if (llvm::Error Err = Materialized.takeError()) return Err; Cache.add(ReqModuleName, ReqFileName, CommandHash, *Materialized); BuiltModuleFiles.addModuleFile(std::move(*Materialized)); continue; } // The persistent module file is stale. Remove it and build a new one. std::error_code EC = llvm::sys::fs::remove(PublishedModuleFilePath); if (EC) return llvm::createStringError( llvm::formatv("Failed to remove stale module file {0}: {1}", PublishedModuleFilePath, EC.message())); } bool PublishedExistingModuleFile = false; llvm::Expected> MF = buildModuleFile( ReqModuleName, ReqFileName, std::move(*Cmd), PublishedModuleFilePath, TFS, BuiltModuleFiles, PublishedExistingModuleFile); if (llvm::Error Err = MF.takeError()) return Err; if (PublishedExistingModuleFile && !IsModuleFileUpToDate(PublishedModuleFilePath, BuiltModuleFiles, TFS.view(std::nullopt))) { return llvm::createStringError( llvm::formatv("Published module file {0} is stale after lock wait", PublishedModuleFilePath)); } auto Materialized = copyModuleFileForRead(ReqModuleName, PublishedModuleFilePath); if (llvm::Error Err = Materialized.takeError()) return Err; log("Built module {0} to {1}", ReqModuleName, (*Materialized)->getModuleFilePath()); Cache.add(ReqModuleName, ReqFileName, CommandHash, *Materialized); BuiltModuleFiles.addModuleFile(std::move(*Materialized)); } return llvm::Error::success(); } bool ModulesBuilder::hasRequiredModules(PathRef File) { std::unique_ptr MDB = Impl->getCDB().getProjectModules(File); if (!MDB) return false; CachingProjectModules CachedMDB(std::move(MDB), Impl->getProjectModulesCache()); return !CachedMDB.getRequiredModules(File).empty(); } std::unique_ptr ModulesBuilder::buildPrerequisiteModulesFor(PathRef File, const ThreadsafeFS &TFS) { std::unique_ptr MDB = Impl->getCDB().getProjectModules(File); if (!MDB) { elog("Failed to get Project Modules information for {0}", File); return std::make_unique(); } CachingProjectModules CachedMDB(std::move(MDB), Impl->getProjectModulesCache()); std::vector RequiredModuleNames = CachedMDB.getRequiredModules(File); if (RequiredModuleNames.empty()) return std::make_unique(); auto RequiredModules = std::make_unique(); for (llvm::StringRef RequiredModuleName : RequiredModuleNames) { // Return early if there is any error. if (llvm::Error Err = Impl->getOrBuildModuleFile( File, RequiredModuleName, TFS, CachedMDB, *RequiredModules.get())) { elog("Failed to build module {0}; due to {1}", RequiredModuleName, toString(std::move(Err))); return std::make_unique(); } } return std::move(RequiredModules); } ModulesBuilder::ModulesBuilder(const GlobalCompilationDatabase &CDB) { Impl = std::make_unique(CDB); } ModulesBuilder::~ModulesBuilder() {} } // namespace clangd } // namespace clang