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);
417 PreambleThrottler *Throttler;
418 std::atomic<bool> Satisfied = {
false};
425class PreambleThread {
427 PreambleThread(llvm::StringRef FileName, ParsingCallbacks &Callbacks,
428 bool StorePreambleInMemory,
bool RunSync,
429 PreambleThrottler *Throttler, SynchronizedTUStatus &Status,
430 TUScheduler::HeaderIncluderCache &HeaderIncluders,
433 StoreInMemory(StorePreambleInMemory), RunSync(RunSync),
434 Throttler(Throttler), Status(Status), ASTPeer(AW),
435 HeaderIncluders(HeaderIncluders) {}
440 void update(std::unique_ptr<CompilerInvocation>
CI, ParseInputs PI,
445 build(std::move(Req));
446 Status.update([](TUStatus &Status) {
452 std::unique_lock<std::mutex> Lock(Mutex);
457 ReqCV.wait(Lock, [
this] {
460 NextReq = std::move(Req);
471 clang::noteBottomOfStack();
473 std::optional<PreambleThrottlerRequest> Throttle;
475 std::unique_lock<std::mutex> Lock(Mutex);
476 assert(!CurrentReq &&
"Already processing a request?");
478 ReqCV.wait(Lock, [&] {
return NextReq || Done; });
483 Throttle.emplace(FileName, Throttler, ReqCV);
484 std::optional<trace::Span> Tracer;
486 if (!Throttle->satisfied()) {
487 Tracer.emplace(
"PreambleThrottle");
488 Status.update([&](TUStatus &Status) {
492 ReqCV.wait(Lock, [&] {
return Throttle->satisfied() || Done; });
499 CurrentReq = std::move(*NextReq);
504 WithContext Guard(std::move(CurrentReq->Ctx));
510 build(std::move(*CurrentReq));
514 bool IsEmpty =
false;
516 std::lock_guard<std::mutex> Lock(Mutex);
524 Status.update([](TUStatus &Status) {
530 dlog(
"Preamble worker for {0} stopped", FileName);
535 dlog(
"Preamble worker for {0} received stop", FileName);
537 std::lock_guard<std::mutex> Lock(Mutex);
545 bool blockUntilIdle(Deadline Timeout)
const {
546 std::unique_lock<std::mutex> Lock(Mutex);
547 return wait(Lock, ReqCV, Timeout, [&] {
return !NextReq && !CurrentReq; });
554 std::unique_ptr<CompilerInvocation>
CI;
562 std::lock_guard<std::mutex> Lock(Mutex);
568 void build(Request Req);
570 mutable std::mutex Mutex;
572 std::optional<Request> NextReq;
573 std::optional<Request> CurrentReq;
576 mutable std::condition_variable ReqCV;
578 std::shared_ptr<const PreambleData> LatestBuild;
581 ParsingCallbacks &Callbacks;
582 const bool StoreInMemory;
584 PreambleThrottler *Throttler;
586 SynchronizedTUStatus &Status;
588 TUScheduler::HeaderIncluderCache &HeaderIncluders;
591class ASTWorkerHandle;
604 friend class ASTWorkerHandle;
606 TUScheduler::ASTCache &LRUCache,
607 TUScheduler::HeaderIncluderCache &HeaderIncluders,
608 Semaphore &Barrier,
bool RunSync,
const TUScheduler::Options &Opts,
609 ParsingCallbacks &Callbacks);
617 static ASTWorkerHandle
619 TUScheduler::ASTCache &IdleASTs,
620 TUScheduler::HeaderIncluderCache &HeaderIncluders,
621 AsyncTaskRunner *Tasks, Semaphore &Barrier,
622 const TUScheduler::Options &Opts, ParsingCallbacks &Callbacks);
627 runWithAST(llvm::StringRef
Name,
628 llvm::unique_function<
void(llvm::Expected<InputsAndAST>)>
Action,
630 bool blockUntilIdle(Deadline Timeout)
const;
632 std::shared_ptr<const PreambleData> getPossiblyStalePreamble(
633 std::shared_ptr<const ASTSignals> *ASTSignals =
nullptr)
const;
639 void updatePreamble(std::unique_ptr<CompilerInvocation>
CI, ParseInputs PI,
640 std::shared_ptr<const PreambleData>
Preamble,
644 tooling::CompileCommand getCurrentCompileCommand()
const;
650 void waitForFirstPreamble()
const;
652 TUScheduler::FileStats stats()
const;
653 bool isASTCached()
const;
671 void generateDiagnostics(std::unique_ptr<CompilerInvocation> Invocation,
672 ParseInputs Inputs, std::vector<Diag>
CIDiags);
674 void updateASTSignals(ParsedAST &
AST);
684 void startTask(llvm::StringRef
Name, llvm::unique_function<
void()> Task,
685 std::optional<UpdateType>
Update,
688 void runTask(llvm::StringRef
Name, llvm::function_ref<
void()> Task);
694 Deadline scheduleLocked();
696 bool shouldSkipHeadLocked()
const;
710 TUScheduler::ASTCache &IdleASTs;
711 TUScheduler::HeaderIncluderCache &HeaderIncluders;
714 const DebouncePolicy UpdateDebounce;
718 const std::function<Context(llvm::StringRef)> ContextProvider;
719 const GlobalCompilationDatabase &CDB;
721 ParsingCallbacks &Callbacks;
725 bool RanASTCallback =
false;
727 mutable std::mutex Mutex;
731 ParseInputs FileInputs;
733 llvm::SmallVector<DebouncePolicy::clock::duration>
737 std::deque<Request> Requests;
738 std::optional<Request> CurrentRequest;
741 mutable std::condition_variable RequestsCV;
742 std::shared_ptr<const ASTSignals> LatestASTSignals;
746 std::optional<std::shared_ptr<const PreambleData>> LatestPreamble;
747 std::deque<Request> PreambleRequests;
750 mutable std::condition_variable PreambleCV;
753 std::mutex PublishMu;
760 bool CanPublishResults =
true;
761 std::atomic<unsigned> ASTBuildCount = {0};
762 std::atomic<unsigned> PreambleBuildCount = {0};
764 SynchronizedTUStatus Status;
765 PreambleThread PreamblePeer;
772class ASTWorkerHandle {
773 friend class ASTWorker;
774 ASTWorkerHandle(std::shared_ptr<ASTWorker> Worker)
775 : Worker(std::move(Worker)) {
776 assert(this->Worker);
780 ASTWorkerHandle(
const ASTWorkerHandle &) =
delete;
781 ASTWorkerHandle &operator=(
const ASTWorkerHandle &) =
delete;
782 ASTWorkerHandle(ASTWorkerHandle &&) =
default;
783 ASTWorkerHandle &operator=(ASTWorkerHandle &&) =
default;
790 ASTWorker &operator*() {
791 assert(Worker &&
"Handle was moved from");
795 ASTWorker *operator->() {
796 assert(Worker &&
"Handle was moved from");
804 std::shared_ptr<const ASTWorker> lock() {
return Worker; }
807 std::shared_ptr<ASTWorker> Worker;
811ASTWorker::create(
PathRef FileName,
const GlobalCompilationDatabase &CDB,
812 TUScheduler::ASTCache &IdleASTs,
813 TUScheduler::HeaderIncluderCache &HeaderIncluders,
814 AsyncTaskRunner *Tasks, Semaphore &Barrier,
815 const TUScheduler::Options &Opts,
816 ParsingCallbacks &Callbacks) {
817 std::shared_ptr<ASTWorker> Worker(
818 new ASTWorker(
FileName, CDB, IdleASTs, HeaderIncluders, Barrier,
819 !Tasks, Opts, Callbacks));
822 [Worker]() { Worker->run(); });
824 [Worker]() { Worker->PreamblePeer.run(); });
827 return ASTWorkerHandle(std::move(Worker));
830ASTWorker::ASTWorker(
PathRef FileName,
const GlobalCompilationDatabase &CDB,
831 TUScheduler::ASTCache &LRUCache,
832 TUScheduler::HeaderIncluderCache &HeaderIncluders,
833 Semaphore &Barrier,
bool RunSync,
834 const TUScheduler::Options &Opts,
835 ParsingCallbacks &Callbacks)
836 : IdleASTs(LRUCache), HeaderIncluders(HeaderIncluders), RunSync(RunSync),
838 ContextProvider(Opts.ContextProvider), CDB(CDB), Callbacks(Callbacks),
839 Barrier(Barrier), Done(false), Status(
FileName, Callbacks),
840 PreamblePeer(
FileName, Callbacks, Opts.StorePreamblesInMemory, RunSync,
841 Opts.PreambleThrottler, Status, HeaderIncluders, *this) {
845 FileInputs.CompileCommand = CDB.getFallbackCommand(
FileName);
848ASTWorker::~ASTWorker() {
852 std::lock_guard<std::mutex> Lock(Mutex);
853 assert(Done &&
"handle was not destroyed");
854 assert(Requests.empty() && !CurrentRequest &&
855 "unprocessed requests when destroying ASTWorker");
861 llvm::StringLiteral TaskName =
"Update";
862 auto Task = [=]()
mutable {
868 auto Cmd = CDB.getCompileCommand(
FileName);
871 if (!Cmd || !isReliable(*Cmd)) {
872 std::string ProxyFile = HeaderIncluders.get(
FileName);
873 if (!ProxyFile.empty()) {
874 auto ProxyCmd = CDB.getCompileCommand(ProxyFile);
875 if (!ProxyCmd || !isReliable(*ProxyCmd)) {
877 HeaderIncluders.remove(ProxyFile);
880 Cmd = tooling::transferCompileCommand(std::move(*ProxyCmd),
FileName);
885 Inputs.CompileCommand = std::move(*Cmd);
889 bool InputsAreTheSame =
890 std::tie(FileInputs.CompileCommand, FileInputs.Contents) ==
893 if (!InputsAreTheSame) {
895 RanASTCallback =
false;
900 std::lock_guard<std::mutex> Lock(Mutex);
904 log(
"ASTWorker building file {0} version {1} with command {2}\n[{3}]\n{4}",
906 Inputs.CompileCommand.Directory,
909 StoreDiags CompilerInvocationDiagConsumer;
910 std::vector<std::string> CC1Args;
912 Inputs, CompilerInvocationDiagConsumer, &CC1Args);
914 if (!CC1Args.empty())
915 vlog(
"Driver produced command: cc1 {0}",
printArgv(CC1Args));
916 std::vector<Diag> CompilerInvocationDiags =
917 CompilerInvocationDiagConsumer.take();
919 elog(
"Could not build CompilerInvocation for file {0}",
FileName);
922 RanASTCallback =
false;
925 std::move(CompilerInvocationDiags),
926 [&](llvm::function_ref<
void()> Publish) {
930 std::lock_guard<std::mutex> Lock(PublishMu);
931 if (CanPublishResults)
936 LatestPreamble.emplace();
939 PreambleCV.notify_all();
945 PreamblePeer.update(std::make_unique<CompilerInvocation>(*Invocation),
952 if (LatestPreamble &&
WantDiags != WantDiagnostics::No)
953 generateDiagnostics(std::move(Invocation), std::move(
Inputs),
954 std::move(CompilerInvocationDiags));
956 std::unique_lock<std::mutex> Lock(Mutex);
957 PreambleCV.wait(Lock, [
this] {
963 return LatestPreamble || !PreambleRequests.empty() || Done;
967 TUScheduler::NoInvalidation);
970void ASTWorker::runWithAST(
971 llvm::StringRef
Name,
972 llvm::unique_function<
void(llvm::Expected<InputsAndAST>)>
Action,
973 TUScheduler::ASTActionInvalidation Invalidation) {
975 static constexpr trace::Metric ASTAccessForRead(
976 "ast_access_read", trace::Metric::Counter,
"result");
979 return Action(llvm::make_error<CancelledError>(Reason));
980 std::optional<std::unique_ptr<ParsedAST>> AST =
981 IdleASTs.take(
this, &ASTAccessForRead);
983 StoreDiags CompilerInvocationDiagConsumer;
984 std::unique_ptr<CompilerInvocation> Invocation =
987 vlog(
"ASTWorker rebuilding evicted AST to run {0}: {1} version {2}",
Name,
992 std::optional<ParsedAST> NewAST;
994 NewAST = ParsedAST::build(
FileName, FileInputs, std::move(Invocation),
995 CompilerInvocationDiagConsumer.take(),
996 getPossiblyStalePreamble());
999 AST = NewAST ? std::make_unique<ParsedAST>(std::move(*NewAST)) : nullptr;
1002 auto _ = llvm::make_scope_exit(
1003 [&AST,
this]() { IdleASTs.put(
this, std::move(*AST)); });
1006 return Action(
error(llvm::errc::invalid_argument,
"invalid AST"));
1008 FileInputs.Version);
1009 Action(InputsAndAST{FileInputs, **AST});
1011 startTask(
Name, std::move(Task), std::nullopt, Invalidation);
1015static void crashDumpCompileCommand(llvm::raw_ostream &
OS,
1016 const tooling::CompileCommand &Command) {
1017 OS <<
" Filename: " << Command.Filename <<
"\n";
1018 OS <<
" Directory: " << Command.Directory <<
"\n";
1019 OS <<
" Command Line:";
1020 for (
auto &Arg : Command.CommandLine) {
1027static void crashDumpFileContents(llvm::raw_ostream &
OS,
1028 const std::string &Contents) {
1033 if (getenv(
"CLANGD_CRASH_DUMP_SOURCE")) {
1034 OS <<
" Contents:\n";
1035 OS << Contents <<
"\n";
1040static void crashDumpParseInputs(llvm::raw_ostream &
OS,
1041 const ParseInputs &FileInputs) {
1042 auto &Command = FileInputs.CompileCommand;
1043 crashDumpCompileCommand(
OS, Command);
1044 OS <<
" Version: " << FileInputs.Version <<
"\n";
1045 crashDumpFileContents(
OS, FileInputs.Contents);
1048void PreambleThread::build(Request Req) {
1049 assert(Req.CI &&
"Got preamble request with null compiler invocation");
1050 const ParseInputs &
Inputs = Req.Inputs;
1051 bool ReusedPreamble =
false;
1053 Status.update([&](TUStatus &Status) {
1054 Status.PreambleActivity = PreambleAction::Building;
1056 auto _ = llvm::make_scope_exit([
this, &Req, &ReusedPreamble] {
1057 ASTPeer.updatePreamble(std::move(Req.CI), std::move(Req.Inputs),
1058 LatestBuild, std::move(Req.CIDiags),
1059 std::move(Req.WantDiags));
1060 if (!ReusedPreamble)
1061 Callbacks.onPreamblePublished(
FileName);
1064 if (!LatestBuild ||
Inputs.ForceRebuild) {
1065 vlog(
"Building first preamble for {0} version {1}",
FileName,
1068 vlog(
"Reusing preamble version {0} for version {1} of {2}",
1070 ReusedPreamble =
true;
1073 vlog(
"Rebuilding invalidated preamble for {0} version {1} (previous was "
1078 ThreadCrashReporter ScopedReporter([&
Inputs]() {
1079 llvm::errs() <<
"Signalled while building preamble\n";
1080 crashDumpParseInputs(llvm::errs(),
Inputs);
1083 PreambleBuildStats Stats;
1084 bool IsFirstPreamble = !LatestBuild;
1087 [&](CapturedASTCtx ASTCtx,
1088 std::shared_ptr<const include_cleaner::PragmaIncludes> PI) {
1089 Callbacks.onPreambleAST(FileName, Inputs.Version, std::move(ASTCtx),
1095 reportPreambleBuild(Stats, IsFirstPreamble);
1096 if (isReliable(LatestBuild->CompileCommand))
1097 HeaderIncluders.update(
FileName, LatestBuild->Includes.allHeaders());
1100void ASTWorker::updatePreamble(std::unique_ptr<CompilerInvocation>
CI,
1102 std::shared_ptr<const PreambleData>
Preamble,
1105 llvm::StringLiteral TaskName =
"Build AST";
1113 if (!LatestPreamble ||
Preamble != *LatestPreamble) {
1114 ++PreambleBuildCount;
1116 IdleASTs.take(
this);
1117 RanASTCallback =
false;
1118 std::lock_guard<std::mutex> Lock(Mutex);
1122 std::swap(*LatestPreamble,
Preamble);
1124 LatestPreamble = std::move(
Preamble);
1127 PreambleCV.notify_all();
1144 generateDiagnostics(std::move(
CI), FileInputs, std::move(
CIDiags));
1147 runTask(TaskName, Task);
1151 std::lock_guard<std::mutex> Lock(Mutex);
1152 PreambleRequests.push_back({std::move(Task), std::string(TaskName),
1153 steady_clock::now(), Context::current().clone(),
1154 std::nullopt, std::nullopt,
1155 TUScheduler::NoInvalidation,
nullptr});
1157 PreambleCV.notify_all();
1158 RequestsCV.notify_all();
1161void ASTWorker::updateASTSignals(ParsedAST &AST) {
1162 auto Signals = std::make_shared<const ASTSignals>(ASTSignals::derive(AST));
1166 std::lock_guard<std::mutex> Lock(Mutex);
1167 std::swap(LatestASTSignals, Signals);
1171void ASTWorker::generateDiagnostics(
1172 std::unique_ptr<CompilerInvocation> Invocation, ParseInputs
Inputs,
1175 static constexpr trace::Metric ASTAccessForDiag(
1176 "ast_access_diag", trace::Metric::Counter,
"result");
1178 assert(LatestPreamble);
1181 std::lock_guard<std::mutex> Lock(PublishMu);
1182 if (!CanPublishResults)
1186 bool InputsAreLatest =
1187 std::tie(FileInputs.CompileCommand, FileInputs.Contents) ==
1194 if (InputsAreLatest && RanASTCallback)
1198 std::string TaskName = llvm::formatv(
"Build AST ({0})",
Inputs.Version);
1199 Status.update([&](TUStatus &Status) {
1200 Status.ASTActivity.K = ASTAction::Building;
1201 Status.ASTActivity.Name = std::move(TaskName);
1206 std::optional<std::unique_ptr<ParsedAST>> AST =
1207 IdleASTs.take(
this, &ASTAccessForDiag);
1208 if (!AST || !InputsAreLatest) {
1209 auto RebuildStartTime = DebouncePolicy::clock::now();
1210 std::optional<ParsedAST> NewAST = ParsedAST::build(
1212 auto RebuildDuration = DebouncePolicy::clock::now() - RebuildStartTime;
1216 std::unique_lock<std::mutex> Lock(Mutex, std::try_to_lock);
1217 if (Lock.owns_lock()) {
1220 if (RebuildTimes.size() == RebuildTimes.capacity())
1221 RebuildTimes.erase(RebuildTimes.begin());
1222 RebuildTimes.push_back(RebuildDuration);
1225 Status.update([&](TUStatus &Status) {
1226 Status.Details.ReuseAST =
false;
1227 Status.Details.BuildFailed = !NewAST;
1229 AST = NewAST ? std::make_unique<ParsedAST>(std::move(*NewAST)) : nullptr;
1231 log(
"Skipping rebuild of the AST for {0}, inputs are the same.",
FileName);
1232 Status.update([](TUStatus &Status) {
1233 Status.Details.ReuseAST =
true;
1234 Status.Details.BuildFailed =
false;
1239 auto RunPublish = [&](llvm::function_ref<void()> Publish) {
1242 std::lock_guard<std::mutex> Lock(PublishMu);
1243 if (CanPublishResults)
1247 trace::Span Span(
"Running main AST callback");
1248 Callbacks.onMainAST(
FileName, **AST, RunPublish);
1249 updateASTSignals(**AST);
1261 if (InputsAreLatest) {
1262 RanASTCallback = *AST !=
nullptr;
1263 IdleASTs.put(
this, std::move(*AST));
1267std::shared_ptr<const PreambleData> ASTWorker::getPossiblyStalePreamble(
1268 std::shared_ptr<const ASTSignals> *ASTSignals)
const {
1269 std::lock_guard<std::mutex> Lock(Mutex);
1271 *ASTSignals = LatestASTSignals;
1272 return LatestPreamble ? *LatestPreamble :
nullptr;
1275void ASTWorker::waitForFirstPreamble()
const {
1276 std::unique_lock<std::mutex> Lock(Mutex);
1277 PreambleCV.wait(Lock, [
this] {
return LatestPreamble || Done; });
1280tooling::CompileCommand ASTWorker::getCurrentCompileCommand()
const {
1281 std::unique_lock<std::mutex> Lock(Mutex);
1282 return FileInputs.CompileCommand;
1285TUScheduler::FileStats ASTWorker::stats()
const {
1286 TUScheduler::FileStats Result;
1287 Result.ASTBuilds = ASTBuildCount;
1288 Result.PreambleBuilds = PreambleBuildCount;
1292 Result.UsedBytesAST = IdleASTs.getUsedBytes(
this);
1293 if (
auto Preamble = getPossiblyStalePreamble())
1294 Result.UsedBytesPreamble =
Preamble->Preamble.getSize();
1298bool ASTWorker::isASTCached()
const {
return IdleASTs.getUsedBytes(
this) != 0; }
1300void ASTWorker::stop() {
1302 std::lock_guard<std::mutex> Lock(PublishMu);
1303 CanPublishResults =
false;
1306 std::lock_guard<std::mutex> Lock(Mutex);
1307 assert(!Done &&
"stop() called twice");
1310 PreamblePeer.stop();
1312 PreambleCV.notify_all();
1314 RequestsCV.notify_all();
1317void ASTWorker::runTask(llvm::StringRef
Name, llvm::function_ref<
void()> Task) {
1318 ThreadCrashReporter ScopedReporter([
this,
Name]() {
1319 llvm::errs() <<
"Signalled during AST worker action: " <<
Name <<
"\n";
1320 crashDumpParseInputs(llvm::errs(), FileInputs);
1322 trace::Span Tracer(
Name);
1323 WithContext WithProvidedContext(ContextProvider(
FileName));
1327void ASTWorker::startTask(llvm::StringRef
Name,
1328 llvm::unique_function<
void()> Task,
1329 std::optional<UpdateType>
Update,
1330 TUScheduler::ASTActionInvalidation Invalidation) {
1332 assert(!Done &&
"running a task after stop()");
1333 runTask(
Name, Task);
1338 std::lock_guard<std::mutex> Lock(Mutex);
1339 assert(!Done &&
"running a task after stop()");
1342 for (
auto &R : llvm::reverse(Requests)) {
1343 if (R.InvalidationPolicy == TUScheduler::InvalidateOnUpdate)
1345 if (R.Update && R.Update->ContentChanged)
1351 Context
Ctx = Context::current().derive(FileBeingProcessed,
FileName);
1354 WithContext WC(std::move(
Ctx));
1356 static_cast<int>(ErrorCode::ContentModified));
1361 if (trace::enabled()) {
1364 WithContext WC(
Ctx.clone());
1365 trace::Span Tracer(
"Queued:" +
Name);
1368 SPAN_ATTACH(Tracer,
"CurrentRequest", CurrentRequest->Name);
1369 llvm::json::Array PreambleRequestsNames;
1370 for (
const auto &Req : PreambleRequests)
1371 PreambleRequestsNames.push_back(Req.Name);
1373 std::move(PreambleRequestsNames));
1374 llvm::json::Array RequestsNames;
1375 for (
const auto &Req : Requests)
1376 RequestsNames.push_back(Req.Name);
1377 SPAN_ATTACH(Tracer,
"RequestsNames", std::move(RequestsNames));
1381 QueueCtx = Context::current().clone();
1383 Requests.push_back({std::move(Task), std::string(
Name), steady_clock::now(),
1387 RequestsCV.notify_all();
1390void ASTWorker::run() {
1391 clang::noteBottomOfStack();
1394 std::unique_lock<std::mutex> Lock(Mutex);
1395 assert(!CurrentRequest &&
"A task is already running, multiple workers?");
1396 for (
auto Wait = scheduleLocked(); !Wait.expired();
1397 Wait = scheduleLocked()) {
1398 assert(PreambleRequests.empty() &&
1399 "Preamble updates should be scheduled immediately");
1401 if (Requests.empty())
1408 std::optional<WithContext>
Ctx;
1409 std::optional<trace::Span> Tracer;
1410 if (!Requests.empty()) {
1411 Ctx.emplace(Requests.front().Ctx.clone());
1412 Tracer.emplace(
"Debounce");
1413 SPAN_ATTACH(*Tracer,
"next_request", Requests.front().Name);
1414 if (!(Wait == Deadline::infinity())) {
1415 Status.update([&](TUStatus &Status) {
1416 Status.ASTActivity.K = ASTAction::Queued;
1417 Status.ASTActivity.Name = Requests.front().
Name;
1420 std::chrono::duration_cast<std::chrono::milliseconds>(
1421 Wait.time() - steady_clock::now())
1426 wait(Lock, RequestsCV, Wait);
1430 if (!PreambleRequests.empty()) {
1431 CurrentRequest = std::move(PreambleRequests.front());
1432 PreambleRequests.pop_front();
1434 CurrentRequest = std::move(Requests.front());
1435 Requests.pop_front();
1440 CurrentRequest->QueueCtx.reset();
1445 std::unique_lock<Semaphore> Lock(Barrier, std::try_to_lock);
1446 if (!Lock.owns_lock()) {
1447 Status.update([&](TUStatus &Status) {
1448 Status.ASTActivity.K = ASTAction::Queued;
1449 Status.ASTActivity.Name = CurrentRequest->Name;
1453 WithContext Guard(std::move(CurrentRequest->Ctx));
1454 Status.update([&](TUStatus &Status) {
1455 Status.ASTActivity.K = ASTAction::RunningAction;
1456 Status.ASTActivity.Name = CurrentRequest->Name;
1458 runTask(CurrentRequest->Name, CurrentRequest->Action);
1461 bool IsEmpty =
false;
1463 std::lock_guard<std::mutex> Lock(Mutex);
1464 CurrentRequest.reset();
1465 IsEmpty = Requests.empty() && PreambleRequests.empty();
1468 Status.update([&](TUStatus &Status) {
1469 Status.ASTActivity.K = ASTAction::Idle;
1470 Status.ASTActivity.Name =
"";
1473 RequestsCV.notify_all();
1477Deadline ASTWorker::scheduleLocked() {
1479 if (!PreambleRequests.empty())
1480 return Deadline::zero();
1481 if (Requests.empty())
1482 return Deadline::infinity();
1484 for (
auto I = Requests.begin(),
E = Requests.end(); I !=
E; ++I) {
1487 if (I->Update == std::nullopt)
1492 if (I->Update == std::nullopt) {
1493 Request R = std::move(*I);
1495 Requests.push_front(std::move(R));
1496 return Deadline::zero();
1499 if (I->Update->Diagnostics == WantDiagnostics::Yes)
1500 I->Update->Diagnostics = WantDiagnostics::Auto;
1503 while (shouldSkipHeadLocked()) {
1504 vlog(
"ASTWorker skipping {0} for {1}", Requests.front().Name,
FileName);
1505 Requests.pop_front();
1507 assert(!Requests.empty() &&
"skipped the whole queue");
1512 for (
const auto &R : Requests)
1513 if (R.Update == std::nullopt ||
1514 R.Update->Diagnostics == WantDiagnostics::Yes)
1515 return Deadline::zero();
1517 Deadline D(Requests.front().AddTime + UpdateDebounce.compute(RebuildTimes));
1522bool ASTWorker::shouldSkipHeadLocked()
const {
1523 assert(!Requests.empty());
1524 auto Next = Requests.begin();
1525 auto Update = Next->Update;
1531 if (Next == Requests.end() || !Next->Update)
1534 switch (
Update->Diagnostics) {
1535 case WantDiagnostics::Yes:
1537 case WantDiagnostics::No:
1539 case WantDiagnostics::Auto:
1541 for (; Next != Requests.end(); ++Next)
1542 if (Next->Update && Next->Update->Diagnostics != WantDiagnostics::No)
1546 llvm_unreachable(
"Unknown WantDiagnostics");
1549bool ASTWorker::blockUntilIdle(Deadline Timeout)
const {
1550 auto WaitUntilASTWorkerIsIdle = [&] {
1551 std::unique_lock<std::mutex> Lock(Mutex);
1552 return wait(Lock, RequestsCV, Timeout, [&] {
1553 return PreambleRequests.empty() && Requests.empty() && !CurrentRequest;
1558 if (!WaitUntilASTWorkerIsIdle())
1563 if (!PreamblePeer.blockUntilIdle(Timeout))
1565 assert(Requests.empty() &&
1566 "No new normal tasks can be scheduled concurrently with "
1567 "blockUntilIdle(): ASTWorker isn't threadsafe");
1569 return WaitUntilASTWorkerIsIdle();
1576std::string renderTUAction(
const PreambleAction PA,
const ASTAction &AA) {
1577 llvm::SmallVector<std::string, 2> Result;
1579 case PreambleAction::Building:
1580 Result.push_back(
"parsing includes");
1582 case PreambleAction::Queued:
1583 Result.push_back(
"includes are queued");
1585 case PreambleAction::Idle:
1590 case ASTAction::Queued:
1591 Result.push_back(
"file is queued");
1593 case ASTAction::RunningAction:
1594 Result.push_back(
"running " + AA.Name);
1596 case ASTAction::Building:
1597 Result.push_back(
"parsing main file");
1599 case ASTAction::Idle:
1605 return llvm::join(Result,
", ");
1611 return llvm::heavyweight_hardware_concurrency().compute_thread_count();
1616 FStatus.
uri = URIForFile::canonicalize(File, File);
1617 FStatus.
state = renderTUAction(PreambleActivity, ASTActivity);
1629 std::unique_ptr<ParsingCallbacks> Callbacks)
1630 : CDB(CDB), Opts(Opts),
1631 Callbacks(Callbacks ? std::move(Callbacks)
1633 Barrier(Opts.AsyncThreadsCount), QuickRunBarrier(Opts.AsyncThreadsCount),
1635 std::make_unique<
ASTCache>(Opts.RetentionPolicy.MaxRetainedASTs)),
1638 if (!Opts.ContextProvider) {
1643 if (0 < Opts.AsyncThreadsCount) {
1644 PreambleTasks.emplace();
1645 WorkerThreads.emplace();
1655 PreambleTasks->wait();
1657 WorkerThreads->wait();
1661 for (
auto &
File : Files)
1662 if (!
File.getValue()->Worker->blockUntilIdle(D))
1665 if (!PreambleTasks->wait(D))
1672 std::unique_ptr<FileData> &FD = Files[
File];
1673 bool NewFile = FD ==
nullptr;
1677 ASTWorkerHandle Worker = ASTWorker::create(
1678 File, CDB, *IdleASTs, *HeaderIncluders,
1679 WorkerThreads ? &*WorkerThreads :
nullptr, Barrier, Opts, *Callbacks);
1680 FD = std::unique_ptr<FileData>(
1681 new FileData{Inputs.Contents, std::move(Worker)});
1683 }
else if (FD->Contents != Inputs.Contents) {
1685 FD->Contents = Inputs.Contents;
1691 LastActiveFile =
File.str();
1696 bool Removed = Files.erase(
File);
1698 elog(
"Trying to remove file from TUScheduler that is not tracked: {0}",
1707 llvm::unique_function<
void()>
Action) {
1712 llvm::unique_function<
void()>
Action) {
1715 runWithSemaphore(
Name,
Path, std::move(
Action), QuickRunBarrier);
1718void TUScheduler::runWithSemaphore(llvm::StringRef
Name, llvm::StringRef
Path,
1719 llvm::unique_function<
void()>
Action,
1722 Path = LastActiveFile;
1724 LastActiveFile =
Path.str();
1725 if (!PreambleTasks) {
1732 std::lock_guard<Semaphore> BarrierLock(Sem);
1733 WithContext WC(std::move(Ctx));
1734 WithContext WithProvidedContext(Opts.ContextProvider(Path));
1741 llvm::unique_function<
void(llvm::Expected<InputsAndAST>)>
Action,
1743 auto It = Files.find(
File);
1744 if (It == Files.end()) {
1745 Action(llvm::make_error<LSPError>(
1749 LastActiveFile =
File.str();
1751 It->second->Worker->runWithAST(
Name, std::move(
Action), Invalidation);
1757 auto It = Files.find(
File);
1758 if (It == Files.end()) {
1759 Action(llvm::make_error<LSPError>(
1760 "trying to get preamble for non-added document",
1764 LastActiveFile =
File.str();
1766 if (!PreambleTasks) {
1769 std::shared_ptr<const ASTSignals> Signals;
1770 std::shared_ptr<const PreambleData>
Preamble =
1771 It->second->Worker->getPossiblyStalePreamble(&Signals);
1774 It->second->Worker->getCurrentCompileCommand(),
1779 std::shared_ptr<const ASTWorker> Worker = It->second->Worker.lock();
1781 Contents = It->second->Contents,
1782 Command = Worker->getCurrentCompileCommand(),
1786 clang::noteBottomOfStack();
1788 llvm::errs() <<
"Signalled during preamble action: " <<
Name <<
"\n";
1789 crashDumpCompileCommand(llvm::errs(), Command);
1790 crashDumpFileContents(llvm::errs(), Contents);
1792 std::shared_ptr<const PreambleData>
Preamble;
1797 Worker->waitForFirstPreamble();
1799 std::shared_ptr<const ASTSignals> Signals;
1800 Preamble = Worker->getPossiblyStalePreamble(&Signals);
1802 std::lock_guard<Semaphore> BarrierLock(Barrier);
1810 PreambleTasks->runAsync(
"task:" + llvm::sys::path::filename(
File),
1815 llvm::StringMap<TUScheduler::FileStats> Result;
1816 for (
const auto &PathAndFile : Files)
1817 Result.try_emplace(PathAndFile.first(),
1818 PathAndFile.second->Worker->stats());
1823 std::vector<Path> Result;
1824 for (
auto &&PathAndFile : Files) {
1825 if (!PathAndFile.second->Worker->isASTCached())
1827 Result.push_back(std::string(PathAndFile.first()));
1832DebouncePolicy::clock::duration
1834 assert(
Min <=
Max &&
"Invalid policy");
1835 if (History.empty())
1840 History = History.take_back(15);
1841 llvm::SmallVector<clock::duration, 15> Recent(History.begin(), History.end());
1842 auto *Median = Recent.begin() + Recent.size() / 2;
1843 std::nth_element(Recent.begin(), Median, Recent.end());
1845 clock::duration Target =
1846 std::chrono::duration_cast<clock::duration>(
RebuildRatio * *Median);
1864 .
addUsage(Opts.StorePreamblesInMemory ? Elem.second.UsedBytesPreamble
1867 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
llvm::raw_string_ostream OS
#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.