diff --git a/include/swift/AST/ASTTypeIDZone.def b/include/swift/AST/ASTTypeIDZone.def index 20c8835dbcf4d..7add2900c8d3a 100644 --- a/include/swift/AST/ASTTypeIDZone.def +++ b/include/swift/AST/ASTTypeIDZone.def @@ -17,6 +17,7 @@ SWIFT_TYPEID(ActorIsolation) SWIFT_TYPEID(AncestryFlags) +SWIFT_TYPEID(BodyAndFingerprint) SWIFT_TYPEID(BodyInitKind) SWIFT_TYPEID(BodyInitKindAndExpr) SWIFT_TYPEID(CtorInitializerKind) diff --git a/include/swift/AST/ASTTypeIDs.h b/include/swift/AST/ASTTypeIDs.h index 71c8a6a484688..0f19111c2582d 100644 --- a/include/swift/AST/ASTTypeIDs.h +++ b/include/swift/AST/ASTTypeIDs.h @@ -81,6 +81,7 @@ enum class AncestryFlags : uint8_t; enum class ImplicitMemberAction : uint8_t; struct FingerprintAndMembers; class Identifier; +class BodyAndFingerprint; // Define the AST type zone (zone 1) #define SWIFT_TYPEID_ZONE AST diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 7ee00f938840b..a9169a15ac870 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -5904,6 +5904,38 @@ class ImportAsMemberStatus { } }; +class BodyAndFingerprint { + llvm::PointerIntPair BodyAndHasFp; + Fingerprint Fp; + +public: + BodyAndFingerprint(BraceStmt *body, Optional fp) + : BodyAndHasFp(body, fp.hasValue()), + Fp(fp.hasValue() ? *fp : Fingerprint::ZERO()) {} + BodyAndFingerprint() : BodyAndFingerprint(nullptr, None) {} + + BraceStmt *getBody() const { return BodyAndHasFp.getPointer(); } + + Optional getFingerprint() const { + if (BodyAndHasFp.getInt()) + return Fp; + else + return None; + } + + void setFingerprint(Optional fp) { + if (fp.hasValue()) { + Fp = *fp; + BodyAndHasFp.setInt(true); + } else { + Fp = Fingerprint::ZERO(); + BodyAndHasFp.setInt(false); + } + } +}; + +void simple_display(llvm::raw_ostream &out, BodyAndFingerprint value); + /// Base class for function-like declarations. class AbstractFunctionDecl : public GenericContext, public ValueDecl { friend class NeedsNewVTableEntryRequest; @@ -5986,7 +6018,7 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl { union { /// This enum member is active if getBodyKind() is BodyKind::Parsed or /// BodyKind::TypeChecked. - BraceStmt *Body; + BodyAndFingerprint BodyAndFP; /// This enum member is active if getBodyKind() is BodyKind::Deserialized. StringRef BodyStringRepresentation; @@ -6022,9 +6054,10 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl { bool Throws, SourceLoc ThrowsLoc, bool HasImplicitSelfDecl, GenericParamList *GenericParams) - : GenericContext(DeclContextKind::AbstractFunctionDecl, Parent, GenericParams), - ValueDecl(Kind, Parent, Name, NameLoc), - Body(nullptr), AsyncLoc(AsyncLoc), ThrowsLoc(ThrowsLoc) { + : GenericContext(DeclContextKind::AbstractFunctionDecl, Parent, + GenericParams), + ValueDecl(Kind, Parent, Name, NameLoc), BodyAndFP(), AsyncLoc(AsyncLoc), + ThrowsLoc(ThrowsLoc) { setBodyKind(BodyKind::None); Bits.AbstractFunctionDecl.HasImplicitSelfDecl = HasImplicitSelfDecl; Bits.AbstractFunctionDecl.Overridden = false; @@ -6195,8 +6228,9 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl { void setBodyToBeReparsed(SourceRange bodyRange); /// Provide the parsed body for the function. - void setBodyParsed(BraceStmt *S) { + void setBodyParsed(BraceStmt *S, Optional fp = None) { setBody(S, BodyKind::Parsed); + BodyAndFP.setFingerprint(fp); } /// Was there a nested type declaration detected when parsing this @@ -6286,6 +6320,15 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl { /// itself. void keepOriginalBodySourceRange(); + /// Retrieve the fingerprint of the body. Note that this is not affected by + /// the body of the local functions or the members of the local types in this + /// function. + Optional getBodyFingerprint() const; + + /// Retrieve the fingerprint of the body including the local type members and + /// the local funcition bodies. + Optional getBodyFingerprintIncludingLocalTypeMembers() const; + /// Retrieve the source range of the *original* function body. /// /// This may be different from \c getBodySourceRange() that returns the source diff --git a/include/swift/AST/DiagnosticsFrontend.def b/include/swift/AST/DiagnosticsFrontend.def index 57ecf4a988d93..42ec976a17a13 100644 --- a/include/swift/AST/DiagnosticsFrontend.def +++ b/include/swift/AST/DiagnosticsFrontend.def @@ -102,6 +102,8 @@ ERROR(error_immediate_mode_primary_file,none, "immediate mode is incompatible with -primary-file", ()) ERROR(error_missing_frontend_action,none, "no frontend action was selected", ()) +ERROR(error_unsupported_frontend_action, none, + "unsupported action: %0", (StringRef)) ERROR(error_invalid_source_location_str,none, "invalid source location string '%0'", (StringRef)) ERROR(error_no_source_location_scope_map,none, diff --git a/include/swift/AST/ParseRequests.h b/include/swift/AST/ParseRequests.h index 3e47fbfe25144..79979be713229 100644 --- a/include/swift/AST/ParseRequests.h +++ b/include/swift/AST/ParseRequests.h @@ -63,11 +63,10 @@ class ParseMembersRequest }; /// Parse the body of a function, initializer, or deinitializer. -class ParseAbstractFunctionBodyRequest : - public SimpleRequest -{ +class ParseAbstractFunctionBodyRequest + : public SimpleRequest { public: using SimpleRequest::SimpleRequest; @@ -75,13 +74,14 @@ class ParseAbstractFunctionBodyRequest : friend SimpleRequest; // Evaluation. - BraceStmt *evaluate(Evaluator &evaluator, AbstractFunctionDecl *afd) const; + BodyAndFingerprint evaluate(Evaluator &evaluator, + AbstractFunctionDecl *afd) const; public: // Caching bool isCached() const { return true; } - Optional getCachedResult() const; - void cacheResult(BraceStmt *value) const; + Optional getCachedResult() const; + void cacheResult(BodyAndFingerprint value) const; }; struct SourceFileParsingResult { diff --git a/include/swift/AST/ParseTypeIDZone.def b/include/swift/AST/ParseTypeIDZone.def index 36f95f7117bb7..c694ff8679d9b 100644 --- a/include/swift/AST/ParseTypeIDZone.def +++ b/include/swift/AST/ParseTypeIDZone.def @@ -20,7 +20,7 @@ SWIFT_REQUEST(Parse, CodeCompletionSecondPassRequest, SWIFT_REQUEST(Parse, ParseMembersRequest, FingerprintAndMembers(IterableDeclContext *), Cached, NoLocationInfo) SWIFT_REQUEST(Parse, ParseAbstractFunctionBodyRequest, - BraceStmt *(AbstractFunctionDecl *), SeparatelyCached, + BodyAndFingerprint(AbstractFunctionDecl *), SeparatelyCached, NoLocationInfo) SWIFT_REQUEST(Parse, ParseSourceFileRequest, SourceFileParsingResult(SourceFile *), SeparatelyCached, diff --git a/include/swift/Basic/SourceManager.h b/include/swift/Basic/SourceManager.h index ca5a8cf5a9cd0..498538ce32f0c 100644 --- a/include/swift/Basic/SourceManager.h +++ b/include/swift/Basic/SourceManager.h @@ -50,19 +50,10 @@ class SourceManager { /// to speed up stats. mutable llvm::DenseMap StatusCache; - struct ReplacedRangeType { - SourceRange Original; - SourceRange New; - ReplacedRangeType() {} - ReplacedRangeType(NoneType) {} - ReplacedRangeType(SourceRange Original, SourceRange New) - : Original(Original), New(New) { - assert(Original.isValid() && New.isValid()); - } - - explicit operator bool() const { return Original.isValid(); } - }; - ReplacedRangeType ReplacedRange; + /// Holds replaced ranges. Keys are orignal ranges, and values are new ranges + /// in different buffers. This is used for code completion and ASTContext + /// reusing compilation. + llvm::DenseMap ReplacedRanges; std::map VirtualFiles; mutable std::pair CachedVFile = {nullptr, nullptr}; @@ -109,8 +100,12 @@ class SourceManager { SourceLoc getCodeCompletionLoc() const; - const ReplacedRangeType &getReplacedRange() const { return ReplacedRange; } - void setReplacedRange(const ReplacedRangeType &val) { ReplacedRange = val; } + const llvm::DenseMap &getReplacedRanges() const { + return ReplacedRanges; + } + void setReplacedRange(SourceRange Orig, SourceRange New) { + ReplacedRanges[Orig] = New; + } /// Returns true if \c LHS is before \c RHS in the source buffer. bool isBeforeInBuffer(SourceLoc LHS, SourceLoc RHS) const { diff --git a/include/swift/Frontend/Frontend.h b/include/swift/Frontend/Frontend.h index 5130b49607224..7b86b1053ba67 100644 --- a/include/swift/Frontend/Frontend.h +++ b/include/swift/Frontend/Frontend.h @@ -53,6 +53,7 @@ namespace swift { +class FrontendObserver; class SerializedModuleLoaderBase; class MemoryBufferSerializedModuleLoader; class SILModule; @@ -663,6 +664,9 @@ class CompilerInstance { /// library, returning \c false if we should continue, i.e. no error. bool loadStdlibIfNeeded(); + /// If \p fn returns true, exits early and returns true. + bool forEachFileToTypeCheck(llvm::function_ref fn); + private: /// Compute the parsing options for a source file in the main module. SourceFile::ParsingOptions getSourceFileParsingOptions(bool forPrimary) const; @@ -676,8 +680,6 @@ class CompilerInstance { bool loadPartialModulesAndImplicitImports( ModuleDecl *mod, SmallVectorImpl &partialModules) const; - void forEachFileToTypeCheck(llvm::function_ref fn); - void finishTypeChecking(); public: diff --git a/include/swift/Frontend/FrontendOptions.h b/include/swift/Frontend/FrontendOptions.h index fc1a33247a43f..ea748d6550679 100644 --- a/include/swift/Frontend/FrontendOptions.h +++ b/include/swift/Frontend/FrontendOptions.h @@ -438,6 +438,10 @@ class FrontendOptions { /// Whether to include symbols with SPI information in the symbol graph. bool IncludeSPISymbolsInSymbolGraph = false; + /// Whether to reuse a frontend (i.e. compiler instance) for multiple + /// compiletions. This prevents ASTContext being freed. + bool ReuseFrontendForMutipleCompilations = false; + /// This is used to obfuscate the serialized search paths so we don't have /// to encode the actual paths into the .swiftmodule file. PathObfuscator serializedPathObfuscator; diff --git a/include/swift/FrontendTool/FrontendTool.h b/include/swift/FrontendTool/FrontendTool.h index 4ef98dda206ac..184e61969182b 100644 --- a/include/swift/FrontendTool/FrontendTool.h +++ b/include/swift/FrontendTool/FrontendTool.h @@ -78,6 +78,8 @@ int performFrontend(ArrayRef args, void *mainAddr, FrontendObserver *observer = nullptr); +bool performCompileStepsPostSema(CompilerInstance &Instance, int &ReturnValue, + FrontendObserver *observer); } // namespace swift diff --git a/include/swift/IDE/CompileInstance.h b/include/swift/IDE/CompileInstance.h new file mode 100644 index 0000000000000..fd4c260ae4654 --- /dev/null +++ b/include/swift/IDE/CompileInstance.h @@ -0,0 +1,83 @@ +//===--- CompileInstance.h ------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_IDE_COMPILEINSTANCE_H +#define SWIFT_IDE_COMPILEINSTANCE_H + +#include "swift/Frontend/Frontend.h" +#include "llvm/ADT/Hashing.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Chrono.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/VirtualFileSystem.h" + +namespace swift { + +class CompilerInstance; +class CompilerInvocation; +class DiagnosticConsumer; + +namespace ide { + +/// Manages \c CompilerInstance for completion like operations. +class CompileInstance { + const std::string &RuntimeResourcePath; + const std::string &DiagnosticDocumentationPath; + + struct Options { + unsigned MaxASTReuseCount = 100; + } Opts; + + std::mutex mtx; + + std::unique_ptr CI; + llvm::hash_code CachedArgHash; + std::atomic CachedCIInvalidated; + unsigned CachedReuseCount; + + /// Perform cached sema. Returns \c true if the CI is not reusable. + bool performCachedSemaIfPossible(DiagnosticConsumer *DiagC); + + /// Setup the CI with \p Args . Returns \c true if failed. + bool setupCI(llvm::ArrayRef Args, + llvm::IntrusiveRefCntPtr FileSystem, + DiagnosticConsumer *DiagC); + + /// Perform Parse and Sema, potentially CI from previous compilation is + /// reused. + void performSema(llvm::ArrayRef Args, + llvm::IntrusiveRefCntPtr FileSystem, + DiagnosticConsumer *DiagC, + std::shared_ptr> CancellationFlag); + +public: + CompileInstance(const std::string &RuntimeResourcePath, + const std::string &DiagnosticDocumentationPath) + : RuntimeResourcePath(RuntimeResourcePath), + DiagnosticDocumentationPath(DiagnosticDocumentationPath), + CachedCIInvalidated(false), CachedReuseCount(0) {} + + /// NOTE: \p Args is only used for checking the equaity of the invocation. + /// Since this function assumes that it is already normalized, exact the same + /// arguments including their order is considered as the same invocation. + bool + performCompile(llvm::ArrayRef Args, + llvm::IntrusiveRefCntPtr FileSystem, + DiagnosticConsumer *DiagC, + std::shared_ptr> CancellationFlag); +}; + +} // namespace ide +} // namespace swift + +#endif // SWIFT_IDE_COMPILEINSTANCE_H diff --git a/include/swift/Parse/Parser.h b/include/swift/Parse/Parser.h index eae8fd780cccd..def788321011a 100644 --- a/include/swift/Parse/Parser.h +++ b/include/swift/Parse/Parser.h @@ -1195,9 +1195,11 @@ class Parser { ParseDeclOptions Flags, DeclAttributes &Attributes, bool HasFuncKeyword = true); - BraceStmt *parseAbstractFunctionBodyImpl(AbstractFunctionDecl *AFD); + BodyAndFingerprint + parseAbstractFunctionBodyImpl(AbstractFunctionDecl *AFD); void parseAbstractFunctionBody(AbstractFunctionDecl *AFD); - BraceStmt *parseAbstractFunctionBodyDelayed(AbstractFunctionDecl *AFD); + BodyAndFingerprint + parseAbstractFunctionBodyDelayed(AbstractFunctionDecl *AFD); ParserResult parseDeclProtocol(ParseDeclOptions Flags, DeclAttributes &Attributes); diff --git a/lib/AST/ASTScopeLookup.cpp b/lib/AST/ASTScopeLookup.cpp index 1e0216e2dbc1f..1878a9c0f14ce 100644 --- a/lib/AST/ASTScopeLookup.cpp +++ b/lib/AST/ASTScopeLookup.cpp @@ -78,10 +78,10 @@ ASTScopeImpl *ASTScopeImpl::findInnermostEnclosingScopeImpl( static SourceLoc translateLocForReplacedRange(SourceManager &sourceMgr, CharSourceRange range, SourceLoc loc) { - if (const auto &replacedRange = sourceMgr.getReplacedRange()) { - if (sourceMgr.rangeContainsTokenLoc(replacedRange.New, loc) && - !sourceMgr.rangeContainsTokenLoc(replacedRange.New, range.getStart())) { - return replacedRange.Original.Start; + for (const auto &pair : sourceMgr.getReplacedRanges()) { + if (sourceMgr.rangeContainsTokenLoc(pair.second, loc) && + !sourceMgr.rangeContainsTokenLoc(pair.second, range.getStart())) { + return pair.first.Start; } } return loc; diff --git a/lib/AST/ASTScopeSourceRange.cpp b/lib/AST/ASTScopeSourceRange.cpp index f6919d24d1d80..805fb0e35c9ab 100644 --- a/lib/AST/ASTScopeSourceRange.cpp +++ b/lib/AST/ASTScopeSourceRange.cpp @@ -55,12 +55,11 @@ void ASTScopeImpl::checkSourceRangeBeforeAddingChild(ASTScopeImpl *child, bool childContainedInParent = [&]() { // HACK: For code completion. Handle replaced range. - if (const auto &replacedRange = sourceMgr.getReplacedRange()) { - auto originalRange = Lexer::getCharSourceRangeFromSourceRange( - sourceMgr, replacedRange.Original); - auto newRange = Lexer::getCharSourceRangeFromSourceRange( - sourceMgr, replacedRange.New); - + for (const auto &pair : sourceMgr.getReplacedRanges()) { + auto originalRange = + Lexer::getCharSourceRangeFromSourceRange(sourceMgr, pair.first); + auto newRange = + Lexer::getCharSourceRangeFromSourceRange(sourceMgr, pair.second); if (range.contains(originalRange) && newRange.contains(childCharRange)) return true; diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index b0b099cebb8ae..50765d0a1326a 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -7429,8 +7429,8 @@ BraceStmt *AbstractFunctionDecl::getBody(bool canSynthesize) const { auto mutableThis = const_cast(this); return evaluateOrDefault(ctx.evaluator, - ParseAbstractFunctionBodyRequest{mutableThis}, - nullptr); + ParseAbstractFunctionBodyRequest{mutableThis}, {}) + .getBody(); } BraceStmt *AbstractFunctionDecl::getTypecheckedBody() const { @@ -7444,7 +7444,12 @@ void AbstractFunctionDecl::setBody(BraceStmt *S, BodyKind NewBodyKind) { assert(getBodyKind() != BodyKind::Skipped && "cannot set a body if it was skipped"); - Body = S; + Optional fp = None; + if (getBodyKind() == BodyKind::TypeChecked || + getBodyKind() == BodyKind::Parsed) { + fp = BodyAndFP.getFingerprint(); + } + BodyAndFP = BodyAndFingerprint(S, fp); setBodyKind(NewBodyKind); // Need to recompute init body kind. @@ -7459,13 +7464,18 @@ void AbstractFunctionDecl::setBodyToBeReparsed(SourceRange bodyRange) { assert(getBodyKind() == BodyKind::Unparsed || getBodyKind() == BodyKind::Parsed || getBodyKind() == BodyKind::TypeChecked); - assert(getASTContext().SourceMgr.rangeContainsTokenLoc( - bodyRange, getASTContext().SourceMgr.getCodeCompletionLoc()) && - "This function is only intended to be used for code completion"); keepOriginalBodySourceRange(); BodyRange = bodyRange; setBodyKind(BodyKind::Unparsed); + + // Remove local type decls from the source file. + if (auto SF = getParentSourceFile()) { + SF->LocalTypeDecls.remove_if([&](TypeDecl *TD) { + auto dc = TD->getDeclContext(); + return dc == this || dc->isChildContextOf(this); + }); + } } SourceRange AbstractFunctionDecl::getBodySourceRange() const { @@ -7503,6 +7513,54 @@ SourceRange AbstractFunctionDecl::getSignatureSourceRange() const { return getNameLoc(); } +Optional AbstractFunctionDecl::getBodyFingerprint() const { + ASTContext &ctx = getASTContext(); + auto mutableThis = const_cast(this); + return evaluateOrDefault(ctx.evaluator, + ParseAbstractFunctionBodyRequest{mutableThis}, {}) + .getFingerprint(); +} + +Optional +AbstractFunctionDecl::getBodyFingerprintIncludingLocalTypeMembers() const { + + class HashCombiner : public ASTWalker { + StableHasher &hasher; + + public: + HashCombiner(StableHasher &hasher) : hasher(hasher) {} + + bool walkToDeclPre(Decl *D) override { + if (D->isImplicit()) + return false; + + if (auto *idc = dyn_cast(D)) { + if (auto fp = idc->getBodyFingerprint()) + hasher.combine(*fp); + + // Since ASTWalker calls 'getMembers()' which might tries to synthesize + // members etc., manually recurse into `getParsedMembers()`. + for (auto *d : idc->getParsedMembers()) + const_cast(d)->walk(*this); + + return false; + } + + if (auto *afd = dyn_cast(D)) { + if (auto fp = afd->getBodyFingerprint()) + hasher.combine(*fp); + } + + return true; + } + }; + + StableHasher hasher = StableHasher::defaultHasher(); + HashCombiner combiner(hasher); + const_cast(this)->walk(combiner); + return Fingerprint(std::move(hasher)); +} + ObjCSelector AbstractFunctionDecl::getObjCSelector(DeclName preferredName, bool skipIsObjCResolution) const { @@ -8850,7 +8908,7 @@ SourceLoc swift::extractNearestSourceLoc(const Decl *decl) { return extractNearestSourceLoc(decl->getDeclContext()); } -Optional +Optional ParseAbstractFunctionBodyRequest::getCachedResult() const { using BodyKind = AbstractFunctionDecl::BodyKind; auto afd = std::get<0>(getStorage()); @@ -8859,11 +8917,11 @@ ParseAbstractFunctionBodyRequest::getCachedResult() const { case BodyKind::SILSynthesize: case BodyKind::None: case BodyKind::Skipped: - return nullptr; + return BodyAndFingerprint{}; case BodyKind::TypeChecked: case BodyKind::Parsed: - return afd->Body; + return afd->BodyAndFP; case BodyKind::Synthesize: case BodyKind::Unparsed: @@ -8872,7 +8930,8 @@ ParseAbstractFunctionBodyRequest::getCachedResult() const { llvm_unreachable("Unhandled BodyKing in switch"); } -void ParseAbstractFunctionBodyRequest::cacheResult(BraceStmt *value) const { +void ParseAbstractFunctionBodyRequest::cacheResult( + BodyAndFingerprint value) const { using BodyKind = AbstractFunctionDecl::BodyKind; auto afd = std::get<0>(getStorage()); switch (afd->getBodyKind()) { @@ -8881,12 +8940,12 @@ void ParseAbstractFunctionBodyRequest::cacheResult(BraceStmt *value) const { case BodyKind::None: case BodyKind::Skipped: // The body is always empty, so don't cache anything. - assert(value == nullptr); + assert(!value.getFingerprint().hasValue() && value.getBody() == nullptr); return; case BodyKind::Parsed: case BodyKind::TypeChecked: - afd->Body = value; + afd->BodyAndFP = value; return; case BodyKind::Synthesize: @@ -8894,7 +8953,14 @@ void ParseAbstractFunctionBodyRequest::cacheResult(BraceStmt *value) const { llvm_unreachable("evaluate() did not set the body kind"); return; } +} +void swift::simple_display(llvm::raw_ostream &out, BodyAndFingerprint value) { + out << "("; + simple_display(out, value.getBody()); + out << ", "; + simple_display(out, value.getFingerprint()); + out << ")"; } void swift::simple_display(llvm::raw_ostream &out, AnyFunctionRef fn) { diff --git a/lib/AST/TypeCheckRequests.cpp b/lib/AST/TypeCheckRequests.cpp index e9674bee0ac1e..ba089793d70a8 100644 --- a/lib/AST/TypeCheckRequests.cpp +++ b/lib/AST/TypeCheckRequests.cpp @@ -1347,7 +1347,7 @@ Optional TypeCheckFunctionBodyRequest::getCachedResult() const { return nullptr; case BodyKind::TypeChecked: - return afd->Body; + return afd->BodyAndFP.getBody(); case BodyKind::Synthesize: case BodyKind::Parsed: diff --git a/lib/Driver/FrontendUtil.cpp b/lib/Driver/FrontendUtil.cpp index df74a502e38d7..aeb35d3238bba 100644 --- a/lib/Driver/FrontendUtil.cpp +++ b/lib/Driver/FrontendUtil.cpp @@ -106,13 +106,16 @@ bool swift::driver::getSingleFrontendInvocationFromDriverArguments( return true; // Don't emit an error; one should already have been emitted SmallPtrSet CompileCommands; - for (const Job *Cmd : C->getJobs()) - if (isa(Cmd->getSource())) + for (const Job *Cmd : C->getJobs()) { + if (isa(Cmd->getSource())) { CompileCommands.insert(Cmd); + } + } if (CompileCommands.size() != 1) { // TODO: include Jobs in the diagnostic. Diags.diagnose(SourceLoc(), diag::error_expected_one_frontend_job); + return true; } const Job *Cmd = *CompileCommands.begin(); diff --git a/lib/Frontend/Frontend.cpp b/lib/Frontend/Frontend.cpp index 949b248e4582d..e4eb3eb19049b 100644 --- a/lib/Frontend/Frontend.cpp +++ b/lib/Frontend/Frontend.cpp @@ -1114,6 +1114,7 @@ void CompilerInstance::performSema() { forEachFileToTypeCheck([&](SourceFile &SF) { performTypeChecking(SF); + return false; }); finishTypeChecking(); @@ -1178,26 +1179,31 @@ bool CompilerInstance::loadPartialModulesAndImplicitImports( return hadLoadError; } -void CompilerInstance::forEachFileToTypeCheck( - llvm::function_ref fn) { +bool CompilerInstance::forEachFileToTypeCheck( + llvm::function_ref fn) { if (isWholeModuleCompilation()) { for (auto fileName : getMainModule()->getFiles()) { auto *SF = dyn_cast(fileName); if (!SF) { continue; } - fn(*SF); + if (fn(*SF)) + return true; + ; } } else { for (auto *SF : getPrimarySourceFiles()) { - fn(*SF); + if (fn(*SF)) + return true; } } + return false; } void CompilerInstance::finishTypeChecking() { forEachFileToTypeCheck([](SourceFile &SF) { performWholeModuleTypeChecking(SF); + return false; }); } @@ -1237,7 +1243,9 @@ CompilerInstance::getSourceFileParsingOptions(bool forPrimary) const { // Enable interface hash computation for primaries or emit-module-separately, // but not in WMO, as it's only currently needed for incremental mode. if (forPrimary || - typeOpts.SkipFunctionBodies == FunctionBodySkipping::NonInlinableWithoutTypes) { + typeOpts.SkipFunctionBodies == + FunctionBodySkipping::NonInlinableWithoutTypes || + frontendOpts.ReuseFrontendForMutipleCompilations) { opts |= SourceFile::ParsingFlags::EnableInterfaceHash; } return opts; diff --git a/lib/FrontendTool/FrontendTool.cpp b/lib/FrontendTool/FrontendTool.cpp index 8abbb180fe57b..1f630b7b69b0e 100644 --- a/lib/FrontendTool/FrontendTool.cpp +++ b/lib/FrontendTool/FrontendTool.cpp @@ -710,7 +710,7 @@ static bool performCompileStepsPostSILGen(CompilerInstance &Instance, int &ReturnValue, FrontendObserver *observer); -static bool performCompileStepsPostSema(CompilerInstance &Instance, +bool swift::performCompileStepsPostSema(CompilerInstance &Instance, int &ReturnValue, FrontendObserver *observer) { const auto &Invocation = Instance.getInvocation(); @@ -1440,6 +1440,14 @@ static void freeASTContextIfPossible(CompilerInstance &Instance) { return; } + // If this instance is used for multiple compilations, we need the ASTContext + // to live. + if (Instance.getInvocation() + .getFrontendOptions() + .ReuseFrontendForMutipleCompilations) { + return; + } + const auto &opts = Instance.getInvocation().getFrontendOptions(); // If there are multiple primary inputs it is too soon to free diff --git a/lib/IDE/CMakeLists.txt b/lib/IDE/CMakeLists.txt index 22461cf17d4af..f5a38459552f6 100644 --- a/lib/IDE/CMakeLists.txt +++ b/lib/IDE/CMakeLists.txt @@ -5,6 +5,7 @@ add_swift_host_library(swiftIDE STATIC CodeCompletionDiagnostics.cpp CodeCompletionResultPrinter.cpp CommentConversion.cpp + CompileInstance.cpp CompletionInstance.cpp ConformingMethodList.cpp ExprContextAnalysis.cpp @@ -26,6 +27,7 @@ target_link_libraries(swiftIDE PRIVATE swiftClangImporter swiftDriver swiftFrontend + swiftFrontendTool swiftIndex swiftParse swiftSema) diff --git a/lib/IDE/CompileInstance.cpp b/lib/IDE/CompileInstance.cpp new file mode 100644 index 0000000000000..8e382d19852cb --- /dev/null +++ b/lib/IDE/CompileInstance.cpp @@ -0,0 +1,332 @@ +//===--- CompileInstance.cpp ----------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2021 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 "swift/IDE/CompileInstance.h" + +#include "swift/AST/ASTContext.h" +#include "swift/AST/DiagnosticEngine.h" +#include "swift/AST/Module.h" +#include "swift/AST/PrettyStackTrace.h" +#include "swift/AST/SourceFile.h" +#include "swift/Basic/Defer.h" +#include "swift/Basic/LangOptions.h" +#include "swift/Basic/PrettyStackTrace.h" +#include "swift/Basic/SourceManager.h" +#include "swift/Driver/FrontendUtil.h" +#include "swift/Frontend/Frontend.h" +#include "swift/FrontendTool/FrontendTool.h" +#include "swift/IDE/Utils.h" +#include "swift/Parse/Lexer.h" +#include "swift/Parse/PersistentParserState.h" +#include "swift/Subsystems.h" +#include "swift/SymbolGraphGen/SymbolGraphOptions.h" +#include "clang/AST/ASTContext.h" +#include "llvm/ADT/Hashing.h" +#include "llvm/Support/MemoryBuffer.h" + +using namespace swift; +using namespace swift::ide; + +// Interface fingerprint check and modified function body collection. +namespace { + +/// Information of modified function body. +struct ModInfo { + /// Function decl in the *original* AST. + AbstractFunctionDecl *FD; + /// Range of the body in the *new* source file buffer. + SourceRange NewSourceRange; + + ModInfo(AbstractFunctionDecl *FD, const SourceRange &NewSourceRange) + : FD(FD), NewSourceRange(NewSourceRange) {} +}; + +static bool collectModifiedFunctions(ArrayRef r1, ArrayRef r2, + llvm::SmallVectorImpl &result) { + assert(r1.size() == r2.size() && + "interface fingerprint matches but diffrent number of children"); + + for (auto i1 = r1.begin(), i2 = r2.begin(), e1 = r1.end(), e2 = r2.end(); + i1 != e1 && i2 != e2; ++i1, ++i2) { + auto &d1 = *i1, &d2 = *i2; + + assert(d1->getKind() == d2->getKind() && + "interface fingerprint matches but diffrent structure"); + + /// FIXME: Nested types. + /// func foo() { + /// struct S { + /// func bar() { ... } + /// } + /// } + /// * Could editing a local-type interface lead to the need for + /// retypechecking other functions? + /// * Can we retypecheck only a function in local types? If only 'bar()' + /// body have changed, we want to only retypecheck 'bar()'. + auto *f1 = dyn_cast(d1); + auto *f2 = dyn_cast(d2); + if (f1 && f2) { + auto fp1 = f1->getBodyFingerprintIncludingLocalTypeMembers(); + auto fp2 = f2->getBodyFingerprintIncludingLocalTypeMembers(); + if (fp1 != fp2) { + // The fingerprint of the body has changed. Record it. + result.emplace_back(f1, f2->getBodySourceRange()); + } + continue; + } + + auto *idc1 = dyn_cast(d1); + auto *idc2 = dyn_cast(d2); + if (idc1 && idc2) { + if (idc1->getBodyFingerprint() != idc2->getBodyFingerprint()) { + // The fingerprint of the interface has changed. We can't reuse this. + return true; + } + + // Recurse into the child IDC members. + if (collectModifiedFunctions(idc1->getParsedMembers(), + idc2->getParsedMembers(), result)) { + return true; + } + } + } + return false; +} + +/// Collect functions in \p SF with modified bodies into \p result . +/// \p tmpSM is used for managing source buffers for new source files. Source +/// range for collected modified function body info is managed by \tmpSM. +/// \p tmpSM must be different from the source manager of \p SF . +static bool +getModifiedFunctionDeclList(const SourceFile &SF, SourceManager &tmpSM, + llvm::SmallVectorImpl &result) { + auto &ctx = SF.getASTContext(); + + auto tmpBuffer = tmpSM.getFileSystem()->getBufferForFile(SF.getFilename()); + if (!tmpBuffer) { + // The file is deleted? + return true; + } + + // Parse the new buffer into temporary SourceFile. + + LangOptions langOpts = ctx.LangOpts; + TypeCheckerOptions typeckOpts = ctx.TypeCheckerOpts; + SearchPathOptions searchPathOpts = ctx.SearchPathOpts; + ClangImporterOptions clangOpts = ctx.ClangImporterOpts; + SILOptions silOpts = ctx.SILOpts; + symbolgraphgen::SymbolGraphOptions symbolOpts = ctx.SymbolGraphOpts; + + DiagnosticEngine tmpDiags(tmpSM); + auto &tmpCtx = *ASTContext::get(langOpts, typeckOpts, silOpts, searchPathOpts, + clangOpts, symbolOpts, tmpSM, tmpDiags); + registerParseRequestFunctions(tmpCtx.evaluator); + registerTypeCheckerRequestFunctions(tmpCtx.evaluator); + + ModuleDecl *tmpM = ModuleDecl::create(Identifier(), tmpCtx); + auto tmpBufferID = tmpSM.addNewSourceBuffer(std::move(*tmpBuffer)); + SourceFile *tmpSF = new (tmpCtx) + SourceFile(*tmpM, SF.Kind, tmpBufferID, SF.getParsingOptions()); + + // If the top-level code has been changed, we can't do anything. + if (SF.getInterfaceHash() != tmpSF->getInterfaceHash()) + return true; + + return collectModifiedFunctions(SF.getTopLevelDecls(), + tmpSF->getTopLevelDecls(), result); +} + +/// Typecheck the body of \p func with the new source text specified with +/// \p newBodyRange managed by \p newSM . +/// +/// This copies the source text of \p newBodyRange to the source manger +/// \p func originally parsed. +void retypeCheckFunctionBody(AbstractFunctionDecl *func, + SourceRange newBodyRange, SourceManager &newSM) { + + // To save the persistent memory in the source manager, add the sliced range + // of the new function body to the source manager. + // NOTE: Using 'getLocForStartOfLine' is to get the correct column value for + // diagnostics on the first line. + auto tmpBufferID = newSM.findBufferContainingLoc(newBodyRange.Start); + auto bufStartLoc = Lexer::getLocForStartOfLine(newSM, newBodyRange.Start); + auto bufEndLoc = Lexer::getLocForEndOfToken(newSM, newBodyRange.End); + + auto bufStartOffset = newSM.getLocOffsetInBuffer(bufStartLoc, tmpBufferID); + auto bufEndOffset = newSM.getLocOffsetInBuffer(bufEndLoc, tmpBufferID); + + auto slicedSourceText = newSM.getEntireTextForBuffer(tmpBufferID) + .slice(bufStartOffset, bufEndOffset); + + auto &origSM = func->getASTContext().SourceMgr; + + auto sliceBufferID = origSM.addMemBufferCopy( + slicedSourceText, newSM.getIdentifierForBuffer(tmpBufferID)); + origSM.openVirtualFile( + origSM.getLocForBufferStart(sliceBufferID), + newSM.getDisplayNameForLoc(bufStartLoc), + newSM.getPresumedLineAndColumnForLoc(bufStartLoc).first - 1); + + // Calculate the body range in the sliced source buffer. + auto rangeStartOffset = + newSM.getLocOffsetInBuffer(newBodyRange.Start, tmpBufferID); + auto rangeEndOffset = + newSM.getLocOffsetInBuffer(newBodyRange.End, tmpBufferID); + auto rangeStartLoc = + origSM.getLocForOffset(sliceBufferID, rangeStartOffset - bufStartOffset); + auto rangeEndLoc = + origSM.getLocForOffset(sliceBufferID, rangeEndOffset - bufStartOffset); + SourceRange newRange{rangeStartLoc, rangeEndLoc}; + + // Reset the body range of the function decl, and re-typecheck it. + origSM.setReplacedRange(func->getOriginalBodySourceRange(), newRange); + func->setBodyToBeReparsed(newRange); + (void)func->getTypecheckedBody(); +} + +} // namespace + +bool CompileInstance::performCachedSemaIfPossible(DiagnosticConsumer *DiagC) { + + // Currently, only '-c' (aka. '-emit-object') action is supported. + assert(CI->getInvocation().getFrontendOptions().RequestedAction == + FrontendOptions::ActionType::EmitObject && + "Unsupported action; only 'EmitObject' is supported"); + + SourceManager &SM = CI->getSourceMgr(); + auto FS = SM.getFileSystem(); + SourceManager tmpSM(FS); + + // Collect modified function body. + SmallVector modifiedFuncDecls; + bool isNotResuable = CI->forEachFileToTypeCheck([&](SourceFile &oldSF) { + return getModifiedFunctionDeclList(oldSF, tmpSM, modifiedFuncDecls); + }); + if (isNotResuable) + return true; + + // OK, we can reuse the AST. + + CI->addDiagnosticConsumer(DiagC); + SWIFT_DEFER { CI->removeDiagnosticConsumer(DiagC); }; + + for (const auto &info : modifiedFuncDecls) { + retypeCheckFunctionBody(info.FD, info.NewSourceRange, tmpSM); + } + + return false; +} + +bool CompileInstance::setupCI( + llvm::ArrayRef origArgs, + llvm::IntrusiveRefCntPtr fileSystem, + DiagnosticConsumer *diagC) { + CI->addDiagnosticConsumer(diagC); + SWIFT_DEFER { CI->removeDiagnosticConsumer(diagC); }; + + auto &Diags = CI->getDiags(); + + SmallVector args; + // Put '-resource-dir' and '-diagnostic-documentation-path' at the top to + // allow overriding them with the passed in arguments. + args.append({"-resource-dir", RuntimeResourcePath.c_str()}); + args.append({"-Xfrontend", "-diagnostic-documentation-path", "-Xfrontend", + DiagnosticDocumentationPath.c_str()}); + args.append(origArgs.begin(), origArgs.end()); + + CompilerInvocation invocation; + bool invocationCreationFailed = + driver::getSingleFrontendInvocationFromDriverArguments( + args, Diags, + [&](ArrayRef FrontendArgs) { + return invocation.parseArgs(FrontendArgs, Diags); + }, + /*ForceNoOutputs=*/false); + if (invocationCreationFailed) { + assert(Diags.hadAnyError()); + return false; + } + + if (invocation.getFrontendOptions().RequestedAction != + FrontendOptions::ActionType::EmitObject) { + Diags.diagnose(SourceLoc(), diag::not_implemented, + "only -c (aka. -emit-object) action is supported"); + return false; + } + + /// Declare the frontend to be used for multiple compilations. + invocation.getFrontendOptions().ReuseFrontendForMutipleCompilations = true; + + std::string InstanceSetupError; + if (CI->setup(invocation, InstanceSetupError)) { + assert(Diags.hadAnyError()); + return false; + } + + return true; +} + +void CompileInstance::performSema( + llvm::ArrayRef Args, + llvm::IntrusiveRefCntPtr fileSystem, + DiagnosticConsumer *DiagC, + std::shared_ptr> CancellationFlag) { + + // Compute the signature of the invocation. + llvm::hash_code ArgsHash(0); + for (auto arg : Args) + ArgsHash = llvm::hash_combine(ArgsHash, StringRef(arg)); + + if (CI && ArgsHash == CachedArgHash && + CachedReuseCount < Opts.MaxASTReuseCount) { + CI->getASTContext().CancellationFlag = CancellationFlag; + if (performCachedSemaIfPossible(DiagC)) { + // If we compileted cacehd Sema operation. We're done. + ++CachedReuseCount; + return; + } + } + + // Performing a new operation. Reset the compiler instance. + CachedArgHash = hash_code(); + CachedReuseCount = 0; + CI = std::make_unique(); + + if (!setupCI(Args, fileSystem, DiagC)) { + // Failed to setup the CI. + CI.reset(); + return; + } + + // CI is potentially reusable. + CachedArgHash = ArgsHash; + + // Perform! + CI->getASTContext().CancellationFlag = CancellationFlag; + CI->performSema(); +} + +bool CompileInstance::performCompile( + llvm::ArrayRef Args, + llvm::IntrusiveRefCntPtr fileSystem, + DiagnosticConsumer *DiagC, + std::shared_ptr> CancellationFlag) { + + performSema(Args, fileSystem, DiagC, CancellationFlag); + if (CI->getDiags().hadAnyError()) + return true; + + // TODO: Cancellation check. + + int ReturnValue = 0; + return performCompileStepsPostSema(*CI, ReturnValue, /*observer=*/nullptr); +} diff --git a/lib/IDE/CompletionInstance.cpp b/lib/IDE/CompletionInstance.cpp index d100ff952d87e..0755ed7d5c690 100644 --- a/lib/IDE/CompletionInstance.cpp +++ b/lib/IDE/CompletionInstance.cpp @@ -438,8 +438,8 @@ bool CompletionInstance::performCachedOperationIfPossible( newBufferStart.getAdvancedLoc(newInfo.EndOffset)); auto *AFD = cast(DC); + SM.setReplacedRange(AFD->getOriginalBodySourceRange(), newBodyRange); AFD->setBodyToBeReparsed(newBodyRange); - SM.setReplacedRange({AFD->getOriginalBodySourceRange(), newBodyRange}); oldSF->clearScope(); traceDC = AFD; diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index d653b788bef4a..937d4df864546 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -7167,7 +7167,8 @@ ParserResult Parser::parseDeclFunc(SourceLoc StaticLoc, /// Parse a function body for \p AFD, setting the body to \p AFD before /// returning it. -BraceStmt *Parser::parseAbstractFunctionBodyImpl(AbstractFunctionDecl *AFD) { +BodyAndFingerprint +Parser::parseAbstractFunctionBodyImpl(AbstractFunctionDecl *AFD) { assert(Tok.is(tok::l_brace)); // Establish the new context. @@ -7191,18 +7192,25 @@ BraceStmt *Parser::parseAbstractFunctionBodyImpl(AbstractFunctionDecl *AFD) { auto *BS = BraceStmt::create(Context, LBraceLoc, ASTNode(CCE), RBraceLoc, /*implicit*/ true); AFD->setBodyParsed(BS); - return BS; + return {BS, Fingerprint::ZERO()}; } } + llvm::SaveAndRestore> T(CurrentTokenHash, + StableHasher::defaultHasher()); + ParserResult Body = parseBraceItemList(diag::invalid_diagnostic); - if (Body.isNull()) - return nullptr; + // Since we know 'Tok.is(tok::l_brace)', the body can't be null. + assert(Body.isNonNull()); + + // Clone the current hasher and extract a Fingerprint. + StableHasher currentHash{*CurrentTokenHash}; + Fingerprint fp(std::move(currentHash)); BraceStmt *BS = Body.get(); // Reset the single expression body status. AFD->setHasSingleExpressionBody(false); - AFD->setBodyParsed(BS); + AFD->setBodyParsed(BS, fp); if (Parser::shouldReturnSingleExpressionElement(BS->getElements())) { auto Element = BS->getLastElement(); @@ -7225,7 +7233,7 @@ BraceStmt *Parser::parseAbstractFunctionBodyImpl(AbstractFunctionDecl *AFD) { if (SE->getNumElements() > 1 && isa(SE->getElement(1))) { // This is an assignment. We don't want to implicitly return // it. - return BS; + return {BS, fp}; } } if (isa(AFD)) { @@ -7246,7 +7254,7 @@ BraceStmt *Parser::parseAbstractFunctionBodyImpl(AbstractFunctionDecl *AFD) { } } - return BS; + return {BS, fp}; } /// Parse function body into \p AFD or skip it for delayed parsing. @@ -7273,7 +7281,8 @@ void Parser::parseAbstractFunctionBody(AbstractFunctionDecl *AFD) { (void)parseAbstractFunctionBodyImpl(AFD); } -BraceStmt *Parser::parseAbstractFunctionBodyDelayed(AbstractFunctionDecl *AFD) { +BodyAndFingerprint +Parser::parseAbstractFunctionBodyDelayed(AbstractFunctionDecl *AFD) { assert(AFD->getBodyKind() == AbstractFunctionDecl::BodyKind::Unparsed && "function body should be delayed"); diff --git a/lib/Parse/ParseRequests.cpp b/lib/Parse/ParseRequests.cpp index 8fbc368e579ac..f99a4d1058ee1 100644 --- a/lib/Parse/ParseRequests.cpp +++ b/lib/Parse/ParseRequests.cpp @@ -87,8 +87,9 @@ ParseMembersRequest::evaluate(Evaluator &evaluator, ctx.AllocateCopy(llvm::makeArrayRef(fingerprintAndMembers.members))}; } -BraceStmt *ParseAbstractFunctionBodyRequest::evaluate( - Evaluator &evaluator, AbstractFunctionDecl *afd) const { +BodyAndFingerprint +ParseAbstractFunctionBodyRequest::evaluate(Evaluator &evaluator, + AbstractFunctionDecl *afd) const { using BodyKind = AbstractFunctionDecl::BodyKind; switch (afd->getBodyKind()) { @@ -96,11 +97,11 @@ BraceStmt *ParseAbstractFunctionBodyRequest::evaluate( case BodyKind::SILSynthesize: case BodyKind::None: case BodyKind::Skipped: - return nullptr; + return {}; case BodyKind::TypeChecked: case BodyKind::Parsed: - return afd->Body; + return afd->BodyAndFP; case BodyKind::Synthesize: { BraceStmt *body; @@ -110,19 +111,20 @@ BraceStmt *ParseAbstractFunctionBodyRequest::evaluate( afd, afd->Synthesizer.Context); assert(body && "cannot synthesize a null body"); afd->setBodyKind(isTypeChecked ? BodyKind::TypeChecked : BodyKind::Parsed); - return body; + return {body, Fingerprint::ZERO()}; } case BodyKind::Unparsed: { // FIXME: How do we configure code completion? SourceFile &sf = *afd->getDeclContext()->getParentSourceFile(); SourceManager &sourceMgr = sf.getASTContext().SourceMgr; - unsigned bufferID = sourceMgr.findBufferContainingLoc(afd->getLoc()); + unsigned bufferID = + sourceMgr.findBufferContainingLoc(afd->getBodySourceRange().Start); Parser parser(bufferID, sf, /*SIL*/ nullptr); parser.SyntaxContext->disable(); - auto body = parser.parseAbstractFunctionBodyDelayed(afd); + auto result = parser.parseAbstractFunctionBodyDelayed(afd); afd->setBodyKind(BodyKind::Parsed); - return body; + return result; } } llvm_unreachable("Unhandled BodyKind in switch"); diff --git a/lib/Parse/ParseStmt.cpp b/lib/Parse/ParseStmt.cpp index afa48feb8faf2..e8be43be76697 100644 --- a/lib/Parse/ParseStmt.cpp +++ b/lib/Parse/ParseStmt.cpp @@ -948,13 +948,19 @@ ParserResult Parser::parseStmtDefer() { // Change the DeclContext for any variables declared in the defer to be within // the defer closure. ParseFunctionBody cc(*this, tempDecl); - + llvm::SaveAndRestore> T( + CurrentTokenHash, StableHasher::defaultHasher()); + ParserResult Body = parseBraceItemList(diag::expected_lbrace_after_defer); if (Body.isNull()) return nullptr; Status |= Body; - tempDecl->setBodyParsed(Body.get()); + + // Clone the current hasher and extract a Fingerprint. + StableHasher currentHash{*CurrentTokenHash}; + Fingerprint fp(std::move(currentHash)); + tempDecl->setBodyParsed(Body.get(), fp); } SourceLoc loc = tempDecl->getBodySourceRange().Start; diff --git a/test/SourceKit/Compile/basic.swift b/test/SourceKit/Compile/basic.swift new file mode 100644 index 0000000000000..b087fdcb74be3 --- /dev/null +++ b/test/SourceKit/Compile/basic.swift @@ -0,0 +1,61 @@ +// RUN: %empty-directory(%t) +// RUN: %empty-directory(%t/out) +// RUN: split-file %s %t + +// RUN: %sourcekitd-test \ +// RUN: -shell -- echo "# start" == \ +// RUN: -req=compile -name c1 -- -c %t/t1.swift -module-name TestModule -o %t/out/test.o == \ +// RUN: -shell -- cp %t/t1.2.swift %t/t1.swift == \ +// RUN: -shell -- echo "# modified" == \ +// RUN: -req=compile -name c1 -- -c %t/t1.swift -module-name TestModule -o %t/out/test.o == \ +// RUN: -shell -- echo "# close" == \ +// RUN: -req=compile.close -name c1 \ +// RUN: | %FileCheck %s + +// CHECK-LABEL: # start +// CHECK-NEXT: { +// CHECK-NEXT: key.diagnostics: [ +// CHECK-NEXT: ], +// CHECK-NEXT: key.value: 0 +// CHECK-NEXT: } + +// CHECK-LABEL: # modified +// CHECK-NEXT: { +// CHECK-NEXT: key.diagnostics: [ +// CHECK-NEXT: { +// CHECK-NEXT: key.line: 6, +// CHECK-NEXT: key.column: 3, +// CHECK-NEXT: key.filepath: "{{.*[/\\]}}t1.swift", +// CHECK-NEXT: key.severity: source.diagnostic.severity.error, +// CHECK-NEXT: key.id: "cannot_find_in_scope", +// CHECK-NEXT: key.description: "cannot find 'foobar' in scope", +// CHECK-NEXT: key.ranges: [ +// CHECK-NEXT: { +// CHECK-NEXT: key.offset: 105, +// CHECK-NEXT: key.length: 6 +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: key.value: 1 +// CHECK-NEXT: } + +// CHECK-LABEL: # close +// CHECK-NEXT: { +// CHECK-NEXT: } + +//--- t1.swift +public func test1() { + struct English {} + print("This is just a test") +} + +//--- t1.2.swift +/// Adding comment. +public func test1() { + print("This is a test refined\(1 + 20)"); + struct Spanish { } + print("test") + foobar +} + diff --git a/tools/SourceKit/include/SourceKit/Core/LangSupport.h b/tools/SourceKit/include/SourceKit/Core/LangSupport.h index 42340687385f8..4395437705dcb 100644 --- a/tools/SourceKit/include/SourceKit/Core/LangSupport.h +++ b/tools/SourceKit/include/SourceKit/Core/LangSupport.h @@ -732,6 +732,11 @@ class ConformingMethodListConsumer { virtual void cancelled() = 0; }; +struct CompilationResult { + unsigned int ResultStatus; + llvm::ArrayRef Diagnostics; +}; + class LangSupport { virtual void anchor(); @@ -938,6 +943,15 @@ class LangSupport { ConformingMethodListConsumer &Consumer, Optional vfsOptions) = 0; + virtual void + performCompile(StringRef Name, ArrayRef Args, + Optional vfsOptions, + SourceKitCancellationToken CancellationToken, + std::function &)> + Receiver) = 0; + + virtual void closeCompile(StringRef Name) = 0; + virtual void getStatistics(StatisticsReceiver) = 0; }; } // namespace SourceKit diff --git a/tools/SourceKit/lib/SwiftLang/CMakeLists.txt b/tools/SourceKit/lib/SwiftLang/CMakeLists.txt index 4e43d38b7db18..f8a7c45a2cd02 100644 --- a/tools/SourceKit/lib/SwiftLang/CMakeLists.txt +++ b/tools/SourceKit/lib/SwiftLang/CMakeLists.txt @@ -1,6 +1,7 @@ add_sourcekit_library(SourceKitSwiftLang CodeCompletionOrganizer.cpp SwiftASTManager.cpp + SwiftCompile.cpp SwiftCompletion.cpp SwiftConformingMethodList.cpp SwiftDocSupport.cpp diff --git a/tools/SourceKit/lib/SwiftLang/SwiftCompile.cpp b/tools/SourceKit/lib/SwiftLang/SwiftCompile.cpp new file mode 100644 index 0000000000000..5de3dd5c932dc --- /dev/null +++ b/tools/SourceKit/lib/SwiftLang/SwiftCompile.cpp @@ -0,0 +1,150 @@ +//===--- SwiftCompile.cpp -------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2021 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 "SwiftEditorDiagConsumer.h" +#include "SwiftLangSupport.h" + +#include "SourceKit/Support/FileSystemProvider.h" + +#include "swift/IDE/CompileInstance.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/MemoryBuffer.h" + +using namespace SourceKit; +using namespace swift; + +std::shared_ptr +compile::SessionManager::getSession(StringRef name) { + llvm::sys::ScopedLock lock(mtx); + auto i = sessions.find(name); + if (i != sessions.end()) { + return i->second; + } + + bool inserted = false; + std::tie(i, inserted) = sessions.try_emplace(name, std::make_shared(RuntimeResourcePath, DiagnosticDocumentationPath)); + assert(inserted); + return i->second; +} + +void compile::SessionManager::clearSession(StringRef name) { + llvm::sys::ScopedLock lock(mtx); + sessions.erase(name); +} + +namespace { +class InvocationRequest final + : public llvm::TrailingObjects { + friend class llvm::TrailingObjects; + size_t numArgs; + + size_t numTrailingObjects(OverloadToken) const { return numArgs; } + + MutableArrayRef getMutableArgs() { + return {getTrailingObjects(), numArgs}; + } + + InvocationRequest(ArrayRef Args) : numArgs(Args.size()) { + // Copy the arguments to the buffer. + char *ptr = getTrailingObjects(); + auto thisArgs = getMutableArgs(); + size_t i = 0; + for (const char *arg : Args) { + thisArgs[i++] = ptr; + auto size = strlen(arg) + 1; + strncpy(ptr, arg, size); + ptr += size; + } + } + +public: + static InvocationRequest *create(ArrayRef Args) { + size_t charBufSize = 0; + for (auto arg : Args) { + charBufSize += strlen(arg) + 1; + } + auto size = InvocationRequest::totalSizeToAlloc(Args.size(), + charBufSize); + auto *data = malloc(size); + return new (data) InvocationRequest(Args); + } + + ArrayRef getArgs() const { + return {getTrailingObjects(), numArgs}; + } +}; +} // namespace + +void compile::SessionManager::performCompileAsync( + StringRef Name, ArrayRef Args, + llvm::IntrusiveRefCntPtr fileSystem, + std::shared_ptr> CancellationFlag, + std::function &)> Receiver) { + auto session = getSession(Name); + + auto *request = InvocationRequest::create(Args); + compileQueue.dispatch( + [session, request, fileSystem, Receiver, CancellationFlag]() { + SWIFT_DEFER { + delete request; + }; + + // Cancelled during async dispatching? + if (CancellationFlag->load(std::memory_order_relaxed)) { + Receiver(RequestResult::cancelled()); + return; + } + + EditorDiagConsumer diagC; + auto stat = + session->performCompile(request->getArgs(), fileSystem, &diagC, + CancellationFlag); + + // Cancelled during the compilation? + if (CancellationFlag->load(std::memory_order_relaxed)) { + Receiver(RequestResult::cancelled()); + return; + } + + SmallVector diagEntryInfos; + diagC.getAllDiagnostics(diagEntryInfos); + Receiver(RequestResult::fromResult( + {stat, diagEntryInfos})); + }, + /*isStackDeep=*/true); +} + +void SwiftLangSupport::performCompile( + StringRef Name, ArrayRef Args, + Optional vfsOptions, + SourceKitCancellationToken CancellationToken, + std::function &)> Receiver) { + + std::string error; + auto fileSystem = getFileSystem(vfsOptions, /*primaryFile=*/None, error); + if (!fileSystem) { + Receiver(RequestResult::fromError(error)); + return; + } + + std::shared_ptr> CancellationFlag = std::make_shared>(false); + ReqTracker->setCancellationHandler(CancellationToken, [CancellationFlag]() { + CancellationFlag->store(true, std::memory_order_relaxed); + }); + + CompileManager.performCompileAsync(Name, Args, std::move(fileSystem), + CancellationFlag, Receiver); +} + +void SwiftLangSupport::closeCompile(StringRef Name) { + CompileManager.clearSession(Name); +} diff --git a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp index 0dbcaf356dcca..7673188fc730f 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp @@ -273,7 +273,8 @@ configureCompletionInstance(std::shared_ptr CompletionInst, SwiftLangSupport::SwiftLangSupport(SourceKit::Context &SKCtx) : NotificationCtr(SKCtx.getNotificationCenter()), - ReqTracker(SKCtx.getRequestTracker()), CCCache(new SwiftCompletionCache) { + ReqTracker(SKCtx.getRequestTracker()), CCCache(new SwiftCompletionCache), + CompileManager(RuntimeResourcePath, DiagnosticDocumentationPath) { llvm::SmallString<128> LibPath(SKCtx.getRuntimeLibPath()); llvm::sys::path::append(LibPath, "swift"); RuntimeResourcePath = std::string(LibPath.str()); @@ -286,6 +287,7 @@ SwiftLangSupport::SwiftLangSupport(SourceKit::Context &SKCtx) RuntimeResourcePath, DiagnosticDocumentationPath); CompletionInst = std::make_shared(); + configureCompletionInstance(CompletionInst, SKCtx.getGlobalConfiguration()); // By default, just use the in-memory cache. diff --git a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h index ddaceafe41530..8d06f35ad4319 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h +++ b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h @@ -24,6 +24,7 @@ #include "swift/AST/DiagnosticConsumer.h" #include "swift/Basic/ThreadSafeRefCounted.h" #include "swift/IDE/CancellableResult.h" +#include "swift/IDE/CompileInstance.h" #include "swift/IDE/CompletionInstance.h" #include "swift/IDE/Indenting.h" #include "swift/IDE/Refactoring.h" @@ -296,6 +297,51 @@ class RequestRenameRangeConsumer : public swift::ide::FindRenameRangesConsumer, const swift::DiagnosticInfo &Info) override; }; +namespace compile { +class Session { + swift::ide::CompileInstance Compiler; + +public: + Session(const std::string &RuntimeResourcePath, + const std::string &DiagnosticDocumentationPath) + : Compiler(RuntimeResourcePath, DiagnosticDocumentationPath) {} + + bool + performCompile(llvm::ArrayRef Args, + llvm::IntrusiveRefCntPtr FileSystem, + swift::DiagnosticConsumer *DiagC, + std::shared_ptr> CancellationFlag) { + return Compiler.performCompile(Args, FileSystem, DiagC, CancellationFlag); + } +}; + +class SessionManager { + const std::string &RuntimeResourcePath; + const std::string &DiagnosticDocumentationPath; + + llvm::StringMap> sessions; + WorkQueue compileQueue{WorkQueue::Dequeuing::Concurrent, + "sourcekit.swift.Compile"}; + mutable llvm::sys::Mutex mtx; + +public: + SessionManager(std::string &RuntimeResourcePath, + std::string &DiagnosticDocumentationPath) + : RuntimeResourcePath(RuntimeResourcePath), + DiagnosticDocumentationPath(DiagnosticDocumentationPath) {} + + std::shared_ptr getSession(StringRef name); + + void clearSession(StringRef name); + + void performCompileAsync( + StringRef Name, ArrayRef Args, + llvm::IntrusiveRefCntPtr fileSystem, + std::shared_ptr> CancellationFlag, + std::function &)> Receiver); +}; +} // namespace compile + struct SwiftStatistics { #define SWIFT_STATISTIC(VAR, UID, DESC) \ Statistic VAR{UIdent{"source.statistic." #UID}, DESC}; @@ -317,6 +363,7 @@ class SwiftLangSupport : public LangSupport { std::shared_ptr Stats; llvm::StringMap> FileSystemProviders; std::shared_ptr CompletionInst; + compile::SessionManager CompileManager; public: explicit SwiftLangSupport(SourceKit::Context &SKCtx); @@ -682,6 +729,15 @@ class SwiftLangSupport : public LangSupport { ConformingMethodListConsumer &Consumer, Optional vfsOptions) override; + void + performCompile(StringRef Name, ArrayRef Args, + Optional vfsOptions, + SourceKitCancellationToken CancellationToken, + std::function &)> + Receiver) override; + + void closeCompile(StringRef Name) override; + void getStatistics(StatisticsReceiver) override; private: diff --git a/tools/SourceKit/tools/sourcekitd-test/TestOptions.cpp b/tools/SourceKit/tools/sourcekitd-test/TestOptions.cpp index 765c8b9a44bc1..1d3dd160a14e7 100644 --- a/tools/SourceKit/tools/sourcekitd-test/TestOptions.cpp +++ b/tools/SourceKit/tools/sourcekitd-test/TestOptions.cpp @@ -151,6 +151,8 @@ bool TestOptions::parseArgs(llvm::ArrayRef Args) { .Case("global-config", SourceKitRequest::GlobalConfiguration) .Case("dependency-updated", SourceKitRequest::DependencyUpdated) .Case("diags", SourceKitRequest::Diagnostics) + .Case("compile", SourceKitRequest::Compile) + .Case("compile.close", SourceKitRequest::CompileClose) #define SEMANTIC_REFACTORING(KIND, NAME, ID) .Case("refactoring." #ID, SourceKitRequest::KIND) #include "swift/IDE/RefactoringKinds.def" .Default(SourceKitRequest::None); diff --git a/tools/SourceKit/tools/sourcekitd-test/TestOptions.h b/tools/SourceKit/tools/sourcekitd-test/TestOptions.h index c651d1b79732d..4fbad1b15dbdf 100644 --- a/tools/SourceKit/tools/sourcekitd-test/TestOptions.h +++ b/tools/SourceKit/tools/sourcekitd-test/TestOptions.h @@ -68,6 +68,8 @@ enum class SourceKitRequest { GlobalConfiguration, DependencyUpdated, Diagnostics, + Compile, + CompileClose, #define SEMANTIC_REFACTORING(KIND, NAME, ID) KIND, #include "swift/IDE/RefactoringKinds.def" }; diff --git a/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp b/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp index 8b56f846b69ed..9209cbb2e3b6b 100644 --- a/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp +++ b/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp @@ -1087,6 +1087,16 @@ static int handleTestInvocation(TestOptions Opts, TestOptions &InitOpts) { case SourceKitRequest::Diagnostics: sourcekitd_request_dictionary_set_uid(Req, KeyRequest, RequestDiagnostics); break; + + case SourceKitRequest::Compile: + sourcekitd_request_dictionary_set_string(Req, KeyName, SemaName.c_str()); + sourcekitd_request_dictionary_set_uid(Req, KeyRequest, RequestCompile); + break; + + case SourceKitRequest::CompileClose: + sourcekitd_request_dictionary_set_string(Req, KeyName, SemaName.c_str()); + sourcekitd_request_dictionary_set_uid(Req, KeyRequest, RequestCompileClose); + break; } if (!SourceFile.empty()) { @@ -1322,6 +1332,12 @@ static bool handleResponse(sourcekitd_response_t Resp, const TestOptions &Opts, case SourceKitRequest::Diagnostics: printRawResponse(Resp); break; + case SourceKitRequest::Compile: + sourcekitd_response_description_dump_filedesc(Resp, STDOUT_FILENO); + break; + case SourceKitRequest::CompileClose: + sourcekitd_response_description_dump_filedesc(Resp, STDOUT_FILENO); + break; case SourceKitRequest::RelatedIdents: printRelatedIdents(Info, SourceFile, Opts.VFSFiles, llvm::outs()); diff --git a/tools/SourceKit/tools/sourcekitd/lib/API/Requests.cpp b/tools/SourceKit/tools/sourcekitd/lib/API/Requests.cpp index 25e9a3d7d4b68..bedb4d1606b09 100644 --- a/tools/SourceKit/tools/sourcekitd/lib/API/Requests.cpp +++ b/tools/SourceKit/tools/sourcekitd/lib/API/Requests.cpp @@ -974,6 +974,46 @@ void handleRequestImpl(sourcekitd_object_t ReqObj, return; } + if (ReqUID == RequestCompile) { + Optional Name = Req.getString(KeyName); + if (!Name.hasValue()) + return Rec(createErrorRequestInvalid("missing 'key.name'")); + + LangSupport &Lang = getGlobalContext().getSwiftLangSupport(); + Lang.performCompile( + *Name, Args, std::move(vfsOptions), CancellationToken, + [Rec](const RequestResult &result) { + if (result.isCancelled()) + return Rec(createErrorRequestCancelled()); + if (result.isError()) + return Rec(createErrorRequestFailed(result.getError())); + + const CompilationResult &info = result.value(); + + ResponseBuilder builder; + + builder.getDictionary().set(KeyValue, info.ResultStatus); + auto diagsArray = builder.getDictionary().setArray(KeyDiagnostics); + for (auto diagInfo : info.Diagnostics) { + auto elem = diagsArray.appendDictionary(); + fillDictionaryForDiagnosticInfo(elem, diagInfo); + } + Rec(builder.createResponse()); + }); + return; + } + + if (ReqUID == RequestCompileClose) { + Optional Name = Req.getString(KeyName); + if (!Name.hasValue()) + return Rec(createErrorRequestInvalid("missing 'key.name'")); + + LangSupport &Lang = getGlobalContext().getSwiftLangSupport(); + Lang.closeCompile(*Name); + + return Rec(ResponseBuilder().createResponse()); + } + if (!SourceFile.hasValue() && !SourceText.hasValue() && ReqUID != RequestCodeCompleteUpdate) return Rec(createErrorRequestInvalid( diff --git a/utils/gyb_sourcekit_support/UIDs.py b/utils/gyb_sourcekit_support/UIDs.py index ee02558e69b75..230c81e6bcf47 100644 --- a/utils/gyb_sourcekit_support/UIDs.py +++ b/utils/gyb_sourcekit_support/UIDs.py @@ -264,6 +264,8 @@ def __init__(self, internal_name, external_name): REQUEST('GlobalConfiguration', 'source.request.configuration.global'), REQUEST('DependencyUpdated', 'source.request.dependency_updated'), REQUEST('Diagnostics', 'source.request.diagnostics'), + REQUEST('Compile', 'source.request.compile'), + REQUEST('CompileClose', 'source.request.compile.close'), ]