-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[clangd] [Modules] Support Reusable Modules Builder #106683
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 3 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
15aa6af
[clangd] [Modules] Support Reusable Modules Builder
ChuanqiXu9 4ed7bf5
Remove unique builder
ChuanqiXu9 dcdd18a
Update
ChuanqiXu9 370b01f
Address comments
ChuanqiXu9 4e0f1e3
Address comments
ChuanqiXu9 821dc8d
address comments
ChuanqiXu9 483ee8d
fmt
ChuanqiXu9 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,8 @@ | |
#include "clang/Frontend/FrontendActions.h" | ||
#include "clang/Serialization/ASTReader.h" | ||
#include "clang/Serialization/InMemoryModuleCache.h" | ||
#include "llvm/ADT/ScopeExit.h" | ||
#include <queue> | ||
|
||
namespace clang { | ||
namespace clangd { | ||
|
@@ -124,6 +126,11 @@ struct ModuleFile { | |
llvm::sys::fs::remove(ModuleFilePath); | ||
} | ||
|
||
StringRef getModuleName() const { return ModuleName; } | ||
|
||
StringRef getModuleFilePath() const { return ModuleFilePath; } | ||
|
||
private: | ||
std::string ModuleName; | ||
std::string ModuleFilePath; | ||
}; | ||
|
@@ -192,33 +199,28 @@ bool IsModuleFilesUpToDate( | |
}); | ||
} | ||
|
||
// StandalonePrerequisiteModules - stands for PrerequisiteModules for which all | ||
// ReusablePrerequisiteModules - stands for PrerequisiteModules for which all | ||
// the required modules are built successfully. All the module files | ||
// are owned by the StandalonePrerequisiteModules class. | ||
// | ||
// Any of the built module files won't be shared with other instances of the | ||
// class. So that we can avoid worrying thread safety. | ||
// | ||
// We don't need to worry about duplicated module names here since the standard | ||
// guarantees the module names should be unique to a program. | ||
class StandalonePrerequisiteModules : public PrerequisiteModules { | ||
// are owned by the modules builder. | ||
class ReusablePrerequisiteModules : public PrerequisiteModules { | ||
public: | ||
StandalonePrerequisiteModules() = default; | ||
ReusablePrerequisiteModules() = default; | ||
|
||
StandalonePrerequisiteModules(const StandalonePrerequisiteModules &) = delete; | ||
StandalonePrerequisiteModules | ||
operator=(const StandalonePrerequisiteModules &) = delete; | ||
StandalonePrerequisiteModules(StandalonePrerequisiteModules &&) = delete; | ||
StandalonePrerequisiteModules | ||
operator=(StandalonePrerequisiteModules &&) = delete; | ||
ReusablePrerequisiteModules(const ReusablePrerequisiteModules &) = delete; | ||
ReusablePrerequisiteModules | ||
operator=(const ReusablePrerequisiteModules &) = delete; | ||
ReusablePrerequisiteModules(ReusablePrerequisiteModules &&) = delete; | ||
ReusablePrerequisiteModules | ||
operator=(ReusablePrerequisiteModules &&) = delete; | ||
|
||
~StandalonePrerequisiteModules() override = default; | ||
~ReusablePrerequisiteModules() override = default; | ||
|
||
void adjustHeaderSearchOptions(HeaderSearchOptions &Options) const override { | ||
// Appending all built module files. | ||
for (auto &RequiredModule : RequiredModules) | ||
Options.PrebuiltModuleFiles.insert_or_assign( | ||
RequiredModule.ModuleName, RequiredModule.ModuleFilePath); | ||
RequiredModule->getModuleName().str(), | ||
RequiredModule->getModuleFilePath().str()); | ||
} | ||
|
||
bool canReuse(const CompilerInvocation &CI, | ||
|
@@ -228,54 +230,30 @@ class StandalonePrerequisiteModules : public PrerequisiteModules { | |
return BuiltModuleNames.contains(ModuleName); | ||
} | ||
|
||
void addModuleFile(llvm::StringRef ModuleName, | ||
llvm::StringRef ModuleFilePath) { | ||
RequiredModules.emplace_back(ModuleName, ModuleFilePath); | ||
BuiltModuleNames.insert(ModuleName); | ||
void addModuleFile(std::shared_ptr<ModuleFile> BMI) { | ||
BuiltModuleNames.insert(BMI->getModuleName()); | ||
RequiredModules.emplace_back(std::move(BMI)); | ||
} | ||
|
||
private: | ||
llvm::SmallVector<ModuleFile, 8> RequiredModules; | ||
llvm::SmallVector<std::shared_ptr<ModuleFile>, 8> RequiredModules; | ||
// A helper class to speedup the query if a module is built. | ||
llvm::StringSet<> BuiltModuleNames; | ||
}; | ||
|
||
// Build a module file for module with `ModuleName`. The information of built | ||
// module file are stored in \param BuiltModuleFiles. | ||
llvm::Error buildModuleFile(llvm::StringRef ModuleName, | ||
const GlobalCompilationDatabase &CDB, | ||
const ThreadsafeFS &TFS, ProjectModules &MDB, | ||
PathRef ModuleFilesPrefix, | ||
StandalonePrerequisiteModules &BuiltModuleFiles) { | ||
if (BuiltModuleFiles.isModuleUnitBuilt(ModuleName)) | ||
return llvm::Error::success(); | ||
|
||
PathRef ModuleUnitFileName = MDB.getSourceForModuleName(ModuleName); | ||
// 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 projects) 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("Failed to get the primary source"); | ||
|
||
/// Build a module file for module with `ModuleName`. The information of built | ||
/// module file are stored in \param BuiltModuleFiles. | ||
llvm::Expected<ModuleFile> | ||
buildModuleFile(llvm::StringRef ModuleName, PathRef ModuleUnitFileName, | ||
const GlobalCompilationDatabase &CDB, const ThreadsafeFS &TFS, | ||
PathRef ModuleFilesPrefix, | ||
const ReusablePrerequisiteModules &BuiltModuleFiles) { | ||
// Try cheap operation earlier to boil-out cheaply if there are problems. | ||
auto Cmd = CDB.getCompileCommand(ModuleUnitFileName); | ||
if (!Cmd) | ||
return llvm::createStringError( | ||
llvm::formatv("No compile command for {0}", ModuleUnitFileName)); | ||
|
||
for (auto &RequiredModuleName : MDB.getRequiredModules(ModuleUnitFileName)) { | ||
// Return early if there are errors building the module file. | ||
if (llvm::Error Err = buildModuleFile(RequiredModuleName, CDB, TFS, MDB, | ||
ModuleFilesPrefix, BuiltModuleFiles)) | ||
return llvm::createStringError( | ||
llvm::formatv("Failed to build dependency {0}: {1}", | ||
RequiredModuleName, llvm::toString(std::move(Err)))); | ||
} | ||
|
||
Cmd->Output = getModuleFilePath(ModuleName, ModuleFilesPrefix); | ||
|
||
ParseInputs Inputs; | ||
|
@@ -316,36 +294,205 @@ llvm::Error buildModuleFile(llvm::StringRef ModuleName, | |
if (Clang->getDiagnostics().hasErrorOccurred()) | ||
return llvm::createStringError("Compilation failed"); | ||
|
||
BuiltModuleFiles.addModuleFile(ModuleName, Inputs.CompileCommand.Output); | ||
return llvm::Error::success(); | ||
return ModuleFile{ModuleName, Inputs.CompileCommand.Output}; | ||
} | ||
|
||
bool ReusablePrerequisiteModules::canReuse( | ||
const CompilerInvocation &CI, | ||
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) const { | ||
if (RequiredModules.empty()) | ||
return true; | ||
|
||
SmallVector<StringRef> BMIPaths; | ||
ChuanqiXu9 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for (auto &MF : RequiredModules) | ||
BMIPaths.push_back(MF->getModuleFilePath()); | ||
return IsModuleFilesUpToDate(BMIPaths, *this, VFS); | ||
} | ||
|
||
class ModuleFileCache { | ||
public: | ||
ModuleFileCache(const GlobalCompilationDatabase &CDB) : CDB(CDB) {} | ||
|
||
llvm::Error | ||
getOrBuildModuleFile(StringRef ModuleName, const ThreadsafeFS &TFS, | ||
ProjectModules &MDB, | ||
ReusablePrerequisiteModules &RequiredModules); | ||
const GlobalCompilationDatabase &getCDB() const { return CDB; } | ||
|
||
std::shared_ptr<ModuleFile> | ||
getValidModuleFile(StringRef ModuleName, ProjectModules &MDB, | ||
const ThreadsafeFS &TFS, | ||
PrerequisiteModules &BuiltModuleFiles); | ||
|
||
void add(StringRef ModuleName, std::shared_ptr<ModuleFile> ModuleFile) { | ||
std::lock_guard<std::mutex> _(ModuleFilesMutex); | ||
|
||
ModuleFiles.insert_or_assign(ModuleName, ModuleFile); | ||
ChuanqiXu9 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
private: | ||
const GlobalCompilationDatabase &CDB; | ||
|
||
llvm::StringMap<std::shared_ptr<ModuleFile>> ModuleFiles; | ||
kadircet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Mutex to guard accesses to ModuleFiles. | ||
std::mutex ModuleFilesMutex; | ||
}; | ||
|
||
/// Collect the directly and indirectly required module names for \param | ||
/// ModuleName. The \param ModuleName is guaranteed to be the first element in | ||
/// \param ModuleNames. | ||
void getAllRequiredModules(ProjectModules &MDB, StringRef ModuleName, | ||
ChuanqiXu9 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
llvm::SmallVector<StringRef> &ModuleNames) { | ||
std::queue<StringRef> Worklist; | ||
llvm::StringSet<> ModuleNamesSet; | ||
Worklist.push(ModuleName); | ||
|
||
while (!Worklist.empty()) { | ||
StringRef CurrentModule = Worklist.front(); | ||
Worklist.pop(); | ||
|
||
if (!ModuleNamesSet.insert(CurrentModule).second) | ||
continue; | ||
|
||
ModuleNames.push_back(CurrentModule); | ||
|
||
for (StringRef RequiredModuleName : | ||
MDB.getRequiredModules(MDB.getSourceForModuleName(CurrentModule))) | ||
if (!ModuleNamesSet.contains(RequiredModuleName)) | ||
kadircet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Worklist.push(RequiredModuleName); | ||
} | ||
} | ||
|
||
std::shared_ptr<ModuleFile> | ||
ModuleFileCache::getValidModuleFile(StringRef ModuleName, ProjectModules &MDB, | ||
const ThreadsafeFS &TFS, | ||
PrerequisiteModules &BuiltModuleFiles) { | ||
{ | ||
std::lock_guard<std::mutex> _(ModuleFilesMutex); | ||
kadircet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (ModuleFiles.find(ModuleName) == ModuleFiles.end()) | ||
ChuanqiXu9 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return nullptr; | ||
} | ||
|
||
llvm::SmallVector<StringRef> ModuleNames; | ||
getAllRequiredModules(MDB, ModuleName, ModuleNames); | ||
|
||
llvm::SmallVector<std::shared_ptr<ModuleFile>> RequiredModuleFiles; | ||
|
||
{ | ||
std::lock_guard<std::mutex> _(ModuleFilesMutex); | ||
|
||
for (StringRef ModuleName : ModuleNames) { | ||
auto Iter = ModuleFiles.find(ModuleName); | ||
if (Iter == ModuleFiles.end()) | ||
return nullptr; | ||
|
||
RequiredModuleFiles.push_back(Iter->second); | ||
} | ||
} | ||
|
||
if (RequiredModuleFiles.empty()) | ||
return nullptr; | ||
kadircet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (llvm::all_of(RequiredModuleFiles, [&](auto MF) { | ||
ChuanqiXu9 marked this conversation as resolved.
Show resolved
Hide resolved
ChuanqiXu9 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return IsModuleFileUpToDate(MF->getModuleFilePath(), BuiltModuleFiles, | ||
TFS.view(std::nullopt)); | ||
})) | ||
return RequiredModuleFiles[0]; | ||
|
||
return nullptr; | ||
} | ||
} // namespace | ||
|
||
class ModulesBuilder::ModulesBuilderImpl { | ||
public: | ||
ModulesBuilderImpl(const GlobalCompilationDatabase &CDB) : Cache(CDB) {} | ||
|
||
const GlobalCompilationDatabase &getCDB() const { return Cache.getCDB(); } | ||
|
||
llvm::Error | ||
getOrBuildModuleFile(StringRef ModuleName, const ThreadsafeFS &TFS, | ||
ProjectModules &MDB, | ||
ReusablePrerequisiteModules &BuiltModuleFiles); | ||
|
||
private: | ||
ModuleFileCache Cache; | ||
}; | ||
|
||
llvm::Error ModulesBuilder::ModulesBuilderImpl::getOrBuildModuleFile( | ||
StringRef ModuleName, const ThreadsafeFS &TFS, ProjectModules &MDB, | ||
ReusablePrerequisiteModules &BuiltModuleFiles) { | ||
if (BuiltModuleFiles.isModuleUnitBuilt(ModuleName)) | ||
return llvm::Error::success(); | ||
|
||
PathRef ModuleUnitFileName = MDB.getSourceForModuleName(ModuleName); | ||
/// 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)); | ||
|
||
for (auto &RequiredModuleName : MDB.getRequiredModules(ModuleUnitFileName)) | ||
// Return early if there are errors building the module file. | ||
if (!getOrBuildModuleFile(RequiredModuleName, TFS, MDB, BuiltModuleFiles)) | ||
return llvm::createStringError( | ||
llvm::formatv("Failed to build module {0}", RequiredModuleName)); | ||
|
||
if (std::shared_ptr<ModuleFile> Cached = | ||
Cache.getValidModuleFile(ModuleName, MDB, TFS, BuiltModuleFiles)) { | ||
kadircet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
log("Reusing module {0} from {1}", ModuleName, Cached->getModuleFilePath()); | ||
BuiltModuleFiles.addModuleFile(Cached); | ||
return llvm::Error::success(); | ||
} | ||
|
||
log("Building module {0}", ModuleName); | ||
|
||
llvm::SmallString<256> ModuleFilesPrefix = | ||
getUniqueModuleFilesPath(ModuleUnitFileName); | ||
|
||
llvm::Expected<ModuleFile> MF = | ||
buildModuleFile(ModuleName, ModuleUnitFileName, getCDB(), TFS, | ||
ModuleFilesPrefix, BuiltModuleFiles); | ||
if (llvm::Error Err = MF.takeError()) | ||
return Err; | ||
|
||
log("Built module {0} to {1}", ModuleName, MF->getModuleFilePath()); | ||
auto BuiltModuleFile = std::make_shared<ModuleFile>(std::move(*MF)); | ||
Cache.add(ModuleName, BuiltModuleFile); | ||
BuiltModuleFiles.addModuleFile(std::move(BuiltModuleFile)); | ||
|
||
return llvm::Error::success(); | ||
} | ||
|
||
std::unique_ptr<PrerequisiteModules> | ||
ModulesBuilder::buildPrerequisiteModulesFor(PathRef File, | ||
const ThreadsafeFS &TFS) const { | ||
std::unique_ptr<ProjectModules> MDB = CDB.getProjectModules(File); | ||
const ThreadsafeFS &TFS) { | ||
std::unique_ptr<ProjectModules> MDB = Impl->getCDB().getProjectModules(File); | ||
if (!MDB) { | ||
elog("Failed to get Project Modules information for {0}", File); | ||
return std::make_unique<FailedPrerequisiteModules>(); | ||
} | ||
|
||
std::vector<std::string> RequiredModuleNames = MDB->getRequiredModules(File); | ||
if (RequiredModuleNames.empty()) | ||
return std::make_unique<StandalonePrerequisiteModules>(); | ||
return std::make_unique<ReusablePrerequisiteModules>(); | ||
|
||
llvm::SmallString<256> ModuleFilesPrefix = getUniqueModuleFilesPath(File); | ||
|
||
log("Trying to build required modules for {0} in {1}", File, | ||
ModuleFilesPrefix); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is no longer the case, you can drop this (and the following log after the build) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
|
||
auto RequiredModules = std::make_unique<StandalonePrerequisiteModules>(); | ||
auto RequiredModules = std::make_unique<ReusablePrerequisiteModules>(); | ||
|
||
for (llvm::StringRef RequiredModuleName : RequiredModuleNames) { | ||
// Return early if there is any error. | ||
if (llvm::Error Err = | ||
buildModuleFile(RequiredModuleName, CDB, TFS, *MDB.get(), | ||
ModuleFilesPrefix, *RequiredModules.get())) { | ||
if (llvm::Error Err = Impl->getOrBuildModuleFile( | ||
RequiredModuleName, TFS, *MDB.get(), *RequiredModules.get())) { | ||
elog("Failed to build module {0}; due to {1}", RequiredModuleName, | ||
toString(std::move(Err))); | ||
return std::make_unique<FailedPrerequisiteModules>(); | ||
|
@@ -357,17 +504,11 @@ ModulesBuilder::buildPrerequisiteModulesFor(PathRef File, | |
return std::move(RequiredModules); | ||
} | ||
|
||
bool StandalonePrerequisiteModules::canReuse( | ||
const CompilerInvocation &CI, | ||
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) const { | ||
if (RequiredModules.empty()) | ||
return true; | ||
|
||
SmallVector<StringRef> BMIPaths; | ||
for (auto &MF : RequiredModules) | ||
BMIPaths.push_back(MF.ModuleFilePath); | ||
return IsModuleFilesUpToDate(BMIPaths, *this, VFS); | ||
ModulesBuilder::ModulesBuilder(const GlobalCompilationDatabase &CDB) { | ||
Impl = std::make_unique<ModulesBuilderImpl>(CDB); | ||
} | ||
|
||
ModulesBuilder::~ModulesBuilder() {} | ||
|
||
} // namespace clangd | ||
} // namespace clang |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.