Skip to content

Commit 0b1eff1

Browse files
committed
[clangd] Refactor IncludeStructure: use File (unsigned) for most computations
Preparation for D108194. Reviewed By: sammccall Differential Revision: https://reviews.llvm.org/D110386
1 parent 74d622d commit 0b1eff1

File tree

5 files changed

+178
-95
lines changed

5 files changed

+178
-95
lines changed

clang-tools-extra/clangd/CodeComplete.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1379,14 +1379,16 @@ class CodeCompleteFlow {
13791379
FileDistanceOptions ProxOpts{}; // Use defaults.
13801380
const auto &SM = Recorder->CCSema->getSourceManager();
13811381
llvm::StringMap<SourceParams> ProxSources;
1382-
for (auto &Entry : Includes.includeDepth(
1383-
SM.getFileEntryForID(SM.getMainFileID())->getName())) {
1384-
auto &Source = ProxSources[Entry.getKey()];
1385-
Source.Cost = Entry.getValue() * ProxOpts.IncludeCost;
1382+
auto MainFileID =
1383+
Includes.getOrCreateID(SM.getFileEntryForID(SM.getMainFileID()));
1384+
for (auto &HeaderIDAndDepth : Includes.includeDepth(MainFileID)) {
1385+
auto &Source =
1386+
ProxSources[Includes.getRealPath(HeaderIDAndDepth.getFirst())];
1387+
Source.Cost = HeaderIDAndDepth.getSecond() * ProxOpts.IncludeCost;
13861388
// Symbols near our transitive includes are good, but only consider
13871389
// things in the same directory or below it. Otherwise there can be
13881390
// many false positives.
1389-
if (Entry.getValue() > 0)
1391+
if (HeaderIDAndDepth.getSecond() > 0)
13901392
Source.MaxUpTraversals = 1;
13911393
}
13921394
FileProximity.emplace(ProxSources, ProxOpts);

clang-tools-extra/clangd/Headers.cpp

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,9 @@ class RecordHeaders : public PPCallbacks {
6767
// Treat as if included from the main file.
6868
IncludingFileEntry = SM.getFileEntryForID(MainFID);
6969
}
70-
Out->recordInclude(IncludingFileEntry->getName(), File->getName(),
71-
File->tryGetRealPathName());
70+
auto IncludingID = Out->getOrCreateID(IncludingFileEntry),
71+
IncludedID = Out->getOrCreateID(File);
72+
Out->IncludeChildren[IncludingID].push_back(IncludedID);
7273
}
7374
}
7475

@@ -154,49 +155,49 @@ collectIncludeStructureCallback(const SourceManager &SM,
154155
return std::make_unique<RecordHeaders>(SM, Out);
155156
}
156157

157-
void IncludeStructure::recordInclude(llvm::StringRef IncludingName,
158-
llvm::StringRef IncludedName,
159-
llvm::StringRef IncludedRealName) {
160-
auto Child = fileIndex(IncludedName);
161-
if (!IncludedRealName.empty() && RealPathNames[Child].empty())
162-
RealPathNames[Child] = std::string(IncludedRealName);
163-
auto Parent = fileIndex(IncludingName);
164-
IncludeChildren[Parent].push_back(Child);
158+
llvm::Optional<IncludeStructure::HeaderID>
159+
IncludeStructure::getID(const FileEntry *Entry) const {
160+
auto It = NameToIndex.find(Entry->getName());
161+
if (It == NameToIndex.end())
162+
return llvm::None;
163+
return It->second;
165164
}
166165

167-
unsigned IncludeStructure::fileIndex(llvm::StringRef Name) {
168-
auto R = NameToIndex.try_emplace(Name, RealPathNames.size());
166+
IncludeStructure::HeaderID
167+
IncludeStructure::getOrCreateID(const FileEntry *Entry) {
168+
auto R = NameToIndex.try_emplace(
169+
Entry->getName(),
170+
static_cast<IncludeStructure::HeaderID>(RealPathNames.size()));
169171
if (R.second)
170172
RealPathNames.emplace_back();
171-
return R.first->getValue();
173+
IncludeStructure::HeaderID Result = R.first->getValue();
174+
std::string &RealPathName = RealPathNames[static_cast<unsigned>(Result)];
175+
if (RealPathName.empty())
176+
RealPathName = Entry->tryGetRealPathName().str();
177+
return Result;
172178
}
173179

174-
llvm::StringMap<unsigned>
175-
IncludeStructure::includeDepth(llvm::StringRef Root) const {
180+
llvm::DenseMap<IncludeStructure::HeaderID, unsigned>
181+
IncludeStructure::includeDepth(HeaderID Root) const {
176182
// Include depth 0 is the main file only.
177-
llvm::StringMap<unsigned> Result;
183+
llvm::DenseMap<HeaderID, unsigned> Result;
184+
assert(static_cast<unsigned>(Root) < RealPathNames.size());
178185
Result[Root] = 0;
179-
std::vector<unsigned> CurrentLevel;
180-
llvm::DenseSet<unsigned> Seen;
181-
auto It = NameToIndex.find(Root);
182-
if (It != NameToIndex.end()) {
183-
CurrentLevel.push_back(It->second);
184-
Seen.insert(It->second);
185-
}
186+
std::vector<IncludeStructure::HeaderID> CurrentLevel;
187+
CurrentLevel.push_back(Root);
188+
llvm::DenseSet<IncludeStructure::HeaderID> Seen;
189+
Seen.insert(Root);
186190

187191
// Each round of BFS traversal finds the next depth level.
188-
std::vector<unsigned> PreviousLevel;
192+
std::vector<IncludeStructure::HeaderID> PreviousLevel;
189193
for (unsigned Level = 1; !CurrentLevel.empty(); ++Level) {
190194
PreviousLevel.clear();
191195
PreviousLevel.swap(CurrentLevel);
192196
for (const auto &Parent : PreviousLevel) {
193197
for (const auto &Child : IncludeChildren.lookup(Parent)) {
194198
if (Seen.insert(Child).second) {
195199
CurrentLevel.push_back(Child);
196-
const auto &Name = RealPathNames[Child];
197-
// Can't include files if we don't have their real path.
198-
if (!Name.empty())
199-
Result[Name] = Level;
200+
Result[Child] = Level;
200201
}
201202
}
202203
}

clang-tools-extra/clangd/Headers.h

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "Protocol.h"
1313
#include "SourceCode.h"
1414
#include "index/Symbol.h"
15+
#include "support/Logger.h"
1516
#include "support/Path.h"
1617
#include "clang/Basic/TokenKinds.h"
1718
#include "clang/Format/Format.h"
@@ -62,7 +63,7 @@ struct Inclusion {
6263
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Inclusion &);
6364
bool operator==(const Inclusion &LHS, const Inclusion &RHS);
6465

65-
// Contains information about one file in the build grpah and its direct
66+
// Contains information about one file in the build graph and its direct
6667
// dependencies. Doesn't own the strings it references (IncludeGraph is
6768
// self-contained).
6869
struct IncludeGraphNode {
@@ -112,34 +113,42 @@ operator|=(IncludeGraphNode::SourceFlag &A, IncludeGraphNode::SourceFlag B) {
112113
// in any non-preamble inclusions.
113114
class IncludeStructure {
114115
public:
115-
std::vector<Inclusion> MainFileIncludes;
116+
// HeaderID identifies file in the include graph. It corresponds to a
117+
// FileEntry rather than a FileID, but stays stable across preamble & main
118+
// file builds.
119+
enum class HeaderID : unsigned {};
120+
121+
llvm::Optional<HeaderID> getID(const FileEntry *Entry) const;
122+
HeaderID getOrCreateID(const FileEntry *Entry);
123+
124+
StringRef getRealPath(HeaderID ID) const {
125+
assert(static_cast<unsigned>(ID) <= RealPathNames.size());
126+
return RealPathNames[static_cast<unsigned>(ID)];
127+
}
116128

117129
// Return all transitively reachable files.
118130
llvm::ArrayRef<std::string> allHeaders() const { return RealPathNames; }
119131

120132
// Return all transitively reachable files, and their minimum include depth.
121133
// All transitive includes (absolute paths), with their minimum include depth.
122134
// Root --> 0, #included file --> 1, etc.
123-
// Root is clang's name for a file, which may not be absolute.
124-
// Usually it should be SM.getFileEntryForID(SM.getMainFileID())->getName().
125-
llvm::StringMap<unsigned> includeDepth(llvm::StringRef Root) const;
135+
// Root is the ID of the header being visited first.
136+
// Usually it is getID(SM.getFileEntryForID(SM.getMainFileID())->getName()).
137+
llvm::DenseMap<HeaderID, unsigned> includeDepth(HeaderID Root) const;
138+
139+
// Maps HeaderID to the ids of the files included from it.
140+
llvm::DenseMap<HeaderID, SmallVector<HeaderID>> IncludeChildren;
126141

127-
// This updates IncludeDepth(), but not MainFileIncludes.
128-
void recordInclude(llvm::StringRef IncludingName,
129-
llvm::StringRef IncludedName,
130-
llvm::StringRef IncludedRealName);
142+
std::vector<Inclusion> MainFileIncludes;
131143

132144
private:
145+
std::vector<std::string> RealPathNames; // In HeaderID order.
146+
// HeaderID maps the FileEntry::Name to the internal representation.
133147
// Identifying files in a way that persists from preamble build to subsequent
134-
// builds is surprisingly hard. FileID is unavailable in InclusionDirective(),
135-
// and RealPathName and UniqueID are not preserved in the preamble.
136-
// We use the FileEntry::Name, which is stable, interned into a "file index".
137-
// The paths we want to expose are the RealPathName, so store those too.
138-
std::vector<std::string> RealPathNames; // In file index order.
139-
unsigned fileIndex(llvm::StringRef Name);
140-
llvm::StringMap<unsigned> NameToIndex; // Values are file indexes.
141-
// Maps a file's index to that of the files it includes.
142-
llvm::DenseMap<unsigned, llvm::SmallVector<unsigned>> IncludeChildren;
148+
// builds is surprisingly hard. FileID is unavailable in
149+
// InclusionDirective(), and RealPathName and UniqueID are not preserved in
150+
// the preamble.
151+
llvm::StringMap<HeaderID> NameToIndex;
143152
};
144153

145154
/// Returns a PPCallback that visits all inclusions in the main file.
@@ -205,4 +214,31 @@ class IncludeInserter {
205214
} // namespace clangd
206215
} // namespace clang
207216

217+
namespace llvm {
218+
219+
// Support Tokens as DenseMap keys.
220+
template <> struct DenseMapInfo<clang::clangd::IncludeStructure::HeaderID> {
221+
static inline clang::clangd::IncludeStructure::HeaderID getEmptyKey() {
222+
return static_cast<clang::clangd::IncludeStructure::HeaderID>(
223+
DenseMapInfo<unsigned>::getEmptyKey());
224+
}
225+
226+
static inline clang::clangd::IncludeStructure::HeaderID getTombstoneKey() {
227+
return static_cast<clang::clangd::IncludeStructure::HeaderID>(
228+
DenseMapInfo<unsigned>::getTombstoneKey());
229+
}
230+
231+
static unsigned
232+
getHashValue(const clang::clangd::IncludeStructure::HeaderID &Tag) {
233+
return hash_value(static_cast<unsigned>(Tag));
234+
}
235+
236+
static bool isEqual(const clang::clangd::IncludeStructure::HeaderID &LHS,
237+
const clang::clangd::IncludeStructure::HeaderID &RHS) {
238+
return LHS == RHS;
239+
}
240+
};
241+
242+
} // namespace llvm
243+
208244
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_HEADERS_H

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

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "clang/Frontend/FrontendActions.h"
1818
#include "clang/Lex/PreprocessorOptions.h"
1919
#include "llvm/ADT/StringRef.h"
20+
#include "llvm/Support/Error.h"
2021
#include "llvm/Support/FormatVariadic.h"
2122
#include "llvm/Support/Path.h"
2223
#include "gmock/gmock.h"
@@ -29,8 +30,10 @@ namespace {
2930
using ::testing::AllOf;
3031
using ::testing::Contains;
3132
using ::testing::ElementsAre;
33+
using ::testing::IsEmpty;
3234
using ::testing::Not;
3335
using ::testing::UnorderedElementsAre;
36+
using ::testing::UnorderedElementsAreArray;
3437

3538
class HeadersTest : public ::testing::Test {
3639
public:
@@ -64,8 +67,15 @@ class HeadersTest : public ::testing::Test {
6467
}
6568

6669
protected:
70+
IncludeStructure::HeaderID getID(StringRef Filename,
71+
IncludeStructure &Includes) {
72+
auto Entry = Clang->getSourceManager().getFileManager().getFile(Filename);
73+
EXPECT_TRUE(Entry);
74+
return Includes.getOrCreateID(*Entry);
75+
}
76+
6777
IncludeStructure collectIncludes() {
68-
auto Clang = setupClang();
78+
Clang = setupClang();
6979
PreprocessOnlyAction Action;
7080
EXPECT_TRUE(
7181
Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0]));
@@ -81,7 +91,7 @@ class HeadersTest : public ::testing::Test {
8191
// inserted.
8292
std::string calculate(PathRef Original, PathRef Preferred = "",
8393
const std::vector<Inclusion> &Inclusions = {}) {
84-
auto Clang = setupClang();
94+
Clang = setupClang();
8595
PreprocessOnlyAction Action;
8696
EXPECT_TRUE(
8797
Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0]));
@@ -107,7 +117,7 @@ class HeadersTest : public ::testing::Test {
107117
}
108118

109119
llvm::Optional<TextEdit> insert(llvm::StringRef VerbatimHeader) {
110-
auto Clang = setupClang();
120+
Clang = setupClang();
111121
PreprocessOnlyAction Action;
112122
EXPECT_TRUE(
113123
Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0]));
@@ -126,6 +136,7 @@ class HeadersTest : public ::testing::Test {
126136
std::string Subdir = testPath("sub");
127137
std::string SearchDirArg = (llvm::Twine("-I") + Subdir).str();
128138
IgnoringDiagConsumer IgnoreDiags;
139+
std::unique_ptr<CompilerInstance> Clang;
129140
};
130141

131142
MATCHER_P(Written, Name, "") { return arg.Written == Name; }
@@ -134,11 +145,11 @@ MATCHER_P(IncludeLine, N, "") { return arg.HashLine == N; }
134145
MATCHER_P(Directive, D, "") { return arg.Directive == D; }
135146

136147
MATCHER_P2(Distance, File, D, "") {
137-
if (arg.getKey() != File)
138-
*result_listener << "file =" << arg.getKey().str();
139-
if (arg.getValue() != D)
140-
*result_listener << "distance =" << arg.getValue();
141-
return arg.getKey() == File && arg.getValue() == D;
148+
if (arg.getFirst() != File)
149+
*result_listener << "file =" << static_cast<unsigned>(arg.getFirst());
150+
if (arg.getSecond() != D)
151+
*result_listener << "distance =" << arg.getSecond();
152+
return arg.getFirst() == File && arg.getSecond() == D;
142153
}
143154

144155
TEST_F(HeadersTest, CollectRewrittenAndResolved) {
@@ -148,12 +159,14 @@ TEST_F(HeadersTest, CollectRewrittenAndResolved) {
148159
std::string BarHeader = testPath("sub/bar.h");
149160
FS.Files[BarHeader] = "";
150161

151-
EXPECT_THAT(collectIncludes().MainFileIncludes,
162+
auto Includes = collectIncludes();
163+
EXPECT_THAT(Includes.MainFileIncludes,
152164
UnorderedElementsAre(
153165
AllOf(Written("\"sub/bar.h\""), Resolved(BarHeader))));
154-
EXPECT_THAT(collectIncludes().includeDepth(MainFile),
155-
UnorderedElementsAre(Distance(MainFile, 0u),
156-
Distance(testPath("sub/bar.h"), 1u)));
166+
EXPECT_THAT(collectIncludes().includeDepth(getID(MainFile, Includes)),
167+
UnorderedElementsAre(
168+
Distance(getID(MainFile, Includes), 0u),
169+
Distance(getID(testPath("sub/bar.h"), Includes), 1u)));
157170
}
158171

159172
TEST_F(HeadersTest, OnlyCollectInclusionsInMain) {
@@ -166,17 +179,21 @@ TEST_F(HeadersTest, OnlyCollectInclusionsInMain) {
166179
FS.Files[MainFile] = R"cpp(
167180
#include "bar.h"
168181
)cpp";
182+
auto Includes = collectIncludes();
169183
EXPECT_THAT(
170184
collectIncludes().MainFileIncludes,
171185
UnorderedElementsAre(AllOf(Written("\"bar.h\""), Resolved(BarHeader))));
172-
EXPECT_THAT(collectIncludes().includeDepth(MainFile),
173-
UnorderedElementsAre(Distance(MainFile, 0u),
174-
Distance(testPath("sub/bar.h"), 1u),
175-
Distance(testPath("sub/baz.h"), 2u)));
186+
EXPECT_THAT(Includes.includeDepth(getID(MainFile, Includes)),
187+
UnorderedElementsAre(
188+
Distance(getID(MainFile, Includes), 0u),
189+
Distance(getID(testPath("sub/bar.h"), Includes), 1u),
190+
Distance(getID(testPath("sub/baz.h"), Includes), 2u)));
176191
// includeDepth() also works for non-main files.
177-
EXPECT_THAT(collectIncludes().includeDepth(testPath("sub/bar.h")),
178-
UnorderedElementsAre(Distance(testPath("sub/bar.h"), 0u),
179-
Distance(testPath("sub/baz.h"), 1u)));
192+
EXPECT_THAT(
193+
collectIncludes().includeDepth(getID(testPath("sub/bar.h"), Includes)),
194+
UnorderedElementsAre(
195+
Distance(getID(testPath("sub/bar.h"), Includes), 0u),
196+
Distance(getID(testPath("sub/baz.h"), Includes), 1u)));
180197
}
181198

182199
TEST_F(HeadersTest, PreambleIncludesPresentOnce) {
@@ -202,8 +219,32 @@ TEST_F(HeadersTest, UnResolvedInclusion) {
202219

203220
EXPECT_THAT(collectIncludes().MainFileIncludes,
204221
UnorderedElementsAre(AllOf(Written("\"foo.h\""), Resolved(""))));
205-
EXPECT_THAT(collectIncludes().includeDepth(MainFile),
206-
UnorderedElementsAre(Distance(MainFile, 0u)));
222+
EXPECT_THAT(collectIncludes().IncludeChildren, IsEmpty());
223+
}
224+
225+
TEST_F(HeadersTest, IncludedFilesGraph) {
226+
FS.Files[MainFile] = R"cpp(
227+
#include "bar.h"
228+
#include "foo.h"
229+
)cpp";
230+
std::string BarHeader = testPath("bar.h");
231+
FS.Files[BarHeader] = "";
232+
std::string FooHeader = testPath("foo.h");
233+
FS.Files[FooHeader] = R"cpp(
234+
#include "bar.h"
235+
#include "baz.h"
236+
)cpp";
237+
std::string BazHeader = testPath("baz.h");
238+
FS.Files[BazHeader] = "";
239+
240+
auto Includes = collectIncludes();
241+
llvm::DenseMap<IncludeStructure::HeaderID,
242+
SmallVector<IncludeStructure::HeaderID>>
243+
Expected = {{getID(MainFile, Includes),
244+
{getID(BarHeader, Includes), getID(FooHeader, Includes)}},
245+
{getID(FooHeader, Includes),
246+
{getID(BarHeader, Includes), getID(BazHeader, Includes)}}};
247+
EXPECT_EQ(Includes.IncludeChildren, Expected);
207248
}
208249

209250
TEST_F(HeadersTest, IncludeDirective) {

0 commit comments

Comments
 (0)