Skip to content

Commit 2214b90

Browse files
committed
[clangd] Make signatureHelp work with stale preambles
Summary: This is achieved by calculating newly added includes and implicitly parsing them as if they were part of the main file. This also gets rid of the need for consistent preamble reads. Reviewers: sammccall Subscribers: ilya-biryukov, javed.absar, MaskRay, jkorous, mgrang, arphaman, jfb, usaxena95, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D77392
1 parent 6e01718 commit 2214b90

File tree

12 files changed

+399
-133
lines changed

12 files changed

+399
-133
lines changed

clang-tools-extra/clangd/ClangdServer.cpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -280,11 +280,8 @@ void ClangdServer::signatureHelp(PathRef File, Position Pos,
280280
Pos, FS, Index));
281281
};
282282

283-
// Unlike code completion, we wait for an up-to-date preamble here.
284-
// Signature help is often triggered after code completion. If the code
285-
// completion inserted a header to make the symbol available, then using
286-
// the old preamble would yield useless results.
287-
WorkScheduler.runWithPreamble("SignatureHelp", File, TUScheduler::Consistent,
283+
// Unlike code completion, we wait for a preamble here.
284+
WorkScheduler.runWithPreamble("SignatureHelp", File, TUScheduler::Stale,
288285
std::move(Action));
289286
}
290287

clang-tools-extra/clangd/CodeComplete.cpp

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,6 +1023,7 @@ struct SemaCompleteInput {
10231023
PathRef FileName;
10241024
const tooling::CompileCommand &Command;
10251025
const PreambleData &Preamble;
1026+
const PreamblePatch &PreamblePatch;
10261027
llvm::StringRef Contents;
10271028
size_t Offset;
10281029
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS;
@@ -1060,7 +1061,6 @@ bool semaCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
10601061
ParseInput.CompileCommand = Input.Command;
10611062
ParseInput.FS = VFS;
10621063
ParseInput.Contents = std::string(Input.Contents);
1063-
ParseInput.Opts = ParseOptions();
10641064

10651065
IgnoreDiagnostics IgnoreDiags;
10661066
auto CI = buildCompilerInvocation(ParseInput, IgnoreDiags);
@@ -1096,6 +1096,7 @@ bool semaCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
10961096
PreambleBounds PreambleRegion =
10971097
ComputePreambleBounds(*CI->getLangOpts(), ContentsBuffer.get(), 0);
10981098
bool CompletingInPreamble = PreambleRegion.Size > Input.Offset;
1099+
Input.PreamblePatch.apply(*CI);
10991100
// NOTE: we must call BeginSourceFile after prepareCompilerInstance. Otherwise
11001101
// the remapped buffers do not get freed.
11011102
auto Clang = prepareCompilerInstance(
@@ -1754,8 +1755,10 @@ codeComplete(PathRef FileName, const tooling::CompileCommand &Command,
17541755
SpecFuzzyFind, Opts);
17551756
return (!Preamble || Opts.RunParser == CodeCompleteOptions::NeverParse)
17561757
? std::move(Flow).runWithoutSema(Contents, *Offset, VFS)
1757-
: std::move(Flow).run(
1758-
{FileName, Command, *Preamble, Contents, *Offset, VFS});
1758+
: std::move(Flow).run({FileName, Command, *Preamble,
1759+
// We want to serve code completions with
1760+
// low latency, so don't bother patching.
1761+
PreamblePatch(), Contents, *Offset, VFS});
17591762
}
17601763

17611764
SignatureHelp signatureHelp(PathRef FileName,
@@ -1775,10 +1778,15 @@ SignatureHelp signatureHelp(PathRef FileName,
17751778
Options.IncludeMacros = false;
17761779
Options.IncludeCodePatterns = false;
17771780
Options.IncludeBriefComments = false;
1778-
IncludeStructure PreambleInclusions; // Unused for signatureHelp
1781+
1782+
ParseInputs PI;
1783+
PI.CompileCommand = Command;
1784+
PI.Contents = Contents.str();
1785+
PI.FS = std::move(VFS);
1786+
auto PP = PreamblePatch::create(FileName, PI, Preamble);
17791787
semaCodeComplete(
17801788
std::make_unique<SignatureHelpCollector>(Options, Index, Result), Options,
1781-
{FileName, Command, Preamble, Contents, *Offset, std::move(VFS)});
1789+
{FileName, Command, Preamble, PP, Contents, *Offset, std::move(PI.FS)});
17821790
return Result;
17831791
}
17841792

clang-tools-extra/clangd/Preamble.cpp

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,39 @@
88

99
#include "Preamble.h"
1010
#include "Compiler.h"
11+
#include "Headers.h"
1112
#include "Logger.h"
1213
#include "Trace.h"
14+
#include "clang/Basic/Diagnostic.h"
15+
#include "clang/Basic/LangOptions.h"
1316
#include "clang/Basic/SourceLocation.h"
17+
#include "clang/Basic/TokenKinds.h"
18+
#include "clang/Frontend/CompilerInvocation.h"
19+
#include "clang/Frontend/FrontendActions.h"
20+
#include "clang/Lex/Lexer.h"
1421
#include "clang/Lex/PPCallbacks.h"
22+
#include "clang/Lex/Preprocessor.h"
1523
#include "clang/Lex/PreprocessorOptions.h"
24+
#include "clang/Tooling/CompilationDatabase.h"
25+
#include "llvm/ADT/ArrayRef.h"
26+
#include "llvm/ADT/IntrusiveRefCntPtr.h"
27+
#include "llvm/ADT/STLExtras.h"
28+
#include "llvm/ADT/SmallString.h"
29+
#include "llvm/ADT/StringRef.h"
30+
#include "llvm/ADT/StringSet.h"
31+
#include "llvm/Support/Error.h"
32+
#include "llvm/Support/ErrorHandling.h"
33+
#include "llvm/Support/FormatVariadic.h"
34+
#include "llvm/Support/MemoryBuffer.h"
35+
#include "llvm/Support/Path.h"
36+
#include "llvm/Support/VirtualFileSystem.h"
37+
#include "llvm/Support/raw_ostream.h"
38+
#include <iterator>
39+
#include <memory>
40+
#include <string>
41+
#include <system_error>
42+
#include <utility>
43+
#include <vector>
1644

1745
namespace clang {
1846
namespace clangd {
@@ -74,6 +102,81 @@ class CppFilePreambleCallbacks : public PreambleCallbacks {
74102
const SourceManager *SourceMgr = nullptr;
75103
};
76104

105+
// Runs preprocessor over preamble section.
106+
class PreambleOnlyAction : public PreprocessorFrontendAction {
107+
protected:
108+
void ExecuteAction() override {
109+
Preprocessor &PP = getCompilerInstance().getPreprocessor();
110+
auto &SM = PP.getSourceManager();
111+
PP.EnterMainSourceFile();
112+
auto Bounds = ComputePreambleBounds(getCompilerInstance().getLangOpts(),
113+
SM.getBuffer(SM.getMainFileID()), 0);
114+
Token Tok;
115+
do {
116+
PP.Lex(Tok);
117+
assert(SM.isInMainFile(Tok.getLocation()));
118+
} while (Tok.isNot(tok::eof) &&
119+
SM.getDecomposedLoc(Tok.getLocation()).second < Bounds.Size);
120+
}
121+
};
122+
123+
/// Gets the includes in the preamble section of the file by running
124+
/// preprocessor over \p Contents. Returned includes do not contain resolved
125+
/// paths. \p VFS and \p Cmd is used to build the compiler invocation, which
126+
/// might stat/read files.
127+
llvm::Expected<std::vector<Inclusion>>
128+
scanPreambleIncludes(llvm::StringRef Contents,
129+
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS,
130+
const tooling::CompileCommand &Cmd) {
131+
// Build and run Preprocessor over the preamble.
132+
ParseInputs PI;
133+
PI.Contents = Contents.str();
134+
PI.FS = std::move(VFS);
135+
PI.CompileCommand = Cmd;
136+
IgnoringDiagConsumer IgnoreDiags;
137+
auto CI = buildCompilerInvocation(PI, IgnoreDiags);
138+
if (!CI)
139+
return llvm::createStringError(llvm::inconvertibleErrorCode(),
140+
"failed to create compiler invocation");
141+
CI->getDiagnosticOpts().IgnoreWarnings = true;
142+
auto ContentsBuffer = llvm::MemoryBuffer::getMemBuffer(Contents);
143+
auto Clang = prepareCompilerInstance(
144+
std::move(CI), nullptr, std::move(ContentsBuffer),
145+
// Provide an empty FS to prevent preprocessor from performing IO. This
146+
// also implies missing resolved paths for includes.
147+
new llvm::vfs::InMemoryFileSystem, IgnoreDiags);
148+
if (Clang->getFrontendOpts().Inputs.empty())
149+
return llvm::createStringError(llvm::inconvertibleErrorCode(),
150+
"compiler instance had no inputs");
151+
// We are only interested in main file includes.
152+
Clang->getPreprocessorOpts().SingleFileParseMode = true;
153+
PreambleOnlyAction Action;
154+
if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0]))
155+
return llvm::createStringError(llvm::inconvertibleErrorCode(),
156+
"failed BeginSourceFile");
157+
Preprocessor &PP = Clang->getPreprocessor();
158+
IncludeStructure Includes;
159+
PP.addPPCallbacks(
160+
collectIncludeStructureCallback(Clang->getSourceManager(), &Includes));
161+
if (llvm::Error Err = Action.Execute())
162+
return std::move(Err);
163+
Action.EndSourceFile();
164+
return Includes.MainFileIncludes;
165+
}
166+
167+
const char *spellingForIncDirective(tok::PPKeywordKind IncludeDirective) {
168+
switch (IncludeDirective) {
169+
case tok::pp_include:
170+
return "include";
171+
case tok::pp_import:
172+
return "import";
173+
case tok::pp_include_next:
174+
return "include_next";
175+
default:
176+
break;
177+
}
178+
llvm_unreachable("not an include directive");
179+
}
77180
} // namespace
78181

79182
PreambleData::PreambleData(const ParseInputs &Inputs,
@@ -166,5 +269,78 @@ bool isPreambleCompatible(const PreambleData &Preamble,
166269
Preamble.Preamble.CanReuse(CI, ContentsBuffer.get(), Bounds,
167270
Inputs.FS.get());
168271
}
272+
273+
PreamblePatch PreamblePatch::create(llvm::StringRef FileName,
274+
const ParseInputs &Modified,
275+
const PreambleData &Baseline) {
276+
// First scan the include directives in Baseline and Modified. These will be
277+
// used to figure out newly added directives in Modified. Scanning can fail,
278+
// the code just bails out and creates an empty patch in such cases, as:
279+
// - If scanning for Baseline fails, no knowledge of existing includes hence
280+
// patch will contain all the includes in Modified. Leading to rebuild of
281+
// whole preamble, which is terribly slow.
282+
// - If scanning for Modified fails, cannot figure out newly added ones so
283+
// there's nothing to do but generate an empty patch.
284+
auto BaselineIncludes = scanPreambleIncludes(
285+
// Contents needs to be null-terminated.
286+
Baseline.Preamble.getContents().str(),
287+
Baseline.StatCache->getConsumingFS(Modified.FS), Modified.CompileCommand);
288+
if (!BaselineIncludes) {
289+
elog("Failed to scan includes for baseline of {0}: {1}", FileName,
290+
BaselineIncludes.takeError());
291+
return {};
292+
}
293+
auto ModifiedIncludes = scanPreambleIncludes(
294+
Modified.Contents, Baseline.StatCache->getConsumingFS(Modified.FS),
295+
Modified.CompileCommand);
296+
if (!ModifiedIncludes) {
297+
elog("Failed to scan includes for modified contents of {0}: {1}", FileName,
298+
ModifiedIncludes.takeError());
299+
return {};
300+
}
301+
302+
PreamblePatch PP;
303+
// This shouldn't coincide with any real file name.
304+
llvm::SmallString<128> PatchName;
305+
llvm::sys::path::append(PatchName, llvm::sys::path::parent_path(FileName),
306+
"__preamble_patch__.h");
307+
PP.PatchFileName = PatchName.str().str();
308+
309+
// We are only interested in newly added includes, record the ones in Baseline
310+
// for exclusion.
311+
llvm::DenseSet<std::pair<tok::PPKeywordKind, llvm::StringRef>>
312+
ExistingIncludes;
313+
for (const auto &Inc : *BaselineIncludes)
314+
ExistingIncludes.insert({Inc.Directive, Inc.Written});
315+
// Calculate extra includes that needs to be inserted.
316+
llvm::raw_string_ostream Patch(PP.PatchContents);
317+
for (const auto &Inc : *ModifiedIncludes) {
318+
if (ExistingIncludes.count({Inc.Directive, Inc.Written}))
319+
continue;
320+
Patch << llvm::formatv("#{0} {1}\n", spellingForIncDirective(Inc.Directive),
321+
Inc.Written);
322+
}
323+
Patch.flush();
324+
325+
// FIXME: Handle more directives, e.g. define/undef.
326+
return PP;
327+
}
328+
329+
void PreamblePatch::apply(CompilerInvocation &CI) const {
330+
// No need to map an empty file.
331+
if (PatchContents.empty())
332+
return;
333+
auto &PPOpts = CI.getPreprocessorOpts();
334+
auto PatchBuffer =
335+
// we copy here to ensure contents are still valid if CI outlives the
336+
// PreamblePatch.
337+
llvm::MemoryBuffer::getMemBufferCopy(PatchContents, PatchFileName);
338+
// CI will take care of the lifetime of the buffer.
339+
PPOpts.addRemappedFile(PatchFileName, PatchBuffer.release());
340+
// The patch will be parsed after loading the preamble ast and before parsing
341+
// the main file.
342+
PPOpts.Includes.push_back(PatchFileName);
343+
}
344+
169345
} // namespace clangd
170346
} // namespace clang

clang-tools-extra/clangd/Preamble.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "clang/Frontend/CompilerInvocation.h"
3333
#include "clang/Frontend/PrecompiledPreamble.h"
3434
#include "clang/Tooling/CompilationDatabase.h"
35+
#include "llvm/ADT/StringRef.h"
3536

3637
#include <memory>
3738
#include <string>
@@ -88,6 +89,31 @@ buildPreamble(PathRef FileName, CompilerInvocation CI,
8889
bool isPreambleCompatible(const PreambleData &Preamble,
8990
const ParseInputs &Inputs, PathRef FileName,
9091
const CompilerInvocation &CI);
92+
93+
/// Stores information required to parse a TU using a (possibly stale) Baseline
94+
/// preamble. Updates compiler invocation to approximately reflect additions to
95+
/// the preamble section of Modified contents, e.g. new include directives.
96+
class PreamblePatch {
97+
public:
98+
// With an empty patch, the preamble is used verbatim.
99+
PreamblePatch() = default;
100+
/// Builds a patch that contains new PP directives introduced to the preamble
101+
/// section of \p Modified compared to \p Baseline.
102+
/// FIXME: This only handles include directives, we should at least handle
103+
/// define/undef.
104+
static PreamblePatch create(llvm::StringRef FileName,
105+
const ParseInputs &Modified,
106+
const PreambleData &Baseline);
107+
/// Adjusts CI (which compiles the modified inputs) to be used with the
108+
/// baseline preamble. This is done by inserting an artifical include to the
109+
/// \p CI that contains new directives calculated in create.
110+
void apply(CompilerInvocation &CI) const;
111+
112+
private:
113+
std::string PatchContents;
114+
std::string PatchFileName;
115+
};
116+
91117
} // namespace clangd
92118
} // namespace clang
93119

clang-tools-extra/clangd/TUScheduler.cpp

Lines changed: 6 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -866,36 +866,6 @@ ASTWorker::getPossiblyStalePreamble() const {
866866
return LatestPreamble;
867867
}
868868

869-
void ASTWorker::getCurrentPreamble(
870-
llvm::unique_function<void(std::shared_ptr<const PreambleData>)> Callback) {
871-
// We could just call startTask() to throw the read on the queue, knowing
872-
// it will run after any updates. But we know this task is cheap, so to
873-
// improve latency we cheat: insert it on the queue after the last update.
874-
std::unique_lock<std::mutex> Lock(Mutex);
875-
auto LastUpdate =
876-
std::find_if(Requests.rbegin(), Requests.rend(),
877-
[](const Request &R) { return R.UpdateType.hasValue(); });
878-
// If there were no writes in the queue, and CurrentRequest is not a write,
879-
// the preamble is ready now.
880-
if (LastUpdate == Requests.rend() &&
881-
(!CurrentRequest || CurrentRequest->UpdateType.hasValue())) {
882-
Lock.unlock();
883-
return Callback(getPossiblyStalePreamble());
884-
}
885-
assert(!RunSync && "Running synchronously, but queue is non-empty!");
886-
Requests.insert(LastUpdate.base(),
887-
Request{[Callback = std::move(Callback), this]() mutable {
888-
Callback(getPossiblyStalePreamble());
889-
},
890-
"GetPreamble", steady_clock::now(),
891-
Context::current().clone(),
892-
/*UpdateType=*/None,
893-
/*InvalidationPolicy=*/TUScheduler::NoInvalidation,
894-
/*Invalidate=*/nullptr});
895-
Lock.unlock();
896-
RequestsCV.notify_all();
897-
}
898-
899869
void ASTWorker::waitForFirstPreamble() const { BuiltFirstPreamble.wait(); }
900870

901871
tooling::CompileCommand ASTWorker::getCurrentCompileCommand() const {
@@ -1307,41 +1277,21 @@ void TUScheduler::runWithPreamble(llvm::StringRef Name, PathRef File,
13071277
return;
13081278
}
13091279

1310-
// Future is populated if the task needs a specific preamble.
1311-
std::future<std::shared_ptr<const PreambleData>> ConsistentPreamble;
1312-
// FIXME: Currently this only holds because ASTWorker blocks after issuing a
1313-
// preamble build. Get rid of consistent reads or make them build on the
1314-
// calling thread instead.
1315-
if (Consistency == Consistent) {
1316-
std::promise<std::shared_ptr<const PreambleData>> Promise;
1317-
ConsistentPreamble = Promise.get_future();
1318-
It->second->Worker->getCurrentPreamble(
1319-
[Promise = std::move(Promise)](
1320-
std::shared_ptr<const PreambleData> Preamble) mutable {
1321-
Promise.set_value(std::move(Preamble));
1322-
});
1323-
}
1324-
13251280
std::shared_ptr<const ASTWorker> Worker = It->second->Worker.lock();
13261281
auto Task =
13271282
[Worker, Consistency, Name = Name.str(), File = File.str(),
13281283
Contents = It->second->Contents,
13291284
Command = Worker->getCurrentCompileCommand(),
13301285
Ctx = Context::current().derive(kFileBeingProcessed, std::string(File)),
1331-
ConsistentPreamble = std::move(ConsistentPreamble),
13321286
Action = std::move(Action), this]() mutable {
13331287
std::shared_ptr<const PreambleData> Preamble;
1334-
if (ConsistentPreamble.valid()) {
1335-
Preamble = ConsistentPreamble.get();
1336-
} else {
1337-
if (Consistency != PreambleConsistency::StaleOrAbsent) {
1338-
// Wait until the preamble is built for the first time, if preamble
1339-
// is required. This avoids extra work of processing the preamble
1340-
// headers in parallel multiple times.
1341-
Worker->waitForFirstPreamble();
1342-
}
1343-
Preamble = Worker->getPossiblyStalePreamble();
1288+
if (Consistency == PreambleConsistency::Stale) {
1289+
// Wait until the preamble is built for the first time, if preamble
1290+
// is required. This avoids extra work of processing the preamble
1291+
// headers in parallel multiple times.
1292+
Worker->waitForFirstPreamble();
13441293
}
1294+
Preamble = Worker->getPossiblyStalePreamble();
13451295

13461296
std::lock_guard<Semaphore> BarrierLock(Barrier);
13471297
WithContext Guard(std::move(Ctx));

0 commit comments

Comments
 (0)