Skip to content

Commit fe6c240

Browse files
authored
[clangd] [C++20] [Modules] Introduce initial support for C++20 Modules (#66462)
Alternatives to https://reviews.llvm.org/D153114. Try to address clangd/clangd#1293. See the links for design ideas and the consensus so far. We want to have some initial support in clang18. This is the initial support for C++20 Modules in clangd. As suggested by sammccall in https://reviews.llvm.org/D153114, we should minimize the scope of the initial patch to make it easier to review and understand so that every one are in the same page: > Don't attempt any cross-file or cross-version coordination: i.e. don't > try to reuse BMIs between different files, don't try to reuse BMIs > between (preamble) reparses of the same file, don't try to persist the > module graph. Instead, when building a preamble, synchronously scan > for the module graph, build the required PCMs on the single preamble > thread with filenames private to that preamble, and then proceed to > build the preamble. This patch reflects the above opinions. # Testing in real-world project I tested this with a modularized library: https://github.com/alibaba/async_simple/tree/CXX20Modules. This library has 3 modules (async_simple, std and asio) and 65 module units. (Note that a module consists of multiple module units). Both `std` module and `asio` module have 100k+ lines of code (maybe more, I didn't count). And async_simple itself has 8k lines of code. This is the scale of the project. The result shows that it works pretty well, ..., well, except I need to wait roughly 10s after opening/editing any file. And this falls in our expectations. We know it is hard to make it perfect in the first move. # What this patch does in detail - Introduced an option `--experimental-modules-support` for the support for C++20 Modules. So that no matter how bad this is, it wouldn't affect current users. Following off the page, we'll assume the option is enabled. - Introduced two classes `ModuleFilesInfo` and `ModuleDependencyScanner`. Now `ModuleDependencyScanner` is only used by `ModuleFilesInfo`. - The class `ModuleFilesInfo` records the built module files for specific single source file. The module files can only be built by the static member function `ModuleFilesInfo::buildModuleFilesInfoFor(PathRef File, ...)`. - The class `PreambleData` adds a new member variable with type `ModuleFilesInfo`. This refers to the needed module files for the current file. It means the module files info is part of the preamble, which is suggested in the first patch too. - In `isPreambleCompatible()`, we add a call to `ModuleFilesInfo::CanReuse()` to check if the built module files are still up to date. - When we build the AST for a source file, we will load the built module files from ModuleFilesInfo. # What we need to do next Let's split the TODOs into clang part and clangd part to make things more clear. The TODOs in the clangd part include: 1. Enable reusing module files across source files. The may require us to bring a ModulesManager like thing which need to handle `scheduling`, `the possibility of BMI version conflicts` and `various events that can invalidate the module graph`. 2. Get a more efficient method to get the `<module-name> -> <module-unit-source>` map. Currently we always scan the whole project during `ModuleFilesInfo::buildModuleFilesInfoFor(PathRef File, ...)`. This is clearly inefficient even if the scanning process is pretty fast. I think the potential solutions include: - Make a global scanner to monitor the state of every source file like I did in the first patch. The pain point is that we need to take care of the data races. - Ask the build systems to provide the map just like we ask them to provide the compilation database. 3. Persist the module files. So that we can reuse module files across clangd invocations or even across clangd instances. TODOs in the clang part include: 1. Clang should offer an option/mode to skip writing/reading the bodies of the functions. Or even if we can requrie the parser to skip parsing the function bodies. And it looks like we can say the support for C++20 Modules is initially workable after we made (1) and (2) (or even without (2)).
1 parent c41fa0f commit fe6c240

24 files changed

+1327
-4
lines changed

clang-tools-extra/clangd/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,14 @@ add_clang_library(clangDaemon
9797
IncludeFixer.cpp
9898
InlayHints.cpp
9999
JSONTransport.cpp
100+
ModulesBuilder.cpp
100101
PathMapping.cpp
101102
Protocol.cpp
102103
Quality.cpp
103104
ParsedAST.cpp
104105
Preamble.cpp
105106
RIFF.cpp
107+
ScanningProjectModules.cpp
106108
Selection.cpp
107109
SemanticHighlighting.cpp
108110
SemanticSelection.cpp
@@ -161,6 +163,7 @@ clang_target_link_libraries(clangDaemon
161163
clangAST
162164
clangASTMatchers
163165
clangBasic
166+
clangDependencyScanning
164167
clangDriver
165168
clangFormat
166169
clangFrontend

clang-tools-extra/clangd/ClangdLSPServer.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "Feature.h"
1515
#include "GlobalCompilationDatabase.h"
1616
#include "LSPBinder.h"
17+
#include "ModulesBuilder.h"
1718
#include "Protocol.h"
1819
#include "SemanticHighlighting.h"
1920
#include "SourceCode.h"
@@ -51,6 +52,7 @@
5152

5253
namespace clang {
5354
namespace clangd {
55+
5456
namespace {
5557
// Tracks end-to-end latency of high level lsp calls. Measurements are in
5658
// seconds.
@@ -563,6 +565,12 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
563565
Mangler.ResourceDir = *Opts.ResourceDir;
564566
CDB.emplace(BaseCDB.get(), Params.initializationOptions.fallbackFlags,
565567
std::move(Mangler));
568+
569+
if (Opts.EnableExperimentalModulesSupport) {
570+
ModulesManager.emplace(*CDB);
571+
Opts.ModulesManager = &*ModulesManager;
572+
}
573+
566574
{
567575
// Switch caller's context with LSPServer's background context. Since we
568576
// rather want to propagate information from LSPServer's context into the

clang-tools-extra/clangd/ClangdLSPServer.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
6363

6464
/// Limit the number of references returned (0 means no limit).
6565
size_t ReferencesLimit = 0;
66+
67+
/// Flag to hint the experimental modules support is enabled.
68+
bool EnableExperimentalModulesSupport = false;
6669
};
6770

6871
ClangdLSPServer(Transport &Transp, const ThreadsafeFS &TFS,
@@ -323,6 +326,8 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
323326
std::optional<OverlayCDB> CDB;
324327
// The ClangdServer is created by the "initialize" LSP method.
325328
std::optional<ClangdServer> Server;
329+
// Manages to build module files.
330+
std::optional<ModulesBuilder> ModulesManager;
326331
};
327332
} // namespace clangd
328333
} // namespace clang

clang-tools-extra/clangd/ClangdServer.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB,
216216
Callbacks *Callbacks)
217217
: FeatureModules(Opts.FeatureModules), CDB(CDB), TFS(TFS),
218218
DynamicIdx(Opts.BuildDynamicSymbolIndex ? new FileIndex() : nullptr),
219+
ModulesManager(Opts.ModulesManager),
219220
ClangTidyProvider(Opts.ClangTidyProvider),
220221
UseDirtyHeaders(Opts.UseDirtyHeaders),
221222
LineFoldingOnly(Opts.LineFoldingOnly),
@@ -308,6 +309,7 @@ void ClangdServer::addDocument(PathRef File, llvm::StringRef Contents,
308309
Inputs.Index = Index;
309310
Inputs.ClangTidyProvider = ClangTidyProvider;
310311
Inputs.FeatureModules = FeatureModules;
312+
Inputs.ModulesManager = ModulesManager;
311313
bool NewFile = WorkScheduler->update(File, Inputs, WantDiags);
312314
// If we loaded Foo.h, we want to make sure Foo.cpp is indexed.
313315
if (NewFile && BackgroundIdx)

clang-tools-extra/clangd/ClangdServer.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "FeatureModule.h"
1717
#include "GlobalCompilationDatabase.h"
1818
#include "Hover.h"
19+
#include "ModulesBuilder.h"
1920
#include "Protocol.h"
2021
#include "SemanticHighlighting.h"
2122
#include "TUScheduler.h"
@@ -112,6 +113,9 @@ class ClangdServer {
112113
/// This throttler controls which preambles may be built at a given time.
113114
clangd::PreambleThrottler *PreambleThrottler = nullptr;
114115

116+
/// Manages to build module files.
117+
ModulesBuilder *ModulesManager = nullptr;
118+
115119
/// If true, ClangdServer builds a dynamic in-memory index for symbols in
116120
/// opened files and uses the index to augment code completion results.
117121
bool BuildDynamicSymbolIndex = false;
@@ -477,6 +481,8 @@ class ClangdServer {
477481
std::unique_ptr<BackgroundIndex> BackgroundIdx;
478482
// Storage for merged views of the various indexes.
479483
std::vector<std::unique_ptr<SymbolIndex>> MergedIdx;
484+
// Manage module files.
485+
ModulesBuilder *ModulesManager = nullptr;
480486

481487
// When set, provides clang-tidy options for a specific file.
482488
TidyProviderRef ClangTidyProvider;

clang-tools-extra/clangd/Compiler.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_COMPILER_H
1717

1818
#include "FeatureModule.h"
19+
#include "ModulesBuilder.h"
1920
#include "TidyProvider.h"
2021
#include "index/Index.h"
2122
#include "support/ThreadsafeFS.h"
@@ -60,6 +61,8 @@ struct ParseInputs {
6061
TidyProviderRef ClangTidyProvider = {};
6162
// Used to acquire ASTListeners when parsing files.
6263
FeatureModuleSet *FeatureModules = nullptr;
64+
// Used to build and manage (C++) modules.
65+
ModulesBuilder *ModulesManager = nullptr;
6366
};
6467

6568
/// Clears \p CI from options that are not supported by clangd, like codegen or

clang-tools-extra/clangd/GlobalCompilationDatabase.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#include "GlobalCompilationDatabase.h"
1010
#include "Config.h"
1111
#include "FS.h"
12+
#include "ProjectModules.h"
13+
#include "ScanningProjectModules.h"
1214
#include "SourceCode.h"
1315
#include "support/Logger.h"
1416
#include "support/Path.h"
@@ -741,6 +743,20 @@ DirectoryBasedGlobalCompilationDatabase::getProjectInfo(PathRef File) const {
741743
return Res->PI;
742744
}
743745

746+
std::unique_ptr<ProjectModules>
747+
DirectoryBasedGlobalCompilationDatabase::getProjectModules(PathRef File) const {
748+
CDBLookupRequest Req;
749+
Req.FileName = File;
750+
Req.ShouldBroadcast = false;
751+
Req.FreshTime = Req.FreshTimeMissing =
752+
std::chrono::steady_clock::time_point::min();
753+
auto Res = lookupCDB(Req);
754+
if (!Res)
755+
return {};
756+
757+
return scanningProjectModules(Res->CDB, Opts.TFS);
758+
}
759+
744760
OverlayCDB::OverlayCDB(const GlobalCompilationDatabase *Base,
745761
std::vector<std::string> FallbackFlags,
746762
CommandMangler Mangler)
@@ -833,6 +849,13 @@ std::optional<ProjectInfo> DelegatingCDB::getProjectInfo(PathRef File) const {
833849
return Base->getProjectInfo(File);
834850
}
835851

852+
std::unique_ptr<ProjectModules>
853+
DelegatingCDB::getProjectModules(PathRef File) const {
854+
if (!Base)
855+
return nullptr;
856+
return Base->getProjectModules(File);
857+
}
858+
836859
tooling::CompileCommand DelegatingCDB::getFallbackCommand(PathRef File) const {
837860
if (!Base)
838861
return GlobalCompilationDatabase::getFallbackCommand(File);

clang-tools-extra/clangd/GlobalCompilationDatabase.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_GLOBALCOMPILATIONDATABASE_H
1010
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_GLOBALCOMPILATIONDATABASE_H
1111

12+
#include "ProjectModules.h"
1213
#include "support/Function.h"
1314
#include "support/Path.h"
1415
#include "support/Threading.h"
@@ -45,6 +46,12 @@ class GlobalCompilationDatabase {
4546
return std::nullopt;
4647
}
4748

49+
/// Get the modules in the closest project to \p File
50+
virtual std::unique_ptr<ProjectModules>
51+
getProjectModules(PathRef File) const {
52+
return nullptr;
53+
}
54+
4855
/// Makes a guess at how to build a file.
4956
/// The default implementation just runs clang on the file.
5057
/// Clangd should treat the results as unreliable.
@@ -76,6 +83,9 @@ class DelegatingCDB : public GlobalCompilationDatabase {
7683

7784
std::optional<ProjectInfo> getProjectInfo(PathRef File) const override;
7885

86+
std::unique_ptr<ProjectModules>
87+
getProjectModules(PathRef File) const override;
88+
7989
tooling::CompileCommand getFallbackCommand(PathRef File) const override;
8090

8191
bool blockUntilIdle(Deadline D) const override;
@@ -122,6 +132,9 @@ class DirectoryBasedGlobalCompilationDatabase
122132
/// \p File's parents.
123133
std::optional<ProjectInfo> getProjectInfo(PathRef File) const override;
124134

135+
std::unique_ptr<ProjectModules>
136+
getProjectModules(PathRef File) const override;
137+
125138
bool blockUntilIdle(Deadline Timeout) const override;
126139

127140
private:

0 commit comments

Comments
 (0)