ioeric updated this revision to Diff 166667.
ioeric added a comment.

- update comment


Repository:
  rCTE Clang Tools Extra

https://reviews.llvm.org/D52419

Files:
  clangd/ClangdServer.cpp
  clangd/ClangdUnit.cpp
  clangd/ClangdUnit.h
  clangd/CodeComplete.cpp
  clangd/CodeComplete.h
  unittests/clangd/ClangdTests.cpp

Index: unittests/clangd/ClangdTests.cpp
===================================================================
--- unittests/clangd/ClangdTests.cpp
+++ unittests/clangd/ClangdTests.cpp
@@ -963,6 +963,89 @@
                                        Field(&CodeCompletion::Name, "baz")));
 }
 
+TEST(ClangdTests, PreambleVFSStatCache) {
+  class ListenStatsFSProvider : public FileSystemProvider {
+  public:
+    ListenStatsFSProvider(llvm::StringMap<unsigned> &CountStats)
+        : CountStats(CountStats) {}
+
+    IntrusiveRefCntPtr<vfs::FileSystem> getFileSystem() override {
+      class ListenStatVFS : public vfs::FileSystem {
+      public:
+        ListenStatVFS(IntrusiveRefCntPtr<vfs::FileSystem> FS,
+                      llvm::StringMap<unsigned> &CountStats)
+            : FS(std::move(FS)), CountStats(CountStats) {}
+
+        vfs::directory_iterator dir_begin(const Twine &Dir,
+                                          std::error_code &EC) override {
+          return FS->dir_begin(Dir, EC);
+        }
+        std::error_code setCurrentWorkingDirectory(const Twine &Path) override {
+          return FS->setCurrentWorkingDirectory(Path);
+        }
+        llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override {
+          return FS->getCurrentWorkingDirectory();
+        }
+        std::error_code
+        getRealPath(const Twine &Path,
+                    SmallVectorImpl<char> &Output) const override {
+          return FS->getRealPath(Path, Output);
+        }
+
+        llvm::ErrorOr<std::unique_ptr<vfs::File>>
+        openFileForRead(const Twine &Path) override {
+          return FS->openFileForRead(Path);
+        }
+
+        llvm::ErrorOr<vfs::Status> status(const Twine &Path) override {
+          auto I =
+              CountStats.try_emplace(llvm::sys::path::filename(Path.str()), 1);
+          if (!I.second)
+            I.first->second += 1;
+          return FS->status(Path);
+        }
+
+      private:
+        IntrusiveRefCntPtr<vfs::FileSystem> FS;
+        llvm::StringMap<unsigned> &CountStats;
+      };
+
+      return IntrusiveRefCntPtr<ListenStatVFS>(
+          new ListenStatVFS(buildTestFS(Files), CountStats));
+    }
+
+    // If relative paths are used, they are resolved with testPath().
+    llvm::StringMap<std::string> Files;
+    llvm::StringMap<unsigned> &CountStats;
+  };
+
+  llvm::StringMap<unsigned> CountStats;
+  ListenStatsFSProvider FS(CountStats);
+  ErrorCheckingDiagConsumer DiagConsumer;
+  MockCompilationDatabase CDB;
+  ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest());
+
+  auto SourcePath = testPath("foo.cpp");
+  auto HeaderPath = testPath("foo.h");
+  FS.Files[HeaderPath] = "struct TestSym {};";
+  Annotations Code(R"cpp(
+    #include "foo.h"
+
+    int main() {
+      TestSy^
+    })cpp");
+
+  runAddDocument(Server, SourcePath, Code.code());
+
+  unsigned Before = CountStats["foo.h"];
+  auto Completions = cantFail(runCodeComplete(Server, SourcePath, Code.point(),
+                                              clangd::CodeCompleteOptions()))
+                         .Completions;
+  EXPECT_EQ(CountStats["foo.h"], Before);
+  EXPECT_THAT(Completions,
+              ElementsAre(Field(&CodeCompletion::Name, "TestSym")));
+}
+
 } // namespace
 } // namespace clangd
 } // namespace clang
Index: clangd/CodeComplete.h
===================================================================
--- clangd/CodeComplete.h
+++ clangd/CodeComplete.h
@@ -20,6 +20,7 @@
 #include "Logger.h"
 #include "Path.h"
 #include "Protocol.h"
+#include "TUScheduler.h"
 #include "index/Index.h"
 #include "clang/Frontend/PrecompiledPreamble.h"
 #include "clang/Sema/CodeCompleteConsumer.h"
@@ -212,11 +213,8 @@
 /// the speculative result is used by code completion (e.g. speculation failed),
 /// the speculative result is not consumed, and `SpecFuzzyFind` is only
 /// destroyed when the async request finishes.
-CodeCompleteResult codeComplete(PathRef FileName,
-                                const tooling::CompileCommand &Command,
-                                PrecompiledPreamble const *Preamble,
-                                const IncludeStructure &PreambleInclusions,
-                                StringRef Contents, Position Pos,
+CodeCompleteResult codeComplete(PathRef FileName, Position Pos,
+                                const InputsAndPreamble &Input,
                                 IntrusiveRefCntPtr<vfs::FileSystem> VFS,
                                 std::shared_ptr<PCHContainerOperations> PCHs,
                                 CodeCompleteOptions Opts,
Index: clangd/CodeComplete.cpp
===================================================================
--- clangd/CodeComplete.cpp
+++ clangd/CodeComplete.cpp
@@ -1000,7 +1000,8 @@
 struct SemaCompleteInput {
   PathRef FileName;
   const tooling::CompileCommand &Command;
-  PrecompiledPreamble const *Preamble;
+  const PrecompiledPreamble *Preamble;
+  const PreambleFileStatusCache *PreambleStatCache;
   StringRef Contents;
   Position Pos;
   IntrusiveRefCntPtr<vfs::FileSystem> VFS;
@@ -1024,12 +1025,16 @@
     // working dirs.
   }
 
+  IntrusiveRefCntPtr<vfs::FileSystem> VFS = Input.VFS;
+  if (Input.PreambleStatCache)
+    VFS = createVFSWithPreambleStatCache(std::move(VFS),
+                                         *Input.PreambleStatCache);
   IgnoreDiagnostics DummyDiagsConsumer;
   auto CI = createInvocationFromCommandLine(
       ArgStrs,
       CompilerInstance::createDiagnostics(new DiagnosticOptions,
                                           &DummyDiagsConsumer, false),
-      Input.VFS);
+      VFS);
   if (!CI) {
     elog("Couldn't create CompilerInvocation");
     return false;
@@ -1069,7 +1074,7 @@
   // the remapped buffers do not get freed.
   auto Clang = prepareCompilerInstance(
       std::move(CI), CompletingInPreamble ? nullptr : Input.Preamble,
-      std::move(ContentsBuffer), std::move(Input.PCHs), std::move(Input.VFS),
+      std::move(ContentsBuffer), std::move(Input.PCHs), std::move(VFS),
       DummyDiagsConsumer);
   Clang->getPreprocessorOpts().SingleFileParseMode = CompletingInPreamble;
   Clang->setCodeCompletionConsumer(Consumer.release());
@@ -1586,15 +1591,20 @@
   return Content.substr(St, Len);
 }
 
-CodeCompleteResult
-codeComplete(PathRef FileName, const tooling::CompileCommand &Command,
-             PrecompiledPreamble const *Preamble,
-             const IncludeStructure &PreambleInclusions, StringRef Contents,
-             Position Pos, IntrusiveRefCntPtr<vfs::FileSystem> VFS,
-             std::shared_ptr<PCHContainerOperations> PCHs,
-             CodeCompleteOptions Opts, SpeculativeFuzzyFind *SpecFuzzyFind) {
-  return CodeCompleteFlow(FileName, PreambleInclusions, SpecFuzzyFind, Opts)
-      .run({FileName, Command, Preamble, Contents, Pos, VFS, PCHs});
+CodeCompleteResult codeComplete(PathRef FileName, Position Pos,
+                                const InputsAndPreamble &Input,
+                                IntrusiveRefCntPtr<vfs::FileSystem> VFS,
+                                std::shared_ptr<PCHContainerOperations> PCHs,
+                                CodeCompleteOptions Opts,
+                                SpeculativeFuzzyFind *SpecFuzzyFind) {
+  return CodeCompleteFlow(FileName,
+                          Input.Preamble ? Input.Preamble->Includes
+                                         : IncludeStructure(),
+                          SpecFuzzyFind, Opts)
+      .run({FileName, Input.Command,
+            Input.Preamble ? &Input.Preamble->Preamble : nullptr,
+            Input.Preamble ? &Input.Preamble->StatCache : nullptr,
+            Input.Contents, Pos, VFS, PCHs});
 }
 
 SignatureHelp signatureHelp(PathRef FileName,
@@ -1614,8 +1624,8 @@
   semaCodeComplete(
       llvm::make_unique<SignatureHelpCollector>(Options, Index, Result),
       Options,
-      {FileName, Command, Preamble, Contents, Pos, std::move(VFS),
-       std::move(PCHs)});
+      {FileName, Command, Preamble, /*PreambleStatCache=*/nullptr, Contents,
+       Pos, std::move(VFS), std::move(PCHs)});
   return Result;
 }
 
Index: clangd/ClangdUnit.h
===================================================================
--- clangd/ClangdUnit.h
+++ clangd/ClangdUnit.h
@@ -15,12 +15,16 @@
 #include "Headers.h"
 #include "Path.h"
 #include "Protocol.h"
+#include "clang/Basic/VirtualFileSystem.h"
 #include "clang/Frontend/FrontendAction.h"
 #include "clang/Frontend/PrecompiledPreamble.h"
 #include "clang/Lex/Preprocessor.h"
 #include "clang/Serialization/ASTBitCodes.h"
 #include "clang/Tooling/CompilationDatabase.h"
 #include "clang/Tooling/Core/Replacement.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/ADT/StringRef.h"
 #include <memory>
 #include <string>
 #include <vector>
@@ -42,17 +46,32 @@
 
 namespace clangd {
 
+/// Cached `stat()` calls during preamble build. The map keys are absolute paths
+/// of the file path in the cached status, which are the paths stored in
+/// preamble.
+using PreambleFileStatusCache = llvm::StringMap<llvm::ErrorOr<vfs::Status>>;
+
+/// Wraps \p FS with the \p StatCache. This should only be used when preamble
+/// is reused (e.g. code completion) and the cached status is valid.
+IntrusiveRefCntPtr<vfs::FileSystem>
+createVFSWithPreambleStatCache(IntrusiveRefCntPtr<vfs::FileSystem> FS,
+                               const PreambleFileStatusCache &StatCache);
+
 // Stores Preamble and associated data.
 struct PreambleData {
   PreambleData(PrecompiledPreamble Preamble, std::vector<Diag> Diags,
-               IncludeStructure Includes);
+               IncludeStructure Includes, PreambleFileStatusCache StatCache);
 
   tooling::CompileCommand CompileCommand;
   PrecompiledPreamble Preamble;
   std::vector<Diag> Diags;
   // Processes like code completions and go-to-definitions will need #include
   // information, and their compile action skips preamble range.
   IncludeStructure Includes;
+  // Cache of FS `stat()` results when building preamble. This can be used to
+  // avoid re-`stat`s header files when preamble is re-used (e.g. in code
+  // completion). Note that the cache is only valid if preamble is reused.
+  const PreambleFileStatusCache StatCache;
 };
 
 /// Information required to run clang, e.g. to parse AST or do code completion.
Index: clangd/ClangdUnit.cpp
===================================================================
--- clangd/ClangdUnit.cpp
+++ clangd/ClangdUnit.cpp
@@ -15,6 +15,7 @@
 #include "Trace.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/Basic/LangOptions.h"
+#include "clang/Basic/VirtualFileSystem.h"
 #include "clang/Frontend/CompilerInstance.h"
 #include "clang/Frontend/CompilerInvocation.h"
 #include "clang/Frontend/FrontendActions.h"
@@ -29,6 +30,7 @@
 #include "clang/Serialization/ASTWriter.h"
 #include "clang/Tooling/CompilationDatabase.h"
 #include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/IntrusiveRefCntPtr.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/Support/CrashRecoveryContext.h"
 #include "llvm/Support/raw_ostream.h"
@@ -114,6 +116,59 @@
   SourceManager *SourceMgr = nullptr;
 };
 
+/// Collect and cache all file status from the underlying file system.
+class CollectStatCacheVFS : public vfs::FileSystem {
+public:
+  CollectStatCacheVFS(IntrusiveRefCntPtr<vfs::FileSystem> FS,
+                      PreambleFileStatusCache &StatCache)
+      : FS(std::move(FS)), StatCache(StatCache) {}
+
+  vfs::directory_iterator dir_begin(const Twine &Dir,
+                                    std::error_code &EC) override {
+    return FS->dir_begin(Dir, EC);
+  }
+  std::error_code setCurrentWorkingDirectory(const Twine &Path) override {
+    return FS->setCurrentWorkingDirectory(Path);
+  }
+  llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override {
+    return FS->getCurrentWorkingDirectory();
+  }
+  std::error_code getRealPath(const Twine &Path,
+                              SmallVectorImpl<char> &Output) const override {
+    return FS->getRealPath(Path, Output);
+  }
+
+  llvm::ErrorOr<std::unique_ptr<vfs::File>>
+  openFileForRead(const Twine &Path) override {
+    auto File = FS->openFileForRead(Path);
+    if (!File || !*File)
+      return File;
+    // Only cache stats for files that exist because, unlike building preamble,
+    // we only care about existing files when reusing preamble.
+    if (auto S = File->get()->status())
+      cacheStatus(std::move(*S));
+    return File;
+  }
+
+  llvm::ErrorOr<vfs::Status> status(const Twine &Path) override {
+    auto S = FS->status(Path);
+    if (S)
+      cacheStatus(*S);
+    return S;
+  }
+
+private:
+  void cacheStatus(vfs::Status S) {
+    SmallString<32> PathStore(S.getName());
+    if (auto Err = makeAbsolute(PathStore))
+      return;
+    // Stores the latest status in cache as it can change in a preamble build.
+    StatCache.insert({PathStore, std::move(S)});
+  }
+  IntrusiveRefCntPtr<vfs::FileSystem> FS;
+  PreambleFileStatusCache &StatCache;
+};
+
 } // namespace
 
 void clangd::dumpAST(ParsedAST &AST, llvm::raw_ostream &OS) {
@@ -246,9 +301,51 @@
 }
 
 PreambleData::PreambleData(PrecompiledPreamble Preamble,
-                           std::vector<Diag> Diags, IncludeStructure Includes)
+                           std::vector<Diag> Diags, IncludeStructure Includes,
+                           PreambleFileStatusCache StatCache)
     : Preamble(std::move(Preamble)), Diags(std::move(Diags)),
-      Includes(std::move(Includes)) {}
+      Includes(std::move(Includes)), StatCache(std::move(StatCache)) {}
+
+IntrusiveRefCntPtr<vfs::FileSystem> clangd::createVFSWithPreambleStatCache(
+    IntrusiveRefCntPtr<vfs::FileSystem> FS,
+    const PreambleFileStatusCache &StatCache) {
+  class CacheVFS : public vfs::FileSystem {
+  public:
+    CacheVFS(IntrusiveRefCntPtr<vfs::FileSystem> FS,
+             const PreambleFileStatusCache &StatCache)
+        : FS(std::move(FS)), StatCache(StatCache) {}
+
+    vfs::directory_iterator dir_begin(const Twine &Dir,
+                                      std::error_code &EC) override {
+      return FS->dir_begin(Dir, EC);
+    }
+    std::error_code setCurrentWorkingDirectory(const Twine &Path) override {
+      return FS->setCurrentWorkingDirectory(Path);
+    }
+    llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override {
+      return FS->getCurrentWorkingDirectory();
+    }
+
+    llvm::ErrorOr<std::unique_ptr<vfs::File>>
+    openFileForRead(const Twine &Path) override {
+      return FS->openFileForRead(Path);
+    }
+    std::error_code getRealPath(const Twine &Path,
+                                SmallVectorImpl<char> &Output) const override {
+      return FS->getRealPath(Path, Output);
+    }
+
+    llvm::ErrorOr<vfs::Status> status(const Twine &Path) override {
+      auto I = StatCache.find(Path.str());
+      return (I != StatCache.end()) ? I->getValue() : FS->status(Path);
+    }
+
+  private:
+    IntrusiveRefCntPtr<vfs::FileSystem> FS;
+    const PreambleFileStatusCache &StatCache;
+  };
+  return IntrusiveRefCntPtr<CacheVFS>(new CacheVFS(std::move(FS), StatCache));
+}
 
 ParsedAST::ParsedAST(std::shared_ptr<const PreambleData> Preamble,
                      std::unique_ptr<CompilerInstance> Clang,
@@ -334,8 +431,12 @@
     // We proceed anyway, our lit-tests rely on results for non-existing working
     // dirs.
   }
+
+  PreambleFileStatusCache StatCache;
+  llvm::IntrusiveRefCntPtr<vfs::FileSystem> CacheVFS(
+      new CollectStatCacheVFS(Inputs.FS, StatCache));
   auto BuiltPreamble = PrecompiledPreamble::Build(
-      CI, ContentsBuffer.get(), Bounds, *PreambleDiagsEngine, Inputs.FS, PCHs,
+      CI, ContentsBuffer.get(), Bounds, *PreambleDiagsEngine, CacheVFS, PCHs,
       StoreInMemory, SerializedDeclsCollector);
 
   // When building the AST for the main file, we do want the function
@@ -347,7 +448,7 @@
          FileName);
     return std::make_shared<PreambleData>(
         std::move(*BuiltPreamble), PreambleDiagnostics.take(),
-        SerializedDeclsCollector.takeIncludes());
+        SerializedDeclsCollector.takeIncludes(), std::move(StatCache));
   } else {
     elog("Could not build a preamble for file {0}", FileName);
     return nullptr;
Index: clangd/ClangdServer.cpp
===================================================================
--- clangd/ClangdServer.cpp
+++ clangd/ClangdServer.cpp
@@ -181,8 +181,6 @@
     if (isCancelled())
       return CB(llvm::make_error<CancelledError>());
 
-    auto PreambleData = IP->Preamble;
-
     llvm::Optional<SpeculativeFuzzyFind> SpecFuzzyFind;
     if (CodeCompleteOpts.Index && CodeCompleteOpts.SpeculativeIndexRequest) {
       SpecFuzzyFind.emplace();
@@ -195,9 +193,7 @@
     // FIXME(ibiryukov): even if Preamble is non-null, we may want to check
     // both the old and the new version in case only one of them matches.
     CodeCompleteResult Result = clangd::codeComplete(
-        File, IP->Command, PreambleData ? &PreambleData->Preamble : nullptr,
-        PreambleData ? PreambleData->Includes : IncludeStructure(),
-        IP->Contents, Pos, FS, PCHs, CodeCompleteOpts,
+        File, Pos, *IP, FS, PCHs, CodeCompleteOpts,
         SpecFuzzyFind ? SpecFuzzyFind.getPointer() : nullptr);
     {
       clang::clangd::trace::Span Tracer("Completion results callback");
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to