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;
106constexpr trace::Metric
107 PreambleBuildFilesystemLatency(
"preamble_fs_latency",
111constexpr trace::Metric PreambleBuildFilesystemLatencyRatio(
114constexpr trace::Metric PreambleBuildSize(
"preamble_build_size",
116constexpr trace::Metric PreambleSerializedSize(
"preamble_serialized_size",
119void reportPreambleBuild(
const PreambleBuildStats &Stats,
120 bool IsFirstPreamble) {
121 auto RecordWithLabel = [&Stats](llvm::StringRef
Label) {
124 PreambleBuildFilesystemLatencyRatio.record(
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);
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;
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];
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)
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) {
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,
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,
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;
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;
609 TUScheduler::ASTCache &LRUCache,
610 TUScheduler::HeaderIncluderCache &HeaderIncluders,
611 Semaphore &Barrier,
bool RunSync,
const TUScheduler::Options &Opts,
612 ParsingCallbacks &Callbacks);
620 static ASTWorkerHandle
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;
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;
815ASTWorker::create(
PathRef FileName,
const GlobalCompilationDatabase &CDB,
816 TUScheduler::ASTCache &IdleASTs,
817 TUScheduler::HeaderIncluderCache &HeaderIncluders,
818 AsyncTaskRunner *Tasks, Semaphore &Barrier,
819 const TUScheduler::Options &Opts,
820 ParsingCallbacks &Callbacks) {
821 std::shared_ptr<ASTWorker> Worker(
822 new ASTWorker(
FileName, CDB, IdleASTs, HeaderIncluders, Barrier,
823 !Tasks, Opts, Callbacks));
826 [Worker]() { Worker->run(); });
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),
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");
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);
893 bool InputsAreTheSame =
894 std::tie(FileInputs.CompileCommand, FileInputs.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}",
910 Inputs.CompileCommand.Directory,
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;
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),
956 if (LatestPreamble &&
WantDiags != WantDiagnostics::No)
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;
971 TUScheduler::NoInvalidation);
974void ASTWorker::runWithAST(
975 llvm::StringRef
Name,
976 llvm::unique_function<
void(llvm::Expected<InputsAndAST>)>
Action,
977 TUScheduler::ASTActionInvalidation Invalidation) {
979 static constexpr trace::Metric ASTAccessForRead(
980 "ast_access_read", trace::Metric::Counter,
"result");
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,
996 std::optional<ParsedAST> NewAST;
998 NewAST = ParsedAST::build(
FileName, FileInputs, std::move(Invocation),
999 CompilerInvocationDiagConsumer.take(),
1000 getPossiblyStalePreamble());
1003 AST = NewAST ? std::make_unique<ParsedAST>(std::move(*NewAST)) : nullptr;
1006 auto _ = llvm::make_scope_exit(
1007 [&AST,
this]() { IdleASTs.put(
this, std::move(*AST)); });
1010 return Action(
error(llvm::errc::invalid_argument,
"invalid AST"));
1012 FileInputs.Version);
1013 Action(InputsAndAST{FileInputs, **AST});
1015 startTask(
Name, std::move(Task), std::nullopt, Invalidation);
1019static void crashDumpCompileCommand(llvm::raw_ostream &
OS,
1020 const tooling::CompileCommand &Command) {
1021 OS <<
" Filename: " << Command.Filename <<
"\n";
1022 OS <<
" Directory: " << Command.Directory <<
"\n";
1023 OS <<
" Command Line:";
1024 for (
auto &Arg : Command.CommandLine) {
1031static void crashDumpFileContents(llvm::raw_ostream &
OS,
1032 const std::string &Contents) {
1037 if (getenv(
"CLANGD_CRASH_DUMP_SOURCE")) {
1038 OS <<
" Contents:\n";
1039 OS << Contents <<
"\n";
1044static void crashDumpParseInputs(llvm::raw_ostream &
OS,
1045 const ParseInputs &FileInputs) {
1046 auto &Command = FileInputs.CompileCommand;
1047 crashDumpCompileCommand(
OS, Command);
1048 OS <<
" Version: " << FileInputs.Version <<
"\n";
1049 crashDumpFileContents(
OS, FileInputs.Contents);
1052void PreambleThread::build(Request Req) {
1053 assert(Req.CI &&
"Got preamble request with null compiler invocation");
1054 const ParseInputs &
Inputs = Req.Inputs;
1055 bool ReusedPreamble =
false;
1057 Status.update([&](TUStatus &Status) {
1058 Status.PreambleActivity = PreambleAction::Building;
1060 auto _ = llvm::make_scope_exit([
this, &Req, &ReusedPreamble] {
1061 ASTPeer.updatePreamble(std::move(Req.CI), std::move(Req.Inputs),
1062 LatestBuild, std::move(Req.CIDiags),
1063 std::move(Req.WantDiags));
1064 if (!ReusedPreamble)
1065 Callbacks.onPreamblePublished(
FileName);
1068 if (!LatestBuild ||
Inputs.ForceRebuild) {
1069 vlog(
"Building first preamble for {0} version {1}",
FileName,
1072 vlog(
"Reusing preamble version {0} for version {1} of {2}",
1074 ReusedPreamble =
true;
1077 vlog(
"Rebuilding invalidated preamble for {0} version {1} (previous was "
1082 ThreadCrashReporter ScopedReporter([&
Inputs]() {
1083 llvm::errs() <<
"Signalled while building preamble\n";
1084 crashDumpParseInputs(llvm::errs(),
Inputs);
1087 PreambleBuildStats Stats;
1088 bool IsFirstPreamble = !LatestBuild;
1091 [&](CapturedASTCtx ASTCtx,
1092 std::shared_ptr<const include_cleaner::PragmaIncludes> PI) {
1093 Callbacks.onPreambleAST(FileName, Inputs.Version, std::move(ASTCtx),
1099 reportPreambleBuild(Stats, IsFirstPreamble);
1100 if (isReliable(LatestBuild->CompileCommand))
1101 HeaderIncluders.update(
FileName, LatestBuild->Includes.allHeaders());
1104void ASTWorker::updatePreamble(std::unique_ptr<CompilerInvocation>
CI,
1106 std::shared_ptr<const PreambleData>
Preamble,
1109 llvm::StringLiteral TaskName =
"Build AST";
1117 if (!LatestPreamble ||
Preamble != *LatestPreamble) {
1118 ++PreambleBuildCount;
1120 IdleASTs.take(
this);
1121 RanASTCallback =
false;
1122 std::lock_guard<std::mutex> Lock(Mutex);
1126 std::swap(*LatestPreamble,
Preamble);
1128 LatestPreamble = std::move(
Preamble);
1131 PreambleCV.notify_all();
1148 generateDiagnostics(std::move(
CI), FileInputs, std::move(
CIDiags));
1151 runTask(TaskName, Task);
1155 std::lock_guard<std::mutex> Lock(Mutex);
1156 PreambleRequests.push_back({std::move(Task), std::string(TaskName),
1157 steady_clock::now(), Context::current().clone(),
1158 std::nullopt, std::nullopt,
1159 TUScheduler::NoInvalidation,
nullptr});
1161 PreambleCV.notify_all();
1162 RequestsCV.notify_all();
1165void ASTWorker::updateASTSignals(ParsedAST &AST) {
1166 auto Signals = std::make_shared<const ASTSignals>(ASTSignals::derive(AST));
1170 std::lock_guard<std::mutex> Lock(Mutex);
1171 std::swap(LatestASTSignals, Signals);
1175void ASTWorker::generateDiagnostics(
1176 std::unique_ptr<CompilerInvocation> Invocation, ParseInputs
Inputs,
1179 static constexpr trace::Metric ASTAccessForDiag(
1180 "ast_access_diag", trace::Metric::Counter,
"result");
1182 assert(LatestPreamble);
1185 std::lock_guard<std::mutex> Lock(PublishMu);
1186 if (!CanPublishResults)
1190 bool InputsAreLatest =
1191 std::tie(FileInputs.CompileCommand, FileInputs.Contents) ==
1198 if (InputsAreLatest && RanASTCallback)
1202 std::string TaskName = llvm::formatv(
"Build AST ({0})",
Inputs.Version);
1203 Status.update([&](TUStatus &Status) {
1204 Status.ASTActivity.K = ASTAction::Building;
1205 Status.ASTActivity.Name = std::move(TaskName);
1210 std::optional<std::unique_ptr<ParsedAST>> AST =
1211 IdleASTs.take(
this, &ASTAccessForDiag);
1212 if (!AST || !InputsAreLatest) {
1213 auto RebuildStartTime = DebouncePolicy::clock::now();
1214 std::optional<ParsedAST> NewAST = ParsedAST::build(
1216 auto RebuildDuration = DebouncePolicy::clock::now() - RebuildStartTime;
1220 std::unique_lock<std::mutex> Lock(Mutex, std::try_to_lock);
1221 if (Lock.owns_lock()) {
1224 if (RebuildTimes.size() == RebuildTimes.capacity())
1225 RebuildTimes.erase(RebuildTimes.begin());
1226 RebuildTimes.push_back(RebuildDuration);
1229 Status.update([&](TUStatus &Status) {
1230 Status.Details.ReuseAST =
false;
1231 Status.Details.BuildFailed = !NewAST;
1233 AST = NewAST ? std::make_unique<ParsedAST>(std::move(*NewAST)) : nullptr;
1235 log(
"Skipping rebuild of the AST for {0}, inputs are the same.",
FileName);
1236 Status.update([](TUStatus &Status) {
1237 Status.Details.ReuseAST =
true;
1238 Status.Details.BuildFailed =
false;
1243 auto RunPublish = [&](llvm::function_ref<void()> Publish) {
1246 std::lock_guard<std::mutex> Lock(PublishMu);
1247 if (CanPublishResults)
1251 trace::Span Span(
"Running main AST callback");
1252 Callbacks.onMainAST(
FileName, **AST, RunPublish);
1253 updateASTSignals(**AST);
1265 if (InputsAreLatest) {
1266 RanASTCallback = *AST !=
nullptr;
1267 IdleASTs.put(
this, std::move(*AST));
1271std::shared_ptr<const PreambleData> ASTWorker::getPossiblyStalePreamble(
1272 std::shared_ptr<const ASTSignals> *ASTSignals)
const {
1273 std::lock_guard<std::mutex> Lock(Mutex);
1275 *ASTSignals = LatestASTSignals;
1276 return LatestPreamble ? *LatestPreamble :
nullptr;
1279void ASTWorker::waitForFirstPreamble()
const {
1280 std::unique_lock<std::mutex> Lock(Mutex);
1281 PreambleCV.wait(Lock, [
this] {
return LatestPreamble || Done; });
1284tooling::CompileCommand ASTWorker::getCurrentCompileCommand()
const {
1285 std::unique_lock<std::mutex> Lock(Mutex);
1286 return FileInputs.CompileCommand;
1289TUScheduler::FileStats ASTWorker::stats()
const {
1290 TUScheduler::FileStats Result;
1291 Result.ASTBuilds = ASTBuildCount;
1292 Result.PreambleBuilds = PreambleBuildCount;
1296 Result.UsedBytesAST = IdleASTs.getUsedBytes(
this);
1297 if (
auto Preamble = getPossiblyStalePreamble())
1298 Result.UsedBytesPreamble =
Preamble->Preamble.getSize();
1302bool ASTWorker::isASTCached()
const {
return IdleASTs.getUsedBytes(
this) != 0; }
1304void ASTWorker::stop() {
1306 std::lock_guard<std::mutex> Lock(PublishMu);
1307 CanPublishResults =
false;
1310 std::lock_guard<std::mutex> Lock(Mutex);
1311 assert(!Done &&
"stop() called twice");
1314 PreamblePeer.stop();
1316 PreambleCV.notify_all();
1318 RequestsCV.notify_all();
1321void ASTWorker::runTask(llvm::StringRef
Name, llvm::function_ref<
void()> Task) {
1322 ThreadCrashReporter ScopedReporter([
this,
Name]() {
1323 llvm::errs() <<
"Signalled during AST worker action: " <<
Name <<
"\n";
1324 crashDumpParseInputs(llvm::errs(), FileInputs);
1326 trace::Span Tracer(
Name);
1327 WithContext WithProvidedContext(ContextProvider(
FileName));
1331void ASTWorker::startTask(llvm::StringRef
Name,
1332 llvm::unique_function<
void()> Task,
1333 std::optional<UpdateType>
Update,
1334 TUScheduler::ASTActionInvalidation Invalidation) {
1336 assert(!Done &&
"running a task after stop()");
1337 runTask(
Name, Task);
1342 std::lock_guard<std::mutex> Lock(Mutex);
1343 assert(!Done &&
"running a task after stop()");
1346 for (
auto &R : llvm::reverse(Requests)) {
1347 if (R.InvalidationPolicy == TUScheduler::InvalidateOnUpdate)
1349 if (R.Update && R.Update->ContentChanged)
1355 Context
Ctx = Context::current().derive(FileBeingProcessed,
FileName);
1358 WithContext WC(std::move(
Ctx));
1360 static_cast<int>(ErrorCode::ContentModified));
1365 if (trace::enabled()) {
1368 WithContext WC(
Ctx.clone());
1369 trace::Span Tracer(
"Queued:" +
Name);
1372 SPAN_ATTACH(Tracer,
"CurrentRequest", CurrentRequest->Name);
1373 llvm::json::Array PreambleRequestsNames;
1374 for (
const auto &Req : PreambleRequests)
1375 PreambleRequestsNames.push_back(Req.Name);
1377 std::move(PreambleRequestsNames));
1378 llvm::json::Array RequestsNames;
1379 for (
const auto &Req : Requests)
1380 RequestsNames.push_back(Req.Name);
1381 SPAN_ATTACH(Tracer,
"RequestsNames", std::move(RequestsNames));
1385 QueueCtx = Context::current().clone();
1387 Requests.push_back({std::move(Task), std::string(
Name), steady_clock::now(),
1391 RequestsCV.notify_all();
1394void ASTWorker::run() {
1395 clang::noteBottomOfStack();
1398 std::unique_lock<std::mutex> Lock(Mutex);
1399 assert(!CurrentRequest &&
"A task is already running, multiple workers?");
1400 for (
auto Wait = scheduleLocked(); !Wait.expired();
1401 Wait = scheduleLocked()) {
1402 assert(PreambleRequests.empty() &&
1403 "Preamble updates should be scheduled immediately");
1405 if (Requests.empty())
1412 std::optional<WithContext>
Ctx;
1413 std::optional<trace::Span> Tracer;
1414 if (!Requests.empty()) {
1415 Ctx.emplace(Requests.front().Ctx.clone());
1416 Tracer.emplace(
"Debounce");
1417 SPAN_ATTACH(*Tracer,
"next_request", Requests.front().Name);
1418 if (!(Wait == Deadline::infinity())) {
1419 Status.update([&](TUStatus &Status) {
1420 Status.ASTActivity.K = ASTAction::Queued;
1421 Status.ASTActivity.Name = Requests.front().
Name;
1424 std::chrono::duration_cast<std::chrono::milliseconds>(
1425 Wait.time() - steady_clock::now())
1430 wait(Lock, RequestsCV, Wait);
1434 if (!PreambleRequests.empty()) {
1435 CurrentRequest = std::move(PreambleRequests.front());
1436 PreambleRequests.pop_front();
1438 CurrentRequest = std::move(Requests.front());
1439 Requests.pop_front();
1444 CurrentRequest->QueueCtx.reset();
1449 std::unique_lock<Semaphore> Lock(Barrier, std::try_to_lock);
1450 if (!Lock.owns_lock()) {
1451 Status.update([&](TUStatus &Status) {
1452 Status.ASTActivity.K = ASTAction::Queued;
1453 Status.ASTActivity.Name = CurrentRequest->Name;
1457 WithContext Guard(std::move(CurrentRequest->Ctx));
1458 Status.update([&](TUStatus &Status) {
1459 Status.ASTActivity.K = ASTAction::RunningAction;
1460 Status.ASTActivity.Name = CurrentRequest->Name;
1462 runTask(CurrentRequest->Name, CurrentRequest->Action);
1465 bool IsEmpty =
false;
1467 std::lock_guard<std::mutex> Lock(Mutex);
1468 CurrentRequest.reset();
1469 IsEmpty = Requests.empty() && PreambleRequests.empty();
1472 Status.update([&](TUStatus &Status) {
1473 Status.ASTActivity.K = ASTAction::Idle;
1474 Status.ASTActivity.Name =
"";
1477 RequestsCV.notify_all();
1481Deadline ASTWorker::scheduleLocked() {
1483 if (!PreambleRequests.empty())
1484 return Deadline::zero();
1485 if (Requests.empty())
1486 return Deadline::infinity();
1488 for (
auto I = Requests.begin(),
E = Requests.end(); I !=
E; ++I) {
1491 if (I->Update == std::nullopt)
1496 if (I->Update == std::nullopt) {
1497 Request R = std::move(*I);
1499 Requests.push_front(std::move(R));
1500 return Deadline::zero();
1503 if (I->Update->Diagnostics == WantDiagnostics::Yes)
1504 I->Update->Diagnostics = WantDiagnostics::Auto;
1507 while (shouldSkipHeadLocked()) {
1508 vlog(
"ASTWorker skipping {0} for {1}", Requests.front().Name,
FileName);
1509 Requests.pop_front();
1511 assert(!Requests.empty() &&
"skipped the whole queue");
1516 for (
const auto &R : Requests)
1517 if (R.Update == std::nullopt ||
1518 R.Update->Diagnostics == WantDiagnostics::Yes)
1519 return Deadline::zero();
1521 Deadline D(Requests.front().AddTime + UpdateDebounce.compute(RebuildTimes));
1526bool ASTWorker::shouldSkipHeadLocked()
const {
1527 assert(!Requests.empty());
1528 auto Next = Requests.begin();
1529 auto Update = Next->Update;
1535 if (Next == Requests.end() || !Next->Update)
1538 switch (
Update->Diagnostics) {
1539 case WantDiagnostics::Yes:
1541 case WantDiagnostics::No:
1543 case WantDiagnostics::Auto:
1545 for (; Next != Requests.end(); ++Next)
1546 if (Next->Update && Next->Update->Diagnostics != WantDiagnostics::No)
1550 llvm_unreachable(
"Unknown WantDiagnostics");
1553bool ASTWorker::blockUntilIdle(Deadline Timeout)
const {
1554 auto WaitUntilASTWorkerIsIdle = [&] {
1555 std::unique_lock<std::mutex> Lock(Mutex);
1556 return wait(Lock, RequestsCV, Timeout, [&] {
1557 return PreambleRequests.empty() && Requests.empty() && !CurrentRequest;
1562 if (!WaitUntilASTWorkerIsIdle())
1567 if (!PreamblePeer.blockUntilIdle(Timeout))
1569 assert(Requests.empty() &&
1570 "No new normal tasks can be scheduled concurrently with "
1571 "blockUntilIdle(): ASTWorker isn't threadsafe");
1573 return WaitUntilASTWorkerIsIdle();
1580std::string renderTUAction(
const PreambleAction PA,
const ASTAction &AA) {
1581 llvm::SmallVector<std::string, 2> Result;
1583 case PreambleAction::Building:
1584 Result.push_back(
"parsing includes");
1586 case PreambleAction::Queued:
1587 Result.push_back(
"includes are queued");
1589 case PreambleAction::Idle:
1594 case ASTAction::Queued:
1595 Result.push_back(
"file is queued");
1597 case ASTAction::RunningAction:
1598 Result.push_back(
"running " + AA.Name);
1600 case ASTAction::Building:
1601 Result.push_back(
"parsing main file");
1603 case ASTAction::Idle:
1609 return llvm::join(Result,
", ");
1615 return llvm::heavyweight_hardware_concurrency().compute_thread_count();
1620 FStatus.
uri = URIForFile::canonicalize(File, File);
1621 FStatus.
state = renderTUAction(PreambleActivity, ASTActivity);
1633 std::unique_ptr<ParsingCallbacks> Callbacks)
1634 : CDB(CDB), Opts(Opts),
1635 Callbacks(Callbacks ? std::move(Callbacks)
1637 Barrier(Opts.AsyncThreadsCount), QuickRunBarrier(Opts.AsyncThreadsCount),
1639 std::make_unique<
ASTCache>(Opts.RetentionPolicy.MaxRetainedASTs)),
1642 if (!Opts.ContextProvider) {
1647 if (0 < Opts.AsyncThreadsCount) {
1648 PreambleTasks.emplace();
1649 WorkerThreads.emplace();
1659 PreambleTasks->wait();
1661 WorkerThreads->wait();
1665 for (
auto &
File : Files)
1666 if (!
File.getValue()->Worker->blockUntilIdle(D))
1669 if (!PreambleTasks->wait(D))
1676 std::unique_ptr<FileData> &FD = Files[
File];
1677 bool NewFile = FD ==
nullptr;
1681 ASTWorkerHandle Worker = ASTWorker::create(
1682 File, CDB, *IdleASTs, *HeaderIncluders,
1683 WorkerThreads ? &*WorkerThreads :
nullptr, Barrier, Opts, *Callbacks);
1684 FD = std::unique_ptr<FileData>(
1685 new FileData{Inputs.Contents, std::move(Worker)});
1687 }
else if (FD->Contents != Inputs.Contents) {
1689 FD->Contents = Inputs.Contents;
1695 LastActiveFile =
File.str();
1700 bool Removed = Files.erase(
File);
1702 elog(
"Trying to remove file from TUScheduler that is not tracked: {0}",
1711 llvm::unique_function<
void()>
Action) {
1716 llvm::unique_function<
void()>
Action) {
1719 runWithSemaphore(
Name,
Path, std::move(
Action), QuickRunBarrier);
1722void TUScheduler::runWithSemaphore(llvm::StringRef
Name, llvm::StringRef
Path,
1723 llvm::unique_function<
void()>
Action,
1726 Path = LastActiveFile;
1728 LastActiveFile =
Path.str();
1729 if (!PreambleTasks) {
1736 std::lock_guard<Semaphore> BarrierLock(Sem);
1737 WithContext WC(std::move(Ctx));
1738 WithContext WithProvidedContext(Opts.ContextProvider(Path));
1745 llvm::unique_function<
void(llvm::Expected<InputsAndAST>)>
Action,
1747 auto It = Files.find(
File);
1748 if (It == Files.end()) {
1749 Action(llvm::make_error<LSPError>(
1753 LastActiveFile =
File.str();
1755 It->second->Worker->runWithAST(
Name, std::move(
Action), Invalidation);
1761 auto It = Files.find(
File);
1762 if (It == Files.end()) {
1763 Action(llvm::make_error<LSPError>(
1764 "trying to get preamble for non-added document",
1768 LastActiveFile =
File.str();
1770 if (!PreambleTasks) {
1773 std::shared_ptr<const ASTSignals> Signals;
1774 std::shared_ptr<const PreambleData>
Preamble =
1775 It->second->Worker->getPossiblyStalePreamble(&Signals);
1778 It->second->Worker->getCurrentCompileCommand(),
1783 std::shared_ptr<const ASTWorker> Worker = It->second->Worker.lock();
1785 Contents = It->second->Contents,
1786 Command = Worker->getCurrentCompileCommand(),
1790 clang::noteBottomOfStack();
1792 llvm::errs() <<
"Signalled during preamble action: " <<
Name <<
"\n";
1793 crashDumpCompileCommand(llvm::errs(), Command);
1794 crashDumpFileContents(llvm::errs(), Contents);
1796 std::shared_ptr<const PreambleData>
Preamble;
1801 Worker->waitForFirstPreamble();
1803 std::shared_ptr<const ASTSignals> Signals;
1804 Preamble = Worker->getPossiblyStalePreamble(&Signals);
1806 std::lock_guard<Semaphore> BarrierLock(Barrier);
1814 PreambleTasks->runAsync(
"task:" + llvm::sys::path::filename(
File),
1819 llvm::StringMap<TUScheduler::FileStats> Result;
1820 for (
const auto &PathAndFile : Files)
1821 Result.try_emplace(PathAndFile.first(),
1822 PathAndFile.second->Worker->stats());
1827 std::vector<Path> Result;
1828 for (
auto &&PathAndFile : Files) {
1829 if (!PathAndFile.second->Worker->isASTCached())
1831 Result.push_back(std::string(PathAndFile.first()));
1836DebouncePolicy::clock::duration
1838 assert(
Min <=
Max &&
"Invalid policy");
1839 if (History.empty())
1844 History = History.take_back(15);
1845 llvm::SmallVector<clock::duration, 15> Recent(History);
1846 auto *Median = Recent.begin() + Recent.size() / 2;
1847 std::nth_element(Recent.begin(), Median, Recent.end());
1849 clock::duration Target =
1850 std::chrono::duration_cast<clock::duration>(
RebuildRatio * *Median);
1868 .
addUsage(Opts.StorePreamblesInMemory ? Elem.second.UsedBytesPreamble
1871 MT.
child(
"header_includer_cache").
addUsage(HeaderIncluders->getUsedBytes());
llvm::SmallString< 256U > Name
const PreambleData & Preamble
std::string Filename
Filename as a string.
llvm::BumpPtrAllocator Arena
std::unique_ptr< CompilerInvocation > CI
WantDiagnostics Diagnostics
std::optional< UpdateType > Update
std::optional< Context > QueueCtx
TUScheduler::ASTActionInvalidation InvalidationPolicy
std::vector< Diag > CIDiags
WantDiagnostics WantDiags
steady_clock::time_point AddTime
llvm::unique_function< void()> Action
#define SPAN_ATTACH(S, Name, Expr)
Attach a key-value pair to a Span event.
void runAsync(const llvm::Twine &Name, llvm::unique_function< void()> Action)
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.
Provides compilation arguments used for parsing C and C++ files.
Values in a Context are indexed by typed keys.
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.
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.
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.
llvm::Error error(std::error_code, std::string &&)
void log(Logger::Level L, const char *Fmt, Ts &&... Vals)
std::function< void()> Canceler
A canceller requests cancellation of a task, when called.
unsigned getDefaultAsyncThreadsCount()
Returns a number of a default async threads to use for TUScheduler.
std::string Path
A typedef to represent a file path.
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)
static clang::clangd::Key< std::string > FileBeingProcessed
bool isPreambleCompatible(const PreambleData &Preamble, const ParseInputs &Inputs, PathRef FileName, const CompilerInvocation &CI)
Returns true if Preamble is reusable for Inputs.
llvm::unique_function< void(llvm::Expected< T >)> Callback
A Callback<T> is a void function that accepts Expected<T>.
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 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)
WantDiagnostics
Determines whether diagnostics should be generated for a file snapshot.
int isCancelled(const Context &Ctx)
If the current context is within a cancelled task, returns the reason.
llvm::StringRef PathRef
A typedef to represent a ref to file path.
void elog(const char *Fmt, Ts &&... Vals)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
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.
double FileSystemTime
Time spent in filesystem operations during the build, in seconds.
size_t BuildSize
Estimate of the memory used while building the preamble.
double TotalBuildTime
Total wall time it took to build preamble, in seconds.
size_t SerializedSize
The serialized size of the preamble.
std::string Contents
Latest inputs, passed to TUScheduler::update().
std::function< Context(PathRef)> ContextProvider
Used to create a context that wraps each single operation.
Represents measurements of clangd events, e.g.
@ Distribution
A distribution of values with a meaningful mean and count.