forked from OSchip/llvm-project
[clangd] Remove threading-related code from ClangdUnit.h
Reviewers: sammccall, hokein, ioeric Reviewed By: sammccall Subscribers: klimek, jkorous-apple, cfe-commits Differential Revision: https://reviews.llvm.org/D43065 llvm-svn: 324725
This commit is contained in:
parent
062eb53781
commit
44ba9e0364
|
@ -25,6 +25,7 @@
|
|||
#include "llvm/ADT/Optional.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
|
|
@ -29,7 +29,6 @@
|
|||
#include "llvm/Support/Format.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
|
||||
using namespace clang::clangd;
|
||||
using namespace clang;
|
||||
|
@ -234,10 +233,6 @@ private:
|
|||
llvm::Optional<LangOptions> LangOpts;
|
||||
};
|
||||
|
||||
template <class T> bool futureIsReady(std::shared_future<T> const &Future) {
|
||||
return Future.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void clangd::dumpAST(ParsedAST &AST, llvm::raw_ostream &OS) {
|
||||
|
@ -250,7 +245,6 @@ ParsedAST::Build(std::unique_ptr<clang::CompilerInvocation> CI,
|
|||
std::unique_ptr<llvm::MemoryBuffer> Buffer,
|
||||
std::shared_ptr<PCHContainerOperations> PCHs,
|
||||
IntrusiveRefCntPtr<vfs::FileSystem> VFS) {
|
||||
|
||||
std::vector<DiagWithFixIts> ASTDiags;
|
||||
StoreDiagsConsumer UnitDiagsConsumer(/*ref*/ ASTDiags);
|
||||
|
||||
|
@ -380,344 +374,145 @@ ParsedAST::ParsedAST(std::shared_ptr<const PreambleData> Preamble,
|
|||
assert(this->Action);
|
||||
}
|
||||
|
||||
ParsedASTWrapper::ParsedASTWrapper(ParsedASTWrapper &&Wrapper)
|
||||
: AST(std::move(Wrapper.AST)) {}
|
||||
|
||||
ParsedASTWrapper::ParsedASTWrapper(llvm::Optional<ParsedAST> AST)
|
||||
: AST(std::move(AST)) {}
|
||||
|
||||
std::shared_ptr<CppFile>
|
||||
CppFile::Create(PathRef FileName, bool StorePreamblesInMemory,
|
||||
std::shared_ptr<PCHContainerOperations> PCHs,
|
||||
ASTParsedCallback ASTCallback) {
|
||||
return std::shared_ptr<CppFile>(new CppFile(FileName, StorePreamblesInMemory,
|
||||
std::move(PCHs),
|
||||
std::move(ASTCallback)));
|
||||
}
|
||||
|
||||
CppFile::CppFile(PathRef FileName, bool StorePreamblesInMemory,
|
||||
std::shared_ptr<PCHContainerOperations> PCHs,
|
||||
ASTParsedCallback ASTCallback)
|
||||
: FileName(FileName), StorePreamblesInMemory(StorePreamblesInMemory),
|
||||
RebuildCounter(0), RebuildInProgress(false), ASTMemUsage(0),
|
||||
PreambleMemUsage(0), PCHs(std::move(PCHs)),
|
||||
ASTCallback(std::move(ASTCallback)) {
|
||||
PCHs(std::move(PCHs)), ASTCallback(std::move(ASTCallback)) {
|
||||
log("Created CppFile for " + FileName);
|
||||
|
||||
std::lock_guard<std::mutex> Lock(Mutex);
|
||||
LatestAvailablePreamble = nullptr;
|
||||
PreamblePromise.set_value(nullptr);
|
||||
PreambleFuture = PreamblePromise.get_future();
|
||||
|
||||
ASTPromise.set_value(std::make_shared<ParsedASTWrapper>(llvm::None));
|
||||
ASTFuture = ASTPromise.get_future();
|
||||
}
|
||||
|
||||
void CppFile::cancelRebuild() { deferCancelRebuild()(); }
|
||||
|
||||
UniqueFunction<void()> CppFile::deferCancelRebuild() {
|
||||
std::unique_lock<std::mutex> Lock(Mutex);
|
||||
// Cancel an ongoing rebuild, if any, and wait for it to finish.
|
||||
unsigned RequestRebuildCounter = ++this->RebuildCounter;
|
||||
// Rebuild asserts that futures aren't ready if rebuild is cancelled.
|
||||
// We want to keep this invariant.
|
||||
if (futureIsReady(PreambleFuture)) {
|
||||
PreamblePromise = std::promise<std::shared_ptr<const PreambleData>>();
|
||||
PreambleFuture = PreamblePromise.get_future();
|
||||
}
|
||||
if (futureIsReady(ASTFuture)) {
|
||||
ASTPromise = std::promise<std::shared_ptr<ParsedASTWrapper>>();
|
||||
ASTFuture = ASTPromise.get_future();
|
||||
}
|
||||
|
||||
Lock.unlock();
|
||||
// Notify about changes to RebuildCounter.
|
||||
RebuildCond.notify_all();
|
||||
|
||||
std::shared_ptr<CppFile> That = shared_from_this();
|
||||
return [That, RequestRebuildCounter]() {
|
||||
std::unique_lock<std::mutex> Lock(That->Mutex);
|
||||
CppFile *This = &*That;
|
||||
This->RebuildCond.wait(Lock, [This, RequestRebuildCounter]() {
|
||||
return !This->RebuildInProgress ||
|
||||
This->RebuildCounter != RequestRebuildCounter;
|
||||
});
|
||||
|
||||
// This computation got cancelled itself, do nothing.
|
||||
if (This->RebuildCounter != RequestRebuildCounter)
|
||||
return;
|
||||
|
||||
// Set empty results for Promises.
|
||||
That->PreambleMemUsage = 0;
|
||||
That->PreamblePromise.set_value(nullptr);
|
||||
That->ASTMemUsage = 0;
|
||||
That->ASTPromise.set_value(std::make_shared<ParsedASTWrapper>(llvm::None));
|
||||
};
|
||||
}
|
||||
|
||||
llvm::Optional<std::vector<DiagWithFixIts>>
|
||||
CppFile::rebuild(ParseInputs &&Inputs) {
|
||||
return deferRebuild(std::move(Inputs))();
|
||||
}
|
||||
log("Rebuilding file " + FileName + " with command [" +
|
||||
Inputs.CompileCommand.Directory + "] " +
|
||||
llvm::join(Inputs.CompileCommand.CommandLine, " "));
|
||||
|
||||
UniqueFunction<llvm::Optional<std::vector<DiagWithFixIts>>()>
|
||||
CppFile::deferRebuild(ParseInputs &&Inputs) {
|
||||
std::shared_ptr<const PreambleData> OldPreamble;
|
||||
std::shared_ptr<PCHContainerOperations> PCHs;
|
||||
unsigned RequestRebuildCounter;
|
||||
std::vector<const char *> ArgStrs;
|
||||
for (const auto &S : Inputs.CompileCommand.CommandLine)
|
||||
ArgStrs.push_back(S.c_str());
|
||||
|
||||
Inputs.FS->setCurrentWorkingDirectory(Inputs.CompileCommand.Directory);
|
||||
|
||||
// Prepare CompilerInvocation.
|
||||
std::unique_ptr<CompilerInvocation> CI;
|
||||
{
|
||||
std::unique_lock<std::mutex> Lock(Mutex);
|
||||
// Increase RebuildCounter to cancel all ongoing FinishRebuild operations.
|
||||
// They will try to exit as early as possible and won't call set_value on
|
||||
// our promises.
|
||||
RequestRebuildCounter = ++this->RebuildCounter;
|
||||
PCHs = this->PCHs;
|
||||
// FIXME(ibiryukov): store diagnostics from CommandLine when we start
|
||||
// reporting them.
|
||||
IgnoreDiagnostics IgnoreDiagnostics;
|
||||
IntrusiveRefCntPtr<DiagnosticsEngine> CommandLineDiagsEngine =
|
||||
CompilerInstance::createDiagnostics(new DiagnosticOptions,
|
||||
&IgnoreDiagnostics, false);
|
||||
CI = createInvocationFromCommandLine(ArgStrs, CommandLineDiagsEngine,
|
||||
Inputs.FS);
|
||||
// createInvocationFromCommandLine sets DisableFree.
|
||||
CI->getFrontendOpts().DisableFree = false;
|
||||
}
|
||||
assert(CI && "Couldn't create CompilerInvocation");
|
||||
|
||||
// Remember the preamble to be used during rebuild.
|
||||
OldPreamble = this->LatestAvailablePreamble;
|
||||
// Setup std::promises and std::futures for Preamble and AST. Corresponding
|
||||
// futures will wait until the rebuild process is finished.
|
||||
if (futureIsReady(this->PreambleFuture)) {
|
||||
this->PreamblePromise =
|
||||
std::promise<std::shared_ptr<const PreambleData>>();
|
||||
this->PreambleFuture = this->PreamblePromise.get_future();
|
||||
}
|
||||
if (futureIsReady(this->ASTFuture)) {
|
||||
this->ASTPromise = std::promise<std::shared_ptr<ParsedASTWrapper>>();
|
||||
this->ASTFuture = this->ASTPromise.get_future();
|
||||
}
|
||||
} // unlock Mutex.
|
||||
// Notify about changes to RebuildCounter.
|
||||
RebuildCond.notify_all();
|
||||
std::unique_ptr<llvm::MemoryBuffer> ContentsBuffer =
|
||||
llvm::MemoryBuffer::getMemBufferCopy(Inputs.Contents, FileName);
|
||||
|
||||
// A helper to function to finish the rebuild. May be run on a different
|
||||
// thread.
|
||||
// Compute updated Preamble.
|
||||
std::shared_ptr<const PreambleData> NewPreamble =
|
||||
rebuildPreamble(*CI, Inputs.FS, *ContentsBuffer);
|
||||
|
||||
// Don't let this CppFile die before rebuild is finished.
|
||||
std::shared_ptr<CppFile> That = shared_from_this();
|
||||
auto FinishRebuild =
|
||||
[OldPreamble, RequestRebuildCounter, PCHs,
|
||||
That](ParseInputs Inputs) mutable /* to allow changing OldPreamble. */
|
||||
-> llvm::Optional<std::vector<DiagWithFixIts>> {
|
||||
log("Rebuilding file " + That->FileName + " with command [" +
|
||||
Inputs.CompileCommand.Directory + "] " +
|
||||
llvm::join(Inputs.CompileCommand.CommandLine, " "));
|
||||
// Remove current AST to avoid wasting memory.
|
||||
AST = llvm::None;
|
||||
// Compute updated AST.
|
||||
llvm::Optional<ParsedAST> NewAST;
|
||||
{
|
||||
trace::Span Tracer("Build");
|
||||
SPAN_ATTACH(Tracer, "File", FileName);
|
||||
NewAST = ParsedAST::Build(std::move(CI), NewPreamble,
|
||||
std::move(ContentsBuffer), PCHs, Inputs.FS);
|
||||
}
|
||||
|
||||
// Only one execution of this method is possible at a time.
|
||||
// RebuildGuard will wait for any ongoing rebuilds to finish and will put us
|
||||
// into a state for doing a rebuild.
|
||||
RebuildGuard Rebuild(*That, RequestRebuildCounter);
|
||||
if (Rebuild.wasCancelledBeforeConstruction())
|
||||
return llvm::None;
|
||||
|
||||
std::vector<const char *> ArgStrs;
|
||||
for (const auto &S : Inputs.CompileCommand.CommandLine)
|
||||
ArgStrs.push_back(S.c_str());
|
||||
|
||||
Inputs.FS->setCurrentWorkingDirectory(Inputs.CompileCommand.Directory);
|
||||
|
||||
std::unique_ptr<CompilerInvocation> CI;
|
||||
{
|
||||
// FIXME(ibiryukov): store diagnostics from CommandLine when we start
|
||||
// reporting them.
|
||||
IgnoreDiagnostics IgnoreDiagnostics;
|
||||
IntrusiveRefCntPtr<DiagnosticsEngine> CommandLineDiagsEngine =
|
||||
CompilerInstance::createDiagnostics(new DiagnosticOptions,
|
||||
&IgnoreDiagnostics, false);
|
||||
CI = createInvocationFromCommandLine(ArgStrs, CommandLineDiagsEngine,
|
||||
Inputs.FS);
|
||||
// createInvocationFromCommandLine sets DisableFree.
|
||||
CI->getFrontendOpts().DisableFree = false;
|
||||
}
|
||||
assert(CI && "Couldn't create CompilerInvocation");
|
||||
|
||||
std::unique_ptr<llvm::MemoryBuffer> ContentsBuffer =
|
||||
llvm::MemoryBuffer::getMemBufferCopy(Inputs.Contents, That->FileName);
|
||||
|
||||
// A helper function to rebuild the preamble or reuse the existing one. Does
|
||||
// not mutate any fields of CppFile, only does the actual computation.
|
||||
// Lamdba is marked mutable to call reset() on OldPreamble.
|
||||
auto DoRebuildPreamble =
|
||||
[&]() mutable -> std::shared_ptr<const PreambleData> {
|
||||
auto Bounds =
|
||||
ComputePreambleBounds(*CI->getLangOpts(), ContentsBuffer.get(), 0);
|
||||
if (OldPreamble &&
|
||||
OldPreamble->Preamble.CanReuse(*CI, ContentsBuffer.get(), Bounds,
|
||||
Inputs.FS.get())) {
|
||||
log("Reusing preamble for file " + Twine(That->FileName));
|
||||
return OldPreamble;
|
||||
}
|
||||
log("Preamble for file " + Twine(That->FileName) +
|
||||
" cannot be reused. Attempting to rebuild it.");
|
||||
// We won't need the OldPreamble anymore, release it so it can be
|
||||
// deleted (if there are no other references to it).
|
||||
OldPreamble.reset();
|
||||
|
||||
trace::Span Tracer("Preamble");
|
||||
SPAN_ATTACH(Tracer, "File", That->FileName);
|
||||
std::vector<DiagWithFixIts> PreambleDiags;
|
||||
StoreDiagsConsumer PreambleDiagnosticsConsumer(/*ref*/ PreambleDiags);
|
||||
IntrusiveRefCntPtr<DiagnosticsEngine> PreambleDiagsEngine =
|
||||
CompilerInstance::createDiagnostics(
|
||||
&CI->getDiagnosticOpts(), &PreambleDiagnosticsConsumer, false);
|
||||
|
||||
// Skip function bodies when building the preamble to speed up building
|
||||
// the preamble and make it smaller.
|
||||
assert(!CI->getFrontendOpts().SkipFunctionBodies);
|
||||
CI->getFrontendOpts().SkipFunctionBodies = true;
|
||||
|
||||
CppFilePreambleCallbacks SerializedDeclsCollector;
|
||||
auto BuiltPreamble = PrecompiledPreamble::Build(
|
||||
*CI, ContentsBuffer.get(), Bounds, *PreambleDiagsEngine, Inputs.FS,
|
||||
PCHs,
|
||||
/*StoreInMemory=*/That->StorePreamblesInMemory,
|
||||
SerializedDeclsCollector);
|
||||
|
||||
// When building the AST for the main file, we do want the function
|
||||
// bodies.
|
||||
CI->getFrontendOpts().SkipFunctionBodies = false;
|
||||
|
||||
if (BuiltPreamble) {
|
||||
log("Built preamble of size " + Twine(BuiltPreamble->getSize()) +
|
||||
" for file " + Twine(That->FileName));
|
||||
|
||||
return std::make_shared<PreambleData>(
|
||||
std::move(*BuiltPreamble),
|
||||
SerializedDeclsCollector.takeTopLevelDeclIDs(),
|
||||
std::move(PreambleDiags));
|
||||
} else {
|
||||
log("Could not build a preamble for file " + Twine(That->FileName));
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
// Compute updated Preamble.
|
||||
std::shared_ptr<const PreambleData> NewPreamble = DoRebuildPreamble();
|
||||
// Publish the new Preamble.
|
||||
{
|
||||
std::lock_guard<std::mutex> Lock(That->Mutex);
|
||||
// We always set LatestAvailablePreamble to the new value, hoping that it
|
||||
// will still be usable in the further requests.
|
||||
That->LatestAvailablePreamble = NewPreamble;
|
||||
if (RequestRebuildCounter != That->RebuildCounter)
|
||||
return llvm::None; // Our rebuild request was cancelled, do nothing.
|
||||
That->PreambleMemUsage =
|
||||
NewPreamble ? NewPreamble->Preamble.getSize() : 0;
|
||||
That->PreamblePromise.set_value(NewPreamble);
|
||||
} // unlock Mutex
|
||||
|
||||
// Prepare the Preamble and supplementary data for rebuilding AST.
|
||||
std::vector<DiagWithFixIts> Diagnostics;
|
||||
if (NewPreamble) {
|
||||
std::vector<DiagWithFixIts> Diagnostics;
|
||||
if (NewAST) {
|
||||
// Collect diagnostics from both the preamble and the AST.
|
||||
if (NewPreamble)
|
||||
Diagnostics.insert(Diagnostics.begin(), NewPreamble->Diags.begin(),
|
||||
NewPreamble->Diags.end());
|
||||
}
|
||||
Diagnostics.insert(Diagnostics.end(), NewAST->getDiagnostics().begin(),
|
||||
NewAST->getDiagnostics().end());
|
||||
}
|
||||
if (ASTCallback && NewAST)
|
||||
ASTCallback(FileName, NewAST.getPointer());
|
||||
|
||||
// Compute updated AST.
|
||||
llvm::Optional<ParsedAST> NewAST;
|
||||
{
|
||||
trace::Span Tracer("Build");
|
||||
SPAN_ATTACH(Tracer, "File", That->FileName);
|
||||
NewAST = ParsedAST::Build(std::move(CI), std::move(NewPreamble),
|
||||
std::move(ContentsBuffer), PCHs, Inputs.FS);
|
||||
}
|
||||
|
||||
if (NewAST) {
|
||||
Diagnostics.insert(Diagnostics.end(), NewAST->getDiagnostics().begin(),
|
||||
NewAST->getDiagnostics().end());
|
||||
if (That->ASTCallback)
|
||||
That->ASTCallback(That->FileName, NewAST.getPointer());
|
||||
} else {
|
||||
// Don't report even Preamble diagnostics if we couldn't build AST.
|
||||
Diagnostics.clear();
|
||||
}
|
||||
|
||||
// Publish the new AST.
|
||||
{
|
||||
std::lock_guard<std::mutex> Lock(That->Mutex);
|
||||
if (RequestRebuildCounter != That->RebuildCounter)
|
||||
return Diagnostics; // Our rebuild request was cancelled, don't set
|
||||
// ASTPromise.
|
||||
|
||||
That->ASTMemUsage = NewAST ? NewAST->getUsedBytes() : 0;
|
||||
That->ASTPromise.set_value(
|
||||
std::make_shared<ParsedASTWrapper>(std::move(NewAST)));
|
||||
} // unlock Mutex
|
||||
|
||||
return Diagnostics;
|
||||
};
|
||||
|
||||
return BindWithForward(FinishRebuild, std::move(Inputs));
|
||||
// Write the results of rebuild into class fields.
|
||||
Preamble = std::move(NewPreamble);
|
||||
AST = std::move(NewAST);
|
||||
return Diagnostics;
|
||||
}
|
||||
|
||||
std::shared_future<std::shared_ptr<const PreambleData>>
|
||||
CppFile::getPreamble() const {
|
||||
std::lock_guard<std::mutex> Lock(Mutex);
|
||||
return PreambleFuture;
|
||||
const std::shared_ptr<const PreambleData> &CppFile::getPreamble() const {
|
||||
return Preamble;
|
||||
}
|
||||
|
||||
std::shared_ptr<const PreambleData> CppFile::getPossiblyStalePreamble() const {
|
||||
std::lock_guard<std::mutex> Lock(Mutex);
|
||||
return LatestAvailablePreamble;
|
||||
}
|
||||
|
||||
std::shared_future<std::shared_ptr<ParsedASTWrapper>> CppFile::getAST() const {
|
||||
std::lock_guard<std::mutex> Lock(Mutex);
|
||||
return ASTFuture;
|
||||
ParsedAST *CppFile::getAST() const {
|
||||
// We could add mutable to AST instead of const_cast here, but that would also
|
||||
// allow writing to AST from const methods.
|
||||
return AST ? const_cast<ParsedAST *>(AST.getPointer()) : nullptr;
|
||||
}
|
||||
|
||||
std::size_t CppFile::getUsedBytes() const {
|
||||
std::lock_guard<std::mutex> Lock(Mutex);
|
||||
// FIXME: We should not store extra size fields. When we store AST and
|
||||
// Preamble directly, not inside futures, we could compute the sizes from the
|
||||
// stored AST and the preamble in this function directly.
|
||||
return ASTMemUsage + PreambleMemUsage;
|
||||
std::size_t Total = 0;
|
||||
if (AST)
|
||||
Total += AST->getUsedBytes();
|
||||
if (StorePreamblesInMemory && Preamble)
|
||||
Total += Preamble->Preamble.getSize();
|
||||
return Total;
|
||||
}
|
||||
|
||||
CppFile::RebuildGuard::RebuildGuard(CppFile &File,
|
||||
unsigned RequestRebuildCounter)
|
||||
: File(File), RequestRebuildCounter(RequestRebuildCounter) {
|
||||
std::unique_lock<std::mutex> Lock(File.Mutex);
|
||||
WasCancelledBeforeConstruction = File.RebuildCounter != RequestRebuildCounter;
|
||||
if (WasCancelledBeforeConstruction)
|
||||
return;
|
||||
|
||||
File.RebuildCond.wait(Lock, [&File, RequestRebuildCounter]() {
|
||||
return !File.RebuildInProgress ||
|
||||
File.RebuildCounter != RequestRebuildCounter;
|
||||
});
|
||||
|
||||
WasCancelledBeforeConstruction = File.RebuildCounter != RequestRebuildCounter;
|
||||
if (WasCancelledBeforeConstruction)
|
||||
return;
|
||||
|
||||
File.RebuildInProgress = true;
|
||||
}
|
||||
|
||||
bool CppFile::RebuildGuard::wasCancelledBeforeConstruction() const {
|
||||
return WasCancelledBeforeConstruction;
|
||||
}
|
||||
|
||||
CppFile::RebuildGuard::~RebuildGuard() {
|
||||
if (WasCancelledBeforeConstruction)
|
||||
return;
|
||||
|
||||
std::unique_lock<std::mutex> Lock(File.Mutex);
|
||||
assert(File.RebuildInProgress);
|
||||
File.RebuildInProgress = false;
|
||||
|
||||
if (File.RebuildCounter == RequestRebuildCounter) {
|
||||
// Our rebuild request was successful.
|
||||
assert(futureIsReady(File.ASTFuture));
|
||||
assert(futureIsReady(File.PreambleFuture));
|
||||
} else {
|
||||
// Our rebuild request was cancelled, because further reparse was requested.
|
||||
assert(!futureIsReady(File.ASTFuture));
|
||||
assert(!futureIsReady(File.PreambleFuture));
|
||||
std::shared_ptr<const PreambleData>
|
||||
CppFile::rebuildPreamble(CompilerInvocation &CI,
|
||||
IntrusiveRefCntPtr<vfs::FileSystem> FS,
|
||||
llvm::MemoryBuffer &ContentsBuffer) const {
|
||||
const auto &OldPreamble = this->Preamble;
|
||||
auto Bounds = ComputePreambleBounds(*CI.getLangOpts(), &ContentsBuffer, 0);
|
||||
if (OldPreamble &&
|
||||
OldPreamble->Preamble.CanReuse(CI, &ContentsBuffer, Bounds, FS.get())) {
|
||||
log("Reusing preamble for file " + Twine(FileName));
|
||||
return OldPreamble;
|
||||
}
|
||||
log("Preamble for file " + Twine(FileName) +
|
||||
" cannot be reused. Attempting to rebuild it.");
|
||||
|
||||
Lock.unlock();
|
||||
File.RebuildCond.notify_all();
|
||||
trace::Span Tracer("Preamble");
|
||||
SPAN_ATTACH(Tracer, "File", FileName);
|
||||
std::vector<DiagWithFixIts> PreambleDiags;
|
||||
StoreDiagsConsumer PreambleDiagnosticsConsumer(/*ref*/ PreambleDiags);
|
||||
IntrusiveRefCntPtr<DiagnosticsEngine> PreambleDiagsEngine =
|
||||
CompilerInstance::createDiagnostics(&CI.getDiagnosticOpts(),
|
||||
&PreambleDiagnosticsConsumer, false);
|
||||
|
||||
// Skip function bodies when building the preamble to speed up building
|
||||
// the preamble and make it smaller.
|
||||
assert(!CI.getFrontendOpts().SkipFunctionBodies);
|
||||
CI.getFrontendOpts().SkipFunctionBodies = true;
|
||||
|
||||
CppFilePreambleCallbacks SerializedDeclsCollector;
|
||||
auto BuiltPreamble = PrecompiledPreamble::Build(
|
||||
CI, &ContentsBuffer, Bounds, *PreambleDiagsEngine, FS, PCHs,
|
||||
/*StoreInMemory=*/StorePreamblesInMemory, SerializedDeclsCollector);
|
||||
|
||||
// When building the AST for the main file, we do want the function
|
||||
// bodies.
|
||||
CI.getFrontendOpts().SkipFunctionBodies = false;
|
||||
|
||||
if (BuiltPreamble) {
|
||||
log("Built preamble of size " + Twine(BuiltPreamble->getSize()) +
|
||||
" for file " + Twine(FileName));
|
||||
|
||||
return std::make_shared<PreambleData>(
|
||||
std::move(*BuiltPreamble),
|
||||
SerializedDeclsCollector.takeTopLevelDeclIDs(),
|
||||
std::move(PreambleDiags));
|
||||
} else {
|
||||
log("Could not build a preamble for file " + Twine(FileName));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
SourceLocation clangd::getBeginningOfIdentifier(ParsedAST &Unit,
|
||||
|
|
|
@ -18,10 +18,9 @@
|
|||
#include "clang/Serialization/ASTBitCodes.h"
|
||||
#include "clang/Tooling/CompilationDatabase.h"
|
||||
#include "clang/Tooling/Core/Replacement.h"
|
||||
#include <atomic>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace llvm {
|
||||
class raw_ostream;
|
||||
|
@ -126,151 +125,44 @@ private:
|
|||
bool PreambleDeclsDeserialized;
|
||||
};
|
||||
|
||||
// Provides thread-safe access to ParsedAST.
|
||||
class ParsedASTWrapper {
|
||||
public:
|
||||
ParsedASTWrapper(ParsedASTWrapper &&Wrapper);
|
||||
ParsedASTWrapper(llvm::Optional<ParsedAST> AST);
|
||||
|
||||
/// Runs \p F on wrapped ParsedAST under lock. Ensures it is not accessed
|
||||
/// concurrently.
|
||||
template <class Func> void runUnderLock(Func F) const {
|
||||
std::lock_guard<std::mutex> Lock(Mutex);
|
||||
F(AST ? AST.getPointer() : nullptr);
|
||||
}
|
||||
|
||||
private:
|
||||
// This wrapper is used as an argument to std::shared_future (and it returns a
|
||||
// const ref in get()), but we need to have non-const ref in order to
|
||||
// implement some features.
|
||||
mutable std::mutex Mutex;
|
||||
mutable llvm::Optional<ParsedAST> AST;
|
||||
};
|
||||
|
||||
using ASTParsedCallback = std::function<void(PathRef Path, ParsedAST *)>;
|
||||
|
||||
/// Manages resources, required by clangd. Allows to rebuild file with new
|
||||
/// contents, and provides AST and Preamble for it.
|
||||
/// NOTE: Threading-related bits of CppFile are now deprecated and will be
|
||||
/// removed soon.
|
||||
class CppFile : public std::enable_shared_from_this<CppFile> {
|
||||
class CppFile {
|
||||
public:
|
||||
// We only allow to create CppFile as shared_ptr, because a future returned by
|
||||
// deferRebuild will hold references to it.
|
||||
static std::shared_ptr<CppFile>
|
||||
Create(PathRef FileName, bool StorePreamblesInMemory,
|
||||
std::shared_ptr<PCHContainerOperations> PCHs,
|
||||
ASTParsedCallback ASTCallback);
|
||||
|
||||
private:
|
||||
CppFile(PathRef FileName, bool StorePreamblesInMemory,
|
||||
std::shared_ptr<PCHContainerOperations> PCHs,
|
||||
ASTParsedCallback ASTCallback);
|
||||
|
||||
public:
|
||||
CppFile(CppFile const &) = delete;
|
||||
CppFile(CppFile &&) = delete;
|
||||
|
||||
/// Cancels a scheduled rebuild, if any, and sets AST and Preamble to nulls.
|
||||
/// If a rebuild is in progress, will wait for it to finish.
|
||||
void cancelRebuild();
|
||||
|
||||
/// Similar to deferRebuild, but sets both Preamble and AST to nulls instead
|
||||
/// of doing an actual parsing. Returned function is a deferred computation
|
||||
/// that will wait for any ongoing rebuilds to finish and actually set the AST
|
||||
/// and Preamble to nulls. It can be run on a different thread. This function
|
||||
/// is useful to cancel ongoing rebuilds, if any, before removing CppFile.
|
||||
/// DEPRECATED. This function will be removed soon, please do not use it.
|
||||
UniqueFunction<void()> deferCancelRebuild();
|
||||
|
||||
/// Rebuild AST and Preamble synchronously on the calling thread.
|
||||
/// Returns a list of diagnostics or a llvm::None, if another rebuild was
|
||||
/// requested in parallel (effectively cancelling this rebuild) before
|
||||
/// diagnostics were produced.
|
||||
/// Rebuild the AST and the preamble.
|
||||
/// Returns a list of diagnostics or llvm::None, if an error occured.
|
||||
llvm::Optional<std::vector<DiagWithFixIts>> rebuild(ParseInputs &&Inputs);
|
||||
|
||||
/// Schedule a rebuild and return a deferred computation that will finish the
|
||||
/// rebuild, that can be called on a different thread.
|
||||
/// After calling this method, resources, available via futures returned by
|
||||
/// getPreamble() and getAST(), will be waiting for rebuild to finish. A
|
||||
/// continuation fininshing rebuild, returned by this function, must be
|
||||
/// computed(i.e., operator() must be called on it) in order to make those
|
||||
/// resources ready. If deferRebuild is called again before the rebuild is
|
||||
/// finished (either because returned future had not been called or because it
|
||||
/// had not returned yet), the previous rebuild request is cancelled and the
|
||||
/// resource futures (returned by getPreamble() or getAST()) that were not
|
||||
/// ready will be waiting for the last rebuild to finish instead.
|
||||
/// The future to finish rebuild returns a list of diagnostics built during
|
||||
/// reparse, or None, if another deferRebuild was called before this
|
||||
/// rebuild was finished.
|
||||
/// DEPRECATED. This function will be removed soon, please do not use it.
|
||||
UniqueFunction<llvm::Optional<std::vector<DiagWithFixIts>>()>
|
||||
deferRebuild(ParseInputs &&Inputs);
|
||||
|
||||
/// Returns a future to get the most fresh PreambleData for a file. The
|
||||
/// future will wait until the Preamble is rebuilt.
|
||||
std::shared_future<std::shared_ptr<const PreambleData>> getPreamble() const;
|
||||
/// Return some preamble for a file. It might be stale, but won't wait for
|
||||
/// rebuild to finish.
|
||||
std::shared_ptr<const PreambleData> getPossiblyStalePreamble() const;
|
||||
|
||||
/// Returns a future to get the most fresh AST for a file. Returned AST is
|
||||
/// wrapped to prevent concurrent accesses.
|
||||
/// We use std::shared_ptr here because MVSC fails to compile non-copyable
|
||||
/// classes as template arguments of promise/future. It is guaranteed to
|
||||
/// always be non-null.
|
||||
std::shared_future<std::shared_ptr<ParsedASTWrapper>> getAST() const;
|
||||
|
||||
/// Returns the last built preamble.
|
||||
const std::shared_ptr<const PreambleData> &getPreamble() const;
|
||||
/// Returns the last built AST.
|
||||
ParsedAST *getAST() const;
|
||||
/// Returns an estimated size, in bytes, currently occupied by the AST and the
|
||||
/// Preamble.
|
||||
std::size_t getUsedBytes() const;
|
||||
|
||||
private:
|
||||
/// A helper guard that manages the state of CppFile during rebuild.
|
||||
class RebuildGuard {
|
||||
public:
|
||||
RebuildGuard(CppFile &File, unsigned RequestRebuildCounter);
|
||||
~RebuildGuard();
|
||||
|
||||
bool wasCancelledBeforeConstruction() const;
|
||||
|
||||
private:
|
||||
CppFile &File;
|
||||
unsigned RequestRebuildCounter;
|
||||
bool WasCancelledBeforeConstruction;
|
||||
};
|
||||
/// Build a new preamble for \p Inputs. If the current preamble can be reused,
|
||||
/// it is returned instead.
|
||||
/// This method is const to ensure we don't incidentally modify any fields.
|
||||
std::shared_ptr<const PreambleData>
|
||||
rebuildPreamble(CompilerInvocation &CI,
|
||||
IntrusiveRefCntPtr<vfs::FileSystem> FS,
|
||||
llvm::MemoryBuffer &ContentsBuffer) const;
|
||||
|
||||
Path FileName;
|
||||
bool StorePreamblesInMemory;
|
||||
|
||||
/// Mutex protects all fields, declared below it, FileName and Command are not
|
||||
/// mutated.
|
||||
mutable std::mutex Mutex;
|
||||
/// A counter to cancel old rebuilds.
|
||||
unsigned RebuildCounter;
|
||||
/// Used to wait when rebuild is finished before starting another one.
|
||||
bool RebuildInProgress;
|
||||
/// Condition variable to indicate changes to RebuildInProgress.
|
||||
std::condition_variable RebuildCond;
|
||||
|
||||
/// Size of the last built AST, in bytes.
|
||||
std::size_t ASTMemUsage;
|
||||
/// Size of the last build Preamble, in bytes.
|
||||
std::size_t PreambleMemUsage;
|
||||
|
||||
/// Promise and future for the latests AST. Fulfilled during rebuild.
|
||||
/// We use std::shared_ptr here because MVSC fails to compile non-copyable
|
||||
/// classes as template arguments of promise/future.
|
||||
std::promise<std::shared_ptr<ParsedASTWrapper>> ASTPromise;
|
||||
std::shared_future<std::shared_ptr<ParsedASTWrapper>> ASTFuture;
|
||||
|
||||
/// Promise and future for the latests Preamble. Fulfilled during rebuild.
|
||||
std::promise<std::shared_ptr<const PreambleData>> PreamblePromise;
|
||||
std::shared_future<std::shared_ptr<const PreambleData>> PreambleFuture;
|
||||
/// Latest preamble that was built. May be stale, but always available without
|
||||
/// waiting for rebuild to finish.
|
||||
std::shared_ptr<const PreambleData> LatestAvailablePreamble;
|
||||
/// Utility class, required by clang.
|
||||
/// The last parsed AST.
|
||||
llvm::Optional<ParsedAST> AST;
|
||||
/// The last built Preamble.
|
||||
std::shared_ptr<const PreambleData> Preamble;
|
||||
/// Utility class required by clang
|
||||
std::shared_ptr<PCHContainerOperations> PCHs;
|
||||
/// This is called after the file is parsed. This can be nullptr if there is
|
||||
/// no callback.
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
#include "llvm/Support/Errc.h"
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
|
||||
namespace clang {
|
||||
namespace clangd {
|
||||
|
@ -66,7 +67,7 @@ class ASTWorkerHandle;
|
|||
/// worker.
|
||||
class ASTWorker {
|
||||
friend class ASTWorkerHandle;
|
||||
ASTWorker(Semaphore &Barrier, std::shared_ptr<CppFile> AST, bool RunSync);
|
||||
ASTWorker(Semaphore &Barrier, CppFile AST, bool RunSync);
|
||||
|
||||
public:
|
||||
/// Create a new ASTWorker and return a handle to it.
|
||||
|
@ -75,7 +76,7 @@ public:
|
|||
/// synchronously instead. \p Barrier is acquired when processing each
|
||||
/// request, it is be used to limit the number of actively running threads.
|
||||
static ASTWorkerHandle Create(AsyncTaskRunner *Tasks, Semaphore &Barrier,
|
||||
std::shared_ptr<CppFile> AST);
|
||||
CppFile AST);
|
||||
~ASTWorker();
|
||||
|
||||
void update(ParseInputs Inputs,
|
||||
|
@ -102,11 +103,14 @@ private:
|
|||
const bool RunSync;
|
||||
Semaphore &Barrier;
|
||||
// AST and FileInputs are only accessed on the processing thread from run().
|
||||
const std::shared_ptr<CppFile> AST;
|
||||
CppFile AST;
|
||||
// Inputs, corresponding to the current state of AST.
|
||||
ParseInputs FileInputs;
|
||||
// Guards members used by both TUScheduler and the worker thread.
|
||||
mutable std::mutex Mutex;
|
||||
std::shared_ptr<const PreambleData> LastBuiltPreamble; /* GUARDED_BY(Mutex) */
|
||||
// Result of getUsedBytes() after the last rebuild or read of AST.
|
||||
std::size_t LastASTSize; /* GUARDED_BY(Mutex) */
|
||||
// Set to true to signal run() to finish processing.
|
||||
bool Done; /* GUARDED_BY(Mutex) */
|
||||
std::queue<RequestWithCtx> Requests; /* GUARDED_BY(Mutex) */
|
||||
|
@ -159,7 +163,7 @@ private:
|
|||
};
|
||||
|
||||
ASTWorkerHandle ASTWorker::Create(AsyncTaskRunner *Tasks, Semaphore &Barrier,
|
||||
std::shared_ptr<CppFile> AST) {
|
||||
CppFile AST) {
|
||||
std::shared_ptr<ASTWorker> Worker(
|
||||
new ASTWorker(Barrier, std::move(AST), /*RunSync=*/!Tasks));
|
||||
if (Tasks)
|
||||
|
@ -168,8 +172,7 @@ ASTWorkerHandle ASTWorker::Create(AsyncTaskRunner *Tasks, Semaphore &Barrier,
|
|||
return ASTWorkerHandle(std::move(Worker));
|
||||
}
|
||||
|
||||
ASTWorker::ASTWorker(Semaphore &Barrier, std::shared_ptr<CppFile> AST,
|
||||
bool RunSync)
|
||||
ASTWorker::ASTWorker(Semaphore &Barrier, CppFile AST, bool RunSync)
|
||||
: RunSync(RunSync), Barrier(Barrier), AST(std::move(AST)), Done(false) {
|
||||
if (RunSync)
|
||||
return;
|
||||
|
@ -193,7 +196,14 @@ void ASTWorker::update(
|
|||
return;
|
||||
}
|
||||
FileInputs = Inputs;
|
||||
auto Diags = AST->rebuild(std::move(Inputs));
|
||||
auto Diags = AST.rebuild(std::move(Inputs));
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> Lock(Mutex);
|
||||
if (AST.getPreamble())
|
||||
LastBuiltPreamble = AST.getPreamble();
|
||||
LastASTSize = AST.getUsedBytes();
|
||||
}
|
||||
// We want to report the diagnostics even if this update was cancelled.
|
||||
// It seems more useful than making the clients wait indefinitely if they
|
||||
// spam us with updates.
|
||||
|
@ -208,17 +218,18 @@ void ASTWorker::update(
|
|||
void ASTWorker::runWithAST(
|
||||
UniqueFunction<void(llvm::Expected<InputsAndAST>)> Action) {
|
||||
auto Task = [=](decltype(Action) Action) {
|
||||
auto ASTWrapper = this->AST->getAST().get();
|
||||
// FIXME: no need to lock here, cleanup the CppFile interface to get rid of
|
||||
// them.
|
||||
ASTWrapper->runUnderLock([&](ParsedAST *AST) {
|
||||
if (!AST) {
|
||||
Action(llvm::make_error<llvm::StringError>(
|
||||
"invalid AST", llvm::errc::invalid_argument));
|
||||
return;
|
||||
}
|
||||
Action(InputsAndAST{FileInputs, *AST});
|
||||
});
|
||||
ParsedAST *ActualAST = AST.getAST();
|
||||
if (!ActualAST) {
|
||||
Action(llvm::make_error<llvm::StringError>("invalid AST",
|
||||
llvm::errc::invalid_argument));
|
||||
return;
|
||||
}
|
||||
Action(InputsAndAST{FileInputs, *ActualAST});
|
||||
|
||||
// Size of the AST might have changed after reads too, e.g. if some decls
|
||||
// were deserialized from preamble.
|
||||
std::lock_guard<std::mutex> Lock(Mutex);
|
||||
LastASTSize = ActualAST->getUsedBytes();
|
||||
};
|
||||
|
||||
startTask(BindWithForward(Task, std::move(Action)), /*isUpdate=*/false,
|
||||
|
@ -227,14 +238,13 @@ void ASTWorker::runWithAST(
|
|||
|
||||
std::shared_ptr<const PreambleData>
|
||||
ASTWorker::getPossiblyStalePreamble() const {
|
||||
return AST->getPossiblyStalePreamble();
|
||||
std::lock_guard<std::mutex> Lock(Mutex);
|
||||
return LastBuiltPreamble;
|
||||
}
|
||||
|
||||
std::size_t ASTWorker::getUsedBytes() const {
|
||||
// FIXME(ibiryukov): we'll need to take locks here after we remove
|
||||
// thread-safety from CppFile. For now, CppFile is thread-safe and we can
|
||||
// safely call methods on it without acquiring a lock.
|
||||
return AST->getUsedBytes();
|
||||
std::lock_guard<std::mutex> Lock(Mutex);
|
||||
return LastASTSize;
|
||||
}
|
||||
|
||||
void ASTWorker::stop() {
|
||||
|
@ -343,7 +353,7 @@ void TUScheduler::update(
|
|||
// Create a new worker to process the AST-related tasks.
|
||||
ASTWorkerHandle Worker = ASTWorker::Create(
|
||||
Tasks ? Tasks.getPointer() : nullptr, Barrier,
|
||||
CppFile::Create(File, StorePreamblesInMemory, PCHOps, ASTCallback));
|
||||
CppFile(File, StorePreamblesInMemory, PCHOps, ASTCallback));
|
||||
FD = std::unique_ptr<FileData>(new FileData{Inputs, std::move(Worker)});
|
||||
} else {
|
||||
FD->Inputs = Inputs;
|
||||
|
|
Loading…
Reference in New Issue