//===--- 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/FileSystem.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" using namespace SourceKit; using namespace swift; using namespace swift::sys; namespace { class StreamDiagConsumer : public DiagnosticConsumer { llvm::raw_ostream &OS; public: StreamDiagConsumer(llvm::raw_ostream &OS) : OS(OS) {} void handleDiagnostic(SourceManager &SM, SourceLoc Loc, DiagnosticKind Kind, StringRef FormatString, ArrayRef FormatArgs, const DiagnosticInfo &Info) override { // FIXME: Print location info if available. switch (Kind) { case DiagnosticKind::Error: OS << "error: "; break; case DiagnosticKind::Warning: OS << "warning: "; break; case DiagnosticKind::Note: OS << "note: "; break; case DiagnosticKind::Remark: OS << "remark: "; break; } DiagnosticEngine::formatDiagnosticText(OS, FormatString, FormatArgs); } }; } // end anonymous namespace 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; }; } // 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) { 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"); } }); } 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()); } }; class ASTProducer : public ThreadSafeRefCountedBase { SwiftInvocationRef InvokRef; SmallVector Stamps; ThreadSafeRefCntPtr AST; SmallVector, 8> DependencyStamps; struct QueuedConsumer { SwiftASTConsumerRef consumer; std::vector snapshots; const void *oncePerASTToken; }; std::vector QueuedConsumers; llvm::sys::Mutex Mtx; public: explicit ASTProducer(SwiftInvocationRef InvokRef) : InvokRef(std::move(InvokRef)) {} ASTUnitRef getExistingAST() { // FIXME: ThreadSafeRefCntPtr is racy. llvm::sys::ScopedLock L(Mtx); return AST; } void getASTUnitAsync(std::shared_ptr Mgr, ArrayRef Snapshots, std::function Receiver); bool shouldRebuild(SwiftASTManager::Implementation &MgrImpl, ArrayRef Snapshots); void enqueueConsumer(SwiftASTConsumerRef Consumer, ArrayRef Snapshots, const void *OncePerASTToken); using ConsumerPredicate = llvm::function_ref)>; std::vector takeConsumers(ConsumerPredicate predicate); size_t getMemoryCost() const { // FIXME: Report the memory cost of the overall CompilerInstance. if (AST && AST->getCompilerInstance().hasASTContext()) return AST->Impl.CompInst.getASTContext().getTotalMemory(); return sizeof(*this) + sizeof(*AST); } private: ASTUnitRef getASTUnitImpl(SwiftASTManager::Implementation &MgrImpl, ArrayRef Snapshots, std::string &Error); ASTUnitRef createASTUnit(SwiftASTManager::Implementation &MgrImpl, ArrayRef Snapshots, std::string &Error); void findSnapshotAndOpenFiles(SwiftASTManager::Implementation &MgrImpl, ArrayRef Snapshots, SmallVectorImpl &Contents, std::string &Error) const; }; typedef IntrusiveRefCntPtr 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 Stats, StringRef RuntimeResourcePath) : EditorDocs(EditorDocs), Stats(Stats), RuntimeResourcePath(RuntimeResourcePath) {} std::shared_ptr EditorDocs; std::shared_ptr Stats; std::string RuntimeResourcePath; SourceManager SourceMgr; Cache ASTCache{ "sourcekit.swift.ASTCache" }; llvm::sys::Mutex CacheMtx; WorkQueue ASTBuildQueue{ WorkQueue::Dequeuing::Serial, "sourcekit.swift.ASTBuilding" }; ASTProducerRef getASTProducer(SwiftInvocationRef InvokRef); FileContent getFileContent(StringRef FilePath, bool IsPrimary, std::string &Error); BufferStamp getBufferStamp(StringRef FilePath); std::unique_ptr getMemoryBuffer(StringRef Filename, std::string &Error); }; SwiftASTManager::SwiftASTManager( std::shared_ptr EditorDocs, std::shared_ptr Stats, StringRef RuntimeResourcePath) : Impl(*new Implementation(EditorDocs, Stats, RuntimeResourcePath)) {} SwiftASTManager::~SwiftASTManager() { delete &Impl; } std::unique_ptr SwiftASTManager::getMemoryBuffer(StringRef Filename, std::string &Error) { return Impl.getMemoryBuffer(Filename, Error); } static FrontendInputsAndOutputs convertFileContentsToInputs(const SmallVectorImpl &contents) { FrontendInputsAndOutputs inputsAndOutputs; for (const FileContent &content : contents) inputsAndOutputs.addInput(InputFile(content)); return inputsAndOutputs; } static FrontendInputsAndOutputs resolveSymbolicLinksInInputs(FrontendInputsAndOutputs &inputsAndOutputs, StringRef UnresolvedPrimaryFile, std::string &Error) { unsigned primaryCount = 0; std::string PrimaryFile = SwiftLangSupport::resolvePathSymlinks(UnresolvedPrimaryFile); // FIXME: The frontend should be dealing with symlinks, maybe similar to // clang's FileManager ? FrontendInputsAndOutputs replacementInputsAndOutputs; for (const InputFile &input : inputsAndOutputs.getAllInputs()) { std::string newFilename = SwiftLangSupport::resolvePathSymlinks(input.file()); bool newIsPrimary = input.isPrimary() || (!PrimaryFile.empty() && PrimaryFile == newFilename); if (newIsPrimary) { ++primaryCount; } assert(primaryCount < 2 && "cannot handle multiple primaries"); replacementInputsAndOutputs.addInput( InputFile(newFilename, newIsPrimary, input.buffer())); } if (PrimaryFile.empty() || primaryCount == 1) { return replacementInputsAndOutputs; } llvm::SmallString<64> Err; llvm::raw_svector_ostream OS(Err); OS << "'" << PrimaryFile << "' is not part of the input files"; Error = OS.str(); return replacementInputsAndOutputs; } bool SwiftASTManager::initCompilerInvocation(CompilerInvocation &Invocation, ArrayRef OrigArgs, DiagnosticEngine &Diags, StringRef UnresolvedPrimaryFile, std::string &Error) { SmallVector Args; // Make sure to put '-resource-dir' at the top to allow overriding it by // the passed in arguments. Args.push_back("-resource-dir"); Args.push_back(Impl.RuntimeResourcePath.c_str()); Args.append(OrigArgs.begin(), OrigArgs.end()); SmallString<32> ErrStr; llvm::raw_svector_ostream ErrOS(ErrStr); StreamDiagConsumer DiagConsumer(ErrOS); Diags.addConsumer(DiagConsumer); bool HadError = driver::getSingleFrontendInvocationFromDriverArguments( Args, Diags, [&](ArrayRef FrontendArgs) { return Invocation.parseArgs(FrontendArgs, Diags); }); // Remove the StreamDiagConsumer as it's no longer needed. std::vector OldC = Diags.takeConsumers(); OldC.erase(std::remove(OldC.begin(), OldC.end(), &DiagConsumer)); for (DiagnosticConsumer *Consumer : OldC) { Diags.addConsumer(*Consumer); } if (HadError) { Error = ErrOS.str(); return true; } Invocation.getFrontendOptions().InputsAndOutputs = resolveSymbolicLinksInInputs( Invocation.getFrontendOptions().InputsAndOutputs, UnresolvedPrimaryFile, Error); if (!Error.empty()) return true; ClangImporterOptions &ImporterOpts = Invocation.getClangImporterOptions(); ImporterOpts.DetailedPreprocessingRecord = true; assert(!Invocation.getModuleName().empty()); Invocation.getLangOptions().AttachCommentsToDecls = true; Invocation.getLangOptions().DiagnosticsEditorMode = true; Invocation.getLangOptions().CollectParsedToken = true; auto &FrontendOpts = Invocation.getFrontendOptions(); if (FrontendOpts.PlaygroundTransform) { // The playground instrumenter changes the AST in ways that disrupt the // SourceKit functionality. Since we don't need the instrumenter, and all we // actually need is the playground semantics visible to the user, like // silencing the "expression resolves to an unused l-value" error, disable it. FrontendOpts.PlaygroundTransform = false; } // Disable the index-store functionality for the sourcekitd requests. FrontendOpts.IndexStorePath.clear(); ImporterOpts.IndexStorePath.clear(); // Force the action type to be -typecheck. This affects importing the // SwiftONoneSupport module. FrontendOpts.RequestedAction = FrontendOptions::ActionType::Typecheck; // We don't care about LLVMArgs FrontendOpts.LLVMArgs.clear(); // Disable expensive SIL options to reduce time spent in SILGen. disableExpensiveSILOptions(Invocation.getSILOptions()); return false; } 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) { DiagnosticEngine Diags(Impl.SourceMgr); EditorDiagConsumer CollectDiagConsumer; Diags.addConsumer(CollectDiagConsumer); CompilerInvocation CompInvok; if (initCompilerInvocation(CompInvok, OrigArgs, Diags, PrimaryFile, 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, ArrayRef Snapshots) { ASTProducerRef Producer = Impl.getASTProducer(InvokRef); if (ASTUnitRef Unit = Producer->getExistingAST()) { if (ASTConsumer->canUseASTWithSnapshots(Unit->getSnapshots())) { ++Impl.Stats->numASTsUsedWithSnaphots; Unit->Impl.consumeAsync(std::move(ASTConsumer), Unit); return; } } Producer->enqueueConsumer(ASTConsumer, Snapshots, OncePerASTToken); auto handleAST = [this, Producer, ASTConsumer](ASTUnitRef unit, StringRef error) { auto consumers = Producer->takeConsumers( [&](SwiftASTConsumer *consumer, ArrayRef snapshots) { return consumer == ASTConsumer.get() || !Producer->shouldRebuild(Impl, snapshots) || (unit && consumer->canUseASTWithSnapshots(snapshots)); }); for (auto &consumer : consumers) { if (unit) unit->Impl.consumeAsync(std::move(consumer), unit); else consumer->failed(error); } }; Producer->getASTUnitAsync(shared_from_this(), Snapshots, std::move(handleAST)); } 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 = new 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, std::move(Buf), IsPrimary, Snap->getStamp()); } FileContent SwiftASTManager::Implementation::getFileContent( StringRef UnresolvedPath, bool IsPrimary, std::string &Error) { 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); auto Buffer = getMemoryBuffer(FilePath, Error); return FileContent(nullptr, UnresolvedPath, std::move(Buffer), IsPrimary, Stamp); } BufferStamp SwiftASTManager::Implementation::getBufferStamp(StringRef FilePath){ if (auto EditorDoc = EditorDocs->findByPath(FilePath)) return EditorDoc->getLatestSnapshot()->getStamp(); llvm::sys::fs::file_status Status; if (std::error_code Ret = llvm::sys::fs::status(FilePath, Status)) { // Failure to read the file. LOG_WARN_FUNC("failed to stat file: " << FilePath << " (" << Ret.message() << ')'); return -1; } return Status.getLastModificationTime().time_since_epoch().count(); } std::unique_ptr SwiftASTManager::Implementation::getMemoryBuffer(StringRef Filename, std::string &Error) { llvm::ErrorOr> FileBufOrErr = llvm::MemoryBuffer::getFile(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; } void ASTProducer::getASTUnitAsync(std::shared_ptr Mgr, ArrayRef Snaps, std::function Receiver) { ASTProducerRef ThisProducer = this; SmallVector Snapshots; Snapshots.append(Snaps.begin(), Snaps.end()); Mgr->Impl.ASTBuildQueue.dispatch([ThisProducer, Mgr, Snapshots, Receiver] { std::string Error; ASTUnitRef Unit = ThisProducer->getASTUnitImpl(Mgr->Impl, Snapshots, Error); Receiver(Unit, Error); }, /*isStackDeep=*/true); } ASTUnitRef ASTProducer::getASTUnitImpl(SwiftASTManager::Implementation &MgrImpl, ArrayRef Snapshots, std::string &Error) { if (!AST || shouldRebuild(MgrImpl, Snapshots)) { bool IsRebuild = AST != nullptr; const InvocationOptions &Opts = InvokRef->Impl.Opts; LOG_FUNC_SECTION(InfoHighPrio) { Log->getOS() << "AST build ("; if (IsRebuild) Log->getOS() << "rebuild"; else Log->getOS() << "first"; Log->getOS() << "): "; Log->getOS() << Opts.Invok.getModuleName() << '/' << Opts.PrimaryFile; } auto NewAST = createASTUnit(MgrImpl, Snapshots, Error); { // FIXME: ThreadSafeRefCntPtr is racy. llvm::sys::ScopedLock L(Mtx); AST = NewAST; } { llvm::sys::ScopedLock L(MgrImpl.CacheMtx); // Re-register the object with the cache to update its memory cost. ASTProducerRef ThisProducer = this; MgrImpl.ASTCache.set(InvokRef->Impl.Key, ThisProducer); } } else { ++MgrImpl.Stats->numASTCacheHits; } return AST; } void ASTProducer::enqueueConsumer(SwiftASTConsumerRef consumer, ArrayRef snapshots, const void *oncePerASTToken) { llvm::sys::ScopedLock L(Mtx); if (oncePerASTToken) { for (auto I = QueuedConsumers.begin(), E = QueuedConsumers.end(); I != E; ++I) { if (I->oncePerASTToken == oncePerASTToken) { I->consumer->cancelled(); QueuedConsumers.erase(I); break; } } } QueuedConsumers.push_back({std::move(consumer), snapshots, oncePerASTToken}); } std::vector ASTProducer::takeConsumers(ConsumerPredicate predicate) { llvm::sys::ScopedLock L(Mtx); std::vector consumers; QueuedConsumers.erase(std::remove_if(QueuedConsumers.begin(), QueuedConsumers.end(), [&](QueuedConsumer &qc) { if (predicate(qc.consumer.get(), qc.snapshots)) { consumers.push_back(std::move(qc.consumer)); return true; } return false; }), QueuedConsumers.end()); return consumers; } bool ASTProducer::shouldRebuild(SwiftASTManager::Implementation &MgrImpl, ArrayRef Snapshots) { const SwiftInvocation::Implementation &Invok = InvokRef->Impl; // Check if the inputs changed. SmallVector InputStamps; InputStamps.reserve( Invok.Opts.Invok.getFrontendOptions().InputsAndOutputs.inputCount()); for (const auto &input : Invok.Opts.Invok.getFrontendOptions().InputsAndOutputs.getAllInputs()) { const std::string &File = input.file(); bool FoundSnapshot = false; for (auto &Snap : Snapshots) { if (Snap->getFilename() == File) { FoundSnapshot = true; InputStamps.push_back(Snap->getStamp()); break; } } if (!FoundSnapshot) InputStamps.push_back(MgrImpl.getBufferStamp(File)); } assert(InputStamps.size() == Invok.Opts.Invok.getFrontendOptions().InputsAndOutputs.inputCount()); if (Stamps != InputStamps) return true; for (auto &Dependency : DependencyStamps) { if (Dependency.second != MgrImpl.getBufferStamp(Dependency.first)) return true; } return false; } static void collectModuleDependencies(ModuleDecl *TopMod, llvm::SmallPtrSetImpl &Visited, SmallVectorImpl &Filenames) { if (!TopMod) return; auto ClangModuleLoader = TopMod->getASTContext().getClangModuleLoader(); SmallVector Imports; TopMod->getImportedModules(Imports, ModuleDecl::ImportFilter::All); for (auto Import : Imports) { ModuleDecl *Mod = Import.second; 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(); 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 }; ASTUnitRef ASTProducer::createASTUnit(SwiftASTManager::Implementation &MgrImpl, ArrayRef Snapshots, std::string &Error) { ++MgrImpl.Stats->numASTBuilds; Stamps.clear(); DependencyStamps.clear(); SmallVector Contents; findSnapshotAndOpenFiles(MgrImpl, Snapshots, Contents, Error); for (auto &Content : Contents) Stamps.push_back(Content.Stamp); ASTUnitRef ASTRef = new ASTUnit(++ASTUnitGeneration, MgrImpl.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 &diags) { Consumer.getAllDiagnostics(diags); }); } CompilerInvocation Invocation; InvokRef->Impl.Opts.applyToSubstitutingInputs( Invocation, convertFileContentsToInputs(Contents)); Invocation.getLangOptions().CollectParsedToken = true; if (CompIns.setup(Invocation)) { // FIXME: Report the diagnostic. LOG_WARN_FUNC("Compilation setup failed!!!"); Error = "compilation setup failed"; return nullptr; } 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, MgrImpl.getBufferStamp(Filename))); } // 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(); std::unique_ptr SILMod = performSILGeneration(*SF, SILOpts); runSILDiagnosticPasses(*SILMod); } } return ASTRef; } void ASTProducer::findSnapshotAndOpenFiles( SwiftASTManager::Implementation &MgrImpl, ArrayRef Snapshots, SmallVectorImpl &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.file(); 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 = MgrImpl.getFileContent(File, IsPrimary, 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()); }