Skip to content

Commit

Permalink
[clangd] Cache FS stat() calls when building preamble.
Browse files Browse the repository at this point in the history
Summary:
The file stats can be reused when preamble is reused (e.g. code
completion). It's safe to assume that cached status is not outdated as we
assume preamble files to remain unchanged.

On real file system, this made code completion ~20% faster on a measured file
(with big preamble). The preamble build time doesn't change much.

Reviewers: sammccall, ilya-biryukov

Reviewed By: sammccall

Subscribers: mgorny, MaskRay, jkorous, arphaman, kadircet, cfe-commits

Differential Revision: https://reviews.llvm.org/D52419

llvm-svn: 343576
  • Loading branch information
Eric Liu committed Oct 2, 2018
1 parent b91e081 commit b1d7542
Show file tree
Hide file tree
Showing 12 changed files with 319 additions and 33 deletions.
1 change: 1 addition & 0 deletions clang-tools-extra/clangd/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ add_clang_library(clangDaemon
DraftStore.cpp
FindSymbols.cpp
FileDistance.cpp
FS.cpp
FuzzyMatch.cpp
GlobalCompilationDatabase.cpp
Headers.cpp
Expand Down
13 changes: 4 additions & 9 deletions clang-tools-extra/clangd/ClangdServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,6 @@ void ClangdServer::codeComplete(PathRef File, Position Pos,
if (isCancelled())
return CB(llvm::make_error<CancelledError>());

auto PreambleData = IP->Preamble;

llvm::Optional<SpeculativeFuzzyFind> SpecFuzzyFind;
if (CodeCompleteOpts.Index && CodeCompleteOpts.SpeculativeIndexRequest) {
SpecFuzzyFind.emplace();
Expand All @@ -195,10 +193,8 @@ void ClangdServer::codeComplete(PathRef File, Position Pos,
// 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,
SpecFuzzyFind ? SpecFuzzyFind.getPointer() : nullptr);
File, IP->Command, IP->Preamble, IP->Contents, Pos, FS, PCHs,
CodeCompleteOpts, SpecFuzzyFind ? SpecFuzzyFind.getPointer() : nullptr);
{
clang::clangd::trace::Span Tracer("Completion results callback");
CB(std::move(Result));
Expand Down Expand Up @@ -231,9 +227,8 @@ void ClangdServer::signatureHelp(PathRef File, Position Pos,
return CB(IP.takeError());

auto PreambleData = IP->Preamble;
CB(clangd::signatureHelp(File, IP->Command,
PreambleData ? &PreambleData->Preamble : nullptr,
IP->Contents, Pos, FS, PCHs, Index));
CB(clangd::signatureHelp(File, IP->Command, PreambleData, IP->Contents, Pos,
FS, PCHs, Index));
};

// Unlike code completion, we wait for an up-to-date preamble here.
Expand Down
26 changes: 17 additions & 9 deletions clang-tools-extra/clangd/ClangdUnit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -246,9 +246,10 @@ const IncludeStructure &ParsedAST::getIncludeStructure() const {
}

PreambleData::PreambleData(PrecompiledPreamble Preamble,
std::vector<Diag> Diags, IncludeStructure Includes)
std::vector<Diag> Diags, IncludeStructure Includes,
std::unique_ptr<PreambleFileStatusCache> StatCache)
: Preamble(std::move(Preamble)), Diags(std::move(Diags)),
Includes(std::move(Includes)) {}
Includes(std::move(Includes)), StatCache(std::move(StatCache)) {}

ParsedAST::ParsedAST(std::shared_ptr<const PreambleData> Preamble,
std::unique_ptr<CompilerInstance> Clang,
Expand Down Expand Up @@ -334,9 +335,12 @@ std::shared_ptr<const PreambleData> clangd::buildPreamble(
// We proceed anyway, our lit-tests rely on results for non-existing working
// dirs.
}

auto StatCache = llvm::make_unique<PreambleFileStatusCache>();
auto BuiltPreamble = PrecompiledPreamble::Build(
CI, ContentsBuffer.get(), Bounds, *PreambleDiagsEngine, Inputs.FS, PCHs,
StoreInMemory, SerializedDeclsCollector);
CI, ContentsBuffer.get(), Bounds, *PreambleDiagsEngine,
StatCache->getProducingFS(Inputs.FS), PCHs, StoreInMemory,
SerializedDeclsCollector);

// When building the AST for the main file, we do want the function
// bodies.
Expand All @@ -347,7 +351,7 @@ std::shared_ptr<const PreambleData> clangd::buildPreamble(
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;
Expand All @@ -361,15 +365,19 @@ llvm::Optional<ParsedAST> clangd::buildAST(
trace::Span Tracer("BuildAST");
SPAN_ATTACH(Tracer, "File", FileName);

if (Inputs.FS->setCurrentWorkingDirectory(Inputs.CompileCommand.Directory)) {
auto VFS = Inputs.FS;
if (Preamble && Preamble->StatCache)
VFS = Preamble->StatCache->getConsumingFS(std::move(VFS));
if (VFS->setCurrentWorkingDirectory(Inputs.CompileCommand.Directory)) {
log("Couldn't set working directory when building the preamble.");
// We proceed anyway, our lit-tests rely on results for non-existing working
// dirs.
}

return ParsedAST::build(
llvm::make_unique<CompilerInvocation>(*Invocation), Preamble,
llvm::MemoryBuffer::getMemBufferCopy(Inputs.Contents), PCHs, Inputs.FS);
return ParsedAST::build(llvm::make_unique<CompilerInvocation>(*Invocation),
Preamble,
llvm::MemoryBuffer::getMemBufferCopy(Inputs.Contents),
PCHs, std::move(VFS));
}

SourceLocation clangd::getBeginningOfIdentifier(ParsedAST &Unit,
Expand Down
7 changes: 6 additions & 1 deletion clang-tools-extra/clangd/ClangdUnit.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDUNIT_H

#include "Diagnostics.h"
#include "FS.h"
#include "Function.h"
#include "Headers.h"
#include "Path.h"
Expand Down Expand Up @@ -45,14 +46,18 @@ namespace clangd {
// Stores Preamble and associated data.
struct PreambleData {
PreambleData(PrecompiledPreamble Preamble, std::vector<Diag> Diags,
IncludeStructure Includes);
IncludeStructure Includes,
std::unique_ptr<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 operations performed when building the preamble.
// When reusing a preamble, this cache can be consumed to save IO.
std::unique_ptr<PreambleFileStatusCache> StatCache;
};

/// Information required to run clang, e.g. to parse AST or do code completion.
Expand Down
27 changes: 17 additions & 10 deletions clang-tools-extra/clangd/CodeComplete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#include "CodeComplete.h"
#include "AST.h"
#include "ClangdUnit.h"
#include "CodeCompletionStrings.h"
#include "Compiler.h"
#include "Diagnostics.h"
Expand Down Expand Up @@ -986,7 +987,7 @@ class SignatureHelpCollector final : public CodeCompleteConsumer {
struct SemaCompleteInput {
PathRef FileName;
const tooling::CompileCommand &Command;
PrecompiledPreamble const *Preamble;
const PreambleData *Preamble;
StringRef Contents;
Position Pos;
IntrusiveRefCntPtr<vfs::FileSystem> VFS;
Expand All @@ -1010,12 +1011,15 @@ bool semaCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
// working dirs.
}

IntrusiveRefCntPtr<vfs::FileSystem> VFS = Input.VFS;
if (Input.Preamble && Input.Preamble->StatCache)
VFS = Input.Preamble->StatCache->getConsumingFS(std::move(VFS));
IgnoreDiagnostics DummyDiagsConsumer;
auto CI = createInvocationFromCommandLine(
ArgStrs,
CompilerInstance::createDiagnostics(new DiagnosticOptions,
&DummyDiagsConsumer, false),
Input.VFS);
VFS);
if (!CI) {
elog("Couldn't create CompilerInvocation");
return false;
Expand Down Expand Up @@ -1054,8 +1058,10 @@ bool semaCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
// NOTE: we must call BeginSourceFile after prepareCompilerInstance. Otherwise
// 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(CI),
(Input.Preamble && !CompletingInPreamble) ? &Input.Preamble->Preamble
: nullptr,
std::move(ContentsBuffer), std::move(Input.PCHs), std::move(VFS),
DummyDiagsConsumer);
Clang->getPreprocessorOpts().SingleFileParseMode = CompletingInPreamble;
Clang->setCodeCompletionConsumer(Consumer.release());
Expand Down Expand Up @@ -1565,19 +1571,20 @@ speculateCompletionFilter(llvm::StringRef Content, Position Pos) {

CodeCompleteResult
codeComplete(PathRef FileName, const tooling::CompileCommand &Command,
PrecompiledPreamble const *Preamble,
const IncludeStructure &PreambleInclusions, StringRef Contents,
Position Pos, IntrusiveRefCntPtr<vfs::FileSystem> VFS,
const PreambleData *Preamble, StringRef Contents, Position Pos,
IntrusiveRefCntPtr<vfs::FileSystem> VFS,
std::shared_ptr<PCHContainerOperations> PCHs,
CodeCompleteOptions Opts, SpeculativeFuzzyFind *SpecFuzzyFind) {
return CodeCompleteFlow(FileName, PreambleInclusions, SpecFuzzyFind, Opts)
return CodeCompleteFlow(FileName,
Preamble ? Preamble->Includes : IncludeStructure(),
SpecFuzzyFind, Opts)
.run({FileName, Command, Preamble, Contents, Pos, VFS, PCHs});
}

SignatureHelp signatureHelp(PathRef FileName,
const tooling::CompileCommand &Command,
PrecompiledPreamble const *Preamble,
StringRef Contents, Position Pos,
const PreambleData *Preamble, StringRef Contents,
Position Pos,
IntrusiveRefCntPtr<vfs::FileSystem> VFS,
std::shared_ptr<PCHContainerOperations> PCHs,
const SymbolIndex *Index) {
Expand Down
8 changes: 4 additions & 4 deletions clang-tools-extra/clangd/CodeComplete.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CODECOMPLETE_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CODECOMPLETE_H

#include "ClangdUnit.h"
#include "Headers.h"
#include "Logger.h"
#include "Path.h"
Expand Down Expand Up @@ -221,8 +222,7 @@ struct SpeculativeFuzzyFind {
/// destroyed when the async request finishes.
CodeCompleteResult codeComplete(PathRef FileName,
const tooling::CompileCommand &Command,
PrecompiledPreamble const *Preamble,
const IncludeStructure &PreambleInclusions,
const PreambleData *Preamble,
StringRef Contents, Position Pos,
IntrusiveRefCntPtr<vfs::FileSystem> VFS,
std::shared_ptr<PCHContainerOperations> PCHs,
Expand All @@ -232,8 +232,8 @@ CodeCompleteResult codeComplete(PathRef FileName,
/// Get signature help at a specified \p Pos in \p FileName.
SignatureHelp signatureHelp(PathRef FileName,
const tooling::CompileCommand &Command,
PrecompiledPreamble const *Preamble,
StringRef Contents, Position Pos,
const PreambleData *Preamble, StringRef Contents,
Position Pos,
IntrusiveRefCntPtr<vfs::FileSystem> VFS,
std::shared_ptr<PCHContainerOperations> PCHs,
const SymbolIndex *Index);
Expand Down
92 changes: 92 additions & 0 deletions clang-tools-extra/clangd/FS.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//===--- FS.cpp - File system related utils ----------------------*- C++-*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "FS.h"
#include "clang/Basic/VirtualFileSystem.h"
#include "llvm/ADT/None.h"

namespace clang {
namespace clangd {

void PreambleFileStatusCache::update(const vfs::FileSystem &FS, vfs::Status S) {
SmallString<32> PathStore(S.getName());
if (auto Err = FS.makeAbsolute(PathStore))
return;
// Stores the latest status in cache as it can change in a preamble build.
StatCache.insert({PathStore, std::move(S)});
}

llvm::Optional<vfs::Status>
PreambleFileStatusCache::lookup(llvm::StringRef File) const {
auto I = StatCache.find(File);
if (I != StatCache.end())
return I->getValue();
return llvm::None;
}

IntrusiveRefCntPtr<vfs::FileSystem> PreambleFileStatusCache::getProducingFS(
IntrusiveRefCntPtr<vfs::FileSystem> FS) {
// This invalidates old status in cache if files are re-`open()`ed or
// re-`stat()`ed in case file status has changed during preamble build.
class CollectFS : public vfs::ProxyFileSystem {
public:
CollectFS(IntrusiveRefCntPtr<vfs::FileSystem> FS,
PreambleFileStatusCache &StatCache)
: ProxyFileSystem(std::move(FS)), StatCache(StatCache) {}

llvm::ErrorOr<std::unique_ptr<vfs::File>>
openFileForRead(const Twine &Path) override {
auto File = getUnderlyingFS().openFileForRead(Path);
if (!File || !*File)
return File;
// Eagerly stat opened file, as the followup `status` call on the file
// doesn't necessarily go through this FS. This puts some extra work on
// preamble build, but it should be worth it as preamble can be reused
// many times (e.g. code completion) and the repeated status call is
// likely to be cached in the underlying file system anyway.
if (auto S = File->get()->status())
StatCache.update(getUnderlyingFS(), std::move(*S));
return File;
}

llvm::ErrorOr<vfs::Status> status(const Twine &Path) override {
auto S = getUnderlyingFS().status(Path);
if (S)
StatCache.update(getUnderlyingFS(), *S);
return S;
}

private:
PreambleFileStatusCache &StatCache;
};
return IntrusiveRefCntPtr<CollectFS>(new CollectFS(std::move(FS), *this));
}

IntrusiveRefCntPtr<vfs::FileSystem> PreambleFileStatusCache::getConsumingFS(
IntrusiveRefCntPtr<vfs::FileSystem> FS) const {
class CacheVFS : public vfs::ProxyFileSystem {
public:
CacheVFS(IntrusiveRefCntPtr<vfs::FileSystem> FS,
const PreambleFileStatusCache &StatCache)
: ProxyFileSystem(std::move(FS)), StatCache(StatCache) {}

llvm::ErrorOr<vfs::Status> status(const Twine &Path) override {
if (auto S = StatCache.lookup(Path.str()))
return *S;
return getUnderlyingFS().status(Path);
}

private:
const PreambleFileStatusCache &StatCache;
};
return IntrusiveRefCntPtr<CacheVFS>(new CacheVFS(std::move(FS), *this));
}

} // namespace clangd
} // namespace clang
65 changes: 65 additions & 0 deletions clang-tools-extra/clangd/FS.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//===--- FS.h - File system related utils ------------------------*- C++-*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_FS_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FS_H

#include "clang/Basic/VirtualFileSystem.h"
#include "llvm/ADT/Optional.h"

namespace clang {
namespace clangd {

/// Records status information for files open()ed or stat()ed during preamble
/// build, so we can avoid stat()s on the underlying FS when reusing the
/// preamble. For example, code completion can re-stat files when getting FileID
/// for source locations stored in preamble (e.g. checking whether a location is
/// in the main file).
///
/// The cache is keyed by absolute path of file name in cached status, as this
/// is what preamble stores.
///
/// The cache is not thread-safe when updates happen, so the use pattern should
/// be:
/// - One FS writes to the cache from one thread (or several but strictly
/// sequenced), e.g. when building preamble.
/// - Sequence point (no writes after this point, no reads before).
/// - Several FSs can read from the cache, e.g. code completions.
///
/// Note that the cache is only valid when reusing preamble.
class PreambleFileStatusCache {
public:
void update(const vfs::FileSystem &FS, vfs::Status S);
/// \p Path is a path stored in preamble.
llvm::Optional<vfs::Status> lookup(llvm::StringRef Path) const;

/// Returns a VFS that collects file status.
/// Only cache stats for files that exist because
/// 1) we only care about existing files when reusing preamble, unlike
/// building preamble.
/// 2) we use the file name in the Status as the cache key.
///
/// Note that the returned VFS should not outlive the cache.
IntrusiveRefCntPtr<vfs::FileSystem>
getProducingFS(IntrusiveRefCntPtr<vfs::FileSystem> FS);

/// Returns a VFS that uses the cache collected.
///
/// Note that the returned VFS should not outlive the cache.
IntrusiveRefCntPtr<vfs::FileSystem>
getConsumingFS(IntrusiveRefCntPtr<vfs::FileSystem> FS) const;

private:
llvm::StringMap<vfs::Status> StatCache;
};

} // namespace clangd
} // namespace clang

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_FS_H

0 comments on commit b1d7542

Please sign in to comment.