sammccall created this revision.
sammccall added a reviewer: hokein.
Herald added subscribers: usaxena95, kadircet, arphaman.
sammccall requested review of this revision.
Herald added subscribers: cfe-commits, MaskRay, ilya-biryukov.
Herald added a project: clang-tools-extra.
During pop() we convert nodes into spans of expanded syntax::Tokens.
If we precompute a range of plausible (expanded) tokens, then we can do an
extremely cheap approximate hit-test against it, because syntax::Tokens are
ordered by pointer.
This would seem not to buy anything (we don't enter nodes unless they overlap
the selection), but in fact the spans we have are for *newly* claimed ranges
(i.e. those unclaimed by any child node).
So if you have:
{ { [[2+2]]; } }
then all of the CompoundStmts pass the hit test and are pushed, but we skip
full hit-testing of the brackets during pop() as they lie outside the range.
This is ~10x average speedup for selectiontree on a bad case I've seen
(large gtest file).
Repository:
rG LLVM Github Monorepo
https://reviews.llvm.org/D117107
Files:
clang-tools-extra/clangd/Selection.cpp
clang-tools-extra/clangd/unittests/SelectionTests.cpp
Index: clang-tools-extra/clangd/unittests/SelectionTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/SelectionTests.cpp
+++ clang-tools-extra/clangd/unittests/SelectionTests.cpp
@@ -201,6 +201,13 @@
)cpp",
nullptr,
},
+ {
+ R"cpp(
+ #define TARGET void foo()
+ [[TAR^GET{ return; }]]
+ )cpp",
+ "FunctionDecl",
+ },
{
R"cpp(
struct S { S(const char*); };
Index: clang-tools-extra/clangd/Selection.cpp
===================================================================
--- clang-tools-extra/clangd/Selection.cpp
+++ clang-tools-extra/clangd/Selection.cpp
@@ -271,6 +271,7 @@
else
S.Selected = SelectionTree::Partial;
}
+ ExpandedTokens = computeExpandedTokens(Buf);
}
// Test whether a consecutive range of tokens is selected.
@@ -279,6 +280,18 @@
test(llvm::ArrayRef<syntax::Token> ExpandedTokens) const {
if (SpelledTokens.empty())
return NoTokens;
+ // Cheap (pointer) check whether any of the tokens could touch selection.
+ // In most cases, the node's overall source range touches ExpandedTokens,
+ // or we would have failed mayHit(). However now we're only considering
+ // the *unclaimed* spans of expanded tokens.
+ // This is a significant performance improvement when a lot of nodes
+ // surround the selection, including when generated by macros.
+ if (ExpandedTokens.empty() ||
+ &ExpandedTokens.front() > &this->ExpandedTokens.back() ||
+ &ExpandedTokens.back() < &this->ExpandedTokens.front()) {
+ return SelectionTree::Unselected;
+ }
+
SelectionTree::Selection Result = NoTokens;
while (!ExpandedTokens.empty()) {
// Take consecutive tokens from the same context together for efficiency.
@@ -301,7 +314,7 @@
// If it returns false, test() will return NoTokens or Unselected.
// If it returns true, test() may return any value.
bool mayHit(SourceRange R) const {
- if (SpelledTokens.empty())
+ if (SpelledTokens.empty() || ExpandedTokens.empty())
return false;
auto B = offsetInSelFile(R.getBegin());
auto E = offsetInSelFile(R.getEnd());
@@ -312,6 +325,62 @@
}
private:
+ // Plausible expanded tokens that might be affected by the selection.
+ // This is an overestimate, it may contain tokens that are not selected.
+ // The point is to allow cheap pruning in test()
+ llvm::ArrayRef<syntax::Token>
+ computeExpandedTokens(const syntax::TokenBuffer &Toks) {
+ if (SpelledTokens.empty())
+ return {};
+
+ bool StartInvalid = false;
+ const syntax::Token *Start = llvm::partition_point(
+ Toks.expandedTokens(),
+ [&, First = SpelledTokens.front().Offset](const syntax::Token &Tok) {
+ // Implausible if upperbound(Tok) < First.
+ SourceLocation Loc = Tok.location();
+ auto Offset = offsetInSelFile(Loc);
+ while (Loc.isValid() && !Offset) {
+ Loc = Loc.isMacroID() ? SM.getImmediateExpansionRange(Loc).getEnd()
+ : SM.getIncludeLoc(SM.getFileID(Loc));
+ Offset = offsetInSelFile(Loc);
+ }
+ if (Offset)
+ return *Offset < First;
+ StartInvalid = false;
+ return false;
+ });
+ if (StartInvalid) {
+ assert(false && "Expanded tokens could not be resolved to main file!");
+ Start = Toks.expandedTokens().begin();
+ }
+
+ bool EndInvalid = false;
+ const syntax::Token *End = llvm::partition_point(
+ Toks.expandedTokens(),
+ [&, Last = SpelledTokens.back().Offset](const syntax::Token &Tok) {
+ // Plausible if lowerbound(Tok) <= Last.
+ SourceLocation Loc = Tok.location();
+ auto Offset = offsetInSelFile(Loc);
+ while (Loc.isValid() && !Offset) {
+ Loc = Loc.isMacroID()
+ ? SM.getImmediateExpansionRange(Loc).getBegin()
+ : SM.getIncludeLoc(SM.getFileID(Loc));
+ Offset = offsetInSelFile(Loc);
+ }
+ if (Offset)
+ return *Offset <= Last;
+ EndInvalid = false;
+ return true;
+ });
+ if (EndInvalid) {
+ assert(false && "Expanded tokens could not be resolved to main file!");
+ Start = Toks.expandedTokens().end();
+ }
+
+ return llvm::makeArrayRef(Start, End);
+ }
+
// Hit-test a consecutive range of tokens from a single file ID.
SelectionTree::Selection
testChunk(FileID FID, llvm::ArrayRef<syntax::Token> Batch) const {
@@ -418,6 +487,7 @@
SelectionTree::Selection Selected;
};
std::vector<Tok> SpelledTokens;
+ llvm::ArrayRef<syntax::Token> ExpandedTokens;
FileID SelFile;
SourceRange SelFileBounds;
const SourceManager &SM;
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits