Skip to content

Commit 4dedd82

Browse files
committed
Re-land [clangd] Elide even more checks in SelectionTree.
This reverts commit 1093b9f. Fix added for implicit-include case.
1 parent 192f8d9 commit 4dedd82

File tree

2 files changed

+103
-18
lines changed

2 files changed

+103
-18
lines changed

clang-tools-extra/clangd/Selection.cpp

Lines changed: 96 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -274,22 +274,37 @@ class SelectionTester {
274274
for (unsigned I = 0; I < Sel.size(); ++I) {
275275
if (shouldIgnore(Sel[I]) || PPIgnored[I])
276276
continue;
277-
SpelledTokens.emplace_back();
278-
Tok &S = SpelledTokens.back();
277+
SelectedSpelled.emplace_back();
278+
Tok &S = SelectedSpelled.back();
279279
S.Offset = SM.getFileOffset(Sel[I].location());
280280
if (S.Offset >= SelBegin && S.Offset + Sel[I].length() <= SelEnd)
281281
S.Selected = SelectionTree::Complete;
282282
else
283283
S.Selected = SelectionTree::Partial;
284284
}
285+
MaybeSelectedExpanded = computeMaybeSelectedExpandedTokens(Buf);
285286
}
286287

287288
// Test whether a consecutive range of tokens is selected.
288289
// The tokens are taken from the expanded token stream.
289290
SelectionTree::Selection
290291
test(llvm::ArrayRef<syntax::Token> ExpandedTokens) const {
291-
if (SpelledTokens.empty())
292+
if (ExpandedTokens.empty())
292293
return NoTokens;
294+
if (SelectedSpelled.empty())
295+
return SelectionTree::Unselected;
296+
// Cheap (pointer) check whether any of the tokens could touch selection.
297+
// In most cases, the node's overall source range touches ExpandedTokens,
298+
// or we would have failed mayHit(). However now we're only considering
299+
// the *unclaimed* spans of expanded tokens.
300+
// This is a significant performance improvement when a lot of nodes
301+
// surround the selection, including when generated by macros.
302+
if (MaybeSelectedExpanded.empty() ||
303+
&ExpandedTokens.front() > &MaybeSelectedExpanded.back() ||
304+
&ExpandedTokens.back() < &MaybeSelectedExpanded.front()) {
305+
return SelectionTree::Unselected;
306+
}
307+
293308
SelectionTree::Selection Result = NoTokens;
294309
while (!ExpandedTokens.empty()) {
295310
// Take consecutive tokens from the same context together for efficiency.
@@ -312,14 +327,14 @@ class SelectionTester {
312327
// If it returns false, test() will return NoTokens or Unselected.
313328
// If it returns true, test() may return any value.
314329
bool mayHit(SourceRange R) const {
315-
if (SpelledTokens.empty())
330+
if (SelectedSpelled.empty() || MaybeSelectedExpanded.empty())
316331
return false;
317332
// If the node starts after the selection ends, it is not selected.
318333
// Tokens a macro location might claim are >= its expansion start.
319334
// So if the expansion start > last selected token, we can prune it.
320335
// (This is particularly helpful for GTest's TEST macro).
321336
if (auto B = offsetInSelFile(getExpansionStart(R.getBegin())))
322-
if (*B > SpelledTokens.back().Offset)
337+
if (*B > SelectedSpelled.back().Offset)
323338
return false;
324339
// If the node ends before the selection begins, it is not selected.
325340
SourceLocation EndLoc = R.getEnd();
@@ -328,12 +343,73 @@ class SelectionTester {
328343
// In the rare case that the expansion range is a char range, EndLoc is
329344
// ~one token too far to the right. We may fail to prune, that's OK.
330345
if (auto E = offsetInSelFile(EndLoc))
331-
if (*E < SpelledTokens.front().Offset)
346+
if (*E < SelectedSpelled.front().Offset)
332347
return false;
333348
return true;
334349
}
335350

336351
private:
352+
// Plausible expanded tokens that might be affected by the selection.
353+
// This is an overestimate, it may contain tokens that are not selected.
354+
// The point is to allow cheap pruning in test()
355+
llvm::ArrayRef<syntax::Token>
356+
computeMaybeSelectedExpandedTokens(const syntax::TokenBuffer &Toks) {
357+
if (SelectedSpelled.empty())
358+
return {};
359+
360+
auto LastAffectedToken = [&](SourceLocation Loc) {
361+
auto Offset = offsetInSelFile(Loc);
362+
while (Loc.isValid() && !Offset) {
363+
Loc = Loc.isMacroID() ? SM.getImmediateExpansionRange(Loc).getEnd()
364+
: SM.getIncludeLoc(SM.getFileID(Loc));
365+
Offset = offsetInSelFile(Loc);
366+
}
367+
return Offset;
368+
};
369+
auto FirstAffectedToken = [&](SourceLocation Loc) {
370+
auto Offset = offsetInSelFile(Loc);
371+
while (Loc.isValid() && !Offset) {
372+
Loc = Loc.isMacroID() ? SM.getImmediateExpansionRange(Loc).getBegin()
373+
: SM.getIncludeLoc(SM.getFileID(Loc));
374+
Offset = offsetInSelFile(Loc);
375+
}
376+
return Offset;
377+
};
378+
379+
const syntax::Token *Start = llvm::partition_point(
380+
Toks.expandedTokens(),
381+
[&, First = SelectedSpelled.front().Offset](const syntax::Token &Tok) {
382+
if (Tok.kind() == tok::eof)
383+
return false;
384+
// Implausible if upperbound(Tok) < First.
385+
if (auto Offset = LastAffectedToken(Tok.location()))
386+
return *Offset < First;
387+
// A prefix of the expanded tokens may be from an an implicit
388+
// inclusion (e.g. preamble patch, or command-line -include).
389+
return true;
390+
});
391+
392+
bool EndInvalid = false;
393+
const syntax::Token *End = std::partition_point(
394+
Start, Toks.expandedTokens().end(),
395+
[&, Last = SelectedSpelled.back().Offset](const syntax::Token &Tok) {
396+
if (Tok.kind() == tok::eof)
397+
return false;
398+
// Plausible if lowerbound(Tok) <= Last.
399+
if (auto Offset = FirstAffectedToken(Tok.location()))
400+
return *Offset <= Last;
401+
// Shouldn't happen: once we've seen tokens traceable to the main
402+
// file, there shouldn't be any more implicit inclusions.
403+
assert(false && "Expanded token could not be resolved to main file!");
404+
EndInvalid = true;
405+
return true; // conservatively assume this token can overlap
406+
});
407+
if (EndInvalid)
408+
End = Toks.expandedTokens().end();
409+
410+
return llvm::makeArrayRef(Start, End);
411+
}
412+
337413
// Hit-test a consecutive range of tokens from a single file ID.
338414
SelectionTree::Selection
339415
testChunk(FileID FID, llvm::ArrayRef<syntax::Token> Batch) const {
@@ -389,19 +465,20 @@ class SelectionTester {
389465
SelectionTree::Selection testTokenRange(unsigned Begin, unsigned End) const {
390466
assert(Begin <= End);
391467
// Outside the selection entirely?
392-
if (End < SpelledTokens.front().Offset ||
393-
Begin > SpelledTokens.back().Offset)
468+
if (End < SelectedSpelled.front().Offset ||
469+
Begin > SelectedSpelled.back().Offset)
394470
return SelectionTree::Unselected;
395471

396472
// Compute range of tokens.
397473
auto B = llvm::partition_point(
398-
SpelledTokens, [&](const Tok &T) { return T.Offset < Begin; });
399-
auto E = std::partition_point(
400-
B, SpelledTokens.end(), [&](const Tok &T) { return T.Offset <= End; });
474+
SelectedSpelled, [&](const Tok &T) { return T.Offset < Begin; });
475+
auto E = std::partition_point(B, SelectedSpelled.end(), [&](const Tok &T) {
476+
return T.Offset <= End;
477+
});
401478

402479
// Aggregate selectedness of tokens in range.
403-
bool ExtendsOutsideSelection = Begin < SpelledTokens.front().Offset ||
404-
End > SpelledTokens.back().Offset;
480+
bool ExtendsOutsideSelection = Begin < SelectedSpelled.front().Offset ||
481+
End > SelectedSpelled.back().Offset;
405482
SelectionTree::Selection Result =
406483
ExtendsOutsideSelection ? SelectionTree::Unselected : NoTokens;
407484
for (auto It = B; It != E; ++It)
@@ -412,13 +489,13 @@ class SelectionTester {
412489
// Is the token at `Offset` selected?
413490
SelectionTree::Selection testToken(unsigned Offset) const {
414491
// Outside the selection entirely?
415-
if (Offset < SpelledTokens.front().Offset ||
416-
Offset > SpelledTokens.back().Offset)
492+
if (Offset < SelectedSpelled.front().Offset ||
493+
Offset > SelectedSpelled.back().Offset)
417494
return SelectionTree::Unselected;
418495
// Find the token, if it exists.
419496
auto It = llvm::partition_point(
420-
SpelledTokens, [&](const Tok &T) { return T.Offset < Offset; });
421-
if (It != SpelledTokens.end() && It->Offset == Offset)
497+
SelectedSpelled, [&](const Tok &T) { return T.Offset < Offset; });
498+
if (It != SelectedSpelled.end() && It->Offset == Offset)
422499
return It->Selected;
423500
return NoTokens;
424501
}
@@ -444,7 +521,8 @@ class SelectionTester {
444521
unsigned Offset;
445522
SelectionTree::Selection Selected;
446523
};
447-
std::vector<Tok> SpelledTokens;
524+
std::vector<Tok> SelectedSpelled;
525+
llvm::ArrayRef<syntax::Token> MaybeSelectedExpanded;
448526
FileID SelFile;
449527
SourceRange SelFileBounds;
450528
const SourceManager &SM;

clang-tools-extra/clangd/unittests/SelectionTests.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,13 @@ TEST(SelectionTest, CommonAncestor) {
201201
)cpp",
202202
nullptr,
203203
},
204+
{
205+
R"cpp(
206+
#define TARGET void foo()
207+
[[TAR^GET{ return; }]]
208+
)cpp",
209+
"FunctionDecl",
210+
},
204211
{
205212
R"cpp(
206213
struct S { S(const char*); };

0 commit comments

Comments
 (0)