57#include "clang-include-cleaner/Record.h"
66#include "clang/Basic/Stack.h"
67#include "clang/Frontend/CompilerInvocation.h"
68#include "clang/Tooling/CompilationDatabase.h"
69#include "llvm/ADT/FunctionExtras.h"
70#include "llvm/ADT/STLExtras.h"
71#include "llvm/ADT/ScopeExit.h"
72#include "llvm/ADT/SmallVector.h"
73#include "llvm/ADT/StringExtras.h"
74#include "llvm/ADT/StringRef.h"
75#include "llvm/Support/Allocator.h"
76#include "llvm/Support/Errc.h"
77#include "llvm/Support/ErrorHandling.h"
78#include "llvm/Support/FormatVariadic.h"
79#include "llvm/Support/Path.h"
80#include "llvm/Support/Threading.h"
81#include "llvm/Support/raw_ostream.h"
85#include <condition_variable>
99using std::chrono::steady_clock;
107 PreambleBuildFilesystemLatency(
"preamble_fs_latency",
114constexpr trace::Metric PreambleBuildSize(
"preamble_build_size",
116constexpr trace::Metric PreambleSerializedSize(
"preamble_serialized_size",
120 bool IsFirstPreamble) {
121 auto RecordWithLabel = [&Stats](llvm::StringRef
Label) {
122 PreambleBuildFilesystemLatency.record(Stats.FileSystemTime,
Label);
123 if (Stats.TotalBuildTime > 0)
124 PreambleBuildFilesystemLatencyRatio.record(
125 Stats.FileSystemTime / Stats.TotalBuildTime,
Label);
128 static llvm::once_flag OnceFlag;
129 llvm::call_once(OnceFlag, [&] { RecordWithLabel(
"first_build"); });
130 RecordWithLabel(IsFirstPreamble ?
"first_build_for_file" :
"rebuild");
132 PreambleBuildSize.record(Stats.BuildSize);
133 PreambleSerializedSize.record(Stats.SerializedSize);
143 return llvm::StringRef(*
File);
153 using Key =
const ASTWorker *;
155 ASTCache(
unsigned MaxRetainedASTs) : MaxRetainedASTs(MaxRetainedASTs) {}
160 std::lock_guard<std::mutex> Lock(Mut);
161 auto It = findByKey(K);
162 if (It == LRU.end() || !It->second)
164 return It->second->getUsedBytes();
169 void put(
Key K, std::unique_ptr<ParsedAST> V) {
170 std::unique_lock<std::mutex> Lock(Mut);
171 assert(findByKey(K) == LRU.end());
173 LRU.insert(LRU.begin(), {K, std::move(V)});
174 if (LRU.size() <= MaxRetainedASTs)
177 std::unique_ptr<ParsedAST> ForCleanup = std::move(LRU.back().second);
188 std::optional<std::unique_ptr<ParsedAST>>
191 std::unique_lock<std::mutex> Lock(Mut);
192 auto Existing = findByKey(K);
193 if (Existing == LRU.end()) {
195 AccessMetric->record(1,
"miss");
199 AccessMetric->record(1,
"hit");
200 std::unique_ptr<ParsedAST> V = std::move(Existing->second);
205 return std::optional<std::unique_ptr<ParsedAST>>(std::move(V));
209 using KVPair = std::pair<Key, std::unique_ptr<ParsedAST>>;
211 std::vector<KVPair>::iterator findByKey(
Key K) {
212 return llvm::find_if(LRU, [K](
const KVPair &P) {
return P.first == K; });
216 unsigned MaxRetainedASTs;
219 std::vector<KVPair> LRU;
252 llvm::BumpPtrAllocator Arena;
254 llvm::StringRef MainFile;
259 llvm::StringMap<Association, llvm::BumpPtrAllocator &> HeaderToMain;
260 llvm::StringMap<Association *, llvm::BumpPtrAllocator &> MainToFirst;
261 std::atomic<size_t> UsedBytes;
262 mutable std::mutex Mu;
264 void invalidate(Association *First) {
265 Association *Current = First;
267 Association *Next = Current->Next;
268 Current->Next =
nullptr;
270 }
while (Current != First);
274 Association *associate(llvm::StringRef MainFile,
275 llvm::ArrayRef<std::string> Headers) {
276 Association *First =
nullptr, *Prev =
nullptr;
277 for (
const std::string &Header : Headers) {
278 auto &Assoc = HeaderToMain[Header];
282 Assoc.MainFile = MainFile;
293 void updateMemoryUsage() {
294 auto StringMapHeap = [](
const auto &Map) {
297 return Map.getNumBuckets() * (
sizeof(
void *) +
sizeof(
unsigned));
299 size_t Usage = Arena.getTotalMemory() + StringMapHeap(MainToFirst) +
300 StringMapHeap(HeaderToMain) +
sizeof(*this);
301 UsedBytes.store(
Usage, std::memory_order_release);
312 std::lock_guard<std::mutex> Lock(Mu);
313 auto It = MainToFirst.try_emplace(MainFile,
nullptr);
314 Association *&First = It.first->second;
317 First = associate(It.first->first(), Headers);
325 std::lock_guard<std::mutex> Lock(Mu);
326 Association *&First = MainToFirst[MainFile];
337 std::lock_guard<std::mutex> Lock(Mu);
338 return HeaderToMain.lookup(Header).MainFile.str();
342 return UsedBytes.load(std::memory_order_acquire);
348bool isReliable(
const tooling::CompileCommand &Cmd) {
349 return Cmd.Heuristic.empty();
354class SynchronizedTUStatus {
356 SynchronizedTUStatus(
PathRef FileName, ParsingCallbacks &Callbacks)
357 : FileName(FileName), Callbacks(Callbacks) {}
359 void update(llvm::function_ref<
void(TUStatus &)> Mutator) {
360 std::lock_guard<std::mutex> Lock(StatusMu);
367 std::lock_guard<std::mutex> Lock(StatusMu);
372 void emitStatusLocked() {
374 Callbacks.onFileUpdated(FileName, Status);
381 bool CanPublish =
true;
382 ParsingCallbacks &Callbacks;
388class PreambleThrottlerRequest {
391 PreambleThrottlerRequest(llvm::StringRef Filename,
392 PreambleThrottler *Throttler,
393 std::condition_variable &CV)
394 : Throttler(Throttler),
395 Satisfied(Throttler == nullptr) {
399 ID = Throttler->acquire(Filename, [&] {
400 Satisfied.store(
true, std::memory_order_release);
405 bool satisfied()
const {
return Satisfied.load(std::memory_order_acquire); }
410 ~PreambleThrottlerRequest() {
412 Throttler->release(ID);
414 PreambleThrottlerRequest(
const PreambleThrottlerRequest &) =
delete;
415 PreambleThrottlerRequest &
416 operator=(
const PreambleThrottlerRequest &) =
delete;
420 PreambleThrottler *Throttler;
421 std::atomic<bool> Satisfied = {
false};
428class PreambleThread {
430 PreambleThread(llvm::StringRef FileName, ParsingCallbacks &Callbacks,
431 bool StorePreambleInMemory,
bool RunSync,
432 PreambleThrottler *Throttler, SynchronizedTUStatus &Status,
433 TUScheduler::HeaderIncluderCache &HeaderIncluders,
435 : FileName(FileName), Callbacks(Callbacks),
436 StoreInMemory(StorePreambleInMemory), RunSync(RunSync),
437 Throttler(Throttler), Status(Status), ASTPeer(AW),
438 HeaderIncluders(HeaderIncluders) {}
443 void update(std::unique_ptr<CompilerInvocation> CI, ParseInputs PI,
445 Request Req = {std::move(CI), std::move(PI), std::move(CIDiags), WantDiags,
448 build(std::move(Req));
449 Status.update([](TUStatus &Status) {
455 std::unique_lock<std::mutex> Lock(Mutex);
460 ReqCV.wait(Lock, [
this] {
463 NextReq = std::move(Req);
474 clang::noteBottomOfStack();
476 std::optional<PreambleThrottlerRequest> Throttle;
478 std::unique_lock<std::mutex> Lock(Mutex);
479 assert(!CurrentReq &&
"Already processing a request?");
481 ReqCV.wait(Lock, [&] {
return NextReq || Done; });
486 Throttle.emplace(FileName, Throttler, ReqCV);
487 std::optional<trace::Span> Tracer;
489 if (!Throttle->satisfied()) {
490 Tracer.emplace(
"PreambleThrottle");
491 Status.update([&](TUStatus &Status) {
495 ReqCV.wait(Lock, [&] {
return Throttle->satisfied() || Done; });
502 CurrentReq = std::move(*NextReq);
507 WithContext Guard(std::move(CurrentReq->Ctx));
513 build(std::move(*CurrentReq));
517 bool IsEmpty =
false;
519 std::lock_guard<std::mutex> Lock(Mutex);
527 Status.update([](TUStatus &Status) {
533 dlog(
"Preamble worker for {0} stopped", FileName);
538 dlog(
"Preamble worker for {0} received stop", FileName);
540 std::lock_guard<std::mutex> Lock(Mutex);
548 bool blockUntilIdle(Deadline Timeout)
const {
549 std::unique_lock<std::mutex> Lock(Mutex);
550 return wait(Lock, ReqCV, Timeout, [&] {
return !NextReq && !CurrentReq; });
557 std::unique_ptr<CompilerInvocation> CI;
559 std::vector<Diag> CIDiags;
565 std::lock_guard<std::mutex> Lock(Mutex);
571 void build(Request Req);
573 mutable std::mutex Mutex;
575 std::optional<Request> NextReq;
576 std::optional<Request> CurrentReq;
579 mutable std::condition_variable ReqCV;
581 std::shared_ptr<const PreambleData> LatestBuild;
584 ParsingCallbacks &Callbacks;
585 const bool StoreInMemory;
587 PreambleThrottler *Throttler;
589 SynchronizedTUStatus &Status;
591 TUScheduler::HeaderIncluderCache &HeaderIncluders;
594class ASTWorkerHandle;
607 friend class ASTWorkerHandle;
608 ASTWorker(
PathRef FileName,
const GlobalCompilationDatabase &CDB,
609 TUScheduler::ASTCache &LRUCache,
610 TUScheduler::HeaderIncluderCache &HeaderIncluders,
611 Semaphore &Barrier,
bool RunSync,
const TUScheduler::Options &Opts,
612 ParsingCallbacks &Callbacks);
620 static ASTWorkerHandle
621 create(
PathRef FileName,
const GlobalCompilationDatabase &CDB,
622 TUScheduler::ASTCache &IdleASTs,
623 TUScheduler::HeaderIncluderCache &HeaderIncluders,
624 AsyncTaskRunner *Tasks, Semaphore &Barrier,
625 const TUScheduler::Options &Opts, ParsingCallbacks &Callbacks);
627 ASTWorker(
const ASTWorker &other) =
delete;
628 ASTWorker &operator=(
const ASTWorker &other) =
delete;
631 runWithAST(llvm::StringRef Name,
632 llvm::unique_function<
void(llvm::Expected<InputsAndAST>)> Action,
634 bool blockUntilIdle(Deadline Timeout)
const;
636 std::shared_ptr<const PreambleData> getPossiblyStalePreamble(
637 std::shared_ptr<const ASTSignals> *ASTSignals =
nullptr)
const;
643 void updatePreamble(std::unique_ptr<CompilerInvocation> CI, ParseInputs PI,
644 std::shared_ptr<const PreambleData>
Preamble,
648 tooling::CompileCommand getCurrentCompileCommand()
const;
654 void waitForFirstPreamble()
const;
656 TUScheduler::FileStats stats()
const;
657 bool isASTCached()
const;
675 void generateDiagnostics(std::unique_ptr<CompilerInvocation> Invocation,
676 ParseInputs Inputs, std::vector<Diag> CIDiags);
678 void updateASTSignals(ParsedAST &
AST);
688 void startTask(llvm::StringRef Name, llvm::unique_function<
void()> Task,
689 std::optional<UpdateType> Update,
692 void runTask(llvm::StringRef Name, llvm::function_ref<
void()> Task);
698 Deadline scheduleLocked();
700 bool shouldSkipHeadLocked()
const;
703 llvm::unique_function<void()> Action;
705 steady_clock::time_point AddTime;
707 std::optional<Context> QueueCtx;
708 std::optional<UpdateType> Update;
714 TUScheduler::ASTCache &IdleASTs;
715 TUScheduler::HeaderIncluderCache &HeaderIncluders;
718 const DebouncePolicy UpdateDebounce;
722 const std::function<Context(llvm::StringRef)> ContextProvider;
723 const GlobalCompilationDatabase &CDB;
725 ParsingCallbacks &Callbacks;
729 bool RanASTCallback =
false;
731 mutable std::mutex Mutex;
735 ParseInputs FileInputs;
737 llvm::SmallVector<DebouncePolicy::clock::duration>
741 std::deque<Request> Requests;
742 std::optional<Request> CurrentRequest;
745 mutable std::condition_variable RequestsCV;
746 std::shared_ptr<const ASTSignals> LatestASTSignals;
750 std::optional<std::shared_ptr<const PreambleData>> LatestPreamble;
751 std::deque<Request> PreambleRequests;
754 mutable std::condition_variable PreambleCV;
757 std::mutex PublishMu;
764 bool CanPublishResults =
true;
765 std::atomic<unsigned> ASTBuildCount = {0};
766 std::atomic<unsigned> PreambleBuildCount = {0};
768 SynchronizedTUStatus Status;
769 PreambleThread PreamblePeer;
776class ASTWorkerHandle {
777 friend class ASTWorker;
778 ASTWorkerHandle(std::shared_ptr<ASTWorker> Worker)
779 : Worker(std::move(Worker)) {
780 assert(this->Worker);
784 ASTWorkerHandle(
const ASTWorkerHandle &) =
delete;
785 ASTWorkerHandle &operator=(
const ASTWorkerHandle &) =
delete;
786 ASTWorkerHandle(ASTWorkerHandle &&) =
default;
787 ASTWorkerHandle &operator=(ASTWorkerHandle &&) =
default;
794 ASTWorker &operator*() {
795 assert(Worker &&
"Handle was moved from");
799 ASTWorker *operator->() {
800 assert(Worker &&
"Handle was moved from");
808 std::shared_ptr<const ASTWorker> lock() {
return Worker; }
811 std::shared_ptr<ASTWorker> Worker;
821 std::shared_ptr<ASTWorker> Worker(
822 new ASTWorker(FileName, CDB, IdleASTs, HeaderIncluders, Barrier,
823 !Tasks, Opts, Callbacks));
825 Tasks->runAsync(
"ASTWorker:" + llvm::sys::path::filename(FileName),
826 [Worker]() { Worker->run(); });
827 Tasks->runAsync(
"PreambleWorker:" + llvm::sys::path::filename(FileName),
828 [Worker]() { Worker->PreamblePeer.run(); });
831 return ASTWorkerHandle(std::move(Worker));
834ASTWorker::ASTWorker(
PathRef FileName,
const GlobalCompilationDatabase &CDB,
835 TUScheduler::ASTCache &LRUCache,
836 TUScheduler::HeaderIncluderCache &HeaderIncluders,
837 Semaphore &Barrier,
bool RunSync,
838 const TUScheduler::Options &Opts,
839 ParsingCallbacks &Callbacks)
840 : IdleASTs(LRUCache), HeaderIncluders(HeaderIncluders), RunSync(RunSync),
841 UpdateDebounce(Opts.UpdateDebounce), FileName(FileName),
842 ContextProvider(Opts.ContextProvider), CDB(CDB), Callbacks(Callbacks),
843 Barrier(Barrier), Done(false), Status(FileName, Callbacks),
844 PreamblePeer(FileName, Callbacks, Opts.StorePreamblesInMemory, RunSync,
845 Opts.PreambleThrottler, Status, HeaderIncluders, *this) {
849 FileInputs.CompileCommand = CDB.getFallbackCommand(FileName);
852ASTWorker::~ASTWorker() {
856 std::lock_guard<std::mutex> Lock(Mutex);
857 assert(Done &&
"handle was not destroyed");
858 assert(Requests.empty() && !CurrentRequest &&
859 "unprocessed requests when destroying ASTWorker");
864 bool ContentChanged) {
865 llvm::StringLiteral TaskName =
"Update";
866 auto Task = [=]()
mutable {
872 auto Cmd = CDB.getCompileCommand(FileName);
875 if (!Cmd || !isReliable(*Cmd)) {
876 std::string ProxyFile = HeaderIncluders.get(FileName);
877 if (!ProxyFile.empty()) {
878 auto ProxyCmd = CDB.getCompileCommand(ProxyFile);
879 if (!ProxyCmd || !isReliable(*ProxyCmd)) {
881 HeaderIncluders.remove(ProxyFile);
884 Cmd = tooling::transferCompileCommand(std::move(*ProxyCmd), FileName);
889 Inputs.CompileCommand = std::move(*Cmd);
891 Inputs.CompileCommand = CDB.getFallbackCommand(FileName);
893 bool InputsAreTheSame =
894 std::tie(FileInputs.CompileCommand, FileInputs.Contents) ==
895 std::tie(Inputs.CompileCommand, Inputs.Contents);
897 if (!InputsAreTheSame) {
899 RanASTCallback =
false;
904 std::lock_guard<std::mutex> Lock(Mutex);
908 log(
"ASTWorker building file {0} version {1} with command {2}\n[{3}]\n{4}",
909 FileName, Inputs.Version, Inputs.CompileCommand.Heuristic,
910 Inputs.CompileCommand.Directory,
911 printArgv(Inputs.CompileCommand.CommandLine));
913 StoreDiags CompilerInvocationDiagConsumer;
914 std::vector<std::string> CC1Args;
916 Inputs, CompilerInvocationDiagConsumer, &CC1Args);
918 if (!CC1Args.empty())
919 vlog(
"Driver produced command: cc1 {0}",
printArgv(CC1Args));
920 std::vector<Diag> CompilerInvocationDiags =
921 CompilerInvocationDiagConsumer.take();
923 elog(
"Could not build CompilerInvocation for file {0}", FileName);
926 RanASTCallback =
false;
928 Callbacks.onFailedAST(FileName, Inputs.Version,
929 std::move(CompilerInvocationDiags),
930 [&](llvm::function_ref<
void()> Publish) {
934 std::lock_guard<std::mutex> Lock(PublishMu);
935 if (CanPublishResults)
940 LatestPreamble.emplace();
943 PreambleCV.notify_all();
949 PreamblePeer.update(std::make_unique<CompilerInvocation>(*Invocation),
950 Inputs, CompilerInvocationDiags, WantDiags);
957 generateDiagnostics(std::move(Invocation), std::move(Inputs),
958 std::move(CompilerInvocationDiags));
960 std::unique_lock<std::mutex> Lock(Mutex);
961 PreambleCV.wait(Lock, [
this] {
967 return LatestPreamble || !PreambleRequests.empty() || Done;
970 startTask(TaskName, std::move(Task), UpdateType{WantDiags, ContentChanged},
974void ASTWorker::runWithAST(
975 llvm::StringRef Name,
976 llvm::unique_function<
void(llvm::Expected<InputsAndAST>)> Action,
979 static constexpr trace::Metric ASTAccessForRead(
981 auto Task = [=, Action = std::move(Action)]()
mutable {
983 return Action(llvm::make_error<CancelledError>(Reason));
984 std::optional<std::unique_ptr<ParsedAST>>
AST =
985 IdleASTs.take(
this, &ASTAccessForRead);
987 StoreDiags CompilerInvocationDiagConsumer;
988 std::unique_ptr<CompilerInvocation> Invocation =
991 vlog(
"ASTWorker rebuilding evicted AST to run {0}: {1} version {2}", Name,
992 FileName, FileInputs.Version);
996 std::optional<ParsedAST> NewAST;
999 CompilerInvocationDiagConsumer.take(),
1000 getPossiblyStalePreamble());
1003 AST = NewAST ? std::make_unique<ParsedAST>(std::move(*NewAST)) : nullptr;
1006 llvm::scope_exit _([&
AST,
this]() { IdleASTs.put(
this, std::move(*
AST)); });
1009 return Action(
error(llvm::errc::invalid_argument,
"invalid AST"));
1010 vlog(
"ASTWorker running {0} on version {2} of {1}", Name, FileName,
1011 FileInputs.Version);
1012 Action(InputsAndAST{FileInputs, **
AST});
1014 startTask(Name, std::move(Task), std::nullopt, Invalidation);
1018static void crashDumpCompileCommand(llvm::raw_ostream &OS,
1019 const tooling::CompileCommand &Command) {
1020 OS <<
" Filename: " << Command.Filename <<
"\n";
1021 OS <<
" Directory: " << Command.Directory <<
"\n";
1022 OS <<
" Command Line:";
1023 for (
auto &Arg : Command.CommandLine) {
1030static void crashDumpFileContents(llvm::raw_ostream &OS,
1031 const std::string &Contents) {
1036 if (getenv(
"CLANGD_CRASH_DUMP_SOURCE")) {
1037 OS <<
" Contents:\n";
1038 OS << Contents <<
"\n";
1043static void crashDumpParseInputs(llvm::raw_ostream &OS,
1044 const ParseInputs &FileInputs) {
1045 auto &Command = FileInputs.CompileCommand;
1046 crashDumpCompileCommand(OS, Command);
1047 OS <<
" Version: " << FileInputs.Version <<
"\n";
1048 crashDumpFileContents(OS, FileInputs.Contents);
1051void PreambleThread::build(Request Req) {
1052 assert(Req.CI &&
"Got preamble request with null compiler invocation");
1053 const ParseInputs &Inputs = Req.Inputs;
1054 bool ReusedPreamble =
false;
1056 Status.update([&](TUStatus &Status) {
1059 llvm::scope_exit _([
this, &Req, &ReusedPreamble] {
1060 ASTPeer.updatePreamble(std::move(Req.CI), std::move(Req.Inputs),
1061 LatestBuild, std::move(Req.CIDiags),
1062 std::move(Req.WantDiags));
1063 if (!ReusedPreamble)
1064 Callbacks.onPreamblePublished(FileName);
1067 if (!LatestBuild || Inputs.ForceRebuild) {
1068 vlog(
"Building first preamble for {0} version {1}", FileName,
1071 vlog(
"Reusing preamble version {0} for version {1} of {2}",
1072 LatestBuild->Version, Inputs.Version, FileName);
1073 ReusedPreamble =
true;
1076 vlog(
"Rebuilding invalidated preamble for {0} version {1} (previous was "
1078 FileName, Inputs.Version, LatestBuild->Version);
1081 ThreadCrashReporter ScopedReporter([&Inputs]() {
1082 llvm::errs() <<
"Signalled while building preamble\n";
1083 crashDumpParseInputs(llvm::errs(), Inputs);
1086 PreambleBuildStats Stats;
1087 bool IsFirstPreamble = !LatestBuild;
1089 FileName, *Req.CI, Inputs, StoreInMemory,
1090 [&](CapturedASTCtx ASTCtx,
1091 std::shared_ptr<const include_cleaner::PragmaIncludes> PI) {
1092 Callbacks.onPreambleAST(FileName, Inputs.Version, std::move(ASTCtx),
1098 reportPreambleBuild(Stats, IsFirstPreamble);
1099 if (isReliable(LatestBuild->CompileCommand))
1100 HeaderIncluders.update(FileName, LatestBuild->Includes.allHeaders());
1103void ASTWorker::updatePreamble(std::unique_ptr<CompilerInvocation> CI,
1105 std::shared_ptr<const PreambleData>
Preamble,
1106 std::vector<Diag> CIDiags,
1108 llvm::StringLiteral TaskName =
"Build AST";
1111 CIDiags = std::move(CIDiags),
1112 WantDiags = std::move(WantDiags)]()
mutable {
1116 if (!LatestPreamble ||
Preamble != *LatestPreamble) {
1117 ++PreambleBuildCount;
1119 IdleASTs.take(
this);
1120 RanASTCallback =
false;
1121 std::lock_guard<std::mutex> Lock(Mutex);
1125 std::swap(*LatestPreamble,
Preamble);
1127 LatestPreamble = std::move(
Preamble);
1130 PreambleCV.notify_all();
1147 generateDiagnostics(std::move(CI), FileInputs, std::move(CIDiags));
1150 runTask(TaskName, Task);
1154 std::lock_guard<std::mutex> Lock(Mutex);
1155 PreambleRequests.push_back({std::move(Task), std::string(TaskName),
1157 std::nullopt, std::nullopt,
1160 PreambleCV.notify_all();
1161 RequestsCV.notify_all();
1164void ASTWorker::updateASTSignals(ParsedAST &
AST) {
1169 std::lock_guard<std::mutex> Lock(Mutex);
1170 std::swap(LatestASTSignals, Signals);
1174void ASTWorker::generateDiagnostics(
1175 std::unique_ptr<CompilerInvocation> Invocation, ParseInputs Inputs,
1176 std::vector<Diag> CIDiags) {
1178 static constexpr trace::Metric ASTAccessForDiag(
1181 assert(LatestPreamble);
1184 std::lock_guard<std::mutex> Lock(PublishMu);
1185 if (!CanPublishResults)
1189 bool InputsAreLatest =
1190 std::tie(FileInputs.CompileCommand, FileInputs.Contents) ==
1191 std::tie(Inputs.CompileCommand, Inputs.Contents);
1197 if (InputsAreLatest && RanASTCallback)
1201 std::string TaskName = llvm::formatv(
"Build AST ({0})", Inputs.Version);
1202 Status.update([&](TUStatus &Status) {
1204 Status.ASTActivity.Name = std::move(TaskName);
1209 std::optional<std::unique_ptr<ParsedAST>>
AST =
1210 IdleASTs.take(
this, &ASTAccessForDiag);
1211 if (!
AST || !InputsAreLatest) {
1212 auto RebuildStartTime = DebouncePolicy::clock::now();
1214 FileName, Inputs, std::move(Invocation), CIDiags, *LatestPreamble);
1215 auto RebuildDuration = DebouncePolicy::clock::now() - RebuildStartTime;
1219 std::unique_lock<std::mutex> Lock(Mutex, std::try_to_lock);
1220 if (Lock.owns_lock()) {
1223 if (RebuildTimes.size() == RebuildTimes.capacity())
1224 RebuildTimes.erase(RebuildTimes.begin());
1225 RebuildTimes.push_back(RebuildDuration);
1228 Status.update([&](TUStatus &Status) {
1229 Status.Details.ReuseAST =
false;
1230 Status.Details.BuildFailed = !NewAST;
1232 AST = NewAST ? std::make_unique<ParsedAST>(std::move(*NewAST)) : nullptr;
1234 log(
"Skipping rebuild of the AST for {0}, inputs are the same.", FileName);
1235 Status.update([](TUStatus &Status) {
1236 Status.Details.ReuseAST =
true;
1237 Status.Details.BuildFailed =
false;
1242 auto RunPublish = [&](llvm::function_ref<void()> Publish) {
1245 std::lock_guard<std::mutex> Lock(PublishMu);
1246 if (CanPublishResults)
1250 trace::Span Span(
"Running main AST callback");
1251 Callbacks.onMainAST(FileName, **
AST, RunPublish);
1252 updateASTSignals(**
AST);
1258 Callbacks.onFailedAST(FileName, Inputs.Version, CIDiags, RunPublish);
1264 if (InputsAreLatest) {
1265 RanASTCallback = *
AST !=
nullptr;
1266 IdleASTs.put(
this, std::move(*
AST));
1270std::shared_ptr<const PreambleData> ASTWorker::getPossiblyStalePreamble(
1271 std::shared_ptr<const ASTSignals> *ASTSignals)
const {
1272 std::lock_guard<std::mutex> Lock(Mutex);
1274 *ASTSignals = LatestASTSignals;
1275 return LatestPreamble ? *LatestPreamble :
nullptr;
1278void ASTWorker::waitForFirstPreamble()
const {
1279 std::unique_lock<std::mutex> Lock(Mutex);
1280 PreambleCV.wait(Lock, [
this] {
return LatestPreamble || Done; });
1283tooling::CompileCommand ASTWorker::getCurrentCompileCommand()
const {
1284 std::unique_lock<std::mutex> Lock(Mutex);
1285 return FileInputs.CompileCommand;
1288TUScheduler::FileStats ASTWorker::stats()
const {
1289 TUScheduler::FileStats Result;
1290 Result.ASTBuilds = ASTBuildCount;
1291 Result.PreambleBuilds = PreambleBuildCount;
1295 Result.UsedBytesAST = IdleASTs.getUsedBytes(
this);
1296 if (
auto Preamble = getPossiblyStalePreamble())
1297 Result.UsedBytesPreamble =
Preamble->Preamble.getSize();
1301bool ASTWorker::isASTCached()
const {
return IdleASTs.getUsedBytes(
this) != 0; }
1303void ASTWorker::stop() {
1305 std::lock_guard<std::mutex> Lock(PublishMu);
1306 CanPublishResults =
false;
1309 std::lock_guard<std::mutex> Lock(Mutex);
1310 assert(!Done &&
"stop() called twice");
1313 PreamblePeer.stop();
1315 PreambleCV.notify_all();
1317 RequestsCV.notify_all();
1320void ASTWorker::runTask(llvm::StringRef Name, llvm::function_ref<
void()> Task) {
1321 ThreadCrashReporter ScopedReporter([
this, Name]() {
1322 llvm::errs() <<
"Signalled during AST worker action: " << Name <<
"\n";
1323 crashDumpParseInputs(llvm::errs(), FileInputs);
1325 trace::Span Tracer(Name);
1326 WithContext WithProvidedContext(ContextProvider(FileName));
1330void ASTWorker::startTask(llvm::StringRef Name,
1331 llvm::unique_function<
void()> Task,
1332 std::optional<UpdateType> Update,
1335 assert(!Done &&
"running a task after stop()");
1336 runTask(Name, Task);
1341 std::lock_guard<std::mutex> Lock(Mutex);
1342 assert(!Done &&
"running a task after stop()");
1344 if (Update && Update->ContentChanged) {
1345 for (
auto &R : llvm::reverse(Requests)) {
1348 if (R.Update && R.Update->ContentChanged)
1357 WithContext WC(std::move(Ctx));
1363 std::optional<Context> QueueCtx;
1367 WithContext WC(Ctx.clone());
1368 trace::Span Tracer(
"Queued:" + Name);
1371 SPAN_ATTACH(Tracer,
"CurrentRequest", CurrentRequest->Name);
1372 llvm::json::Array PreambleRequestsNames;
1373 for (
const auto &Req : PreambleRequests)
1374 PreambleRequestsNames.push_back(Req.Name);
1376 std::move(PreambleRequestsNames));
1377 llvm::json::Array RequestsNames;
1378 for (
const auto &Req : Requests)
1379 RequestsNames.push_back(Req.Name);
1380 SPAN_ATTACH(Tracer,
"RequestsNames", std::move(RequestsNames));
1386 Requests.push_back({std::move(Task), std::string(Name), steady_clock::now(),
1387 std::move(Ctx), std::move(QueueCtx), Update,
1388 Invalidation, std::move(Invalidate)});
1390 RequestsCV.notify_all();
1393void ASTWorker::run() {
1394 clang::noteBottomOfStack();
1397 std::unique_lock<std::mutex> Lock(Mutex);
1398 assert(!CurrentRequest &&
"A task is already running, multiple workers?");
1399 for (
auto Wait = scheduleLocked(); !Wait.expired();
1400 Wait = scheduleLocked()) {
1401 assert(PreambleRequests.empty() &&
1402 "Preamble updates should be scheduled immediately");
1404 if (Requests.empty())
1411 std::optional<WithContext> Ctx;
1412 std::optional<trace::Span> Tracer;
1413 if (!Requests.empty()) {
1414 Ctx.emplace(Requests.front().Ctx.clone());
1415 Tracer.emplace(
"Debounce");
1416 SPAN_ATTACH(*Tracer,
"next_request", Requests.front().Name);
1418 Status.update([&](TUStatus &Status) {
1420 Status.ASTActivity.Name = Requests.front().Name;
1423 std::chrono::duration_cast<std::chrono::milliseconds>(
1424 Wait.time() - steady_clock::now())
1429 wait(Lock, RequestsCV, Wait);
1433 if (!PreambleRequests.empty()) {
1434 CurrentRequest = std::move(PreambleRequests.front());
1435 PreambleRequests.pop_front();
1437 CurrentRequest = std::move(Requests.front());
1438 Requests.pop_front();
1443 CurrentRequest->QueueCtx.reset();
1448 std::unique_lock<Semaphore> Lock(Barrier, std::try_to_lock);
1449 if (!Lock.owns_lock()) {
1450 Status.update([&](TUStatus &Status) {
1452 Status.ASTActivity.Name = CurrentRequest->Name;
1456 WithContext Guard(std::move(CurrentRequest->Ctx));
1457 Status.update([&](TUStatus &Status) {
1459 Status.ASTActivity.Name = CurrentRequest->Name;
1461 runTask(CurrentRequest->Name, CurrentRequest->Action);
1464 bool IsEmpty =
false;
1466 std::lock_guard<std::mutex> Lock(Mutex);
1467 CurrentRequest.reset();
1468 IsEmpty = Requests.empty() && PreambleRequests.empty();
1471 Status.update([&](TUStatus &Status) {
1473 Status.ASTActivity.Name =
"";
1476 RequestsCV.notify_all();
1480Deadline ASTWorker::scheduleLocked() {
1482 if (!PreambleRequests.empty())
1484 if (Requests.empty())
1487 for (
auto I = Requests.begin(), E = Requests.end(); I != E; ++I) {
1490 if (I->Update == std::nullopt)
1495 if (I->Update == std::nullopt) {
1496 Request R = std::move(*I);
1498 Requests.push_front(std::move(R));
1506 while (shouldSkipHeadLocked()) {
1507 vlog(
"ASTWorker skipping {0} for {1}", Requests.front().Name, FileName);
1508 Requests.pop_front();
1510 assert(!Requests.empty() &&
"skipped the whole queue");
1515 for (
const auto &R : Requests)
1516 if (R.Update == std::nullopt ||
1520 Deadline
D(Requests.front().AddTime + UpdateDebounce.compute(RebuildTimes));
1525bool ASTWorker::shouldSkipHeadLocked()
const {
1526 assert(!Requests.empty());
1527 auto Next = Requests.begin();
1528 auto Update = Next->Update;
1534 if (Next == Requests.end() || !Next->Update)
1537 switch (Update->Diagnostics) {
1544 for (; Next != Requests.end(); ++Next)
1549 llvm_unreachable(
"Unknown WantDiagnostics");
1552bool ASTWorker::blockUntilIdle(Deadline Timeout)
const {
1553 auto WaitUntilASTWorkerIsIdle = [&] {
1554 std::unique_lock<std::mutex> Lock(Mutex);
1555 return wait(Lock, RequestsCV, Timeout, [&] {
1556 return PreambleRequests.empty() && Requests.empty() && !CurrentRequest;
1561 if (!WaitUntilASTWorkerIsIdle())
1566 if (!PreamblePeer.blockUntilIdle(Timeout))
1568 assert(Requests.empty() &&
1569 "No new normal tasks can be scheduled concurrently with "
1570 "blockUntilIdle(): ASTWorker isn't threadsafe");
1572 return WaitUntilASTWorkerIsIdle();
1579std::string renderTUAction(
const PreambleAction PA,
const ASTAction &AA) {
1580 llvm::SmallVector<std::string, 2> Result;
1583 Result.push_back(
"parsing includes");
1586 Result.push_back(
"includes are queued");
1594 Result.push_back(
"file is queued");
1597 Result.push_back(
"running " + AA.Name);
1600 Result.push_back(
"parsing main file");
1608 return llvm::join(Result,
", ");
1614 return llvm::heavyweight_hardware_concurrency().compute_thread_count();
1632 std::unique_ptr<ParsingCallbacks> Callbacks)
1633 : CDB(CDB), Opts(Opts),
1634 Callbacks(Callbacks ? std::
move(Callbacks)
1636 Barrier(Opts.AsyncThreadsCount), QuickRunBarrier(Opts.AsyncThreadsCount),
1638 std::make_unique<
ASTCache>(Opts.RetentionPolicy.MaxRetainedASTs)),
1641 if (!Opts.ContextProvider) {
1642 this->Opts.ContextProvider = [](llvm::StringRef) {
1643 return Context::current().clone();
1646 if (0 < Opts.AsyncThreadsCount) {
1647 PreambleTasks.emplace();
1648 WorkerThreads.emplace();
1658 PreambleTasks->wait();
1660 WorkerThreads->wait();
1664 for (
auto &
File : Files)
1665 if (!
File.getValue()->Worker->blockUntilIdle(D))
1668 if (!PreambleTasks->wait(D))
1675 std::unique_ptr<FileData> &FD = Files[
File];
1676 bool NewFile = FD ==
nullptr;
1677 bool ContentChanged =
false;
1680 ASTWorkerHandle Worker = ASTWorker::create(
1681 File, CDB, *IdleASTs, *HeaderIncluders,
1682 WorkerThreads ? &*WorkerThreads :
nullptr, Barrier, Opts, *Callbacks);
1683 FD = std::unique_ptr<FileData>(
1685 ContentChanged =
true;
1686 }
else if (FD->Contents != Inputs.
Contents) {
1687 ContentChanged =
true;
1690 FD->Worker->update(std::move(Inputs), WantDiags, ContentChanged);
1694 LastActiveFile =
File.str();
1699 bool Removed = Files.erase(
File);
1701 elog(
"Trying to remove file from TUScheduler that is not tracked: {0}",
1710 llvm::unique_function<
void()> Action) {
1711 runWithSemaphore(Name,
Path, std::move(Action), Barrier);
1715 llvm::unique_function<
void()> Action) {
1718 runWithSemaphore(Name,
Path, std::move(Action), QuickRunBarrier);
1721void TUScheduler::runWithSemaphore(llvm::StringRef Name, llvm::StringRef
Path,
1722 llvm::unique_function<
void()> Action,
1725 Path = LastActiveFile;
1727 LastActiveFile =
Path.str();
1728 if (!PreambleTasks) {
1732 PreambleTasks->runAsync(Name, [
this, &Sem, Ctx = Context::current().clone(),
1734 Action = std::move(Action)]()
mutable {
1735 std::lock_guard<Semaphore> BarrierLock(Sem);
1736 WithContext WC(std::move(Ctx));
1737 WithContext WithProvidedContext(Opts.ContextProvider(Path));
1744 llvm::unique_function<
void(llvm::Expected<InputsAndAST>)> Action,
1746 auto It = Files.find(
File);
1747 if (It == Files.end()) {
1748 Action(llvm::make_error<LSPError>(
1752 LastActiveFile =
File.str();
1754 It->second->Worker->runWithAST(Name, std::move(Action), Invalidation);
1760 auto It = Files.find(
File);
1761 if (It == Files.end()) {
1762 Action(llvm::make_error<LSPError>(
1763 "trying to get preamble for non-added document",
1767 LastActiveFile =
File.str();
1769 if (!PreambleTasks) {
1772 std::shared_ptr<const ASTSignals> Signals;
1773 std::shared_ptr<const PreambleData>
Preamble =
1774 It->second->Worker->getPossiblyStalePreamble(&Signals);
1777 It->second->Worker->getCurrentCompileCommand(),
1782 std::shared_ptr<const ASTWorker> Worker = It->second->Worker.lock();
1783 auto Task = [Worker, Consistency, Name = Name.str(),
File =
File.str(),
1784 Contents = It->second->Contents,
1785 Command = Worker->getCurrentCompileCommand(),
1788 Action = std::move(Action),
this]()
mutable {
1789 clang::noteBottomOfStack();
1791 llvm::errs() <<
"Signalled during preamble action: " << Name <<
"\n";
1792 crashDumpCompileCommand(llvm::errs(), Command);
1793 crashDumpFileContents(llvm::errs(), Contents);
1795 std::shared_ptr<const PreambleData>
Preamble;
1800 Worker->waitForFirstPreamble();
1802 std::shared_ptr<const ASTSignals> Signals;
1803 Preamble = Worker->getPossiblyStalePreamble(&Signals);
1805 std::lock_guard<Semaphore> BarrierLock(Barrier);
1813 PreambleTasks->runAsync(
"task:" + llvm::sys::path::filename(
File),
1818 llvm::StringMap<TUScheduler::FileStats> Result;
1819 for (
const auto &PathAndFile : Files)
1820 Result.try_emplace(PathAndFile.first(),
1821 PathAndFile.second->Worker->stats());
1826 std::vector<Path> Result;
1827 for (
auto &&PathAndFile : Files) {
1828 if (!PathAndFile.second->Worker->isASTCached())
1830 Result.push_back(std::string(PathAndFile.first()));
1835DebouncePolicy::clock::duration
1837 assert(
Min <=
Max &&
"Invalid policy");
1838 if (History.empty())
1843 History = History.take_back(15);
1844 llvm::SmallVector<clock::duration, 15> Recent(History);
1845 auto *Median = Recent.begin() + Recent.size() / 2;
1846 std::nth_element(Recent.begin(), Median, Recent.end());
1848 clock::duration Target =
1849 std::chrono::duration_cast<clock::duration>(
RebuildRatio * *Median);
1867 .
addUsage(Opts.StorePreamblesInMemory ? Elem.second.UsedBytesPreamble
1870 MT.
child(
"header_includer_cache").
addUsage(HeaderIncluders->getUsedBytes());
void elog(const char *Fmt, Ts &&... Vals)
#define SPAN_ATTACH(S, Name, Expr)
Attach a key-value pair to a Span event.
Runs tasks on separate (detached) threads and wait for all tasks to finish.
Context derive(const Key< Type > &Key, std::decay_t< Type > Value) const &
Derives a child context It is safe to move or destroy a parent context after calling derive().
Context clone() const
Clone this context object.
static const Context & current()
Returns the context for the current thread, creating it if needed.
A point in time we can wait for.
static Deadline infinity()
Provides compilation arguments used for parsing C and C++ files.
Values in a Context are indexed by typed keys.
static std::optional< ParsedAST > build(llvm::StringRef Filename, const ParseInputs &Inputs, std::unique_ptr< clang::CompilerInvocation > CI, llvm::ArrayRef< Diag > CompilerInvocationDiags, std::shared_ptr< const PreambleData > Preamble)
Attempts to run Clang and store the parsed AST.
Limits the number of threads that can acquire the lock at the same time.
An LRU cache of idle ASTs.
void put(Key K, std::unique_ptr< ParsedAST > V)
Store the value in the pool, possibly removing the last used AST.
std::size_t getUsedBytes(Key K)
Returns result of getUsedBytes() for the AST cached by K.
std::optional< std::unique_ptr< ParsedAST > > take(Key K, const trace::Metric *AccessMetric=nullptr)
Returns the cached value for K, or std::nullopt if the value is not in the cache anymore.
ASTCache(unsigned MaxRetainedASTs)
void runWithPreamble(llvm::StringRef Name, PathRef File, PreambleConsistency Consistency, Callback< InputsAndPreamble > Action)
Schedule an async read of the preamble.
void remove(PathRef File)
Remove File from the list of tracked files and schedule removal of its resources.
static std::optional< llvm::StringRef > getFileBeingProcessedInContext()
std::vector< Path > getFilesWithCachedAST() const
Returns a list of files with ASTs currently stored in memory.
llvm::StringMap< FileStats > fileStats() const
Returns resources used for each of the currently open files.
void profile(MemoryTree &MT) const
bool blockUntilIdle(Deadline D) const
Wait until there are no scheduled or running tasks.
bool update(PathRef File, ParseInputs Inputs, WantDiagnostics WD)
Schedule an update for File.
PreambleConsistency
Controls whether preamble reads wait for the preamble to be up-to-date.
@ Stale
The preamble may be generated from an older version of the file.
void run(llvm::StringRef Name, llvm::StringRef Path, llvm::unique_function< void()> Action)
Schedule an async task with no dependencies.
TUScheduler(const GlobalCompilationDatabase &CDB, const Options &Opts, std::unique_ptr< ParsingCallbacks > ASTCallbacks=nullptr)
void runQuick(llvm::StringRef Name, llvm::StringRef Path, llvm::unique_function< void()> Action)
Similar to run, except the task is expected to be quick.
ASTActionInvalidation
Defines how a runWithAST action is implicitly cancelled by other actions.
@ NoInvalidation
The request will run unless explicitly cancelled.
@ InvalidateOnUpdate
The request will be implicitly cancelled by a subsequent update().
void runWithAST(llvm::StringRef Name, PathRef File, Callback< InputsAndAST > Action, ASTActionInvalidation=NoInvalidation)
Schedule an async read of the AST.
Allows setting per-thread abort/kill signal callbacks, to print additional information about the cras...
WithContext replaces Context::current() with a provided scope.
Records an event whose duration is the lifetime of the Span object.
bool enabled()
Returns true if there is an active tracer.
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
unsigned getDefaultAsyncThreadsCount()
Returns a number of a default async threads to use for TUScheduler.
std::unique_ptr< CompilerInvocation > buildCompilerInvocation(const ParseInputs &Inputs, clang::DiagnosticConsumer &D, std::vector< std::string > *CC1Args)
Builds compiler invocation that could be used to build AST or preamble.
void vlog(const char *Fmt, Ts &&... Vals)
llvm::unique_function< void(llvm::Expected< T >)> Callback
A Callback<T> is a void function that accepts Expected<T>.
static clang::clangd::Key< std::string > FileBeingProcessed
llvm::Error error(std::error_code EC, const char *Fmt, Ts &&... Vals)
bool isPreambleCompatible(const PreambleData &Preamble, const ParseInputs &Inputs, PathRef FileName, const CompilerInvocation &CI)
Returns true if Preamble is reusable for Inputs.
std::shared_ptr< const PreambleData > buildPreamble(PathRef FileName, CompilerInvocation CI, const ParseInputs &Inputs, bool StoreInMemory, PreambleParsedCallback PreambleCallback, PreambleBuildStats *Stats)
Build a preamble for the new inputs unless an old one can be reused.
std::pair< Context, Canceler > cancelableTask(int Reason)
Defines a new task whose cancellation may be requested.
void log(const char *Fmt, Ts &&... Vals)
void wait(std::unique_lock< std::mutex > &Lock, std::condition_variable &CV, Deadline D)
Wait once on CV for the specified duration.
std::string printArgv(llvm::ArrayRef< llvm::StringRef > Args)
llvm::StringRef PathRef
A typedef to represent a ref to file path.
WantDiagnostics
Determines whether diagnostics should be generated for a file snapshot.
@ Auto
Diagnostics must not be generated for this snapshot.
@ No
Diagnostics must be generated for this snapshot.
std::string Path
A typedef to represent a file path.
std::function< void()> Canceler
A canceller requests cancellation of a task, when called.
int isCancelled(const Context &Ctx)
If the current context is within a cancelled task, returns the reason.
void elog(const char *Fmt, Ts &&... Vals)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
static ASTSignals derive(const ParsedAST &AST)
Clangd may wait after an update to see if another one comes along.
clock::duration compute(llvm::ArrayRef< clock::duration > History) const
Compute the time to debounce based on this policy and recent build times.
float RebuildRatio
Target debounce, as a fraction of file rebuild time.
clock::duration Min
The minimum time that we always debounce for.
clock::duration Max
The maximum time we may debounce for.
static DebouncePolicy fixed(clock::duration)
A policy that always returns the same duration, useful for tests.
Clangd extension: indicates the current state of the file in clangd, sent from server via the textDoc...
URIForFile uri
The text document's URI.
std::string state
The human-readable string presents the current state of the file, can be shown in the UI (e....
A tree that can be used to represent memory usage of nested components while preserving the hierarchy...
void addUsage(size_t Increment)
Increases size of current node by Increment.
MemoryTree & child(llvm::StringLiteral Name)
No copy of the Name.
MemoryTree & detail(llvm::StringRef Name)
Makes a copy of the Name in detailed mode, returns current node otherwise.
Timings and statistics from the premble build.
std::string Contents
Latest inputs, passed to TUScheduler::update().
FileStatus render(PathRef File) const
Serialize this to an LSP file status item.
PreambleAction PreambleActivity
static URIForFile canonicalize(llvm::StringRef AbsPath, llvm::StringRef TUPath)
Canonicalizes AbsPath via URI.
Represents measurements of clangd events, e.g.
@ Counter
An aggregate number whose rate of change over time is meaningful.
@ Distribution
A distribution of values with a meaningful mean and count.