//===--- 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 Args; const std::string PrimaryFile; const CompilerInvocation Invok; InvocationOptions(ArrayRef 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 &Args, std::string &PrimaryFile) const; private: static std::vector _convertArgs(ArrayRef CArgs) { std::vector Args; Args.reserve(CArgs.size()); for (auto Arg : CArgs) Args.push_back(Arg); return Args; } }; struct ASTKey { llvm::FoldingSetNodeID FSID; }; template size_t getVectorMemoryCost(const std::vector &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 &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 &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 Stats; SmallVector Snapshots; EditorDiagConsumer CollectDiagConsumer; CompilerInstance CompInst; WorkQueue Queue{ WorkQueue::Dequeuing::Serial, "sourcekit.swift.ConsumeAST" }; Implementation(uint64_t Generation, std::shared_ptr 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 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 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 Fn) { Impl.Queue.dispatch(std::move(Fn)); } } // namespace SourceKit namespace { typedef uint64_t BufferStamp; struct FileContent { ImmutableTextSnapshotRef Snapshot; std::string Filename; std::unique_ptr Buffer; bool IsPrimary; BufferStamp Stamp; FileContent(ImmutableTextSnapshotRef Snapshot, std::string Filename, std::unique_ptr 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 { /// 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 FileSystem; /// The contents of all explicit input files of the compiler invoation, which /// can be determined at construction time of the \c ASTBuildOperation. const std::vector FileContents; /// \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, 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> CancellationFlag = std::make_shared>(false); /// A callback that's called when the operation finishes. Used to remove it /// from the \c ASTProducer that scheduled it. const std::function 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 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); /// 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 \c FileContents containing all files explicitly /// referenced by the compiler invocation. std::vector fileContentsForFilesInCompilerInvocation(); public: ASTBuildOperation(IntrusiveRefCntPtr FileSystem, SwiftInvocationRef InvokRef, SwiftASTManagerRef ASTManager, std::function DidFinishCallback) : InvokRef(InvokRef), FileSystem(FileSystem), ASTManager(ASTManager), DidFinishCallback(DidFinishCallback) { // 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 &>(this->FileContents) = fileContentsForFilesInCompilerInvocation(); } ~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 getFileContents() const { return FileContents; } /// 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(FileContents) + 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 fileSystem); /// 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; /// 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 { 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 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. /// /// Assumes that \c BuildOperationsMtx has been claimed. ASTBuildOperationRef getBuildOperationForConsumer( SwiftASTConsumerRef Consumer, IntrusiveRefCntPtr FileSystem, 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 FileSystem, SwiftASTManagerRef Mgr); size_t getMemoryCost() const { size_t Cost = sizeof(*this); for (auto &BuildOp : BuildOperations) { Cost += BuildOp->getMemoryCost(); } return Cost; } }; typedef std::shared_ptr ASTProducerRef; } // end anonymous namespace namespace swift { namespace sys { template <> struct CacheValueCostInfo { static size_t getCost(const ASTProducer &Unit) { return Unit.getMemoryCost(); } }; template <> struct CacheKeyHashInfo { static uintptr_t getHashValue(const ASTKey &Key) { return Key.FSID.ComputeHash(); } static bool isEqual(void *LHS, void *RHS) { return static_cast(LHS)->FSID == static_cast(RHS)->FSID; } }; } // namespace sys } // namespace swift struct SwiftASTManager::Implementation { explicit Implementation( std::shared_ptr EditorDocs, std::shared_ptr Config, std::shared_ptr Stats, std::shared_ptr ReqTracker, StringRef RuntimeResourcePath, StringRef DiagnosticDocumentationPath) : EditorDocs(EditorDocs), Config(Config), Stats(Stats), ReqTracker(ReqTracker), RuntimeResourcePath(RuntimeResourcePath), DiagnosticDocumentationPath(DiagnosticDocumentationPath), SessionTimestamp(llvm::sys::toTimeT(std::chrono::system_clock::now())) { } std::shared_ptr EditorDocs; std::shared_ptr Config; std::shared_ptr Stats; std::shared_ptr ReqTracker; std::string RuntimeResourcePath; std::string DiagnosticDocumentationPath; SourceManager SourceMgr; Cache 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; }; /// FIXME: Once we no longer support implicit cancellation using /// OncePerASTToken, we can stop keeping track of ScheduledConsumers and /// completely rely on RequestTracker for cancellation. llvm::sys::Mutex ScheduledConsumersMtx; std::vector 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 FileSystem, std::string &Error) const; BufferStamp getBufferStamp(StringRef FilePath, IntrusiveRefCntPtr FileSystem, bool CheckEditorDocs = true) const; std::unique_ptr getMemoryBuffer(StringRef Filename, IntrusiveRefCntPtr FileSystem, std::string &Error) const; }; SwiftASTManager::SwiftASTManager( std::shared_ptr EditorDocs, std::shared_ptr Config, std::shared_ptr Stats, std::shared_ptr ReqTracker, StringRef RuntimeResourcePath, StringRef DiagnosticDocumentationPath) : Impl(*new Implementation(EditorDocs, Config, Stats, ReqTracker, RuntimeResourcePath, DiagnosticDocumentationPath)) {} SwiftASTManager::~SwiftASTManager() { delete &Impl; } std::unique_ptr SwiftASTManager::getMemoryBuffer(StringRef Filename, std::string &Error) { return Impl.getMemoryBuffer(Filename, llvm::vfs::getRealFileSystem(), Error); } static FrontendInputsAndOutputs convertFileContentsToInputs(ArrayRef contents) { FrontendInputsAndOutputs inputsAndOutputs; for (const FileContent &content : contents) inputsAndOutputs.addInput(InputFile(content)); return inputsAndOutputs; } bool SwiftASTManager::initCompilerInvocation( CompilerInvocation &Invocation, ArrayRef OrigArgs, DiagnosticEngine &Diags, StringRef UnresolvedPrimaryFile, std::string &Error) { return initCompilerInvocation(Invocation, OrigArgs, Diags, UnresolvedPrimaryFile, llvm::vfs::getRealFileSystem(), Error); } bool SwiftASTManager::initCompilerInvocation( CompilerInvocation &Invocation, ArrayRef OrigArgs, DiagnosticEngine &Diags, StringRef UnresolvedPrimaryFile, llvm::IntrusiveRefCntPtr 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 OrigArgs, StringRef PrimaryFile, std::string &Error) { DiagnosticEngine Diagnostics(Impl.SourceMgr); return initCompilerInvocation(CompInvok, OrigArgs, Diagnostics, PrimaryFile, Error); } bool SwiftASTManager::initCompilerInvocationNoInputs( swift::CompilerInvocation &Invocation, ArrayRef OrigArgs, swift::DiagnosticEngine &Diags, std::string &Error, bool AllowInputs) { SmallVector 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 OrigArgs, StringRef PrimaryFile, std::string &Error) { return getInvocation(OrigArgs, PrimaryFile, llvm::vfs::getRealFileSystem(), Error); } SwiftInvocationRef SwiftASTManager::getInvocation( ArrayRef OrigArgs, StringRef PrimaryFile, llvm::IntrusiveRefCntPtr 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 &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 fileSystem) { 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}); } Producer->enqueueConsumer(ASTConsumer, fileSystem, shared_from_this()); auto WeakConsumer = SwiftASTConsumerWeakRef(ASTConsumer); Impl.ReqTracker->setCancellationHandler(CancellationToken, [WeakConsumer] { if (auto Consumer = WeakConsumer.lock()) { Consumer->requestCancellation(); } }); } void SwiftASTManager::removeCachedAST(SwiftInvocationRef Invok) { Impl.ASTCache.remove(Invok->Impl.Key); } ASTProducerRef SwiftASTManager::Implementation::getASTProducer(SwiftInvocationRef InvokRef) { llvm::sys::ScopedLock L(CacheMtx); llvm::Optional OptProducer = ASTCache.get(InvokRef->Impl.Key); if (OptProducer.hasValue()) return OptProducer.getValue(); ASTProducerRef Producer = std::make_shared(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 FileSystem, std::string &Error) const { std::string FilePath = SwiftLangSupport::resolvePathSymlinks(UnresolvedPath); if (auto EditorDoc = EditorDocs->findByPath(FilePath, /*IsRealpath=*/true)) return getFileContentFromSnap(EditorDoc->getLatestSnapshot(), IsPrimary, FilePath); // FIXME: Is there a way to get timestamp and buffer for a file atomically ? // No need to check EditorDocs again. We did so above. auto Stamp = getBufferStamp(FilePath, FileSystem, /*CheckEditorDocs=*/false); auto Buffer = getMemoryBuffer(FilePath, FileSystem, Error); return FileContent(nullptr, UnresolvedPath.str(), std::move(Buffer), IsPrimary, Stamp); } BufferStamp SwiftASTManager::Implementation::getBufferStamp( StringRef FilePath, llvm::IntrusiveRefCntPtr FileSystem, bool CheckEditorDocs) const { assert(FileSystem); if (CheckEditorDocs) { 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 SwiftASTManager::Implementation::getMemoryBuffer( StringRef Filename, llvm::IntrusiveRefCntPtr FileSystem, std::string &Error) const { assert(FileSystem); llvm::ErrorOr> 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; } std::vector ASTBuildOperation::fileContentsForFilesInCompilerInvocation() { const InvocationOptions &Opts = InvokRef->Impl.Opts; std::string Error; // is ignored std::vector FileContents; FileContents.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(); auto Content = ASTManager->Impl.getFileContent(Filename, IsPrimary, FileSystem, Error); if (!Content.Buffer) { LOG_WARN_FUNC("failed getting file contents for " << Filename << ": " << Error); // File may not exist, continue and recover as if it was empty. Content.Buffer = llvm::WritableMemoryBuffer::getNewMemBuffer(0, Filename); } FileContents.push_back(std::move(Content)); } assert(FileContents.size() == Opts.Invok.getFrontendOptions().InputsAndOutputs.inputCount()); return FileContents; } bool ASTBuildOperation::matchesSourceState( llvm::IntrusiveRefCntPtr OtherFileSystem) { const InvocationOptions &Opts = InvokRef->Impl.Opts; auto Inputs = Opts.Invok.getFrontendOptions().InputsAndOutputs.getAllInputs(); for (size_t I = 0; I < Inputs.size(); I++) { if (getFileContents()[I].Stamp != ASTManager->Impl.getBufferStamp(Inputs[I].getFileName(), OtherFileSystem)) { 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 &Visited, SmallVectorImpl &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 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 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; } ASTUnitRef ASTRef = new ASTUnit(++ASTUnitGeneration, ASTManager->Impl.Stats); for (auto &Content : getFileContents()) { 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 &diags) { Consumer.getAllDiagnostics(diags); }); } CompilerInvocation Invocation; InvokRef->Impl.Opts.applyToSubstitutingInputs( Invocation, convertFileContentsToInputs(getFileContents())); 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 Visited; SmallVector 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 SILMod = performASTLowering(*SF, TC, SILOpts); runSILDiagnosticPasses(*SILMod); } } return ASTRef; } 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(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 FileSystem, SwiftASTManagerRef Mgr) { for (auto &BuildOp : llvm::reverse(BuildOperations)) { if (BuildOp->isCancelled()) { continue; } std::vector Snapshots; Snapshots.reserve(BuildOp->getFileContents().size()); for (auto &FileContent : BuildOp->getFileContents()) { Snapshots.push_back(FileContent.Snapshot); } if (BuildOp->matchesSourceState(FileSystem)) { ++Mgr->Impl.Stats->numASTCacheHits; return BuildOp; } else if (Consumer->canUseASTWithSnapshots(Snapshots)) { ++Mgr->Impl.Stats->numASTsUsedWithSnaphots; return BuildOp; } } return nullptr; } void ASTProducer::enqueueConsumer( SwiftASTConsumerRef Consumer, IntrusiveRefCntPtr FileSystem, 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 BuildOperationsLock(BuildOperationsMtx); if (auto BuildOp = getBuildOperationForConsumer(Consumer, FileSystem, 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, Mgr); return; } } else { auto WeakThis = std::weak_ptr(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( FileSystem, InvokRef, Mgr, DidFinishCallback); BuildOperations.push_back(NewBuildOp); NewBuildOp->addConsumer(Consumer); NewBuildOp->schedule(Mgr->Impl.ASTBuildQueue); } }