[clangd] Shard preamble symbols in dynamic index

Summary:
This reduces memory usage by dynamic index from more than 400MB to 32MB
when all files in clang-tools-extra/clangd/*.cpp are active in clangd.

Reviewers: sammccall

Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, usaxena95, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D77732
This commit is contained in:
Kadir Cetinkaya 2020-04-08 16:14:53 +02:00
parent 9bd6b77ac6
commit dffa9dfbda
No known key found for this signature in database
GPG Key ID: E39E36B8D2057ED6
6 changed files with 385 additions and 165 deletions

View File

@ -61,51 +61,6 @@ namespace clang {
namespace clangd {
namespace {
// Resolves URI to file paths with cache.
class URIToFileCache {
public:
URIToFileCache(llvm::StringRef HintPath) : HintPath(HintPath) {}
llvm::StringRef resolve(llvm::StringRef FileURI) {
auto I = URIToPathCache.try_emplace(FileURI);
if (I.second) {
auto Path = URI::resolve(FileURI, HintPath);
if (!Path) {
elog("Failed to resolve URI {0}: {1}", FileURI, Path.takeError());
assert(false && "Failed to resolve URI");
return "";
}
I.first->second = *Path;
}
return I.first->second;
}
private:
std::string HintPath;
llvm::StringMap<std::string> URIToPathCache;
};
// We keep only the node "U" and its edges. Any node other than "U" will be
// empty in the resultant graph.
IncludeGraph getSubGraph(const URI &U, const IncludeGraph &FullGraph) {
IncludeGraph IG;
std::string FileURI = U.toString();
auto Entry = IG.try_emplace(FileURI).first;
auto &Node = Entry->getValue();
Node = FullGraph.lookup(Entry->getKey());
Node.URI = Entry->getKey();
// URIs inside nodes must point into the keys of the same IncludeGraph.
for (auto &Include : Node.DirectIncludes) {
auto I = IG.try_emplace(Include).first;
I->getValue().URI = I->getKey();
Include = I->getKey();
}
return IG;
}
// We cannot use vfs->makeAbsolute because Cmd.FileName is either absolute or
// relative to Cmd.Directory, which might not be the same as current working
// directory.
@ -219,108 +174,44 @@ void BackgroundIndex::update(
llvm::StringRef MainFile, IndexFileIn Index,
const llvm::StringMap<ShardVersion> &ShardVersionsSnapshot,
bool HadErrors) {
// Partition symbols/references into files.
struct File {
llvm::DenseSet<const Symbol *> Symbols;
llvm::DenseSet<const Ref *> Refs;
llvm::DenseSet<const Relation *> Relations;
FileDigest Digest;
};
llvm::StringMap<File> Files;
URIToFileCache URICache(MainFile);
llvm::StringMap<FileDigest> FilesToUpdate;
for (const auto &IndexIt : *Index.Sources) {
const auto &IGN = IndexIt.getValue();
// Note that sources do not contain any information regarding missing
// headers, since we don't even know what absolute path they should fall in.
const auto AbsPath = URICache.resolve(IGN.URI);
auto AbsPath = llvm::cantFail(URI::resolve(IGN.URI, MainFile),
"Failed to resovle URI");
const auto DigestIt = ShardVersionsSnapshot.find(AbsPath);
// File has different contents, or indexing was successful this time.
if (DigestIt == ShardVersionsSnapshot.end() ||
DigestIt->getValue().Digest != IGN.Digest ||
(DigestIt->getValue().HadErrors && !HadErrors))
Files.try_emplace(AbsPath).first->getValue().Digest = IGN.Digest;
}
// This map is used to figure out where to store relations.
llvm::DenseMap<SymbolID, File *> SymbolIDToFile;
for (const auto &Sym : *Index.Symbols) {
if (Sym.CanonicalDeclaration) {
auto DeclPath = URICache.resolve(Sym.CanonicalDeclaration.FileURI);
const auto FileIt = Files.find(DeclPath);
if (FileIt != Files.end()) {
FileIt->second.Symbols.insert(&Sym);
SymbolIDToFile[Sym.ID] = &FileIt->second;
}
}
// For symbols with different declaration and definition locations, we store
// the full symbol in both the header file and the implementation file, so
// that merging can tell the preferred symbols (from canonical headers) from
// other symbols (e.g. forward declarations).
if (Sym.Definition &&
Sym.Definition.FileURI != Sym.CanonicalDeclaration.FileURI) {
auto DefPath = URICache.resolve(Sym.Definition.FileURI);
const auto FileIt = Files.find(DefPath);
if (FileIt != Files.end())
FileIt->second.Symbols.insert(&Sym);
}
}
llvm::DenseMap<const Ref *, SymbolID> RefToIDs;
for (const auto &SymRefs : *Index.Refs) {
for (const auto &R : SymRefs.second) {
auto Path = URICache.resolve(R.Location.FileURI);
const auto FileIt = Files.find(Path);
if (FileIt != Files.end()) {
auto &F = FileIt->getValue();
RefToIDs[&R] = SymRefs.first;
F.Refs.insert(&R);
}
}
}
for (const auto &Rel : *Index.Relations) {
const auto FileIt = SymbolIDToFile.find(Rel.Subject);
if (FileIt != SymbolIDToFile.end())
FileIt->second->Relations.insert(&Rel);
FilesToUpdate[AbsPath] = IGN.Digest;
}
// Shard slabs into files.
FileShardedIndex ShardedIndex(std::move(Index), MainFile);
// Build and store new slabs for each updated file.
for (const auto &FileIt : Files) {
llvm::StringRef Path = FileIt.getKey();
SymbolSlab::Builder Syms;
RefSlab::Builder Refs;
RelationSlab::Builder Relations;
for (const auto *S : FileIt.second.Symbols)
Syms.insert(*S);
for (const auto *R : FileIt.second.Refs)
Refs.insert(RefToIDs[R], *R);
for (const auto *Rel : FileIt.second.Relations)
Relations.insert(*Rel);
auto SS = std::make_unique<SymbolSlab>(std::move(Syms).build());
auto RS = std::make_unique<RefSlab>(std::move(Refs).build());
auto RelS = std::make_unique<RelationSlab>(std::move(Relations).build());
auto IG = std::make_unique<IncludeGraph>(
getSubGraph(URI::create(Path), Index.Sources.getValue()));
for (const auto &FileIt : FilesToUpdate) {
PathRef Path = FileIt.first();
auto IF = ShardedIndex.getShard(Path);
// Only store command line hash for main files of the TU, since our
// current model keeps only one version of a header file.
if (Path != MainFile)
IF.Cmd.reset();
// We need to store shards before updating the index, since the latter
// consumes slabs.
// FIXME: Also skip serializing the shard if it is already up-to-date.
BackgroundIndexStorage *IndexStorage = IndexStorageFactory(Path);
IndexFileOut Shard;
Shard.Symbols = SS.get();
Shard.Refs = RS.get();
Shard.Relations = RelS.get();
Shard.Sources = IG.get();
// Only store command line hash for main files of the TU, since our
// current model keeps only one version of a header file.
if (Path == MainFile)
Shard.Cmd = Index.Cmd.getPointer();
if (auto Error = IndexStorage->storeShard(Path, Shard))
if (auto Error = IndexStorageFactory(Path)->storeShard(Path, IF))
elog("Failed to write background-index shard for file {0}: {1}", Path,
std::move(Error));
{
std::lock_guard<std::mutex> Lock(ShardVersionsMu);
auto Hash = FileIt.second.Digest;
const auto &Hash = FileIt.getValue();
auto DigestIt = ShardVersions.try_emplace(Path);
ShardVersion &SV = DigestIt.first->second;
// Skip if file is already up to date, unless previous index was broken
@ -333,8 +224,11 @@ void BackgroundIndex::update(
// This can override a newer version that is added in another thread, if
// this thread sees the older version but finishes later. This should be
// rare in practice.
IndexedSymbols.update(Path, std::move(SS), std::move(RS), std::move(RelS),
Path == MainFile);
IndexedSymbols.update(
Path, std::make_unique<SymbolSlab>(std::move(*IF.Symbols)),
std::make_unique<RefSlab>(std::move(*IF.Refs)),
std::make_unique<RelationSlab>(std::move(*IF.Relations)),
Path == MainFile);
}
}
}

View File

@ -10,11 +10,17 @@
#include "CollectMacros.h"
#include "Logger.h"
#include "ParsedAST.h"
#include "Path.h"
#include "SymbolCollector.h"
#include "index/CanonicalIncludes.h"
#include "index/Index.h"
#include "index/MemIndex.h"
#include "index/Merge.h"
#include "index/Ref.h"
#include "index/Relation.h"
#include "index/Serialization.h"
#include "index/Symbol.h"
#include "index/SymbolID.h"
#include "index/SymbolOrigin.h"
#include "index/dex/Dex.h"
#include "clang/AST/ASTContext.h"
@ -23,19 +29,24 @@
#include "clang/Lex/MacroInfo.h"
#include "clang/Lex/Preprocessor.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
#include <memory>
#include <tuple>
#include <utility>
#include <vector>
namespace clang {
namespace clangd {
namespace {
static SlabTuple indexSymbols(ASTContext &AST, std::shared_ptr<Preprocessor> PP,
llvm::ArrayRef<Decl *> DeclsToIndex,
const MainFileMacros *MacroRefsToIndex,
const CanonicalIncludes &Includes,
bool IsIndexMainAST, llvm::StringRef Version) {
SlabTuple indexSymbols(ASTContext &AST, std::shared_ptr<Preprocessor> PP,
llvm::ArrayRef<Decl *> DeclsToIndex,
const MainFileMacros *MacroRefsToIndex,
const CanonicalIncludes &Includes, bool IsIndexMainAST,
llvm::StringRef Version) {
SymbolCollector::Options CollectorOpts;
CollectorOpts.CollectIncludePath = true;
CollectorOpts.Includes = &Includes;
@ -85,6 +96,131 @@ static SlabTuple indexSymbols(ASTContext &AST, std::shared_ptr<Preprocessor> PP,
std::move(Relations));
}
// Resolves URI to file paths with cache.
class URIToFileCache {
public:
URIToFileCache(PathRef HintPath) : HintPath(HintPath) {}
llvm::StringRef operator[](llvm::StringRef FileURI) {
if (FileURI.empty())
return "";
auto I = URIToPathCache.try_emplace(FileURI);
if (I.second) {
I.first->second = llvm::cantFail(URI::resolve(FileURI, HintPath),
"Failed to resolve URI");
}
return I.first->second;
}
private:
PathRef HintPath;
llvm::StringMap<std::string> URIToPathCache;
};
// We keep only the node "U" and its edges. Any node other than "U" will be
// empty in the resultant graph.
IncludeGraph getSubGraph(llvm::StringRef URI, const IncludeGraph &FullGraph) {
IncludeGraph IG;
auto Entry = IG.try_emplace(URI).first;
auto &Node = Entry->getValue();
Node = FullGraph.lookup(Entry->getKey());
Node.URI = Entry->getKey();
// URIs inside nodes must point into the keys of the same IncludeGraph.
for (auto &Include : Node.DirectIncludes) {
auto I = IG.try_emplace(Include).first;
I->getValue().URI = I->getKey();
Include = I->getKey();
}
return IG;
}
} // namespace
FileShardedIndex::FileShardedIndex(IndexFileIn Input, PathRef HintPath)
: Index(std::move(Input)) {
URIToFileCache UriToFile(HintPath);
// Used to build RelationSlabs.
llvm::DenseMap<SymbolID, FileShard *> SymbolIDToFile;
// Attribute each Symbol to both their declaration and definition locations.
if (Index.Symbols) {
for (const auto &S : *Index.Symbols) {
auto File = UriToFile[S.CanonicalDeclaration.FileURI];
auto It = Shards.try_emplace(File);
It.first->getValue().Symbols.insert(&S);
SymbolIDToFile[S.ID] = &It.first->getValue();
// Only bother if definition file is different than declaration file.
if (S.Definition &&
S.Definition.FileURI != S.CanonicalDeclaration.FileURI) {
auto File = UriToFile[S.Definition.FileURI];
auto It = Shards.try_emplace(File);
It.first->getValue().Symbols.insert(&S);
}
}
}
// Attribute references into each file they occured in.
if (Index.Refs) {
for (const auto &SymRefs : *Index.Refs) {
for (const auto &R : SymRefs.second) {
auto File = UriToFile[R.Location.FileURI];
const auto It = Shards.try_emplace(File);
It.first->getValue().Refs.insert(&R);
RefToSymID[&R] = SymRefs.first;
}
}
}
// Attribute relations to the file declaraing their Subject as Object might
// not have been indexed, see SymbolCollector::processRelations for details.
if (Index.Relations) {
for (const auto &R : *Index.Relations) {
auto *File = SymbolIDToFile.lookup(R.Subject);
assert(File && "unknown subject in relation");
File->Relations.insert(&R);
}
}
// Store only the direct includes of a file in a shard.
if (Index.Sources) {
const auto &FullGraph = *Index.Sources;
for (const auto &It : FullGraph) {
auto File = UriToFile[It.first()];
auto ShardIt = Shards.try_emplace(File);
ShardIt.first->getValue().IG = getSubGraph(It.first(), FullGraph);
}
}
}
std::vector<PathRef> FileShardedIndex::getAllFiles() const {
return {Shards.keys().begin(), Shards.keys().end()};
}
IndexFileIn FileShardedIndex::getShard(PathRef File) const {
auto It = Shards.find(File);
assert(It != Shards.end() && "received unknown file");
IndexFileIn IF;
IF.Sources = It->getValue().IG;
IF.Cmd = Index.Cmd;
SymbolSlab::Builder SymB;
for (const auto *S : It->getValue().Symbols)
SymB.insert(*S);
IF.Symbols = std::move(SymB).build();
RefSlab::Builder RefB;
for (const auto *Ref : It->getValue().Refs) {
auto SID = RefToSymID.lookup(Ref);
RefB.insert(SID, *Ref);
}
IF.Refs = std::move(RefB).build();
RelationSlab::Builder RelB;
for (const auto *Rel : It->getValue().Relations) {
RelB.insert(*Rel);
}
IF.Relations = std::move(RelB).build();
return IF;
}
SlabTuple indexMainDecls(ParsedAST &AST) {
return indexSymbols(AST.getASTContext(), AST.getPreprocessorPtr(),
AST.getLocalTopLevelDecls(), &AST.getMacros(),
@ -254,15 +390,24 @@ void FileIndex::updatePreamble(PathRef Path, llvm::StringRef Version,
ASTContext &AST,
std::shared_ptr<Preprocessor> PP,
const CanonicalIncludes &Includes) {
auto Slabs = indexHeaderSymbols(Version, AST, std::move(PP), Includes);
PreambleSymbols.update(
Path, std::make_unique<SymbolSlab>(std::move(std::get<0>(Slabs))),
std::make_unique<RefSlab>(),
std::make_unique<RelationSlab>(std::move(std::get<2>(Slabs))),
/*CountReferences=*/false);
IndexFileIn IF;
std::tie(IF.Symbols, std::ignore, IF.Relations) =
indexHeaderSymbols(Version, AST, std::move(PP), Includes);
FileShardedIndex ShardedIndex(std::move(IF), Path);
for (PathRef File : ShardedIndex.getAllFiles()) {
auto IF = ShardedIndex.getShard(File);
PreambleSymbols.update(
File, std::make_unique<SymbolSlab>(std::move(*IF.Symbols)),
std::make_unique<RefSlab>(),
std::make_unique<RelationSlab>(std::move(*IF.Relations)),
/*CountReferences=*/false);
}
PreambleIndex.reset(
PreambleSymbols.buildIndex(UseDex ? IndexType::Heavy : IndexType::Light,
DuplicateHandling::PickOne));
vlog("Build dynamic index for header symbols with estimated memory usage of "
"{0} bytes",
PreambleIndex.estimateMemoryUsage());
}
void FileIndex::updateMain(PathRef Path, ParsedAST &AST) {
@ -274,6 +419,9 @@ void FileIndex::updateMain(PathRef Path, ParsedAST &AST) {
/*CountReferences=*/true);
MainFileIndex.reset(
MainFileSymbols.buildIndex(IndexType::Light, DuplicateHandling::Merge));
vlog("Build dynamic index for main-file symbols with estimated memory usage "
"of {0} bytes",
MainFileIndex.estimateMemoryUsage());
}
} // namespace clangd

View File

@ -15,14 +15,24 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_FILEINDEX_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_FILEINDEX_H
#include "Headers.h"
#include "Index.h"
#include "MemIndex.h"
#include "Merge.h"
#include "Path.h"
#include "index/CanonicalIncludes.h"
#include "index/Ref.h"
#include "index/Relation.h"
#include "index/Serialization.h"
#include "index/Symbol.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Tooling/CompilationDatabase.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include <memory>
#include <vector>
namespace clang {
class ASTContext;
@ -109,17 +119,15 @@ public:
private:
bool UseDex; // FIXME: this should be always on.
// Contains information from each file's preamble only.
// These are large, but update fairly infrequently (preambles are stable).
// Contains information from each file's preamble only. Symbols and relations
// are sharded per declaration file to deduplicate multiple symbols and reduce
// memory usage.
// Missing information:
// - symbol refs (these are always "from the main file")
// - definition locations in the main file
//
// FIXME: Because the preambles for different TUs have large overlap and
// FileIndex doesn't deduplicate, this uses lots of extra RAM.
// The biggest obstacle in fixing this: the obvious approach of partitioning
// by declaring file (rather than main file) fails if headers provide
// different symbols based on preprocessor state.
// Note that we store only one version of a header, hence symbols appearing in
// different PP states will be missing.
FileSymbols PreambleSymbols;
SwapIndex PreambleIndex;
@ -146,6 +154,43 @@ SlabTuple indexHeaderSymbols(llvm::StringRef Version, ASTContext &AST,
std::shared_ptr<Preprocessor> PP,
const CanonicalIncludes &Includes);
/// Takes slabs coming from a TU (multiple files) and shards them per
/// declaration location.
struct FileShardedIndex {
/// \p HintPath is used to convert file URIs stored in symbols into absolute
/// paths.
explicit FileShardedIndex(IndexFileIn Input, PathRef HintPath);
/// Returns absolute paths for all files that has a shard.
std::vector<PathRef> getAllFiles() const;
/// Generates index shard for the \p File. Note that this function results in
/// a copy of all the relevant data.
/// Returned index will always have Symbol/Refs/Relation Slabs set, even if
/// they are empty.
IndexFileIn getShard(PathRef File) const;
private:
// Contains all the information that belongs to a single file.
struct FileShard {
// Either declared or defined in the file.
llvm::DenseSet<const Symbol *> Symbols;
// Reference occurs in the file.
llvm::DenseSet<const Ref *> Refs;
// Subject is declared in the file.
llvm::DenseSet<const Relation *> Relations;
// Contains edges for only the direct includes.
IncludeGraph IG;
};
// Keeps all the information alive.
const IndexFileIn Index;
// Mapping from absolute paths to slab information.
llvm::StringMap<FileShard> Shards;
// Used to build RefSlabs.
llvm::DenseMap<const Ref *, SymbolID> RefToSymID;
};
} // namespace clangd
} // namespace clang

View File

@ -283,6 +283,18 @@ bool SymbolCollector::handleDeclOccurrence(
if (!ID)
return true;
// ND is the canonical (i.e. first) declaration. If it's in the main file
// (which is not a header), then no public declaration was visible, so assume
// it's main-file only.
bool IsMainFileOnly =
SM.isWrittenInMainFile(SM.getExpansionLoc(ND->getBeginLoc())) &&
!isHeaderFile(SM.getFileEntryForID(SM.getMainFileID())->getName(),
ASTCtx->getLangOpts());
// In C, printf is a redecl of an implicit builtin! So check OrigD instead.
if (ASTNode.OrigD->isImplicit() ||
!shouldCollectSymbol(*ND, *ASTCtx, Opts, IsMainFileOnly))
return true;
// Note: we need to process relations for all decl occurrences, including
// refs, because the indexing code only populates relations for specific
// occurrences. For example, RelationBaseOf is only populated for the
@ -297,17 +309,6 @@ bool SymbolCollector::handleDeclOccurrence(
if (IsOnlyRef && !CollectRef)
return true;
// ND is the canonical (i.e. first) declaration. If it's in the main file
// (which is not a header), then no public declaration was visible, so assume
// it's main-file only.
bool IsMainFileOnly =
SM.isWrittenInMainFile(SM.getExpansionLoc(ND->getBeginLoc())) &&
!isHeaderFile(SM.getFileEntryForID(SM.getMainFileID())->getName(),
ASTCtx->getLangOpts());
// In C, printf is a redecl of an implicit builtin! So check OrigD instead.
if (ASTNode.OrigD->isImplicit() ||
!shouldCollectSymbol(*ND, *ASTCtx, Opts, IsMainFileOnly))
return true;
// Do not store references to main-file symbols.
// Unlike other fields, e.g. Symbols (which use spelling locations), we use
// file locations for references (as it aligns the behavior of clangd's

View File

@ -9,13 +9,19 @@
#include "AST.h"
#include "Annotations.h"
#include "Compiler.h"
#include "Headers.h"
#include "ParsedAST.h"
#include "SyncAPI.h"
#include "TestFS.h"
#include "TestTU.h"
#include "URI.h"
#include "index/CanonicalIncludes.h"
#include "index/FileIndex.h"
#include "index/Index.h"
#include "index/Ref.h"
#include "index/Relation.h"
#include "index/Serialization.h"
#include "index/Symbol.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Frontend/Utils.h"
#include "clang/Index/IndexSymbol.h"
@ -23,6 +29,7 @@
#include "clang/Tooling/CompilationDatabase.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <utility>
using ::testing::_;
using ::testing::AllOf;
@ -151,8 +158,9 @@ void update(FileIndex &M, llvm::StringRef Basename, llvm::StringRef Code) {
File.HeaderFilename = (Basename + ".h").str();
File.HeaderCode = std::string(Code);
auto AST = File.build();
M.updatePreamble(File.Filename, /*Version=*/"null", AST.getASTContext(),
AST.getPreprocessorPtr(), AST.getCanonicalIncludes());
M.updatePreamble(testPath(File.Filename), /*Version=*/"null",
AST.getASTContext(), AST.getPreprocessorPtr(),
AST.getCanonicalIncludes());
}
TEST(FileIndexTest, CustomizedURIScheme) {
@ -393,8 +401,9 @@ TEST(FileIndexTest, Relations) {
TU.HeaderCode = "class A {}; class B : public A {};";
auto AST = TU.build();
FileIndex Index;
Index.updatePreamble(TU.Filename, /*Version=*/"null", AST.getASTContext(),
AST.getPreprocessorPtr(), AST.getCanonicalIncludes());
Index.updatePreamble(testPath(TU.Filename), /*Version=*/"null",
AST.getASTContext(), AST.getPreprocessorPtr(),
AST.getCanonicalIncludes());
SymbolID A = findSymbol(TU.headerSymbols(), "A").ID;
uint32_t Results = 0;
RelationsRequest Req;
@ -477,6 +486,128 @@ TEST(FileSymbolsTest, CountReferencesWithRefSlabs) {
AllOf(QName("2"), NumReferences(1u)),
AllOf(QName("3"), NumReferences(1u))));
}
TEST(FileIndexTest, StalePreambleSymbolsDeleted) {
FileIndex M;
TestTU File;
File.HeaderFilename = "a.h";
File.Filename = "f1.cpp";
File.HeaderCode = "int a;";
auto AST = File.build();
M.updatePreamble(testPath(File.Filename), /*Version=*/"null",
AST.getASTContext(), AST.getPreprocessorPtr(),
AST.getCanonicalIncludes());
EXPECT_THAT(runFuzzyFind(M, ""), UnorderedElementsAre(QName("a")));
File.Filename = "f2.cpp";
File.HeaderCode = "int b;";
AST = File.build();
M.updatePreamble(testPath(File.Filename), /*Version=*/"null",
AST.getASTContext(), AST.getPreprocessorPtr(),
AST.getCanonicalIncludes());
EXPECT_THAT(runFuzzyFind(M, ""), UnorderedElementsAre(QName("b")));
}
TEST(FileShardedIndexTest, Sharding) {
auto AHeaderUri = URI::create(testPath("a.h")).toString();
auto BHeaderUri = URI::create(testPath("b.h")).toString();
auto BSourceUri = URI::create(testPath("b.cc")).toString();
auto Sym1 = symbol("1");
Sym1.CanonicalDeclaration.FileURI = AHeaderUri.c_str();
auto Sym2 = symbol("2");
Sym2.CanonicalDeclaration.FileURI = BHeaderUri.c_str();
Sym2.Definition.FileURI = BSourceUri.c_str();
IndexFileIn IF;
{
SymbolSlab::Builder B;
// Should be stored in only a.h
B.insert(Sym1);
// Should be stored in both b.h and b.cc
B.insert(Sym2);
IF.Symbols = std::move(B).build();
}
{
// Should be stored in b.cc
IF.Refs = std::move(*refSlab(Sym1.ID, BSourceUri.c_str()).release());
}
{
RelationSlab::Builder B;
// Should be stored in a.h
B.insert(Relation{Sym1.ID, RelationKind::BaseOf, Sym2.ID});
// Should be stored in b.h
B.insert(Relation{Sym2.ID, RelationKind::BaseOf, Sym1.ID});
IF.Relations = std::move(B).build();
}
IF.Sources.emplace();
IncludeGraph &IG = *IF.Sources;
{
// b.cc includes b.h
auto &Node = IG[BSourceUri];
Node.DirectIncludes = {BHeaderUri};
Node.URI = BSourceUri;
}
{
// b.h includes a.h
auto &Node = IG[BHeaderUri];
Node.DirectIncludes = {AHeaderUri};
Node.URI = BHeaderUri;
}
{
// a.h includes nothing.
auto &Node = IG[AHeaderUri];
Node.DirectIncludes = {};
Node.URI = AHeaderUri;
}
IF.Cmd = tooling::CompileCommand(testRoot(), "b.cc", {"clang"}, "out");
FileShardedIndex ShardedIndex(std::move(IF), testPath("b.cc"));
ASSERT_THAT(
ShardedIndex.getAllFiles(),
UnorderedElementsAre(testPath("a.h"), testPath("b.h"), testPath("b.cc")));
{
auto Shard = ShardedIndex.getShard(testPath("a.h"));
EXPECT_THAT(Shard.Symbols.getValue(), UnorderedElementsAre(QName("1")));
EXPECT_THAT(Shard.Refs.getValue(), IsEmpty());
EXPECT_THAT(
Shard.Relations.getValue(),
UnorderedElementsAre(Relation{Sym1.ID, RelationKind::BaseOf, Sym2.ID}));
ASSERT_THAT(Shard.Sources.getValue().keys(),
UnorderedElementsAre(AHeaderUri));
EXPECT_THAT(Shard.Sources->lookup(AHeaderUri).DirectIncludes, IsEmpty());
EXPECT_TRUE(Shard.Cmd.hasValue());
}
{
auto Shard = ShardedIndex.getShard(testPath("b.h"));
EXPECT_THAT(Shard.Symbols.getValue(), UnorderedElementsAre(QName("2")));
EXPECT_THAT(Shard.Refs.getValue(), IsEmpty());
EXPECT_THAT(
Shard.Relations.getValue(),
UnorderedElementsAre(Relation{Sym2.ID, RelationKind::BaseOf, Sym1.ID}));
ASSERT_THAT(Shard.Sources.getValue().keys(),
UnorderedElementsAre(BHeaderUri, AHeaderUri));
EXPECT_THAT(Shard.Sources->lookup(BHeaderUri).DirectIncludes,
UnorderedElementsAre(AHeaderUri));
EXPECT_TRUE(Shard.Cmd.hasValue());
}
{
auto Shard = ShardedIndex.getShard(testPath("b.cc"));
EXPECT_THAT(Shard.Symbols.getValue(), UnorderedElementsAre(QName("2")));
EXPECT_THAT(Shard.Refs.getValue(), UnorderedElementsAre(Pair(Sym1.ID, _)));
EXPECT_THAT(Shard.Relations.getValue(), IsEmpty());
ASSERT_THAT(Shard.Sources.getValue().keys(),
UnorderedElementsAre(BSourceUri, BHeaderUri));
EXPECT_THAT(Shard.Sources->lookup(BSourceUri).DirectIncludes,
UnorderedElementsAre(BHeaderUri));
EXPECT_TRUE(Shard.Cmd.hasValue());
}
}
} // namespace
} // namespace clangd
} // namespace clang

View File

@ -116,9 +116,10 @@ SymbolSlab TestTU::headerSymbols() const {
std::unique_ptr<SymbolIndex> TestTU::index() const {
auto AST = build();
auto Idx = std::make_unique<FileIndex>(/*UseDex=*/true);
Idx->updatePreamble(Filename, /*Version=*/"null", AST.getASTContext(),
AST.getPreprocessorPtr(), AST.getCanonicalIncludes());
Idx->updateMain(Filename, AST);
Idx->updatePreamble(testPath(Filename), /*Version=*/"null",
AST.getASTContext(), AST.getPreprocessorPtr(),
AST.getCanonicalIncludes());
Idx->updateMain(testPath(Filename), AST);
return std::move(Idx);
}