#include "TUScheduler.h"
#include "CompileCommands.h"
#include "Compiler.h"
#include "Config.h"
#include "Diagnostics.h"
#include "GlobalCompilationDatabase.h"
#include "ParsedAST.h"
#include "Preamble.h"
#include "clang-include-cleaner/Record.h"
#include "support/Cancellation.h"
#include "support/Context.h"
#include "support/Logger.h"
#include "support/MemoryTree.h"
#include "support/Path.h"
#include "support/ThreadCrashReporter.h"
#include "support/Threading.h"
#include "support/Trace.h"
#include "clang/Basic/Stack.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Tooling/CompilationDatabase.h"
#include "llvm/ADT/FunctionExtras.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Allocator.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Threading.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <queue>
#include <string>
#include <thread>
#include <type_traits>
#include <utility>
#include <vector>
namespace clang {
namespace clangd {
using std::chrono::steady_clock;
namespace {
constexpr trace::Metric
PreambleBuildFilesystemLatency("preamble_fs_latency",
trace::Metric::Distribution, "build_type");
constexpr trace::Metric PreambleBuildFilesystemLatencyRatio(
"preamble_fs_latency_ratio", trace::Metric::Distribution, "build_type");
constexpr trace::Metric PreambleBuildSize("preamble_build_size",
trace::Metric::Distribution);
constexpr trace::Metric PreambleSerializedSize("preamble_serialized_size",
trace::Metric::Distribution);
void reportPreambleBuild(const PreambleBuildStats &Stats,
bool IsFirstPreamble) {
auto RecordWithLabel = [&Stats](llvm::StringRef Label) {
PreambleBuildFilesystemLatency.record(Stats.FileSystemTime, Label);
if (Stats.TotalBuildTime > 0)
PreambleBuildFilesystemLatencyRatio.record(
Stats.FileSystemTime / Stats.TotalBuildTime, Label);
};
static llvm::once_flag OnceFlag;
llvm::call_once(OnceFlag, [&] { RecordWithLabel("first_build"); });
RecordWithLabel(IsFirstPreamble ? "first_build_for_file" : "rebuild");
PreambleBuildSize.record(Stats.BuildSize);
PreambleSerializedSize.record(Stats.SerializedSize);
}
class ASTWorker;
}
static clang::clangd::Key<std::string> FileBeingProcessed;
std::optional<llvm::StringRef> TUScheduler::getFileBeingProcessedInContext() {
if (auto *File = Context::current().get(FileBeingProcessed))
return llvm::StringRef(*File);
return std::nullopt;
}
class TUScheduler::ASTCache {
public:
using Key = const ASTWorker *;
ASTCache(unsigned MaxRetainedASTs) : MaxRetainedASTs(MaxRetainedASTs) {}
std::size_t getUsedBytes(Key K) {
std::lock_guard<std::mutex> Lock(Mut);
auto It = findByKey(K);
if (It == LRU.end() || !It->second)
return 0;
return It->second->getUsedBytes();
}
void put(Key K, std::unique_ptr<ParsedAST> V) {
std::unique_lock<std::mutex> Lock(Mut);
assert(findByKey(K) == LRU.end());
LRU.insert(LRU.begin(), {K, std::move(V)});
if (LRU.size() <= MaxRetainedASTs)
return;
std::unique_ptr<ParsedAST> ForCleanup = std::move(LRU.back().second);
LRU.pop_back();
Lock.unlock();
ForCleanup.reset();
}
std::optional<std::unique_ptr<ParsedAST>>
take(Key K, const trace::Metric *AccessMetric = nullptr) {
std::unique_lock<std::mutex> Lock(Mut);
auto Existing = findByKey(K);
if (Existing == LRU.end()) {
if (AccessMetric)
AccessMetric->record(1, "miss");
return std::nullopt;
}
if (AccessMetric)
AccessMetric->record(1, "hit");
std::unique_ptr<ParsedAST> V = std::move(Existing->second);
LRU.erase(Existing);
return std::optional<std::unique_ptr<ParsedAST>>(std::move(V));
}
private:
using KVPair = std::pair<Key, std::unique_ptr<ParsedAST>>;
std::vector<KVPair>::iterator findByKey(Key K) {
return llvm::find_if(LRU, [K](const KVPair &P) { return P.first == K; });
}
std::mutex Mut;
unsigned MaxRetainedASTs;
std::vector<KVPair> LRU;
};
class TUScheduler::HeaderIncluderCache {
llvm::BumpPtrAllocator Arena;
struct Association {
llvm::StringRef MainFile;
Association *Next;
};
llvm::StringMap<Association, llvm::BumpPtrAllocator &> HeaderToMain;
llvm::StringMap<Association *, llvm::BumpPtrAllocator &> MainToFirst;
std::atomic<size_t> UsedBytes;
mutable std::mutex Mu;
void invalidate(Association *First) {
Association *Current = First;
do {
Association *Next = Current->Next;
Current->Next = nullptr;
Current = Next;
} while (Current != First);
}
Association *associate(llvm::StringRef MainFile,
llvm::ArrayRef<std::string> Headers) {
Association *First = nullptr, *Prev = nullptr;
for (const std::string &Header : Headers) {
auto &Assoc = HeaderToMain[Header];
if (Assoc.Next)
continue;
Assoc.MainFile = MainFile;
Assoc.Next = Prev;
Prev = &Assoc;
if (!First)
First = &Assoc;
}
if (First)
First->Next = Prev;
return First;
}
void updateMemoryUsage() {
auto StringMapHeap = [](const auto &Map) {
return Map.getNumBuckets() * (sizeof(void *) + sizeof(unsigned));
};
size_t Usage = Arena.getTotalMemory() + StringMapHeap(MainToFirst) +
StringMapHeap(HeaderToMain) + sizeof(*this);
UsedBytes.store(Usage, std::memory_order_release);
}
public:
HeaderIncluderCache() : HeaderToMain(Arena), MainToFirst(Arena) {
updateMemoryUsage();
}
void update(PathRef MainFile, llvm::ArrayRef<std::string> Headers) {
std::lock_guard<std::mutex> Lock(Mu);
auto It = MainToFirst.try_emplace(MainFile, nullptr);
Association *&First = It.first->second;
if (First)
invalidate(First);
First = associate(It.first->first(), Headers);
updateMemoryUsage();
}
void remove(PathRef MainFile) {
std::lock_guard<std::mutex> Lock(Mu);
Association *&First = MainToFirst[MainFile];
if (First) {
invalidate(First);
First = nullptr;
}
}
std::string get(PathRef Header) const {
std::lock_guard<std::mutex> Lock(Mu);
return HeaderToMain.lookup(Header).MainFile.str();
}
size_t getUsedBytes() const {
return UsedBytes.load(std::memory_order_acquire);
}
};
namespace {
bool isReliable(const tooling::CompileCommand &Cmd) {
return Cmd.Heuristic.empty();
}
class SynchronizedTUStatus {
public:
SynchronizedTUStatus(PathRef FileName, ParsingCallbacks &Callbacks)
: FileName(FileName), Callbacks(Callbacks) {}
void update(llvm::function_ref<void(TUStatus &)> Mutator) {
std::lock_guard<std::mutex> Lock(StatusMu);
Mutator(Status);
emitStatusLocked();
}
void stop() {
std::lock_guard<std::mutex> Lock(StatusMu);
CanPublish = false;
}
private:
void emitStatusLocked() {
if (CanPublish)
Callbacks.onFileUpdated(FileName, Status);
}
const Path FileName;
std::mutex StatusMu;
TUStatus Status;
bool CanPublish = true;
ParsingCallbacks &Callbacks;
};
class PreambleThrottlerRequest {
public:
PreambleThrottlerRequest(llvm::StringRef Filename,
PreambleThrottler *Throttler,
std::condition_variable &CV)
: Throttler(Throttler),
Satisfied(Throttler == nullptr) {
if (!Throttler)
return;
ID = Throttler->acquire(Filename, [&] {
Satisfied.store(true, std::memory_order_release);
CV.notify_all();
});
}
bool satisfied() const { return Satisfied.load(std::memory_order_acquire); }
~PreambleThrottlerRequest() {
if (Throttler)
Throttler->release(ID);
}
private:
PreambleThrottler::RequestID ID;
PreambleThrottler *Throttler;
std::atomic<bool> Satisfied = {false};
};
class PreambleThread {
public:
PreambleThread(llvm::StringRef FileName, ParsingCallbacks &Callbacks,
bool StorePreambleInMemory, bool RunSync,
PreambleThrottler *Throttler, SynchronizedTUStatus &Status,
TUScheduler::HeaderIncluderCache &HeaderIncluders,
ASTWorker &AW)
: FileName(FileName), Callbacks(Callbacks),
StoreInMemory(StorePreambleInMemory), RunSync(RunSync),
Throttler(Throttler), Status(Status), ASTPeer(AW),
HeaderIncluders(HeaderIncluders) {}
void update(std::unique_ptr<CompilerInvocation> CI, ParseInputs PI,
std::vector<Diag> CIDiags, WantDiagnostics WantDiags) {
Request Req = {std::move(CI), std::move(PI), std::move(CIDiags), WantDiags,
Context::current().clone()};
if (RunSync) {
build(std::move(Req));
Status.update([](TUStatus &Status) {
Status.PreambleActivity = PreambleAction::Idle;
});
return;
}
{
std::unique_lock<std::mutex> Lock(Mutex);
ReqCV.wait(Lock, [this] {
return !NextReq || NextReq->WantDiags != WantDiagnostics::Yes;
});
NextReq = std::move(Req);
}
ReqCV.notify_all();
}
void run() {
clang::noteBottomOfStack();
while (true) {
std::optional<PreambleThrottlerRequest> Throttle;
{
std::unique_lock<std::mutex> Lock(Mutex);
assert(!CurrentReq && "Already processing a request?");
ReqCV.wait(Lock, [&] { return NextReq || Done; });
if (Done)
break;
{
Throttle.emplace(FileName, Throttler, ReqCV);
std::optional<trace::Span> Tracer;
if (!Throttle->satisfied()) {
Tracer.emplace("PreambleThrottle");
Status.update([&](TUStatus &Status) {
Status.PreambleActivity = PreambleAction::Queued;
});
}
ReqCV.wait(Lock, [&] { return Throttle->satisfied() || Done; });
}
if (Done)
break;
CurrentReq = std::move(*NextReq);
NextReq.reset();
}
{
WithContext Guard(std::move(CurrentReq->Ctx));
build(std::move(*CurrentReq));
}
Throttle.reset();
bool IsEmpty = false;
{
std::lock_guard<std::mutex> Lock(Mutex);
CurrentReq.reset();
IsEmpty = !NextReq;
}
if (IsEmpty) {
Status.update([](TUStatus &Status) {
Status.PreambleActivity = PreambleAction::Idle;
});
}
ReqCV.notify_all();
}
dlog("Preamble worker for {0} stopped", FileName);
}
void stop() {
dlog("Preamble worker for {0} received stop", FileName);
{
std::lock_guard<std::mutex> Lock(Mutex);
Done = true;
NextReq.reset();
}
ReqCV.notify_all();
}
bool blockUntilIdle(Deadline Timeout) const {
std::unique_lock<std::mutex> Lock(Mutex);
return wait(Lock, ReqCV, Timeout, [&] { return !NextReq && !CurrentReq; });
}
private:
struct Request {
std::unique_ptr<CompilerInvocation> CI;
ParseInputs Inputs;
std::vector<Diag> CIDiags;
WantDiagnostics WantDiags;
Context Ctx;
};
bool isDone() {
std::lock_guard<std::mutex> Lock(Mutex);
return Done;
}
void build(Request Req);
mutable std::mutex Mutex;
bool Done = false;
std::optional<Request> NextReq;
std::optional<Request> CurrentReq;
mutable std::condition_variable ReqCV;
std::shared_ptr<const PreambleData> LatestBuild;
const Path FileName;
ParsingCallbacks &Callbacks;
const bool StoreInMemory;
const bool RunSync;
PreambleThrottler *Throttler;
SynchronizedTUStatus &Status;
ASTWorker &ASTPeer;
TUScheduler::HeaderIncluderCache &HeaderIncluders;
};
class ASTWorkerHandle;
class ASTWorker {
friend class ASTWorkerHandle;
ASTWorker(PathRef FileName, const GlobalCompilationDatabase &CDB,
TUScheduler::ASTCache &LRUCache,
TUScheduler::HeaderIncluderCache &HeaderIncluders,
Semaphore &Barrier, bool RunSync, const TUScheduler::Options &Opts,
ParsingCallbacks &Callbacks);
public:
static ASTWorkerHandle
create(PathRef FileName, const GlobalCompilationDatabase &CDB,
TUScheduler::ASTCache &IdleASTs,
TUScheduler::HeaderIncluderCache &HeaderIncluders,
AsyncTaskRunner *Tasks, Semaphore &Barrier,
const TUScheduler::Options &Opts, ParsingCallbacks &Callbacks);
~ASTWorker();
void update(ParseInputs Inputs, WantDiagnostics, bool ContentChanged);
void
runWithAST(llvm::StringRef Name,
llvm::unique_function<void(llvm::Expected<InputsAndAST>)> Action,
TUScheduler::ASTActionInvalidation);
bool blockUntilIdle(Deadline Timeout) const;
std::shared_ptr<const PreambleData> getPossiblyStalePreamble(
std::shared_ptr<const ASTSignals> *ASTSignals = nullptr) const;
void updatePreamble(std::unique_ptr<CompilerInvocation> CI, ParseInputs PI,
std::shared_ptr<const PreambleData> Preamble,
std::vector<Diag> CIDiags, WantDiagnostics WantDiags);
tooling::CompileCommand getCurrentCompileCommand() const;
void waitForFirstPreamble() const;
TUScheduler::FileStats stats() const;
bool isASTCached() const;
private:
struct UpdateType {
WantDiagnostics Diagnostics;
bool ContentChanged;
};
void generateDiagnostics(std::unique_ptr<CompilerInvocation> Invocation,
ParseInputs Inputs, std::vector<Diag> CIDiags);
void updateASTSignals(ParsedAST &AST);
void run();
void stop();
void startTask(llvm::StringRef Name, llvm::unique_function<void()> Task,
std::optional<UpdateType> Update,
TUScheduler::ASTActionInvalidation);
void runTask(llvm::StringRef Name, llvm::function_ref<void()> Task);
Deadline scheduleLocked();
bool shouldSkipHeadLocked() const;
struct Request {
llvm::unique_function<void()> Action;
std::string Name;
steady_clock::time_point AddTime;
Context Ctx;
std::optional<Context> QueueCtx;
std::optional<UpdateType> Update;
TUScheduler::ASTActionInvalidation InvalidationPolicy;
Canceler Invalidate;
};
TUScheduler::ASTCache &IdleASTs;
TUScheduler::HeaderIncluderCache &HeaderIncluders;
const bool RunSync;
const DebouncePolicy UpdateDebounce;
const Path FileName;
const std::function<Context(llvm::StringRef)> ContextProvider;
const GlobalCompilationDatabase &CDB;
ParsingCallbacks &Callbacks;
Semaphore &Barrier;
bool RanASTCallback = false;
mutable std::mutex Mutex;
ParseInputs FileInputs;
llvm::SmallVector<DebouncePolicy::clock::duration>
RebuildTimes;
bool Done;
std::deque<Request> Requests;
std::optional<Request> CurrentRequest;
mutable std::condition_variable RequestsCV;
std::shared_ptr<const ASTSignals> LatestASTSignals;
std::optional<std::shared_ptr<const PreambleData>> LatestPreamble;
std::deque<Request> PreambleRequests;
mutable std::condition_variable PreambleCV;
std::mutex PublishMu;
bool CanPublishResults = true;
std::atomic<unsigned> ASTBuildCount = {0};
std::atomic<unsigned> PreambleBuildCount = {0};
SynchronizedTUStatus Status;
PreambleThread PreamblePeer;
};
class ASTWorkerHandle {
friend class ASTWorker;
ASTWorkerHandle(std::shared_ptr<ASTWorker> Worker)
: Worker(std::move(Worker)) {
assert(this->Worker);
}
public:
ASTWorkerHandle(const ASTWorkerHandle &) = delete;
ASTWorkerHandle &operator=(const ASTWorkerHandle &) = delete;
ASTWorkerHandle(ASTWorkerHandle &&) = default;
ASTWorkerHandle &operator=(ASTWorkerHandle &&) = default;
~ASTWorkerHandle() {
if (Worker)
Worker->stop();
}
ASTWorker &operator*() {
assert(Worker && "Handle was moved from");
return *Worker;
}
ASTWorker *operator->() {
assert(Worker && "Handle was moved from");
return Worker.get();
}
std::shared_ptr<const ASTWorker> lock() { return Worker; }
private:
std::shared_ptr<ASTWorker> Worker;
};
ASTWorkerHandle
ASTWorker::create(PathRef FileName, const GlobalCompilationDatabase &CDB,
TUScheduler::ASTCache &IdleASTs,
TUScheduler::HeaderIncluderCache &HeaderIncluders,
AsyncTaskRunner *Tasks, Semaphore &Barrier,
const TUScheduler::Options &Opts,
ParsingCallbacks &Callbacks) {
std::shared_ptr<ASTWorker> Worker(
new ASTWorker(FileName, CDB, IdleASTs, HeaderIncluders, Barrier,
!Tasks, Opts, Callbacks));
if (Tasks) {
Tasks->runAsync("ASTWorker:" + llvm::sys::path::filename(FileName),
[Worker]() { Worker->run(); });
Tasks->runAsync("PreambleWorker:" + llvm::sys::path::filename(FileName),
[Worker]() { Worker->PreamblePeer.run(); });
}
return ASTWorkerHandle(std::move(Worker));
}
ASTWorker::ASTWorker(PathRef FileName, const GlobalCompilationDatabase &CDB,
TUScheduler::ASTCache &LRUCache,
TUScheduler::HeaderIncluderCache &HeaderIncluders,
Semaphore &Barrier, bool RunSync,
const TUScheduler::Options &Opts,
ParsingCallbacks &Callbacks)
: IdleASTs(LRUCache), HeaderIncluders(HeaderIncluders), RunSync(RunSync),
UpdateDebounce(Opts.UpdateDebounce), FileName(FileName),
ContextProvider(Opts.ContextProvider), CDB(CDB), Callbacks(Callbacks),
Barrier(Barrier), Done(false), Status(FileName, Callbacks),
PreamblePeer(FileName, Callbacks, Opts.StorePreamblesInMemory, RunSync,
Opts.PreambleThrottler, Status, HeaderIncluders, *this) {
FileInputs.CompileCommand = CDB.getFallbackCommand(FileName);
}
ASTWorker::~ASTWorker() {
IdleASTs.take(this);
#ifndef NDEBUG
std::lock_guard<std::mutex> Lock(Mutex);
assert(Done && "handle was not destroyed");
assert(Requests.empty() && !CurrentRequest &&
"unprocessed requests when destroying ASTWorker");
#endif
}
void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags,
bool ContentChanged) {
llvm::StringLiteral TaskName = "Update";
auto Task = [=]() mutable {
auto Cmd = CDB.getCompileCommand(FileName);
if (!Cmd || !isReliable(*Cmd)) {
std::string ProxyFile = HeaderIncluders.get(FileName);
if (!ProxyFile.empty()) {
auto ProxyCmd = CDB.getCompileCommand(ProxyFile);
if (!ProxyCmd || !isReliable(*ProxyCmd)) {
HeaderIncluders.remove(ProxyFile);
} else {
Cmd = tooling::transferCompileCommand(std::move(*ProxyCmd), FileName);
}
}
}
if (Cmd)
Inputs.CompileCommand = std::move(*Cmd);
else
Inputs.CompileCommand = CDB.getFallbackCommand(FileName);
bool InputsAreTheSame =
std::tie(FileInputs.CompileCommand, FileInputs.Contents) ==
std::tie(Inputs.CompileCommand, Inputs.Contents);
if (!InputsAreTheSame) {
IdleASTs.take(this);
RanASTCallback = false;
}
{
std::lock_guard<std::mutex> Lock(Mutex);
FileInputs = Inputs;
}
log("ASTWorker building file {0} version {1} with command {2}\n[{3}]\n{4}",
FileName, Inputs.Version, Inputs.CompileCommand.Heuristic,
Inputs.CompileCommand.Directory,
printArgv(Inputs.CompileCommand.CommandLine));
StoreDiags CompilerInvocationDiagConsumer;
std::vector<std::string> CC1Args;
std::unique_ptr<CompilerInvocation> Invocation = buildCompilerInvocation(
Inputs, CompilerInvocationDiagConsumer, &CC1Args);
if (!CC1Args.empty())
vlog("Driver produced command: cc1 {0}", printArgv(CC1Args));
std::vector<Diag> CompilerInvocationDiags =
CompilerInvocationDiagConsumer.take();
if (!Invocation) {
elog("Could not build CompilerInvocation for file {0}", FileName);
IdleASTs.take(this);
RanASTCallback = false;
Callbacks.onFailedAST(FileName, Inputs.Version,
std::move(CompilerInvocationDiags),
[&](llvm::function_ref<void()> Publish) {
std::lock_guard<std::mutex> Lock(PublishMu);
if (CanPublishResults)
Publish();
});
LatestPreamble.emplace();
PreambleCV.notify_all();
return;
}
PreamblePeer.update(std::make_unique<CompilerInvocation>(*Invocation),
Inputs, CompilerInvocationDiags, WantDiags);
if (LatestPreamble && WantDiags != WantDiagnostics::No)
generateDiagnostics(std::move(Invocation), std::move(Inputs),
std::move(CompilerInvocationDiags));
std::unique_lock<std::mutex> Lock(Mutex);
PreambleCV.wait(Lock, [this] {
return LatestPreamble || !PreambleRequests.empty() || Done;
});
};
startTask(TaskName, std::move(Task), UpdateType{WantDiags, ContentChanged},
TUScheduler::NoInvalidation);
}
void ASTWorker::runWithAST(
llvm::StringRef Name,
llvm::unique_function<void(llvm::Expected<InputsAndAST>)> Action,
TUScheduler::ASTActionInvalidation Invalidation) {
static constexpr trace::Metric ASTAccessForRead(
"ast_access_read", trace::Metric::Counter, "result");
auto Task = [=, Action = std::move(Action)]() mutable {
if (auto Reason = isCancelled())
return Action(llvm::make_error<CancelledError>(Reason));
std::optional<std::unique_ptr<ParsedAST>> AST =
IdleASTs.take(this, &ASTAccessForRead);
if (!AST) {
StoreDiags CompilerInvocationDiagConsumer;
std::unique_ptr<CompilerInvocation> Invocation =
buildCompilerInvocation(FileInputs, CompilerInvocationDiagConsumer);
vlog("ASTWorker rebuilding evicted AST to run {0}: {1} version {2}", Name,
FileName, FileInputs.Version);
std::optional<ParsedAST> NewAST;
if (Invocation) {
NewAST = ParsedAST::build(FileName, FileInputs, std::move(Invocation),
CompilerInvocationDiagConsumer.take(),
getPossiblyStalePreamble());
++ASTBuildCount;
}
AST = NewAST ? std::make_unique<ParsedAST>(std::move(*NewAST)) : nullptr;
}
auto _ = llvm::make_scope_exit(
[&AST, this]() { IdleASTs.put(this, std::move(*AST)); });
if (!*AST)
return Action(error(llvm::errc::invalid_argument, "invalid AST"));
vlog("ASTWorker running {0} on version {2} of {1}", Name, FileName,
FileInputs.Version);
Action(InputsAndAST{FileInputs, **AST});
};
startTask(Name, std::move(Task), std::nullopt, Invalidation);
}
static void crashDumpCompileCommand(llvm::raw_ostream &OS,
const tooling::CompileCommand &Command) {
OS << " Filename: " << Command.Filename << "\n";
OS << " Directory: " << Command.Directory << "\n";
OS << " Command Line:";
for (auto &Arg : Command.CommandLine) {
OS << " " << Arg;
}
OS << "\n";
}
static void crashDumpFileContents(llvm::raw_ostream &OS,
const std::string &Contents) {
if (getenv("CLANGD_CRASH_DUMP_SOURCE")) {
OS << " Contents:\n";
OS << Contents << "\n";
}
}
static void crashDumpParseInputs(llvm::raw_ostream &OS,
const ParseInputs &FileInputs) {
auto &Command = FileInputs.CompileCommand;
crashDumpCompileCommand(OS, Command);
OS << " Version: " << FileInputs.Version << "\n";
crashDumpFileContents(OS, FileInputs.Contents);
}
void PreambleThread::build(Request Req) {
assert(Req.CI && "Got preamble request with null compiler invocation");
const ParseInputs &Inputs = Req.Inputs;
bool ReusedPreamble = false;
Status.update([&](TUStatus &Status) {
Status.PreambleActivity = PreambleAction::Building;
});
auto _ = llvm::make_scope_exit([this, &Req, &ReusedPreamble] {
ASTPeer.updatePreamble(std::move(Req.CI), std::move(Req.Inputs),
LatestBuild, std::move(Req.CIDiags),
std::move(Req.WantDiags));
if (!ReusedPreamble)
Callbacks.onPreamblePublished(FileName);
});
if (!LatestBuild || Inputs.ForceRebuild) {
vlog("Building first preamble for {0} version {1}", FileName,
Inputs.Version);
} else if (isPreambleCompatible(*LatestBuild, Inputs, FileName, *Req.CI)) {
vlog("Reusing preamble version {0} for version {1} of {2}",
LatestBuild->Version, Inputs.Version, FileName);
ReusedPreamble = true;
return;
} else {
vlog("Rebuilding invalidated preamble for {0} version {1} (previous was "
"version {2})",
FileName, Inputs.Version, LatestBuild->Version);
}
ThreadCrashReporter ScopedReporter([&Inputs]() {
llvm::errs() << "Signalled while building preamble\n";
crashDumpParseInputs(llvm::errs(), Inputs);
});
PreambleBuildStats Stats;
bool IsFirstPreamble = !LatestBuild;
LatestBuild = clang::clangd::buildPreamble(
FileName, *Req.CI, Inputs, StoreInMemory,
[&](CapturedASTCtx ASTCtx,
std::shared_ptr<const include_cleaner::PragmaIncludes> PI) {
Callbacks.onPreambleAST(FileName, Inputs.Version, std::move(ASTCtx),
std::move(PI));
},
&Stats);
if (!LatestBuild)
return;
reportPreambleBuild(Stats, IsFirstPreamble);
if (isReliable(LatestBuild->CompileCommand))
HeaderIncluders.update(FileName, LatestBuild->Includes.allHeaders());
}
void ASTWorker::updatePreamble(std::unique_ptr<CompilerInvocation> CI,
ParseInputs PI,
std::shared_ptr<const PreambleData> Preamble,
std::vector<Diag> CIDiags,
WantDiagnostics WantDiags) {
llvm::StringLiteral TaskName = "Build AST";
auto Task = [this, Preamble = std::move(Preamble), CI = std::move(CI),
CIDiags = std::move(CIDiags),
WantDiags = std::move(WantDiags)]() mutable {
if (!LatestPreamble || Preamble != *LatestPreamble) {
++PreambleBuildCount;
IdleASTs.take(this);
RanASTCallback = false;
std::lock_guard<std::mutex> Lock(Mutex);
if (LatestPreamble)
std::swap(*LatestPreamble, Preamble);
else
LatestPreamble = std::move(Preamble);
}
PreambleCV.notify_all();
Preamble.reset();
if (WantDiags == WantDiagnostics::No)
return;
generateDiagnostics(std::move(CI), FileInputs, std::move(CIDiags));
};
if (RunSync) {
runTask(TaskName, Task);
return;
}
{
std::lock_guard<std::mutex> Lock(Mutex);
PreambleRequests.push_back({std::move(Task), std::string(TaskName),
steady_clock::now(), Context::current().clone(),
std::nullopt, std::nullopt,
TUScheduler::NoInvalidation, nullptr});
}
PreambleCV.notify_all();
RequestsCV.notify_all();
}
void ASTWorker::updateASTSignals(ParsedAST &AST) {
auto Signals = std::make_shared<const ASTSignals>(ASTSignals::derive(AST));
{
std::lock_guard<std::mutex> Lock(Mutex);
std::swap(LatestASTSignals, Signals);
}
}
void ASTWorker::generateDiagnostics(
std::unique_ptr<CompilerInvocation> Invocation, ParseInputs Inputs,
std::vector<Diag> CIDiags) {
static constexpr trace::Metric ASTAccessForDiag(
"ast_access_diag", trace::Metric::Counter, "result");
assert(Invocation);
assert(LatestPreamble);
{
std::lock_guard<std::mutex> Lock(PublishMu);
if (!CanPublishResults)
return;
}
bool InputsAreLatest =
std::tie(FileInputs.CompileCommand, FileInputs.Contents) ==
std::tie(Inputs.CompileCommand, Inputs.Contents);
if (InputsAreLatest && RanASTCallback)
return;
std::string TaskName = llvm::formatv("Build AST ({0})", Inputs.Version);
Status.update([&](TUStatus &Status) {
Status.ASTActivity.K = ASTAction::Building;
Status.ASTActivity.Name = std::move(TaskName);
});
std::optional<std::unique_ptr<ParsedAST>> AST =
IdleASTs.take(this, &ASTAccessForDiag);
if (!AST || !InputsAreLatest) {
auto RebuildStartTime = DebouncePolicy::clock::now();
std::optional<ParsedAST> NewAST = ParsedAST::build(
FileName, Inputs, std::move(Invocation), CIDiags, *LatestPreamble);
auto RebuildDuration = DebouncePolicy::clock::now() - RebuildStartTime;
++ASTBuildCount;
std::unique_lock<std::mutex> Lock(Mutex, std::try_to_lock);
if (Lock.owns_lock()) {
if (RebuildTimes.size() == RebuildTimes.capacity())
RebuildTimes.erase(RebuildTimes.begin());
RebuildTimes.push_back(RebuildDuration);
Lock.unlock();
}
Status.update([&](TUStatus &Status) {
Status.Details.ReuseAST = false;
Status.Details.BuildFailed = !NewAST;
});
AST = NewAST ? std::make_unique<ParsedAST>(std::move(*NewAST)) : nullptr;
} else {
log("Skipping rebuild of the AST for {0}, inputs are the same.", FileName);
Status.update([](TUStatus &Status) {
Status.Details.ReuseAST = true;
Status.Details.BuildFailed = false;
});
}
auto RunPublish = [&](llvm::function_ref<void()> Publish) {
std::lock_guard<std::mutex> Lock(PublishMu);
if (CanPublishResults)
Publish();
};
if (*AST) {
trace::Span Span("Running main AST callback");
Callbacks.onMainAST(FileName, **AST, RunPublish);
updateASTSignals(**AST);
} else {
Callbacks.onFailedAST(FileName, Inputs.Version, CIDiags, RunPublish);
}
if (InputsAreLatest) {
RanASTCallback = *AST != nullptr;
IdleASTs.put(this, std::move(*AST));
}
}
std::shared_ptr<const PreambleData> ASTWorker::getPossiblyStalePreamble(
std::shared_ptr<const ASTSignals> *ASTSignals) const {
std::lock_guard<std::mutex> Lock(Mutex);
if (ASTSignals)
*ASTSignals = LatestASTSignals;
return LatestPreamble ? *LatestPreamble : nullptr;
}
void ASTWorker::waitForFirstPreamble() const {
std::unique_lock<std::mutex> Lock(Mutex);
PreambleCV.wait(Lock, [this] { return LatestPreamble || Done; });
}
tooling::CompileCommand ASTWorker::getCurrentCompileCommand() const {
std::unique_lock<std::mutex> Lock(Mutex);
return FileInputs.CompileCommand;
}
TUScheduler::FileStats ASTWorker::stats() const {
TUScheduler::FileStats Result;
Result.ASTBuilds = ASTBuildCount;
Result.PreambleBuilds = PreambleBuildCount;
Result.UsedBytesAST = IdleASTs.getUsedBytes(this);
if (auto Preamble = getPossiblyStalePreamble())
Result.UsedBytesPreamble = Preamble->Preamble.getSize();
return Result;
}
bool ASTWorker::isASTCached() const { return IdleASTs.getUsedBytes(this) != 0; }
void ASTWorker::stop() {
{
std::lock_guard<std::mutex> Lock(PublishMu);
CanPublishResults = false;
}
{
std::lock_guard<std::mutex> Lock(Mutex);
assert(!Done && "stop() called twice");
Done = true;
}
PreamblePeer.stop();
PreambleCV.notify_all();
Status.stop();
RequestsCV.notify_all();
}
void ASTWorker::runTask(llvm::StringRef Name, llvm::function_ref<void()> Task) {
ThreadCrashReporter ScopedReporter([this, Name]() {
llvm::errs() << "Signalled during AST worker action: " << Name << "\n";
crashDumpParseInputs(llvm::errs(), FileInputs);
});
trace::Span Tracer(Name);
WithContext WithProvidedContext(ContextProvider(FileName));
Task();
}
void ASTWorker::startTask(llvm::StringRef Name,
llvm::unique_function<void()> Task,
std::optional<UpdateType> Update,
TUScheduler::ASTActionInvalidation Invalidation) {
if (RunSync) {
assert(!Done && "running a task after stop()");
runTask(Name, Task);
return;
}
{
std::lock_guard<std::mutex> Lock(Mutex);
assert(!Done && "running a task after stop()");
if (Update && Update->ContentChanged) {
for (auto &R : llvm::reverse(Requests)) {
if (R.InvalidationPolicy == TUScheduler::InvalidateOnUpdate)
R.Invalidate();
if (R.Update && R.Update->ContentChanged)
break;
}
}
Context Ctx = Context::current().derive(FileBeingProcessed, FileName);
Canceler Invalidate = nullptr;
if (Invalidation) {
WithContext WC(std::move(Ctx));
std::tie(Ctx, Invalidate) = cancelableTask(
static_cast<int>(ErrorCode::ContentModified));
}
std::optional<Context> QueueCtx;
if (trace::enabled()) {
WithContext WC(Ctx.clone());
trace::Span Tracer("Queued:" + Name);
if (Tracer.Args) {
if (CurrentRequest)
SPAN_ATTACH(Tracer, "CurrentRequest", CurrentRequest->Name);
llvm::json::Array PreambleRequestsNames;
for (const auto &Req : PreambleRequests)
PreambleRequestsNames.push_back(Req.Name);
SPAN_ATTACH(Tracer, "PreambleRequestsNames",
std::move(PreambleRequestsNames));
llvm::json::Array RequestsNames;
for (const auto &Req : Requests)
RequestsNames.push_back(Req.Name);
SPAN_ATTACH(Tracer, "RequestsNames", std::move(RequestsNames));
}
QueueCtx = Context::current().clone();
}
Requests.push_back({std::move(Task), std::string(Name), steady_clock::now(),
std::move(Ctx), std::move(QueueCtx), Update,
Invalidation, std::move(Invalidate)});
}
RequestsCV.notify_all();
}
void ASTWorker::run() {
clang::noteBottomOfStack();
while (true) {
{
std::unique_lock<std::mutex> Lock(Mutex);
assert(!CurrentRequest && "A task is already running, multiple workers?");
for (auto Wait = scheduleLocked(); !Wait.expired();
Wait = scheduleLocked()) {
assert(PreambleRequests.empty() &&
"Preamble updates should be scheduled immediately");
if (Done) {
if (Requests.empty())
return;
break;
}
std::optional<WithContext> Ctx;
std::optional<trace::Span> Tracer;
if (!Requests.empty()) {
Ctx.emplace(Requests.front().Ctx.clone());
Tracer.emplace("Debounce");
SPAN_ATTACH(*Tracer, "next_request", Requests.front().Name);
if (!(Wait == Deadline::infinity())) {
Status.update([&](TUStatus &Status) {
Status.ASTActivity.K = ASTAction::Queued;
Status.ASTActivity.Name = Requests.front().Name;
});
SPAN_ATTACH(*Tracer, "sleep_ms",
std::chrono::duration_cast<std::chrono::milliseconds>(
Wait.time() - steady_clock::now())
.count());
}
}
wait(Lock, RequestsCV, Wait);
}
if (!PreambleRequests.empty()) {
CurrentRequest = std::move(PreambleRequests.front());
PreambleRequests.pop_front();
} else {
CurrentRequest = std::move(Requests.front());
Requests.pop_front();
}
}
CurrentRequest->QueueCtx.reset();
{
std::unique_lock<Semaphore> Lock(Barrier, std::try_to_lock);
if (!Lock.owns_lock()) {
Status.update([&](TUStatus &Status) {
Status.ASTActivity.K = ASTAction::Queued;
Status.ASTActivity.Name = CurrentRequest->Name;
});
Lock.lock();
}
WithContext Guard(std::move(CurrentRequest->Ctx));
Status.update([&](TUStatus &Status) {
Status.ASTActivity.K = ASTAction::RunningAction;
Status.ASTActivity.Name = CurrentRequest->Name;
});
runTask(CurrentRequest->Name, CurrentRequest->Action);
}
bool IsEmpty = false;
{
std::lock_guard<std::mutex> Lock(Mutex);
CurrentRequest.reset();
IsEmpty = Requests.empty() && PreambleRequests.empty();
}
if (IsEmpty) {
Status.update([&](TUStatus &Status) {
Status.ASTActivity.K = ASTAction::Idle;
Status.ASTActivity.Name = "";
});
}
RequestsCV.notify_all();
}
}
Deadline ASTWorker::scheduleLocked() {
if (!PreambleRequests.empty())
return Deadline::zero();
if (Requests.empty())
return Deadline::infinity();
for (auto I = Requests.begin(), E = Requests.end(); I != E; ++I) {
if (!isCancelled(I->Ctx)) {
if (I->Update == std::nullopt)
break;
continue;
}
if (I->Update == std::nullopt) {
Request R = std::move(*I);
Requests.erase(I);
Requests.push_front(std::move(R));
return Deadline::zero();
}
if (I->Update->Diagnostics == WantDiagnostics::Yes)
I->Update->Diagnostics = WantDiagnostics::Auto;
}
while (shouldSkipHeadLocked()) {
vlog("ASTWorker skipping {0} for {1}", Requests.front().Name, FileName);
Requests.pop_front();
}
assert(!Requests.empty() && "skipped the whole queue");
for (const auto &R : Requests)
if (R.Update == std::nullopt ||
R.Update->Diagnostics == WantDiagnostics::Yes)
return Deadline::zero();
Deadline D(Requests.front().AddTime + UpdateDebounce.compute(RebuildTimes));
return D;
}
bool ASTWorker::shouldSkipHeadLocked() const {
assert(!Requests.empty());
auto Next = Requests.begin();
auto Update = Next->Update;
if (!Update)
return false;
++Next;
if (Next == Requests.end() || !Next->Update)
return false;
switch (Update->Diagnostics) {
case WantDiagnostics::Yes:
return false;
case WantDiagnostics::No:
return true;
case WantDiagnostics::Auto:
for (; Next != Requests.end(); ++Next)
if (Next->Update && Next->Update->Diagnostics != WantDiagnostics::No)
return true;
return false;
}
llvm_unreachable("Unknown WantDiagnostics");
}
bool ASTWorker::blockUntilIdle(Deadline Timeout) const {
auto WaitUntilASTWorkerIsIdle = [&] {
std::unique_lock<std::mutex> Lock(Mutex);
return wait(Lock, RequestsCV, Timeout, [&] {
return PreambleRequests.empty() && Requests.empty() && !CurrentRequest;
});
};
if (!WaitUntilASTWorkerIsIdle())
return false;
if (!PreamblePeer.blockUntilIdle(Timeout))
return false;
assert(Requests.empty() &&
"No new normal tasks can be scheduled concurrently with "
"blockUntilIdle(): ASTWorker isn't threadsafe");
return WaitUntilASTWorkerIsIdle();
}
std::string renderTUAction(const PreambleAction PA, const ASTAction &AA) {
llvm::SmallVector<std::string, 2> Result;
switch (PA) {
case PreambleAction::Building:
Result.push_back("parsing includes");
break;
case PreambleAction::Queued:
Result.push_back("includes are queued");
break;
case PreambleAction::Idle:
break;
}
switch (AA.K) {
case ASTAction::Queued:
Result.push_back("file is queued");
break;
case ASTAction::RunningAction:
Result.push_back("running " + AA.Name);
break;
case ASTAction::Building:
Result.push_back("parsing main file");
break;
case ASTAction::Idle:
break;
}
if (Result.empty())
return "idle";
return llvm::join(Result, ", ");
}
}
unsigned getDefaultAsyncThreadsCount() {
return llvm::heavyweight_hardware_concurrency().compute_thread_count();
}
FileStatus TUStatus::render(PathRef File) const {
FileStatus FStatus;
FStatus.uri = URIForFile::canonicalize(File, File);
FStatus.state = renderTUAction(PreambleActivity, ASTActivity);
return FStatus;
}
struct TUScheduler::FileData {
std::string Contents;
ASTWorkerHandle Worker;
};
TUScheduler::TUScheduler(const GlobalCompilationDatabase &CDB,
const Options &Opts,
std::unique_ptr<ParsingCallbacks> Callbacks)
: CDB(CDB), Opts(Opts),
Callbacks(Callbacks ? std::move(Callbacks)
: std::make_unique<ParsingCallbacks>()),
Barrier(Opts.AsyncThreadsCount), QuickRunBarrier(Opts.AsyncThreadsCount),
IdleASTs(
std::make_unique<ASTCache>(Opts.RetentionPolicy.MaxRetainedASTs)),
HeaderIncluders(std::make_unique<HeaderIncluderCache>()) {
if (!Opts.ContextProvider) {
this->Opts.ContextProvider = [](llvm::StringRef) {
return Context::current().clone();
};
}
if (0 < Opts.AsyncThreadsCount) {
PreambleTasks.emplace();
WorkerThreads.emplace();
}
}
TUScheduler::~TUScheduler() {
Files.clear();
if (PreambleTasks)
PreambleTasks->wait();
if (WorkerThreads)
WorkerThreads->wait();
}
bool TUScheduler::blockUntilIdle(Deadline D) const {
for (auto &File : Files)
if (!File.getValue()->Worker->blockUntilIdle(D))
return false;
if (PreambleTasks)
if (!PreambleTasks->wait(D))
return false;
return true;
}
bool TUScheduler::update(PathRef File, ParseInputs Inputs,
WantDiagnostics WantDiags) {
std::unique_ptr<FileData> &FD = Files[File];
bool NewFile = FD == nullptr;
bool ContentChanged = false;
if (!FD) {
ASTWorkerHandle Worker = ASTWorker::create(
File, CDB, *IdleASTs, *HeaderIncluders,
WorkerThreads ? &*WorkerThreads : nullptr, Barrier, Opts, *Callbacks);
FD = std::unique_ptr<FileData>(
new FileData{Inputs.Contents, std::move(Worker)});
ContentChanged = true;
} else if (FD->Contents != Inputs.Contents) {
ContentChanged = true;
FD->Contents = Inputs.Contents;
}
FD->Worker->update(std::move(Inputs), WantDiags, ContentChanged);
if (ContentChanged)
LastActiveFile = File.str();
return NewFile;
}
void TUScheduler::remove(PathRef File) {
bool Removed = Files.erase(File);
if (!Removed)
elog("Trying to remove file from TUScheduler that is not tracked: {0}",
File);
}
void TUScheduler::run(llvm::StringRef Name, llvm::StringRef Path,
llvm::unique_function<void()> Action) {
runWithSemaphore(Name, Path, std::move(Action), Barrier);
}
void TUScheduler::runQuick(llvm::StringRef Name, llvm::StringRef Path,
llvm::unique_function<void()> Action) {
runWithSemaphore(Name, Path, std::move(Action), QuickRunBarrier);
}
void TUScheduler::runWithSemaphore(llvm::StringRef Name, llvm::StringRef Path,
llvm::unique_function<void()> Action,
Semaphore &Sem) {
if (Path.empty())
Path = LastActiveFile;
else
LastActiveFile = Path.str();
if (!PreambleTasks) {
WithContext WithProvidedContext(Opts.ContextProvider(Path));
return Action();
}
PreambleTasks->runAsync(Name, [this, &Sem, Ctx = Context::current().clone(),
Path(Path.str()),
Action = std::move(Action)]() mutable {
std::lock_guard<Semaphore> BarrierLock(Sem);
WithContext WC(std::move(Ctx));
WithContext WithProvidedContext(Opts.ContextProvider(Path));
Action();
});
}
void TUScheduler::runWithAST(
llvm::StringRef Name, PathRef File,
llvm::unique_function<void(llvm::Expected<InputsAndAST>)> Action,
TUScheduler::ASTActionInvalidation Invalidation) {
auto It = Files.find(File);
if (It == Files.end()) {
Action(llvm::make_error<LSPError>(
"trying to get AST for non-added document", ErrorCode::InvalidParams));
return;
}
LastActiveFile = File.str();
It->second->Worker->runWithAST(Name, std::move(Action), Invalidation);
}
void TUScheduler::runWithPreamble(llvm::StringRef Name, PathRef File,
PreambleConsistency Consistency,
Callback<InputsAndPreamble> Action) {
auto It = Files.find(File);
if (It == Files.end()) {
Action(llvm::make_error<LSPError>(
"trying to get preamble for non-added document",
ErrorCode::InvalidParams));
return;
}
LastActiveFile = File.str();
if (!PreambleTasks) {
trace::Span Tracer(Name);
SPAN_ATTACH(Tracer, "file", File);
std::shared_ptr<const ASTSignals> Signals;
std::shared_ptr<const PreambleData> Preamble =
It->second->Worker->getPossiblyStalePreamble(&Signals);
WithContext WithProvidedContext(Opts.ContextProvider(File));
Action(InputsAndPreamble{It->second->Contents,
It->second->Worker->getCurrentCompileCommand(),
Preamble.get(), Signals.get()});
return;
}
std::shared_ptr<const ASTWorker> Worker = It->second->Worker.lock();
auto Task = [Worker, Consistency, Name = Name.str(), File = File.str(),
Contents = It->second->Contents,
Command = Worker->getCurrentCompileCommand(),
Ctx = Context::current().derive(FileBeingProcessed,
std::string(File)),
Action = std::move(Action), this]() mutable {
clang::noteBottomOfStack();
ThreadCrashReporter ScopedReporter([&Name, &Contents, &Command]() {
llvm::errs() << "Signalled during preamble action: " << Name << "\n";
crashDumpCompileCommand(llvm::errs(), Command);
crashDumpFileContents(llvm::errs(), Contents);
});
std::shared_ptr<const PreambleData> Preamble;
if (Consistency == PreambleConsistency::Stale) {
Worker->waitForFirstPreamble();
}
std::shared_ptr<const ASTSignals> Signals;
Preamble = Worker->getPossiblyStalePreamble(&Signals);
std::lock_guard<Semaphore> BarrierLock(Barrier);
WithContext Guard(std::move(Ctx));
trace::Span Tracer(Name);
SPAN_ATTACH(Tracer, "file", File);
WithContext WithProvidedContext(Opts.ContextProvider(File));
Action(InputsAndPreamble{Contents, Command, Preamble.get(), Signals.get()});
};
PreambleTasks->runAsync("task:" + llvm::sys::path::filename(File),
std::move(Task));
}
llvm::StringMap<TUScheduler::FileStats> TUScheduler::fileStats() const {
llvm::StringMap<TUScheduler::FileStats> Result;
for (const auto &PathAndFile : Files)
Result.try_emplace(PathAndFile.first(),
PathAndFile.second->Worker->stats());
return Result;
}
std::vector<Path> TUScheduler::getFilesWithCachedAST() const {
std::vector<Path> Result;
for (auto &&PathAndFile : Files) {
if (!PathAndFile.second->Worker->isASTCached())
continue;
Result.push_back(std::string(PathAndFile.first()));
}
return Result;
}
DebouncePolicy::clock::duration
DebouncePolicy::compute(llvm::ArrayRef<clock::duration> History) const {
assert(Min <= Max && "Invalid policy");
if (History.empty())
return Max;
History = History.take_back(15);
llvm::SmallVector<clock::duration, 15> Recent(History.begin(), History.end());
auto *Median = Recent.begin() + Recent.size() / 2;
std::nth_element(Recent.begin(), Median, Recent.end());
clock::duration Target =
std::chrono::duration_cast<clock::duration>(RebuildRatio * *Median);
if (Target > Max)
return Max;
if (Target < Min)
return Min;
return Target;
}
DebouncePolicy DebouncePolicy::fixed(clock::duration T) {
DebouncePolicy P;
P.Min = P.Max = T;
return P;
}
void TUScheduler::profile(MemoryTree &MT) const {
for (const auto &Elem : fileStats()) {
MT.detail(Elem.first())
.child("preamble")
.addUsage(Opts.StorePreamblesInMemory ? Elem.second.UsedBytesPreamble
: 0);
MT.detail(Elem.first()).child("ast").addUsage(Elem.second.UsedBytesAST);
MT.child("header_includer_cache").addUsage(HeaderIncluders->getUsedBytes());
}
}
}
}