diff --git a/clang-tools-extra/clangd/index/Background.cpp b/clang-tools-extra/clangd/index/Background.cpp --- a/clang-tools-extra/clangd/index/Background.cpp +++ b/clang-tools-extra/clangd/index/Background.cpp @@ -25,6 +25,7 @@ #include "index/SymbolCollector.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" +#include "llvm/ADT/Hashing.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/StringMap.h" @@ -373,6 +374,11 @@ 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.CmdHash = Index.CmdHash.getPointer(); + if (auto Error = IndexStorage->storeShard(Path, Shard)) elog("Failed to write background-index shard for file {0}: {1}", Path, std::move(Error)); @@ -425,6 +431,9 @@ BackgroundIndexStorage *IndexStorage) { trace::Span Tracer("BackgroundIndex"); SPAN_ATTACH(Tracer, "file", Cmd.Filename); + size_t CmdHash = + llvm::hash_combine(Cmd.Filename, llvm::join(Cmd.CommandLine, ""), + Cmd.Directory, Cmd.Heuristic, Cmd.Output); auto AbsolutePath = getAbsolutePath(Cmd); auto FS = FSProvider.getFileSystem(); @@ -479,6 +488,7 @@ Action->EndSourceFile(); + Index.CmdHash = CmdHash; assert(Index.Symbols && Index.Refs && Index.Sources && "Symbols, Refs and Sources must be set."); diff --git a/clang-tools-extra/clangd/index/Serialization.h b/clang-tools-extra/clangd/index/Serialization.h --- a/clang-tools-extra/clangd/index/Serialization.h +++ b/clang-tools-extra/clangd/index/Serialization.h @@ -44,6 +44,7 @@ llvm::Optional Relations; // Keys are URIs of the source files. llvm::Optional Sources; + llvm::Optional CmdHash; }; // Parse an index file. The input must be a RIFF or YAML file. llvm::Expected readIndexFile(llvm::StringRef); @@ -57,13 +58,15 @@ const IncludeGraph *Sources = nullptr; // TODO: Support serializing Dex posting lists. IndexFileFormat Format = IndexFileFormat::RIFF; + const size_t *CmdHash = nullptr; IndexFileOut() = default; IndexFileOut(const IndexFileIn &I) : Symbols(I.Symbols ? I.Symbols.getPointer() : nullptr), Refs(I.Refs ? I.Refs.getPointer() : nullptr), Relations(I.Relations ? I.Relations.getPointer() : nullptr), - Sources(I.Sources ? I.Sources.getPointer() : nullptr) {} + Sources(I.Sources ? I.Sources.getPointer() : nullptr), + CmdHash(I.CmdHash ? I.CmdHash.getPointer() : nullptr) {} }; // Serializes an index file. llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const IndexFileOut &O); diff --git a/clang-tools-extra/clangd/index/Serialization.cpp b/clang-tools-extra/clangd/index/Serialization.cpp --- a/clang-tools-extra/clangd/index/Serialization.cpp +++ b/clang-tools-extra/clangd/index/Serialization.cpp @@ -13,6 +13,7 @@ #include "SymbolOrigin.h" #include "Trace.h" #include "dex/Dex.h" +#include "llvm/ADT/StringRef.h" #include "llvm/Support/Compression.h" #include "llvm/Support/Endian.h" #include "llvm/Support/Error.h" @@ -415,7 +416,7 @@ // The current versioning scheme is simple - non-current versions are rejected. // If you make a breaking change, bump this version number to invalidate stored // data. Later we may want to support some backward compatibility. -constexpr static uint32_t Version = 10; +constexpr static uint32_t Version = 11; llvm::Expected readRIFF(llvm::StringRef Data) { auto RIFF = riff::readFile(Data); @@ -490,6 +491,12 @@ return makeError("malformed or truncated relations"); Result.Relations = std::move(Relations).build(); } + if (Chunks.count("cmdl")) { + Reader Cmd(Chunks.lookup("cmdl")); + if (Cmd.err()) + return makeError("malformed or truncated commandline section"); + Result.CmdHash = Cmd.consume32(); + } return std::move(Result); } @@ -592,6 +599,15 @@ RIFF.Chunks.push_back({riff::fourCC("srcs"), SrcsSection}); } + llvm::SmallString<4> Cmd; + if (Data.CmdHash) { + { + llvm::raw_svector_ostream CmdOS(Cmd); + write32(llvm::hash_combine(*Data.CmdHash), CmdOS); + } + RIFF.Chunks.push_back({riff::fourCC("cmdl"), Cmd}); + } + OS << RIFF; } diff --git a/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp b/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp --- a/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp +++ b/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp @@ -526,5 +526,40 @@ "unittest:///B.h")); } +TEST_F(BackgroundIndexTest, CmdLineHash) { + MockFSProvider FS; + llvm::StringMap Storage; + size_t CacheHits = 0; + MemoryShardStorage MSS(Storage, CacheHits); + OverlayCDB CDB(/*Base=*/nullptr); + BackgroundIndex Idx(Context::empty(), FS, CDB, + [&](llvm::StringRef) { return &MSS; }); + + tooling::CompileCommand Cmd; + FS.Files[testPath("A.cc")] = "#include \"A.h\""; + FS.Files[testPath("A.h")] = ""; + Cmd.Filename = "../A.cc"; + Cmd.Directory = testPath("build"); + Cmd.CommandLine = {"clang++", "../A.cc"}; + CDB.setCompileCommand(testPath("build/../A.cc"), Cmd); + ASSERT_TRUE(Idx.blockUntilIdleForTest()); + + // Make sure we only store the shard for main file. + EXPECT_THAT(Storage.keys(), ElementsAre(testPath("A.cc"), testPath("A.h"))); + size_t HashInitial = *MSS.loadShard(testPath("A.cc"))->CmdHash; + EXPECT_FALSE(MSS.loadShard(testPath("A.h"))->CmdHash); + + // FIXME: Changing compile commands should be enough to invalidate the cache. + FS.Files[testPath("A.cc")] = " "; + Cmd.CommandLine = {"clang++", "../A.cc", "-Dfoo"}; + CDB.setCompileCommand(testPath("build/../A.cc"), Cmd); + ASSERT_TRUE(Idx.blockUntilIdleForTest()); + + EXPECT_THAT(Storage.keys(), ElementsAre(testPath("A.cc"), testPath("A.h"))); + size_t HashSecondary = *MSS.loadShard(testPath("A.cc"))->CmdHash; + EXPECT_NE(HashInitial, HashSecondary); + EXPECT_FALSE(MSS.loadShard(testPath("A.h"))->CmdHash); +} + } // namespace clangd } // namespace clang