Files
swift-mirror/tools/SourceKit/lib/SwiftLang/SwiftASTManager.cpp
Alex Hoppen 7d5ee83a61 [SourceKit] Allow explicit cancellation of requests with a cancellation token
The key changes here are
- To keep track of cancellation tokens for all `ScheduledConsumer`s in `SwiftASTManager`
- Generate unique request handles for all incoming requests (`create_request_handle `), use these request handles as cancellation tokens and return them from the `sourcekitd_send_request` methods
- Implement cancellation with `sourcekitd_cancel_request` as the entry point and `SwiftASTManager::cancelASTConsumer` as the termination point

Everything else is just plumbing the cancellation token through the various abstraction layers.

rdar://83391505
2021-09-30 11:45:24 +02:00

1311 lines
49 KiB
C++

//===--- SwiftASTManager.cpp ----------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#include "SwiftASTManager.h"
#include "SwiftEditorDiagConsumer.h"
#include "SwiftInvocation.h"
#include "SwiftLangSupport.h"
#include "SourceKit/Core/Context.h"
#include "SourceKit/Support/Concurrency.h"
#include "SourceKit/Support/ImmutableTextBuffer.h"
#include "SourceKit/Support/Logging.h"
#include "SourceKit/Support/Tracing.h"
#include "swift/Basic/Cache.h"
#include "swift/Driver/FrontendUtil.h"
#include "swift/Frontend/Frontend.h"
#include "swift/Frontend/PrintingDiagnosticConsumer.h"
#include "swift/Strings.h"
#include "swift/Subsystems.h"
#include "swift/SILOptimizer/PassManager/Passes.h"
// This is included only for createLazyResolver(). Move to different header ?
#include "swift/Sema/IDETypeChecking.h"
#include "llvm/ADT/FoldingSet.h"
#include "llvm/Support/Chrono.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
using namespace SourceKit;
using namespace swift;
using namespace swift::sys;
void SwiftASTConsumer::failed(StringRef Error) { }
//===----------------------------------------------------------------------===//
// SwiftInvocation
//===----------------------------------------------------------------------===//
namespace {
struct InvocationOptions {
const std::vector<std::string> Args;
const std::string PrimaryFile;
const CompilerInvocation Invok;
InvocationOptions(ArrayRef<const char *> CArgs, StringRef PrimaryFile,
CompilerInvocation Invok)
: Args(_convertArgs(CArgs)),
PrimaryFile(PrimaryFile),
Invok(std::move(Invok)) {
// Assert invocation with a primary file. We want to avoid full typechecking
// for all files.
assert(!this->PrimaryFile.empty());
assert(this->Invok.getFrontendOptions()
.InputsAndOutputs.hasUniquePrimaryInput() &&
"Must have exactly one primary input for code completion, etc.");
}
void applyTo(CompilerInvocation &CompInvok) const;
void
applyToSubstitutingInputs(CompilerInvocation &CompInvok,
FrontendInputsAndOutputs &&InputsAndOutputs) const;
void profile(llvm::FoldingSetNodeID &ID) const;
void raw(std::vector<std::string> &Args, std::string &PrimaryFile) const;
private:
static std::vector<std::string> _convertArgs(ArrayRef<const char *> CArgs) {
std::vector<std::string> Args;
Args.reserve(CArgs.size());
for (auto Arg : CArgs)
Args.push_back(Arg);
return Args;
}
};
struct ASTKey {
llvm::FoldingSetNodeID FSID;
};
template <typename T>
size_t getVectorMemoryCost(const std::vector<T> &Vec) {
return Vec.capacity() * sizeof(T);
}
} // end anonymous namespace
struct SwiftInvocation::Implementation {
InvocationOptions Opts;
ASTKey Key;
explicit Implementation(InvocationOptions opts) : Opts(std::move(opts)) {
Opts.profile(Key.FSID);
}
};
SwiftInvocation::~SwiftInvocation() {
delete &Impl;
}
void SwiftInvocation::applyTo(swift::CompilerInvocation &CompInvok) const {
return Impl.Opts.applyTo(CompInvok);
}
void SwiftInvocation::raw(std::vector<std::string> &Args,
std::string &PrimaryFile) const {
return Impl.Opts.raw(Args, PrimaryFile);
}
void InvocationOptions::applyTo(CompilerInvocation &CompInvok) const {
CompInvok = this->Invok;
}
void InvocationOptions::applyToSubstitutingInputs(
CompilerInvocation &CompInvok,
FrontendInputsAndOutputs &&inputsAndOutputs) const {
CompInvok = this->Invok;
CompInvok.getFrontendOptions().InputsAndOutputs = inputsAndOutputs;
}
void InvocationOptions::raw(std::vector<std::string> &Args,
std::string &PrimaryFile) const {
Args.assign(this->Args.begin(), this->Args.end());
PrimaryFile = this->PrimaryFile;
}
void InvocationOptions::profile(llvm::FoldingSetNodeID &ID) const {
// FIXME: This ties ASTs to every argument and the exact order that they were
// provided, preventing much sharing of ASTs.
// Note though that previously we tried targeting specific options considered
// semantically relevant but it proved too fragile (very easy to miss some new
// compiler invocation option).
// Possibly have all compiler invocation options auto-generated from a
// tablegen definition file, thus forcing a decision for each option if it is
// ok to share ASTs with the option differing.
for (auto &Arg : Args)
ID.AddString(Arg);
ID.AddString(PrimaryFile);
}
//===----------------------------------------------------------------------===//
// SwiftASTManager
//===----------------------------------------------------------------------===//
namespace SourceKit {
struct ASTUnit::Implementation {
const uint64_t Generation;
std::shared_ptr<SwiftStatistics> Stats;
SmallVector<ImmutableTextSnapshotRef, 4> Snapshots;
EditorDiagConsumer CollectDiagConsumer;
CompilerInstance CompInst;
WorkQueue Queue{ WorkQueue::Dequeuing::Serial, "sourcekit.swift.ConsumeAST" };
Implementation(uint64_t Generation, std::shared_ptr<SwiftStatistics> Stats)
: Generation(Generation), Stats(Stats) {}
void consumeAsync(SwiftASTConsumerRef ASTConsumer, ASTUnitRef ASTRef);
};
void ASTUnit::Implementation::consumeAsync(SwiftASTConsumerRef ConsumerRef,
ASTUnitRef ASTRef) {
#if defined(_WIN32)
// Windows uses more up for stack space (why?) than macOS/Linux which
// causes stack overflows in a dispatch thread with 64k stack. Passing
// useDeepStack=true means it's given a _beginthreadex thread with an 8MB
// stack.
bool useDeepStack = true;
#else
bool useDeepStack = false;
#endif
Queue.dispatch([ASTRef, ConsumerRef]{
SwiftASTConsumer &ASTConsumer = *ConsumerRef;
CompilerInstance &CI = ASTRef->getCompilerInstance();
if (CI.getPrimarySourceFile()) {
ASTConsumer.handlePrimaryAST(ASTRef);
} else {
LOG_WARN_FUNC("did not find primary SourceFile");
ConsumerRef->failed("did not find primary SourceFile");
}
}, useDeepStack);
}
ASTUnit::ASTUnit(uint64_t Generation, std::shared_ptr<SwiftStatistics> Stats)
: Impl(*new Implementation(Generation, Stats)) {
auto numASTs = ++Stats->numASTsInMem;
Stats->maxASTsInMem.updateMax(numASTs);
}
ASTUnit::~ASTUnit() {
--Impl.Stats->numASTsInMem;
delete &Impl;
}
swift::CompilerInstance &ASTUnit::getCompilerInstance() const {
return Impl.CompInst;
}
uint64_t ASTUnit::getGeneration() const {
return Impl.Generation;
}
ArrayRef<ImmutableTextSnapshotRef> ASTUnit::getSnapshots() const {
return Impl.Snapshots;
}
SourceFile &ASTUnit::getPrimarySourceFile() const {
return *Impl.CompInst.getPrimarySourceFile();
}
EditorDiagConsumer &ASTUnit::getEditorDiagConsumer() const {
return Impl.CollectDiagConsumer;
}
void ASTUnit::performAsync(std::function<void()> Fn) {
Impl.Queue.dispatch(std::move(Fn));
}
} // namespace SourceKit
namespace {
typedef uint64_t BufferStamp;
struct FileContent {
ImmutableTextSnapshotRef Snapshot;
std::string Filename;
std::unique_ptr<llvm::MemoryBuffer> Buffer;
bool IsPrimary;
BufferStamp Stamp;
FileContent(ImmutableTextSnapshotRef Snapshot, std::string Filename,
std::unique_ptr<llvm::MemoryBuffer> Buffer, bool IsPrimary,
BufferStamp Stamp)
: Snapshot(std::move(Snapshot)), Filename(Filename),
Buffer(std::move(Buffer)), IsPrimary(IsPrimary), Stamp(Stamp) {}
explicit operator InputFile() const {
return InputFile(Filename, IsPrimary, Buffer.get());
}
};
/// An \c ASTBuildOperations builds an AST. Once the AST is built, it informs
/// a list of \c SwiftASTConsumers about the built AST.
/// It also supports cancellation with the following paradigm: If an \c
/// SwiftASTConsumer is no longer needed, it can be cancelled, which will remove
/// it from the \c ASTBuildOperation. If the \c ASTBuildOperation has no more
/// consumers attached to it, it will cancel the AST build at the next
/// opportunity.
class ASTBuildOperation
: public std::enable_shared_from_this<ASTBuildOperation> {
/// After the AST has been built, the corresponding result.
struct ASTBuildResult {
/// The AST that was created by the build operation.
ASTUnitRef AST;
/// An error message emitted by the creation of the AST. There might still
/// be an AST if an error occurred, but it's usefulnes depends on the
/// severity of the error.
std::string Error;
/// Whether the build operation was cancelled. There might be an AST and
/// error but their usefulness depends on when the operation was cancelled.
bool Cancelled;
/// Whether the result contains any values, i.e. whether the operation has
/// produced a result yet.
bool HasValue;
ASTBuildResult() : HasValue(false) {}
void emplace(ASTUnitRef AST, std::string Error, bool Cancelled) {
assert(!HasValue && "Should only emplace a result once");
this->HasValue = true;
this->AST = AST;
this->Error = Error;
this->Cancelled = Cancelled;
}
operator bool() const { return HasValue; }
size_t getMemoryCost() {
size_t Cost = sizeof(*this) + Error.size();
if (AST) {
Cost += sizeof(*AST);
if (AST->getCompilerInstance().hasASTContext()) {
Cost += AST->Impl.CompInst.getASTContext().getTotalMemory();
}
}
return Cost;
}
};
/// Parameters necessary to build the AST.
const SwiftInvocationRef InvokRef;
const IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem;
const std::vector<ImmutableTextSnapshotRef> Snapshots;
/// Stamps of files used to build the AST. \c Stamps contains the stamps of
/// all explicit input files, which can be determined at construction time of
/// the \c ASTBuildOperation.
const std::vector<BufferStamp> Stamps;
/// \c DependencyStamps contains the stamps of all module depenecies needed
/// for the AST build. These stamps are only known after the AST is built.
/// Before the AST has been built, we thus assume that all dependency stamps
/// match. This seems to be a reasonable assumption since the dependencies
/// shouldn't change (much) in the time between an \c ASTBuildOperation is
/// created and until it produced an AST.
SmallVector<std::pair<std::string, BufferStamp>, 8> DependencyStamps = {};
/// The ASTManager from which this operation got scheduled. Used to update
/// global stats and access the file system.
SwiftASTManagerRef ASTManager;
/// A flag to cancel the AST build. If this flag is set to \c true, the type
/// checker will cancel type checking at the next possible opportunity.
const std::shared_ptr<std::atomic<bool>> CancellationFlag =
std::make_shared<std::atomic<bool>>(false);
/// A callback that's called when the operation finishes. Used to remove it
/// from the \c ASTProducer that scheduled it.
const std::function<void(void)> DidFinishCallback;
/// The consumers and result are guarded by the same mutex to avoid
/// simultaneously adding a consumer and setting the result, which might cause
/// the consumer's callback to neither be called when it gets added to this
/// operation, nor when the operation finishes.
llvm::sys::Mutex ConsumersAndResultMtx;
/// The consumers that should be informed about this AST once it finishes
/// building. When this vector is empty, the AST build can be cancelled.
SmallVector<SwiftASTConsumerRef, 4> Consumers = {};
/// Once the build operation has finished, its result, which can be an AST, an
/// error or the fact that it has been cancelled.
ASTBuildResult Result;
enum class State { Created, Queued, Running, Finished };
/// The state the operation is in. Only used in assertions to verify no state
/// is skipped or executed twice.
State OperationState = State::Created;
/// Inform a consumer that the AST has been built or that the build failed
/// with an error.
void informConsumer(SwiftASTConsumerRef Consumer);
/// Actually build the AST unit, synchronously on the current thread. If an
/// error occurred during the build, \p Error will contain the message. In
/// case of an error, a non-null AST may still be returned. Its usefulness
/// depends on the severity of the error.
ASTUnitRef buildASTUnit(std::string &Error);
/// Retrieve the contents of all files needed for the compiler invocation to
/// build this AST. For files contained in \c Snapshots use the snapshot's
/// content. For all other files, consult the file system.
void findSnapshotAndOpenFiles(SmallVectorImpl<FileContent> &Contents,
std::string &Error) const;
/// Transition the build operation to \p NewState, asserting that the current
/// state is \p ExpectedOldState.
void transitionToState(State NewState, State ExpectedOldState) {
assert(OperationState == ExpectedOldState);
OperationState = NewState;
}
/// Create a vector of text snapshots containing all files explicitly
/// referenced by the compiler invocation and a vector of buffer stamps of
/// those files. For all files that have a snapshot specified in \p
/// FixedSnapshots, that snapshot is used. For all other files \p FileSystem
/// will be consulted for the current contents of the file.
std::pair<std::vector<ImmutableTextSnapshotRef>, std::vector<BufferStamp>>
snapshotAndStampsForFilesInCompilerInvocation(
ArrayRef<ImmutableTextSnapshotRef> FixedSnapshots);
public:
ASTBuildOperation(IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
ArrayRef<ImmutableTextSnapshotRef> Snapshots,
SwiftInvocationRef InvokRef, SwiftASTManagerRef ASTManager,
std::function<void(void)> DidFinishCallback)
: InvokRef(InvokRef), FileSystem(FileSystem), ASTManager(ASTManager),
DidFinishCallback(DidFinishCallback) {
auto SnapshotsAndStamps =
snapshotAndStampsForFilesInCompilerInvocation(Snapshots);
// const_cast is fine here. We just want to guard against modifying these
// fields later on. It's fine to set them in the constructor.
const_cast<std::vector<ImmutableTextSnapshotRef> &>(this->Snapshots) =
SnapshotsAndStamps.first;
const_cast<std::vector<BufferStamp> &>(this->Stamps) =
SnapshotsAndStamps.second;
}
~ASTBuildOperation() {
assert(OperationState == State::Finished &&
"ASTBuildOperations should only be destructed once they have "
"produced an AST or are finished. Otherwise, some consumers might "
"not receive their callback.");
}
ArrayRef<ImmutableTextSnapshotRef> getSnapshots() const { return Snapshots; }
/// Returns true if the build operation has finished.
bool isFinished() {
llvm::sys::ScopedLock L(ConsumersAndResultMtx);
return Result.HasValue;
}
bool isCancelled() {
llvm::sys::ScopedLock L(ConsumersAndResultMtx);
return (Result.HasValue && Result.Cancelled) ||
CancellationFlag->load(std::memory_order_relaxed);
}
size_t getMemoryCost() {
return sizeof(*this) + getVectorMemoryCost(Snapshots) +
getVectorMemoryCost(Stamps) + Result.getMemoryCost();
}
/// Schedule building this AST on the given \p Queue.
void schedule(WorkQueue Queue);
/// Inform the given \p Consumer when the AST has been built. If the build
/// operation has already built the AST, the consumer is directly informed.
/// Returns \c true if the \p Consumer was added. Returns \c false if the
/// operation has already been cancelled, in which case the consumer should be
/// scheduled on a different build operation. This ensures that we don't hit
/// a race condition when a build operation gets cancelled in between when it
/// gets selected as a viable candidate but before the consumer gets added to
/// it.
bool addConsumer(SwiftASTConsumerRef Consumer);
/// Determines whether the AST built from this build operation can be used for
/// the given source state. Note that before the AST is built, this does not
/// consider depenencies needed for the AST build that are not explicitly
/// listed in the input files. As such, this might be \c true before the AST
/// build and \c false after the AST has been built. See documentation on \c
/// DependencyStamps for more info.
bool matchesSourceState(IntrusiveRefCntPtr<llvm::vfs::FileSystem> fileSystem,
ArrayRef<ImmutableTextSnapshotRef> Snapshots);
/// Called when a consumer is cancelled. This calls \c cancelled on the
/// consumer, removes it from the \c Consumers severed by this build operation
/// and, if no consumers are left, cancels the AST build of this operation.
void requestConsumerCancellation(SwiftASTConsumerRef Consumer);
};
using ASTBuildOperationRef = std::shared_ptr<ASTBuildOperation>;
/// An \c ASTProducer produces ASTs for a given compiler invocation through
/// multiple \c ASTBuildOperations.
/// While \c ASTBuildOperations only build ASTs for a single snapshot, \c
/// ASTProducer also keeps track of ASTs built from different (older) snapshots.
/// It is thus able to serve an \c SwiftASTConsumer with an AST from an older
/// snapshot, should it accept it by returning \c true in \c
/// canUseASTWithSnapshots.
class ASTProducer : public std::enable_shared_from_this<ASTProducer> {
SwiftInvocationRef InvokRef;
/// The build operations that have been scheduled by this producer. Some of
/// these operations might already have finished, effectively caching an old
/// AST, one might currently be building an AST and some might be waiting to
/// execute. Operations are guaranteed to be in FIFO order, that is the first
/// one in the vector is the oldes build operation.
SmallVector<ASTBuildOperationRef, 4> BuildOperations = {};
llvm::sys::Mutex BuildOperationsMtx;
/// Erase all finished build operations with a result except for the latest
/// one which contains a successful results.
/// This cleans up all stale build operations (probably containing old ASTs),
/// but keeps the latest AST around, so that new consumers can be served from
/// it, if possible.
///
/// Assumes that \c BuildOperationsMtx has been claimed.
void cleanBuildOperations() {
auto ReverseOperations = llvm::reverse(BuildOperations);
auto LastOperationWithResultIt =
llvm::find_if(ReverseOperations, [](ASTBuildOperationRef BuildOp) {
return BuildOp->isFinished() && !BuildOp->isCancelled();
});
ASTBuildOperationRef LastOperationWithResult = nullptr;
if (LastOperationWithResultIt != ReverseOperations.end()) {
LastOperationWithResult = *LastOperationWithResultIt;
}
llvm::erase_if(BuildOperations, [LastOperationWithResult](
ASTBuildOperationRef BuildOp) {
return BuildOp->isFinished() && BuildOp != LastOperationWithResult;
});
}
/// Returns the latest build operation which can serve the \p Consumer or
/// \c nullptr if no such build operation exists.
/// \p FileSystem and \p Snapshots describe the source state that the \p
/// Consumer expects.
///
/// Assumes that \c BuildOperationsMtx has been claimed.
ASTBuildOperationRef getBuildOperationForConsumer(
SwiftASTConsumerRef Consumer,
IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
ArrayRef<ImmutableTextSnapshotRef> Snapshots, SwiftASTManagerRef Mgr);
public:
explicit ASTProducer(SwiftInvocationRef InvokRef)
: InvokRef(std::move(InvokRef)) {}
/// Schedules the given \p Consumer to the latest suitable build operation.
/// Independently of what happens, the consumer will receive either a \c
/// cancelled, \c failed or \c handlePrimaryAST callback.
void enqueueConsumer(SwiftASTConsumerRef Consumer,
IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
ArrayRef<ImmutableTextSnapshotRef> Snapshots,
SwiftASTManagerRef Mgr);
size_t getMemoryCost() const {
size_t Cost = sizeof(*this);
for (auto &BuildOp : BuildOperations) {
Cost += BuildOp->getMemoryCost();
}
return Cost;
}
};
typedef std::shared_ptr<ASTProducer> ASTProducerRef;
} // end anonymous namespace
namespace swift {
namespace sys {
template <>
struct CacheValueCostInfo<ASTProducer> {
static size_t getCost(const ASTProducer &Unit) {
return Unit.getMemoryCost();
}
};
template <>
struct CacheKeyHashInfo<ASTKey> {
static uintptr_t getHashValue(const ASTKey &Key) {
return Key.FSID.ComputeHash();
}
static bool isEqual(void *LHS, void *RHS) {
return static_cast<ASTKey*>(LHS)->FSID == static_cast<ASTKey*>(RHS)->FSID;
}
};
} // namespace sys
} // namespace swift
struct SwiftASTManager::Implementation {
explicit Implementation(
std::shared_ptr<SwiftEditorDocumentFileMap> EditorDocs,
std::shared_ptr<GlobalConfig> Config,
std::shared_ptr<SwiftStatistics> Stats, StringRef RuntimeResourcePath,
StringRef DiagnosticDocumentationPath)
: EditorDocs(EditorDocs), Config(Config), Stats(Stats),
RuntimeResourcePath(RuntimeResourcePath),
DiagnosticDocumentationPath(DiagnosticDocumentationPath),
SessionTimestamp(llvm::sys::toTimeT(std::chrono::system_clock::now())) {
}
std::shared_ptr<SwiftEditorDocumentFileMap> EditorDocs;
std::shared_ptr<GlobalConfig> Config;
std::shared_ptr<SwiftStatistics> Stats;
std::string RuntimeResourcePath;
std::string DiagnosticDocumentationPath;
SourceManager SourceMgr;
Cache<ASTKey, ASTProducerRef> ASTCache{ "sourcekit.swift.ASTCache" };
llvm::sys::Mutex CacheMtx;
std::time_t SessionTimestamp;
/// A consumer that has been scheduled using \c processASTAsync.
/// The \c OncePerASTToken allows us to cancel previously scheduled consumers
/// if a new request/consumer with the same \c OncePerASTToken comes in.
/// Since we only keep a reference to the consumers to cancel them, the
/// reference to the consumer itself is weak - if it's already deallocated,
/// there is no need to cancel it anymore.
/// The \c CancellationToken that allows cancellation of this consumer.
/// Multiple consumers might share the same \c CancellationToken if they were
/// created from the same SourceKit request. E.g. a \c CursorInfoConsumer
/// might schedule a second \c CursorInfoConsumer if it discovers that the AST
/// that was used to serve the first request is not up-to-date enough.
/// If \c CancellationToken is \c nullptr, the consumer can't be cancelled
/// using a cancellation token.
struct ScheduledConsumer {
SwiftASTConsumerWeakRef Consumer;
const void *OncePerASTToken;
SourceKitCancellationToken CancellationToken;
};
llvm::sys::Mutex ScheduledConsumersMtx;
std::vector<ScheduledConsumer> ScheduledConsumers;
/// Queue guaranteeing that only one \c ASTBuildOperation builds an AST at a
/// time.
WorkQueue ASTBuildQueue{ WorkQueue::Dequeuing::Serial,
"sourcekit.swift.ASTBuilding" };
/// Remove all scheduled consumers that don't exist anymore. This is just a
/// garbage-collection operation to make sure the \c ScheduledConsumers vector
/// doesn't explode. One should never make assumptions that all consumers in
/// \c ScheduledConsumers are alive.
void cleanDeletedConsumers() {
llvm::sys::ScopedLock L(ScheduledConsumersMtx);
llvm::erase_if(ScheduledConsumers, [](ScheduledConsumer Consumer) {
return Consumer.Consumer.expired();
});
}
ASTProducerRef getASTProducer(SwiftInvocationRef InvokRef);
FileContent
getFileContent(StringRef FilePath, bool IsPrimary,
IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
std::string &Error) const;
BufferStamp
getBufferStamp(StringRef FilePath,
IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem) const;
std::unique_ptr<llvm::MemoryBuffer>
getMemoryBuffer(StringRef Filename,
IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
std::string &Error) const;
};
SwiftASTManager::SwiftASTManager(
std::shared_ptr<SwiftEditorDocumentFileMap> EditorDocs,
std::shared_ptr<GlobalConfig> Config,
std::shared_ptr<SwiftStatistics> Stats, StringRef RuntimeResourcePath,
StringRef DiagnosticDocumentationPath)
: Impl(*new Implementation(EditorDocs, Config, Stats, RuntimeResourcePath,
DiagnosticDocumentationPath)) {}
SwiftASTManager::~SwiftASTManager() {
delete &Impl;
}
std::unique_ptr<llvm::MemoryBuffer>
SwiftASTManager::getMemoryBuffer(StringRef Filename, std::string &Error) {
return Impl.getMemoryBuffer(Filename, llvm::vfs::getRealFileSystem(), Error);
}
static FrontendInputsAndOutputs
convertFileContentsToInputs(const SmallVectorImpl<FileContent> &contents) {
FrontendInputsAndOutputs inputsAndOutputs;
for (const FileContent &content : contents)
inputsAndOutputs.addInput(InputFile(content));
return inputsAndOutputs;
}
bool SwiftASTManager::initCompilerInvocation(
CompilerInvocation &Invocation, ArrayRef<const char *> OrigArgs,
DiagnosticEngine &Diags, StringRef UnresolvedPrimaryFile,
std::string &Error) {
return initCompilerInvocation(Invocation, OrigArgs, Diags,
UnresolvedPrimaryFile,
llvm::vfs::getRealFileSystem(),
Error);
}
bool SwiftASTManager::initCompilerInvocation(
CompilerInvocation &Invocation, ArrayRef<const char *> OrigArgs,
DiagnosticEngine &Diags, StringRef UnresolvedPrimaryFile,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
std::string &Error) {
return ide::initCompilerInvocation(
Invocation, OrigArgs, Diags, UnresolvedPrimaryFile, FileSystem,
Impl.RuntimeResourcePath, Impl.DiagnosticDocumentationPath,
Impl.SessionTimestamp, Error);
}
bool SwiftASTManager::initCompilerInvocation(CompilerInvocation &CompInvok,
ArrayRef<const char *> OrigArgs,
StringRef PrimaryFile,
std::string &Error) {
DiagnosticEngine Diagnostics(Impl.SourceMgr);
return initCompilerInvocation(CompInvok, OrigArgs, Diagnostics, PrimaryFile,
Error);
}
bool SwiftASTManager::initCompilerInvocationNoInputs(
swift::CompilerInvocation &Invocation, ArrayRef<const char *> OrigArgs,
swift::DiagnosticEngine &Diags, std::string &Error, bool AllowInputs) {
SmallVector<const char *, 16> Args(OrigArgs.begin(), OrigArgs.end());
// Use stdin as a .swift input to satisfy the driver.
Args.push_back("-");
if (initCompilerInvocation(Invocation, Args, Diags, "", Error))
return true;
if (!AllowInputs &&
Invocation.getFrontendOptions().InputsAndOutputs.inputCount() > 1) {
Error = "unexpected input in compiler arguments";
return true;
}
// Clear the inputs.
Invocation.getFrontendOptions().InputsAndOutputs.clearInputs();
return false;
}
SwiftInvocationRef SwiftASTManager::getInvocation(
ArrayRef<const char *> OrigArgs, StringRef PrimaryFile, std::string &Error) {
return getInvocation(OrigArgs, PrimaryFile, llvm::vfs::getRealFileSystem(),
Error);
}
SwiftInvocationRef SwiftASTManager::getInvocation(
ArrayRef<const char *> OrigArgs, StringRef PrimaryFile,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
std::string &Error) {
assert(FileSystem);
DiagnosticEngine Diags(Impl.SourceMgr);
EditorDiagConsumer CollectDiagConsumer;
Diags.addConsumer(CollectDiagConsumer);
CompilerInvocation CompInvok;
if (initCompilerInvocation(CompInvok, OrigArgs, Diags, PrimaryFile,
FileSystem, Error)) {
// We create a traced operation here to represent the failure to parse
// arguments since we cannot reach `createAST` where that would normally
// happen.
trace::TracedOperation TracedOp(trace::OperationKind::PerformSema);
if (TracedOp.enabled()) {
trace::SwiftInvocation TraceInfo;
trace::initTraceInfo(TraceInfo, PrimaryFile, OrigArgs);
TracedOp.setDiagnosticProvider(
[&CollectDiagConsumer](SmallVectorImpl<DiagnosticEntryInfo> &diags) {
CollectDiagConsumer.getAllDiagnostics(diags);
});
TracedOp.start(TraceInfo);
}
return nullptr;
}
InvocationOptions Opts(OrigArgs, PrimaryFile, CompInvok);
return new SwiftInvocation(
*new SwiftInvocation::Implementation(std::move(Opts)));
}
void SwiftASTManager::processASTAsync(
SwiftInvocationRef InvokRef, SwiftASTConsumerRef ASTConsumer,
const void *OncePerASTToken, SourceKitCancellationToken CancellationToken,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fileSystem,
ArrayRef<ImmutableTextSnapshotRef> Snapshots) {
assert(fileSystem);
ASTProducerRef Producer = Impl.getASTProducer(InvokRef);
Impl.cleanDeletedConsumers();
{
llvm::sys::ScopedLock L(Impl.ScheduledConsumersMtx);
if (OncePerASTToken) {
// Cancel any consumers with the same OncePerASTToken.
for (auto ScheduledConsumer : Impl.ScheduledConsumers) {
if (ScheduledConsumer.OncePerASTToken == OncePerASTToken) {
if (auto Consumer = ScheduledConsumer.Consumer.lock()) {
Consumer->requestCancellation();
}
}
}
}
Impl.ScheduledConsumers.push_back(
{ASTConsumer, OncePerASTToken, CancellationToken});
}
Producer->enqueueConsumer(ASTConsumer, fileSystem, Snapshots,
shared_from_this());
}
void SwiftASTManager::cancelASTConsumer(
SourceKitCancellationToken CancellationToken) {
if (!CancellationToken) {
return;
}
Impl.cleanDeletedConsumers();
llvm::sys::ScopedLock L(Impl.ScheduledConsumersMtx);
for (auto ScheduledConsumer : Impl.ScheduledConsumers) {
if (ScheduledConsumer.CancellationToken == CancellationToken) {
if (auto Consumer = ScheduledConsumer.Consumer.lock()) {
Consumer->requestCancellation();
// Multiple consumers might share the same cancellation token (see
// documentation on ScheduledConsumer), so we can't break here.
}
}
}
}
void SwiftASTManager::removeCachedAST(SwiftInvocationRef Invok) {
Impl.ASTCache.remove(Invok->Impl.Key);
}
ASTProducerRef
SwiftASTManager::Implementation::getASTProducer(SwiftInvocationRef InvokRef) {
llvm::sys::ScopedLock L(CacheMtx);
llvm::Optional<ASTProducerRef> OptProducer = ASTCache.get(InvokRef->Impl.Key);
if (OptProducer.hasValue())
return OptProducer.getValue();
ASTProducerRef Producer = std::make_shared<ASTProducer>(InvokRef);
ASTCache.set(InvokRef->Impl.Key, Producer);
return Producer;
}
static FileContent getFileContentFromSnap(ImmutableTextSnapshotRef Snap,
bool IsPrimary, StringRef FilePath) {
auto Buf = llvm::MemoryBuffer::getMemBufferCopy(
Snap->getBuffer()->getText(), FilePath);
return FileContent(Snap, FilePath.str(), std::move(Buf), IsPrimary,
Snap->getStamp());
}
FileContent SwiftASTManager::Implementation::getFileContent(
StringRef UnresolvedPath, bool IsPrimary,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
std::string &Error) const {
std::string FilePath = SwiftLangSupport::resolvePathSymlinks(UnresolvedPath);
if (auto EditorDoc = EditorDocs->findByPath(FilePath))
return getFileContentFromSnap(EditorDoc->getLatestSnapshot(), IsPrimary,
FilePath);
// FIXME: Is there a way to get timestamp and buffer for a file atomically ?
auto Stamp = getBufferStamp(FilePath, FileSystem);
auto Buffer = getMemoryBuffer(FilePath, FileSystem, Error);
return FileContent(nullptr, UnresolvedPath.str(), std::move(Buffer),
IsPrimary, Stamp);
}
BufferStamp SwiftASTManager::Implementation::getBufferStamp(
StringRef FilePath,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem) const {
assert(FileSystem);
if (auto EditorDoc = EditorDocs->findByPath(FilePath))
return EditorDoc->getLatestSnapshot()->getStamp();
auto StatusOrErr = FileSystem->status(FilePath);
if (std::error_code Err = StatusOrErr.getError()) {
// Failure to read the file.
LOG_WARN_FUNC("failed to stat file: " << FilePath << " (" << Err.message()
<< ')');
return -1;
}
return StatusOrErr.get().getLastModificationTime().time_since_epoch().count();
}
std::unique_ptr<llvm::MemoryBuffer>
SwiftASTManager::Implementation::getMemoryBuffer(
StringRef Filename,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
std::string &Error) const {
assert(FileSystem);
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> FileBufOrErr =
FileSystem->getBufferForFile(Filename);
if (FileBufOrErr)
return std::move(FileBufOrErr.get());
llvm::raw_string_ostream OSErr(Error);
OSErr << "error opening input file '" << Filename << "' ("
<< FileBufOrErr.getError().message() << ')';
return nullptr;
}
/// If \p Snapshots contains a snapshot for the given \p Filename return it.
/// Otherwise, return \c None.
Optional<ImmutableTextSnapshotRef>
findSnapshot(ArrayRef<ImmutableTextSnapshotRef> Snapshots,
const std::string &Filename) {
for (auto &Snap : Snapshots) {
if (Snap->getFilename() == Filename) {
return Snap;
}
}
return None;
}
std::pair<std::vector<ImmutableTextSnapshotRef>, std::vector<BufferStamp>>
ASTBuildOperation::snapshotAndStampsForFilesInCompilerInvocation(
ArrayRef<ImmutableTextSnapshotRef> FixedSnapshots) {
const InvocationOptions &Opts = InvokRef->Impl.Opts;
std::string Error; // is ignored
std::vector<ImmutableTextSnapshotRef> Snapshots = FixedSnapshots.vec();
std::vector<BufferStamp> Stamps;
Stamps.reserve(Opts.Invok.getFrontendOptions().InputsAndOutputs.inputCount());
// IMPORTANT: The computation of stamps must match the one in
// matchesSourceState.
for (const auto &input :
Opts.Invok.getFrontendOptions().InputsAndOutputs.getAllInputs()) {
const std::string &Filename = input.getFileName();
bool IsPrimary = input.isPrimary();
if (auto FoundSnapshot = findSnapshot(Snapshots, Filename)) {
Stamps.push_back(FoundSnapshot.getValue()->getStamp());
} else {
auto Content = ASTManager->Impl.getFileContent(Filename, IsPrimary,
FileSystem, Error);
Stamps.push_back(Content.Stamp);
if (Content.Snapshot) {
Snapshots.push_back(Content.Snapshot);
}
}
}
assert(Stamps.size() ==
Opts.Invok.getFrontendOptions().InputsAndOutputs.inputCount());
return std::make_pair(Snapshots, Stamps);
}
bool ASTBuildOperation::matchesSourceState(
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> OtherFileSystem,
ArrayRef<ImmutableTextSnapshotRef> OtherSnapshots) {
const SwiftInvocation::Implementation &Invok = InvokRef->Impl;
// Check if the inputs changed.
std::vector<BufferStamp> InputStamps;
InputStamps.reserve(
Invok.Opts.Invok.getFrontendOptions().InputsAndOutputs.inputCount());
// IMPORTANT: The computation of stamps must match the one in
// snapshotAndStampsForFilesInCompilerInvocation.
for (const auto &input :
Invok.Opts.Invok.getFrontendOptions().InputsAndOutputs.getAllInputs()) {
const std::string &File = input.getFileName();
if (auto FoundSnapshot = findSnapshot(OtherSnapshots, File)) {
InputStamps.push_back(FoundSnapshot.getValue()->getStamp());
} else {
InputStamps.push_back(
ASTManager->Impl.getBufferStamp(File, OtherFileSystem));
}
}
assert(InputStamps.size() ==
Invok.Opts.Invok.getFrontendOptions().InputsAndOutputs.inputCount());
if (Stamps != InputStamps)
return false;
for (auto &Dependency : DependencyStamps) {
if (Dependency.second !=
ASTManager->Impl.getBufferStamp(Dependency.first, OtherFileSystem))
return false;
}
return true;
}
void ASTBuildOperation::requestConsumerCancellation(
SwiftASTConsumerRef Consumer) {
llvm::sys::ScopedLock L(ConsumersAndResultMtx);
// No need to check if we have already called the consumer here, because it
// is removed from `Consumers` if it's informed about a result from
// `schedule()`.
auto ConsumerIndex = llvm::find_if(
Consumers, [&Consumer](SwiftASTConsumerRef ConsumerInQueue) {
return ConsumerInQueue == Consumer;
});
if (ConsumerIndex == Consumers.end()) {
// Consumer no longer tracked by this build operation. Did it finish
// already?
return;
}
Consumers.erase(ConsumerIndex);
Consumer->cancelled();
if (Consumers.empty()) {
// If there are no more consumers waiting for this result, cancel the AST
// build.
CancellationFlag->store(true, std::memory_order_relaxed);
}
}
static void collectModuleDependencies(ModuleDecl *TopMod,
llvm::SmallPtrSetImpl<ModuleDecl *> &Visited,
SmallVectorImpl<std::string> &Filenames) {
if (!TopMod)
return;
auto ClangModuleLoader = TopMod->getASTContext().getClangModuleLoader();
ModuleDecl::ImportFilter ImportFilter = {
ModuleDecl::ImportFilterKind::Exported,
ModuleDecl::ImportFilterKind::Default};
if (Visited.empty()) {
// Only collect implementation-only dependencies from the main module.
ImportFilter |= ModuleDecl::ImportFilterKind::ImplementationOnly;
}
// FIXME: ImportFilterKind::ShadowedByCrossImportOverlay?
SmallVector<ImportedModule, 8> Imports;
TopMod->getImportedModules(Imports, ImportFilter);
for (auto Import : Imports) {
ModuleDecl *Mod = Import.importedModule;
if (Mod->isSystemModule())
continue;
// FIXME: Setup dependencies on the included headers.
if (ClangModuleLoader &&
Mod == ClangModuleLoader->getImportedHeaderModule())
continue;
bool NewVisit = Visited.insert(Mod).second;
if (!NewVisit)
continue;
// FIXME: Handle modules with multiple source files; these will fail on
// getModuleFilename() (by returning an empty path). Note that such modules
// may be heterogeneous.
{
std::string Path = Mod->getModuleFilename().str();
if (Path.empty() || Path == TopMod->getModuleFilename())
continue; // this is a submodule.
Filenames.push_back(std::move(Path));
}
bool IsClangModule = false;
for (auto File : Mod->getFiles()) {
if (File->getKind() == FileUnitKind::ClangModule) {
IsClangModule = true;
break;
}
}
if (IsClangModule) {
// No need to keep track of the clang module dependencies.
continue;
}
collectModuleDependencies(Mod, Visited, Filenames);
}
}
static std::atomic<uint64_t> ASTUnitGeneration{ 0 };
void ASTBuildOperation::informConsumer(SwiftASTConsumerRef Consumer) {
assert(Result &&
"Can't inform consumer about result if we don't have a result yet");
Consumer->removeCancellationRequestCallback();
if (Result.Cancelled) {
assert(false && "We should only cancel the build operation if there are no "
"more consumers attached to it and should not accept any "
"new consumers if the build operation was cancelled. Thus "
"this case should never happen.");
Consumer->cancelled();
} else if (Result.AST) {
Result.AST->Impl.consumeAsync(Consumer, Result.AST);
} else {
Consumer->failed(Result.Error);
}
}
ASTUnitRef ASTBuildOperation::buildASTUnit(std::string &Error) {
++ASTManager->Impl.Stats->numASTBuilds;
const InvocationOptions &Opts = InvokRef->Impl.Opts;
LOG_FUNC_SECTION(InfoHighPrio) {
Log->getOS() << "AST build: ";
Log->getOS() << Opts.Invok.getModuleName() << '/' << Opts.PrimaryFile;
}
SmallVector<FileContent, 8> Contents;
findSnapshotAndOpenFiles(Contents, Error);
ASTUnitRef ASTRef = new ASTUnit(++ASTUnitGeneration, ASTManager->Impl.Stats);
for (auto &Content : Contents) {
if (Content.Snapshot)
ASTRef->Impl.Snapshots.push_back(Content.Snapshot);
}
auto &CompIns = ASTRef->Impl.CompInst;
auto &Consumer = ASTRef->Impl.CollectDiagConsumer;
// Display diagnostics to stderr.
CompIns.addDiagnosticConsumer(&Consumer);
trace::TracedOperation TracedOp(trace::OperationKind::PerformSema);
trace::SwiftInvocation TraceInfo;
if (TracedOp.enabled()) {
trace::initTraceInfo(TraceInfo, InvokRef->Impl.Opts.PrimaryFile,
InvokRef->Impl.Opts.Args);
TracedOp.setDiagnosticProvider(
[&Consumer](SmallVectorImpl<DiagnosticEntryInfo> &diags) {
Consumer.getAllDiagnostics(diags);
});
}
CompilerInvocation Invocation;
InvokRef->Impl.Opts.applyToSubstitutingInputs(
Invocation, convertFileContentsToInputs(Contents));
Invocation.getLangOptions().CollectParsedToken = true;
Invocation.getTypeCheckerOptions().CancellationFlag = CancellationFlag;
if (FileSystem != llvm::vfs::getRealFileSystem()) {
CompIns.getSourceMgr().setFileSystem(FileSystem);
}
if (CompIns.setup(Invocation)) {
// FIXME: Report the diagnostic.
LOG_WARN_FUNC("Compilation setup failed!!!");
Error = "compilation setup failed";
return nullptr;
}
if (CompIns.loadStdlibIfNeeded()) {
LOG_WARN_FUNC("Loading the stdlib failed");
Error = "Loading the stdlib failed";
return nullptr;
}
registerIDERequestFunctions(CompIns.getASTContext().evaluator);
if (TracedOp.enabled()) {
TracedOp.start(TraceInfo);
}
CloseClangModuleFiles scopedCloseFiles(
*CompIns.getASTContext().getClangModuleLoader());
Consumer.setInputBufferIDs(ASTRef->getCompilerInstance().getInputBufferIDs());
CompIns.performSema();
llvm::SmallPtrSet<ModuleDecl *, 16> Visited;
SmallVector<std::string, 8> Filenames;
collectModuleDependencies(CompIns.getMainModule(), Visited, Filenames);
// FIXME: There exists a small window where the module file may have been
// modified after compilation finished and before we get its stamp.
for (auto &Filename : Filenames) {
DependencyStamps.push_back(std::make_pair(
Filename, ASTManager->Impl.getBufferStamp(Filename, FileSystem)));
}
// Since we only typecheck the primary file (plus referenced constructs
// from other files), any error is likely to break SIL generation.
if (!Consumer.hadAnyError()) {
// FIXME: Any error anywhere in the SourceFile will switch off SIL
// diagnostics. This means that this can happen:
// - The user sees a SIL diagnostic in one function
// - The user edits another function in the same file and introduces a
// typechecking error.
// - The SIL diagnostic in the first function will be gone.
//
// Could we maybe selectively SILGen functions from the SourceFile, so
// that we avoid SILGen'ing the second function with the typecheck error
// but still allow SILGen'ing the first function ?
// Or try to keep track of SIL diagnostics emitted previously ?
// FIXME: We should run SIL diagnostics asynchronously after typechecking
// so that they don't delay reporting of typechecking diagnostics and they
// don't block any other AST processing for the same SwiftInvocation.
if (auto SF = CompIns.getPrimarySourceFile()) {
SILOptions SILOpts = Invocation.getSILOptions();
auto &TC = CompIns.getSILTypes();
std::unique_ptr<SILModule> SILMod = performASTLowering(*SF, TC, SILOpts);
runSILDiagnosticPasses(*SILMod);
}
}
return ASTRef;
}
void ASTBuildOperation::findSnapshotAndOpenFiles(
SmallVectorImpl<FileContent> &Contents, std::string &Error) const {
const InvocationOptions &Opts = InvokRef->Impl.Opts;
for (const auto &input :
Opts.Invok.getFrontendOptions().InputsAndOutputs.getAllInputs()) {
const std::string &File = input.getFileName();
bool IsPrimary = input.isPrimary();
bool FoundSnapshot = false;
for (auto &Snap : Snapshots) {
if (Snap->getFilename() == File) {
FoundSnapshot = true;
Contents.push_back(getFileContentFromSnap(Snap, IsPrimary, File));
break;
}
}
if (FoundSnapshot)
continue;
auto Content =
ASTManager->Impl.getFileContent(File, IsPrimary, FileSystem, Error);
if (!Content.Buffer) {
LOG_WARN_FUNC("failed getting file contents for " << File << ": "
<< Error);
// File may not exist, continue and recover as if it was empty.
Content.Buffer = llvm::WritableMemoryBuffer::getNewMemBuffer(0, File);
}
Contents.push_back(std::move(Content));
}
assert(Contents.size() ==
Opts.Invok.getFrontendOptions().InputsAndOutputs.inputCount());
}
void ASTBuildOperation::schedule(WorkQueue Queue) {
transitionToState(State::Queued, /*ExpectedOldState=*/State::Created);
auto SharedThis = shared_from_this();
// Capture `SharedThis` in the dispatched lambda to keep `this` alive.
// Capture `this` for a more convenient access of members.
Queue.dispatch(
[this, SharedThis] {
transitionToState(State::Running, /*ExpectedOldState=*/State::Queued);
SWIFT_DEFER {
transitionToState(State::Finished,
/*ExpectedOldState=*/State::Running);
};
{
llvm::sys::ScopedLock L(ConsumersAndResultMtx);
if (Consumers.empty()) {
// There are no consumers - no point creating the AST anymore.
Result.emplace(/*AST=*/nullptr, /*Error=*/"", /*Cancelled=*/true);
return;
}
if (CancellationFlag->load(std::memory_order_relaxed)) {
assert(false && "We should only set the cancellation flag if there "
"are no more consumers");
for (auto &Consumer : Consumers) {
Consumer->cancelled();
}
}
}
std::string Error;
assert(!Result && "We should only be producing a result once");
ASTUnitRef AST = buildASTUnit(Error);
{
llvm::sys::ScopedLock L(ConsumersAndResultMtx);
bool WasCancelled = CancellationFlag->load(std::memory_order_relaxed);
Result.emplace(AST, Error, WasCancelled);
for (auto &Consumer : Consumers) {
informConsumer(Consumer);
}
Consumers = {};
}
DidFinishCallback();
},
/*isStackDeep=*/true);
}
bool ASTBuildOperation::addConsumer(SwiftASTConsumerRef Consumer) {
llvm::sys::ScopedLock L(ConsumersAndResultMtx);
if (isCancelled()) {
return false;
}
if (Result) {
informConsumer(Consumer);
} else {
assert(OperationState != State::Finished);
auto WeakThis = std::weak_ptr<ASTBuildOperation>(shared_from_this());
Consumer->setCancellationRequestCallback(
[WeakThis](SwiftASTConsumerRef Consumer) {
if (auto This = WeakThis.lock()) {
This->requestConsumerCancellation(Consumer);
}
});
Consumers.push_back(Consumer);
}
return true;
}
ASTBuildOperationRef ASTProducer::getBuildOperationForConsumer(
SwiftASTConsumerRef Consumer,
IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
ArrayRef<ImmutableTextSnapshotRef> Snapshots, SwiftASTManagerRef Mgr) {
for (auto &BuildOp : llvm::reverse(BuildOperations)) {
if (BuildOp->isCancelled()) {
continue;
}
if (BuildOp->matchesSourceState(FileSystem, Snapshots)) {
++Mgr->Impl.Stats->numASTCacheHits;
return BuildOp;
} else if (Consumer->canUseASTWithSnapshots(BuildOp->getSnapshots())) {
++Mgr->Impl.Stats->numASTsUsedWithSnaphots;
return BuildOp;
}
}
return nullptr;
}
void ASTProducer::enqueueConsumer(
SwiftASTConsumerRef Consumer,
IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
ArrayRef<ImmutableTextSnapshotRef> Snapshots, SwiftASTManagerRef Mgr) {
// We can't use a llvm::sys::ScopedLock here because we need to unlock it
// before calling enqueueConsumer again in the !WasAdded case below.
std::unique_lock<llvm::sys::Mutex> BuildOperationsLock(BuildOperationsMtx);
if (auto BuildOp =
getBuildOperationForConsumer(Consumer, FileSystem, Snapshots, Mgr)) {
bool WasAdded = BuildOp->addConsumer(Consumer);
if (!WasAdded) {
// The build operation was cancelled after the call to
// getBuildOperationForConsumer but before the consumer could be added.
// This should be an absolute edge case. Let's just try again.
BuildOperationsLock.unlock();
enqueueConsumer(Consumer, FileSystem, Snapshots, Mgr);
return;
}
} else {
auto WeakThis = std::weak_ptr<ASTProducer>(shared_from_this());
auto DidFinishCallback = [WeakThis, Mgr]() {
if (auto This = WeakThis.lock()) {
{
llvm::sys::ScopedLock L(This->BuildOperationsMtx);
This->cleanBuildOperations();
}
// Re-register the object with the cache to update its memory cost.
Mgr->Impl.ASTCache.set(This->InvokRef->Impl.Key, This);
}
};
ASTBuildOperationRef NewBuildOp = std::make_shared<ASTBuildOperation>(
FileSystem, Snapshots, InvokRef, Mgr, DidFinishCallback);
BuildOperations.push_back(NewBuildOp);
NewBuildOp->addConsumer(Consumer);
NewBuildOp->schedule(Mgr->Impl.ASTBuildQueue);
}
}