Author: Chuanqi Xu Date: 2025-09-05T14:28:42+08:00 New Revision: 1c19fda62fe57c34448efe5ac756f5ddb6b376e6
URL: https://github.com/llvm/llvm-project/commit/1c19fda62fe57c34448efe5ac756f5ddb6b376e6 DIFF: https://github.com/llvm/llvm-project/commit/1c19fda62fe57c34448efe5ac756f5ddb6b376e6.diff LOG: [clangd] [C++20 Modules] Try to use prebuilt modules (#155360) If there are already built module files (and we consume it), we can avoid rebuilding them. The patch tries to use the "-fmodule-file=" information from the compilation database to find the prebuilt module files, and it they are good, use them. This patch also did some relevent refactoring for `ModuleFile`. Introducing `PrebuiltModuleFile` and `BuiltModuleFile` to make the ownership clear. And renamed some variables which was named "ModuleFile". Added: Modified: clang-tools-extra/clangd/ModulesBuilder.cpp clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp Removed: ################################################################################ diff --git a/clang-tools-extra/clangd/ModulesBuilder.cpp b/clang-tools-extra/clangd/ModulesBuilder.cpp index 64f22b9fdf743..0177a1751bd60 100644 --- a/clang-tools-extra/clangd/ModulesBuilder.cpp +++ b/clang-tools-extra/clangd/ModulesBuilder.cpp @@ -103,10 +103,13 @@ class FailedPrerequisiteModules : public PrerequisiteModules { } }; -struct ModuleFile { +/// 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; @@ -128,21 +131,58 @@ struct ModuleFile { new (this) ModuleFile(std::move(Other)); return *this; } - - ~ModuleFile() { - if (!ModuleFilePath.empty() && !DebugModulesBuilder) - llvm::sys::fs::remove(ModuleFilePath); - } + virtual ~ModuleFile() = default; StringRef getModuleName() const { return ModuleName; } StringRef getModuleFilePath() const { return ModuleFilePath; } -private: +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<PrebuiltModuleFile> make(StringRef ModuleName, + PathRef ModuleFilePath) { + return std::make_shared<PrebuiltModuleFile>(ModuleName, ModuleFilePath, + CtorTag{}); + } +}; + +/// Represents a module file built by us. We're responsible to remove it. +class BuiltModuleFile : 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<BuiltModuleFile> make(StringRef ModuleName, + PathRef ModuleFilePath) { + return std::make_shared<BuiltModuleFile>(ModuleName, ModuleFilePath, + CtorTag{}); + } + + virtual ~BuiltModuleFile() { + if (!ModuleFilePath.empty() && !DebugModulesBuilder) + llvm::sys::fs::remove(ModuleFilePath); + } +}; + // ReusablePrerequisiteModules - stands for PrerequisiteModules for which all // the required modules are built successfully. All the module files // are owned by the modules builder. @@ -171,9 +211,9 @@ class ReusablePrerequisiteModules : public PrerequisiteModules { std::string getAsString() const { std::string Result; llvm::raw_string_ostream OS(Result); - for (const auto &ModuleFile : RequiredModules) { - OS << "-fmodule-file=" << ModuleFile->getModuleName() << "=" - << ModuleFile->getModuleFilePath() << " "; + for (const auto &MF : RequiredModules) { + OS << "-fmodule-file=" << MF->getModuleName() << "=" + << MF->getModuleFilePath() << " "; } return Result; } @@ -185,9 +225,9 @@ class ReusablePrerequisiteModules : public PrerequisiteModules { return BuiltModuleNames.contains(ModuleName); } - void addModuleFile(std::shared_ptr<const ModuleFile> ModuleFile) { - BuiltModuleNames.insert(ModuleFile->getModuleName()); - RequiredModules.emplace_back(std::move(ModuleFile)); + void addModuleFile(std::shared_ptr<const ModuleFile> MF) { + BuiltModuleNames.insert(MF->getModuleName()); + RequiredModules.emplace_back(std::move(MF)); } private: @@ -265,7 +305,7 @@ bool IsModuleFilesUpToDate( /// Build a module file for module with `ModuleName`. The information of built /// module file are stored in \param BuiltModuleFiles. -llvm::Expected<ModuleFile> +llvm::Expected<std::shared_ptr<BuiltModuleFile>> buildModuleFile(llvm::StringRef ModuleName, PathRef ModuleUnitFileName, const GlobalCompilationDatabase &CDB, const ThreadsafeFS &TFS, const ReusablePrerequisiteModules &BuiltModuleFiles) { @@ -340,7 +380,7 @@ buildModuleFile(llvm::StringRef ModuleName, PathRef ModuleUnitFileName, ModuleUnitFileName)); } - return ModuleFile{ModuleName, Inputs.CompileCommand.Output}; + return BuiltModuleFile::make(ModuleName, Inputs.CompileCommand.Output); } bool ReusablePrerequisiteModules::canReuse( @@ -509,10 +549,51 @@ class ModulesBuilder::ModulesBuilderImpl { ReusablePrerequisiteModules &BuiltModuleFiles); private: + /// Try to get prebuilt module files from the compilation database. + void getPrebuiltModuleFile(StringRef ModuleName, PathRef ModuleUnitFileName, + const ThreadsafeFS &TFS, + ReusablePrerequisiteModules &BuiltModuleFiles); + ModuleFileCache Cache; ModuleNameToSourceCache ProjectModulesCache; }; +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; + + if (IsModuleFileUpToDate(ModuleFilePath, BuiltModuleFiles, + TFS.view(std::nullopt))) { + log("Reusing prebuilt module file {0} of module {1} for {2}", + ModuleFilePath, ModuleName, ModuleUnitFileName); + BuiltModuleFiles.addModuleFile( + PrebuiltModuleFile::make(ModuleName, ModuleFilePath)); + } + } +} + llvm::Error ModulesBuilder::ModulesBuilderImpl::getOrBuildModuleFile( PathRef RequiredSource, StringRef ModuleName, const ThreadsafeFS &TFS, CachingProjectModules &MDB, ReusablePrerequisiteModules &BuiltModuleFiles) { @@ -532,6 +613,11 @@ llvm::Error ModulesBuilder::ModulesBuilderImpl::getOrBuildModuleFile( 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) { @@ -551,15 +637,14 @@ llvm::Error ModulesBuilder::ModulesBuilderImpl::getOrBuildModuleFile( std::string ReqFileName = MDB.getSourceForModuleName(ReqModuleName, RequiredSource); - llvm::Expected<ModuleFile> MF = buildModuleFile( + llvm::Expected<std::shared_ptr<BuiltModuleFile>> MF = buildModuleFile( ReqModuleName, ReqFileName, getCDB(), TFS, BuiltModuleFiles); if (llvm::Error Err = MF.takeError()) return Err; - log("Built module {0} to {1}", ReqModuleName, MF->getModuleFilePath()); - auto BuiltModuleFile = std::make_shared<const ModuleFile>(std::move(*MF)); - Cache.add(ReqModuleName, BuiltModuleFile); - BuiltModuleFiles.addModuleFile(std::move(BuiltModuleFile)); + log("Built module {0} to {1}", ReqModuleName, (*MF)->getModuleFilePath()); + Cache.add(ReqModuleName, *MF); + BuiltModuleFiles.addModuleFile(std::move(*MF)); } return llvm::Error::success(); diff --git a/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp b/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp index ae2d98ce90d8f..3132959a5967c 100644 --- a/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp +++ b/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp @@ -644,6 +644,35 @@ import M; EXPECT_EQ(CDB.getGlobalScanningCount(), 1u); } +TEST_F(PrerequisiteModulesTests, PrebuiltModuleFileTest) { + MockDirectoryCompilationDatabase CDB(TestDir, FS); + + CDB.addFile("M.cppm", R"cpp( +export module M; + )cpp"); + + CDB.addFile("U.cpp", R"cpp( +import M; + )cpp"); + + // Use ModulesBuilder to produce the prebuilt module file. + ModulesBuilder Builder(CDB); + auto ModuleInfo = + Builder.buildPrerequisiteModulesFor(getFullPath("U.cpp"), FS); + HeaderSearchOptions HS(TestDir); + ModuleInfo->adjustHeaderSearchOptions(HS); + + CDB.ExtraClangFlags.push_back("-fmodule-file=M=" + + HS.PrebuiltModuleFiles["M"]); + ModulesBuilder Builder2(CDB); + auto ModuleInfo2 = + Builder2.buildPrerequisiteModulesFor(getFullPath("U.cpp"), FS); + HeaderSearchOptions HS2(TestDir); + ModuleInfo2->adjustHeaderSearchOptions(HS2); + + EXPECT_EQ(HS.PrebuiltModuleFiles, HS2.PrebuiltModuleFiles); +} + } // namespace } // namespace clang::clangd _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits