//===--- ParseableInterfaceSupport.cpp - swiftinterface files ------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2018 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 // //===----------------------------------------------------------------------===// #define DEBUG_TYPE "textual-module-interface" #include "swift/AST/ASTContext.h" #include "swift/AST/Decl.h" #include "swift/AST/DiagnosticsFrontend.h" #include "swift/AST/DiagnosticsSema.h" #include "swift/AST/ExistentialLayout.h" #include "swift/AST/FileSystem.h" #include "swift/AST/Module.h" #include "swift/AST/ProtocolConformance.h" #include "swift/Basic/STLExtras.h" #include "swift/Frontend/Frontend.h" #include "swift/Frontend/ParseableInterfaceSupport.h" #include "swift/Frontend/PrintingDiagnosticConsumer.h" #include "swift/SILOptimizer/PassManager/Passes.h" #include "swift/Serialization/SerializationOptions.h" #include "clang/Basic/Module.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Lex/Preprocessor.h" #include "clang/Lex/PreprocessorOptions.h" #include "clang/Lex/HeaderSearch.h" #include "llvm/ADT/Hashing.h" #include "llvm/ADT/StringSet.h" #include "llvm/Support/xxhash.h" #include "llvm/Support/Debug.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/CrashRecoveryContext.h" #include "llvm/Support/Path.h" #include "llvm/Support/Regex.h" #include "llvm/Support/StringSaver.h" using namespace swift; using FileDependency = SerializationOptions::FileDependency; #define SWIFT_INTERFACE_FORMAT_VERSION_KEY "swift-interface-format-version" #define SWIFT_TOOLS_VERSION_KEY "swift-tools-version" #define SWIFT_MODULE_FLAGS_KEY "swift-module-flags" static swift::version::Version InterfaceFormatVersion({1, 0}); static bool extractSwiftInterfaceVersionAndArgs(DiagnosticEngine &Diags, SourceLoc DiagLoc, llvm::vfs::FileSystem &FS, StringRef SwiftInterfacePathIn, swift::version::Version &Vers, llvm::StringSaver &SubArgSaver, SmallVectorImpl &SubArgs) { auto FileOrError = swift::vfs::getFileOrSTDIN(FS, SwiftInterfacePathIn); if (!FileOrError) { Diags.diagnose(DiagLoc, diag::error_open_input_file, SwiftInterfacePathIn, FileOrError.getError().message()); return true; } auto SB = FileOrError.get()->getBuffer(); auto VersRe = getSwiftInterfaceFormatVersionRegex(); auto FlagRe = getSwiftInterfaceModuleFlagsRegex(); SmallVector VersMatches, FlagMatches; if (!VersRe.match(SB, &VersMatches)) { Diags.diagnose(DiagLoc, diag::error_extracting_version_from_parseable_interface); return true; } if (!FlagRe.match(SB, &FlagMatches)) { Diags.diagnose(DiagLoc, diag::error_extracting_flags_from_parseable_interface); return true; } assert(VersMatches.size() == 2); assert(FlagMatches.size() == 2); Vers = swift::version::Version(VersMatches[1], SourceLoc(), &Diags); llvm::cl::TokenizeGNUCommandLine(FlagMatches[1], SubArgSaver, SubArgs); return false; } static std::unique_ptr getBufferOfDependency(llvm::vfs::FileSystem &FS, StringRef ModulePath, StringRef DepPath, DiagnosticEngine &Diags, SourceLoc DiagLoc) { auto DepBuf = FS.getBufferForFile(DepPath, /*FileSize=*/-1, /*RequiresNullTerminator=*/false); if (!DepBuf) { Diags.diagnose(DiagLoc, diag::missing_dependency_of_parseable_module_interface, DepPath, ModulePath, DepBuf.getError().message()); return nullptr; } return std::move(DepBuf.get()); } static Optional getStatusOfDependency(llvm::vfs::FileSystem &FS, StringRef ModulePath, StringRef DepPath, DiagnosticEngine &Diags, SourceLoc DiagLoc) { auto Status = FS.status(DepPath); if (!Status) { Diags.diagnose(DiagLoc, diag::missing_dependency_of_parseable_module_interface, DepPath, ModulePath, Status.getError().message()); return None; } return Status.get(); } /// Construct a cache key for the .swiftmodule being generated. There is a /// balance to be struck here between things that go in the cache key and /// things that go in the "up to date" check of the cache entry. We want to /// avoid fighting over a single cache entry too much when (say) running /// different compiler versions on the same machine or different inputs /// that happen to have the same short module name, so we will disambiguate /// those in the key. But we want to invalidate and rebuild a cache entry /// -- rather than making a new one and potentially filling up the cache /// with dead entries -- when other factors change, such as the contents of /// the .swiftinterface input or its dependencies. static std::string getCacheHash(ASTContext &Ctx, const CompilerInvocation &SubInvocation, StringRef InPath) { // Start with the compiler version (which will be either tag names or revs). // Explicitly don't pass in the "effective" language version -- this would // mean modules built in different -swift-version modes would rebuild their // dependencies. llvm::hash_code H = hash_value(swift::version::getSwiftFullVersion()); // Simplest representation of input "identity" (not content) is just a // pathname, and probably all we can get from the VFS in this regard anyways. H = hash_combine(H, InPath); // Include the target CPU architecture. In practice, .swiftinterface files // will be in architecture-specific subdirectories and would have // architecture-specific pieces #if'd out. However, it doesn't hurt to // include it, and it guards against mistakenly reusing cached modules across // architectures. H = hash_combine(H, SubInvocation.getLangOptions().Target.getArchName()); // The SDK path is going to affect how this module is imported, so include it. H = hash_combine(H, SubInvocation.getSDKPath()); return llvm::APInt(64, H).toString(36, /*Signed=*/false); } static CompilerInvocation createInvocationForBuildingFromInterface(ASTContext &Ctx, StringRef ModuleName, StringRef CacheDir, StringRef PrebuiltCacheDir) { auto &SearchPathOpts = Ctx.SearchPathOpts; auto &LangOpts = Ctx.LangOpts; CompilerInvocation SubInvocation; // Start with a SubInvocation that copies various state from our // invoking ASTContext. SubInvocation.setImportSearchPaths(SearchPathOpts.ImportSearchPaths); SubInvocation.setFrameworkSearchPaths(SearchPathOpts.FrameworkSearchPaths); SubInvocation.setSDKPath(SearchPathOpts.SDKPath); SubInvocation.setInputKind(InputFileKind::SwiftModuleInterface); SubInvocation.setRuntimeResourcePath(SearchPathOpts.RuntimeResourcePath); SubInvocation.setTargetTriple(LangOpts.Target); SubInvocation.setModuleName(ModuleName); SubInvocation.setClangModuleCachePath(CacheDir); SubInvocation.getFrontendOptions().PrebuiltModuleCachePath = PrebuiltCacheDir; // Respect the detailed-record preprocessor setting of the parent context. // This, and the "raw" clang module format it implicitly enables, are required // by sourcekitd. if (auto *ClangLoader = Ctx.getClangModuleLoader()) { auto &Opts = ClangLoader->getClangInstance().getPreprocessorOpts(); if (Opts.DetailedRecord) { SubInvocation.getClangImporterOptions().DetailedPreprocessingRecord = true; } } // Inhibit warnings from the SubInvocation since we are assuming the user // is not in a position to fix them. SubInvocation.getDiagnosticOptions().SuppressWarnings = true; // Inherit this setting down so that it can affect error diagnostics (mostly // by making them non-fatal). SubInvocation.getLangOptions().DebuggerSupport = LangOpts.DebuggerSupport; // Disable this; deinitializers always get printed with `@objc` even in // modules that don't import Foundation. SubInvocation.getLangOptions().EnableObjCAttrRequiresFoundation = false; return SubInvocation; } /// Calculate an output filename in \p SubInvocation's cache path that includes /// a hash of relevant key data. static void computeCachedOutputPath(ASTContext &Ctx, const CompilerInvocation &SubInvocation, StringRef InPath, llvm::SmallString<256> &OutPath) { OutPath = SubInvocation.getClangModuleCachePath(); llvm::sys::path::append(OutPath, SubInvocation.getModuleName()); OutPath.append("-"); OutPath.append(getCacheHash(Ctx, SubInvocation, InPath)); OutPath.append("."); auto OutExt = file_types::getExtension(file_types::TY_SwiftModuleFile); OutPath.append(OutExt); } void ParseableInterfaceModuleLoader::configureSubInvocationInputsAndOutputs( CompilerInvocation &SubInvocation, StringRef InPath, StringRef OutPath) { auto &SubFEOpts = SubInvocation.getFrontendOptions(); SubFEOpts.RequestedAction = FrontendOptions::ActionType::EmitModuleOnly; SubFEOpts.EnableParseableModuleInterface = true; SubFEOpts.InputsAndOutputs.addPrimaryInputFile(InPath); SupplementaryOutputPaths SOPs; SOPs.ModuleOutputPath = OutPath.str(); // Pick a primary output path that will cause problems to use. StringRef MainOut = "/"; SubFEOpts.InputsAndOutputs.setMainAndSupplementaryOutputs({MainOut}, {SOPs}); } // Checks that a dependency read from the cached module is up to date compared // to the interface file it represents. If it's up-to-date, return its status, // so we can switch hash-based dependencies to mtimes after we've validated // the hashes. static Optional dependencyIsUpToDate(llvm::vfs::FileSystem &FS, FileDependency In, StringRef ModulePath, DiagnosticEngine &Diags, SourceLoc DiagLoc) { auto Status = getStatusOfDependency(FS, ModulePath, In.getPath(), Diags, DiagLoc); if (!Status) return None; // If the sizes differ, then we know the file has changed. if (Status->getSize() != In.getSize()) return None; // Otherwise, if this dependency is verified by modification time, check // it vs. the modification time of the file. uint64_t mtime = Status->getLastModificationTime().time_since_epoch().count(); if (In.isModificationTimeBased()) return mtime == In.getModificationTime() ? Status : None; // Slow path: if the dependency is verified by content hash, check it vs. the // hash of the file. auto buf = getBufferOfDependency(FS, ModulePath, In.getPath(), Diags, DiagLoc); if (!buf) return None; if (xxHash64(buf->getBuffer()) == In.getContentHash()) return Status; return None; } } // Check that the output .swiftmodule file is at least as new as all the // dependencies it read when it was built last time. static bool swiftModuleIsUpToDate(llvm::vfs::FileSystem &FS, std::pair ModuleID, StringRef OutPath, DiagnosticEngine &Diags, DependencyTracker *OuterTracker) { auto OutBuf = FS.getBufferForFile(OutPath); if (!OutBuf) return false; LLVM_DEBUG(llvm::dbgs() << "Validating deps of " << OutPath << "\n"); SmallVector AllDeps; auto VI = serialization::validateSerializedAST( OutBuf.get()->getBuffer(), /*ExtendedValidationInfo=*/nullptr, &AllDeps); if (VI.status != serialization::Status::Valid) return false; assert(VI.name == ModuleID.first.str() && "we built a module at this path with a different name?"); for (auto In : AllDeps) { if (OuterTracker) OuterTracker->addDependency(In.Path, /*IsSystem=*/false); if (!dependencyIsUpToDate(FS, In, OutPath, Diags, ModuleID.second)) { LLVM_DEBUG(llvm::dbgs() << "Dep " << In.Path << " is directly out of date\n"); return false; } LLVM_DEBUG(llvm::dbgs() << "Dep " << In.Path << " is up to date\n"); } return true; } /// Populate the provided \p Deps with \c FileDependency entries including: /// /// - \p InPath - The .swiftinterface input file /// /// - All the dependencies mentioned by \p SubInstance's DependencyTracker, /// that were read while compiling the module. /// /// - For any file in the latter set that is itself a .swiftmodule /// living in \p ModuleCachePath, all of _its_ dependencies, copied /// out to avoid having to do recursive scanning when rechecking this /// dependency in the future. static bool collectDepsForSerialization(llvm::vfs::FileSystem &FS, CompilerInstance &SubInstance, StringRef InPath, StringRef ModuleCachePath, SmallVectorImpl &Deps, bool IsHashBased, DiagnosticEngine &Diags, SourceLoc DiagLoc, DependencyTracker *OuterTracker) { auto DTDeps = SubInstance.getDependencyTracker()->getDependencies(); SmallVector InitialDepNames(DTDeps.begin(), DTDeps.end()); InitialDepNames.push_back(InPath); llvm::StringSet<> AllDepNames; for (auto const &DepName : InitialDepNames) { if (AllDepNames.insert(DepName).second && OuterTracker) { OuterTracker->addDependency(DepName, /*IsSystem=*/false); } auto DepBuf = getBufferOfDependency(FS, InPath, DepName, Diags, DiagLoc); if (!DepBuf) return true; auto Status = getStatusOfDependency(FS, InPath, DepName, Diags, DiagLoc); if (!Status) return true; if (IsHashBased) { uint64_t hash = xxHash64(DepBuf->getBuffer()); Deps.push_back( FileDependency::hashBased(DepName, Status->getSize(), hash)); } else { uint64_t mtime = Status->getLastModificationTime().time_since_epoch().count(); Deps.push_back( FileDependency::modTimeBased(DepName, Status->getSize(), mtime)); } if (ModuleCachePath.empty()) continue; // If Dep is itself a .swiftmodule in the cache dir, pull out its deps // and include them in our own, so we have a single-file view of // transitive deps: removes redundancies, and avoids opening and reading // multiple swiftmodules during future loads. auto Ext = llvm::sys::path::extension(DepName); auto Ty = file_types::lookupTypeForExtension(Ext); if (Ty == file_types::TY_SwiftModuleFile && DepName.startswith(ModuleCachePath)) { SmallVector SubDeps; auto VI = serialization::validateSerializedAST( DepBuf->getBuffer(), /*ExtendedValidationInfo=*/nullptr, &SubDeps); if (VI.status != serialization::Status::Valid) { Diags.diagnose(DiagLoc, diag::error_extracting_dependencies_from_cached_module, DepName); return true; } for (auto const &SubDep : SubDeps) { if (AllDepNames.insert(SubDep.getPath()).second) { Deps.push_back(SubDep); if (OuterTracker) OuterTracker->addDependency(SubDep.getPath(), /*IsSystem=*/false); } } } } return false; } bool ParseableInterfaceModuleLoader::buildSwiftModuleFromSwiftInterface( llvm::vfs::FileSystem &FS, DiagnosticEngine &Diags, SourceLoc DiagLoc, CompilerInvocation &SubInvocation, StringRef InPath, StringRef OutPath, StringRef ModuleCachePath, DependencyTracker *OuterTracker, bool ShouldSerializeDeps) { bool SubError = false; bool RunSuccess = llvm::CrashRecoveryContext().RunSafelyOnThread([&] { // Note that we don't assume ModuleCachePath is the same as the Clang // module cache path at this point. if (!ModuleCachePath.empty()) (void)llvm::sys::fs::create_directory(ModuleCachePath); configureSubInvocationInputsAndOutputs(SubInvocation, InPath, OutPath); FrontendOptions &FEOpts = SubInvocation.getFrontendOptions(); const auto &InputInfo = FEOpts.InputsAndOutputs.firstInput(); StringRef InPath = InputInfo.file(); const auto &OutputInfo = InputInfo.getPrimarySpecificPaths().SupplementaryOutputs; StringRef OutPath = OutputInfo.ModuleOutputPath; llvm::BumpPtrAllocator SubArgsAlloc; llvm::StringSaver SubArgSaver(SubArgsAlloc); SmallVector SubArgs; swift::version::Version Vers; if (extractSwiftInterfaceVersionAndArgs(Diags, DiagLoc, FS, InPath, Vers, SubArgSaver, SubArgs)) { SubError = true; return; } // For now: we support anything with the same "major version" and assume // minor versions might be interesting for debugging, or special-casing a // compatible field variant. if (Vers.asMajorVersion() != InterfaceFormatVersion.asMajorVersion()) { Diags.diagnose(DiagLoc, diag::unsupported_version_of_parseable_interface, InPath, Vers); SubError = true; return; } SmallString<32> ExpectedModuleName = SubInvocation.getModuleName(); if (SubInvocation.parseArgs(SubArgs, Diags)) { SubError = true; return; } if (SubInvocation.getModuleName() != ExpectedModuleName) { auto DiagKind = diag::serialization_name_mismatch; if (SubInvocation.getLangOptions().DebuggerSupport) DiagKind = diag::serialization_name_mismatch_repl; Diags.diagnose(DiagLoc, DiagKind, SubInvocation.getModuleName(), ExpectedModuleName); SubError = true; return; } // Optimize emitted modules. This has to happen after we parse arguments, // because parseSILOpts would override the current optimization mode. SubInvocation.getSILOptions().OptMode = OptimizationMode::ForSpeed; // Build the .swiftmodule; this is a _very_ abridged version of the logic in // performCompile in libFrontendTool, specialized, to just the one // module-serialization task we're trying to do here. LLVM_DEBUG(llvm::dbgs() << "Setting up instance to compile " << InPath << " to " << OutPath << "\n"); CompilerInstance SubInstance; SubInstance.getSourceMgr().setFileSystem(&FS); ForwardingDiagnosticConsumer FDC(Diags); SubInstance.addDiagnosticConsumer(&FDC); SubInstance.createDependencyTracker(/*TrackSystemDeps=*/false); if (SubInstance.setup(SubInvocation)) { SubError = true; return; } LLVM_DEBUG(llvm::dbgs() << "Performing sema\n"); SubInstance.performSema(); if (SubInstance.getASTContext().hadError()) { LLVM_DEBUG(llvm::dbgs() << "encountered errors\n"); SubError = true; return; } SILOptions &SILOpts = SubInvocation.getSILOptions(); auto Mod = SubInstance.getMainModule(); auto SILMod = performSILGeneration(Mod, SILOpts); if (!SILMod) { LLVM_DEBUG(llvm::dbgs() << "SILGen did not produce a module\n"); SubError = true; return; } // Setup the callbacks for serialization, which can occur during the // optimization pipeline. SerializationOptions SerializationOpts; std::string OutPathStr = OutPath; SerializationOpts.OutputPath = OutPathStr.c_str(); SerializationOpts.ModuleLinkName = FEOpts.ModuleLinkName; SmallVector Deps; if (collectDepsForSerialization( FS, SubInstance, InPath, ModuleCachePath, Deps, FEOpts.SerializeParseableModuleInterfaceDependencyHashes, Diags, DiagLoc, OuterTracker)) { SubError = true; return; } if (ShouldSerializeDeps) SerializationOpts.Dependencies = Deps; SILMod->setSerializeSILAction([&]() { serialize(Mod, SerializationOpts, SILMod.get()); }); LLVM_DEBUG(llvm::dbgs() << "Running SIL processing passes\n"); if (SubInstance.performSILProcessing(SILMod.get())) { LLVM_DEBUG(llvm::dbgs() << "encountered errors\n"); SubError = true; return; } SubError = SubInstance.getDiags().hadAnyError(); }); return !RunSuccess || SubError; } static bool serializedASTLooksValidOrCannotBeRead(llvm::vfs::FileSystem &FS, StringRef ModPath) { auto ModBuf = FS.getBufferForFile(ModPath, /*FileSize=*/-1, /*RequiresNullTerminator=*/false); if (!ModBuf) return ModBuf.getError() != std::errc::no_such_file_or_directory; auto VI = serialization::validateSerializedAST(ModBuf.get()->getBuffer()); return VI.status == serialization::Status::Valid; } /// Load a .swiftmodule associated with a .swiftinterface either from a /// cache or by converting it in a subordinate \c CompilerInstance, caching /// the results. std::error_code ParseableInterfaceModuleLoader::findModuleFilesInDirectory( AccessPathElem ModuleID, StringRef DirPath, StringRef ModuleFilename, StringRef ModuleDocFilename, std::unique_ptr *ModuleBuffer, std::unique_ptr *ModuleDocBuffer) { namespace path = llvm::sys::path; // If running in OnlySerialized mode, ParseableInterfaceModuleLoader // should not have been constructed at all. assert(LoadMode != ModuleLoadingMode::OnlySerialized); auto &FS = *Ctx.SourceMgr.getFileSystem(); auto &Diags = Ctx.Diags; llvm::SmallString<256> ModPath, InPath, OutPath; // First check to see if the .swiftinterface exists at all. Bail if not. ModPath = DirPath; path::append(ModPath, ModuleFilename); auto Ext = file_types::getExtension(file_types::TY_SwiftParseableInterfaceFile); InPath = ModPath; path::replace_extension(InPath, Ext); if (!FS.exists(InPath)) return std::make_error_code(std::errc::no_such_file_or_directory); // Next, if we're in the load mode that prefers .swiftmodules, see if there's // one here we can _likely_ load (validates OK). If so, bail early with // errc::not_supported, so the next (serialized) loader in the chain will load // it. Alternately, if there's a .swiftmodule present but we can't even read // it (for whatever reason), we should let the other module loader diagnose // it. if (LoadMode == ModuleLoadingMode::PreferSerialized && serializedASTLooksValidOrCannotBeRead(FS, ModPath)) { return std::make_error_code(std::errc::not_supported); } // If we have a prebuilt cache path, check that too if the interface comes // from the SDK. if (!PrebuiltCacheDir.empty()) { StringRef SDKPath = Ctx.SearchPathOpts.SDKPath; if (!SDKPath.empty() && hasPrefix(path::begin(InPath), path::end(InPath), path::begin(SDKPath), path::end(SDKPath))) { // Assemble the expected path: $PREBUILT_CACHE/Foo.swiftmodule or // $PREBUILT_CACHE/Foo.swiftmodule/arch.swiftmodule. Note that there's no // cache key here. OutPath = PrebuiltCacheDir; // FIXME: Would it be possible to only have architecture-specific names // here? Then we could skip this check. StringRef InParentDirName = path::filename(path::parent_path(InPath)); if (path::extension(InParentDirName) == ".swiftmodule") { assert(path::stem(InParentDirName) == ModuleID.first.str()); path::append(OutPath, InParentDirName); } path::append(OutPath, ModuleFilename); if (!swiftModuleIsUpToDate(FS, ModuleID, OutPath, Diags, dependencyTracker)) { OutPath.clear(); } } } if (OutPath.empty()) { // At this point we're either in PreferParseable mode or there's no credible // adjacent .swiftmodule so we'll go ahead and start trying to convert the // .swiftinterface. // Set up a _potential_ sub-invocation to consume the .swiftinterface and // emit the .swiftmodule. CompilerInvocation SubInvocation = createInvocationForBuildingFromInterface(Ctx, ModuleID.first.str(), CacheDir, PrebuiltCacheDir); computeCachedOutputPath(Ctx, SubInvocation, InPath, OutPath); // Evaluate if we need to run this sub-invocation, and if so run it. if (!swiftModuleIsUpToDate(FS, ModuleID, OutPath, Diags, dependencyTracker)) { if (buildSwiftModuleFromSwiftInterface(FS, Diags, ModuleID.second, SubInvocation, InPath, OutPath, CacheDir, dependencyTracker, /*ShouldSerializeDeps*/true)) return std::make_error_code(std::errc::invalid_argument); } } // Finish off by delegating back up to the SerializedModuleLoaderBase // routine that can load the recently-manufactured serialized module. LLVM_DEBUG(llvm::dbgs() << "Loading " << OutPath << " via normal module loader\n"); llvm::SmallString<256> DocPath{DirPath}; path::append(DocPath, ModuleDocFilename); auto ErrorCode = SerializedModuleLoaderBase::openModuleFiles( ModuleID, OutPath, DocPath, ModuleBuffer, ModuleDocBuffer); LLVM_DEBUG(llvm::dbgs() << "Loaded " << OutPath << " via normal module loader"); if (ErrorCode) { LLVM_DEBUG(llvm::dbgs() << " with error: " << ErrorCode.message()); } LLVM_DEBUG(llvm::dbgs() << "\n"); return ErrorCode; } bool ParseableInterfaceModuleLoader::buildSwiftModuleFromSwiftInterface( ASTContext &Ctx, StringRef CacheDir, StringRef PrebuiltCacheDir, StringRef ModuleName, StringRef InPath, StringRef OutPath) { CompilerInvocation SubInvocation = createInvocationForBuildingFromInterface(Ctx, ModuleName, CacheDir, PrebuiltCacheDir); auto &FS = *Ctx.SourceMgr.getFileSystem(); auto &Diags = Ctx.Diags; // FIXME: We don't really want to ignore dependencies here, but we have to // identify which ones are important, and make them relocatable // (SDK-relative) if we want to ship the built swiftmodules to another // machine. Just leave them out for now. return buildSwiftModuleFromSwiftInterface(FS, Diags, /*DiagLoc*/SourceLoc(), SubInvocation, InPath, OutPath, /*CachePath*/"", /*OuterTracker*/nullptr, /*ShouldSerializeDeps*/false); } /// Diagnose any scoped imports in \p imports, i.e. those with a non-empty /// access path. These are not yet supported by parseable interfaces, since the /// information about the declaration kind is not preserved through the binary /// serialization that happens as an intermediate step in non-whole-module /// builds. /// /// These come from declarations like `import class FooKit.MainFooController`. static void diagnoseScopedImports(DiagnosticEngine &diags, ArrayRef imports){ for (const ModuleDecl::ImportedModule &importPair : imports) { if (importPair.first.empty()) continue; diags.diagnose(importPair.first.front().second, diag::parseable_interface_scoped_import_unsupported); } } /// Prints to \p out a comment containing a format version number, tool version /// string as well as any relevant command-line flags in \p Opts used to /// construct \p M. static void printToolVersionAndFlagsComment(raw_ostream &out, ParseableInterfaceOptions const &Opts, ModuleDecl *M) { auto &Ctx = M->getASTContext(); auto ToolsVersion = swift::version::getSwiftFullVersion( Ctx.LangOpts.EffectiveLanguageVersion); out << "// " SWIFT_INTERFACE_FORMAT_VERSION_KEY ": " << InterfaceFormatVersion << "\n"; out << "// " SWIFT_TOOLS_VERSION_KEY ": " << ToolsVersion << "\n"; out << "// " SWIFT_MODULE_FLAGS_KEY ": " << Opts.ParseableInterfaceFlags << "\n"; } llvm::Regex swift::getSwiftInterfaceFormatVersionRegex() { return llvm::Regex("^// " SWIFT_INTERFACE_FORMAT_VERSION_KEY ": ([0-9\\.]+)$", llvm::Regex::Newline); } llvm::Regex swift::getSwiftInterfaceModuleFlagsRegex() { return llvm::Regex("^// " SWIFT_MODULE_FLAGS_KEY ":(.*)$", llvm::Regex::Newline); } /// Extract the specified-or-defaulted -module-cache-path that winds up in /// the clang importer, for reuse as the .swiftmodule cache path when /// building a ParseableInterfaceModuleLoader. std::string swift::getModuleCachePathFromClang(const clang::CompilerInstance &Clang) { if (!Clang.hasPreprocessor()) return ""; std::string SpecificModuleCachePath = Clang.getPreprocessor() .getHeaderSearchInfo() .getModuleCachePath(); // The returned-from-clang module cache path includes a suffix directory // that is specific to the clang version and invocation; we want the // directory above that. return llvm::sys::path::parent_path(SpecificModuleCachePath); } /// Prints the imported modules in \p M to \p out in the form of \c import /// source declarations. static void printImports(raw_ostream &out, ModuleDecl *M) { // FIXME: This is very similar to what's in Serializer::writeInputBlock, but // it's not obvious what higher-level optimization would be factored out here. SmallVector allImports; M->getImportedModules(allImports, ModuleDecl::ImportFilter::All); ModuleDecl::removeDuplicateImports(allImports); diagnoseScopedImports(M->getASTContext().Diags, allImports); // Collect the public imports as a subset so that we can mark them with // '@_exported'. SmallVector publicImports; M->getImportedModules(publicImports, ModuleDecl::ImportFilter::Public); llvm::SmallSet publicImportSet; publicImportSet.insert(publicImports.begin(), publicImports.end()); for (auto import : allImports) { if (import.second->isOnoneSupportModule() || import.second->isBuiltinModule()) { continue; } if (publicImportSet.count(import)) out << "@_exported "; out << "import "; import.second->getReverseFullModuleName().printForward(out); // Write the access path we should be honoring but aren't. // (See diagnoseScopedImports above.) if (!import.first.empty()) { out << "/*"; for (const auto &accessPathElem : import.first) out << "." << accessPathElem.first; out << "*/"; } out << "\n"; } } // FIXME: Copied from ASTPrinter.cpp... static bool isPublicOrUsableFromInline(const ValueDecl *VD) { AccessScope scope = VD->getFormalAccessScope(/*useDC*/nullptr, /*treatUsableFromInlineAsPublic*/true); return scope.isPublic(); } static bool isPublicOrUsableFromInline(Type ty) { // Note the double negative here: we're looking for any referenced decls that // are *not* public-or-usableFromInline. return !ty.findIf([](Type typePart) -> bool { // FIXME: If we have an internal typealias for a non-internal type, we ought // to be able to print it by desugaring. if (auto *aliasTy = dyn_cast(typePart.getPointer())) return !isPublicOrUsableFromInline(aliasTy->getDecl()); if (auto *nominal = typePart->getAnyNominal()) return !isPublicOrUsableFromInline(nominal); return false; }); } namespace { /// Collects protocols that are conformed to by a particular nominal. Since /// ASTPrinter will only print the public ones, the non-public ones get left by /// the wayside. This is a problem when a non-public protocol inherits from a /// public protocol; the generated parseable interface still needs to make that /// dependency public. /// /// The solution implemented here is to generate synthetic extensions that /// declare the extra conformances. This isn't perfect (it loses the sugared /// spelling of the protocol type, as well as the locality in the file), but it /// does work. class InheritedProtocolCollector { static const StringLiteral DummyProtocolName; /// Protocols that will be included by the ASTPrinter without any extra work. SmallVector IncludedProtocols; /// Protocols that will not be printed by the ASTPrinter. SmallVector ExtraProtocols; /// Protocols that can be printed, but whose conformances are constrained with /// something that \e can't be printed. SmallVector ConditionalConformanceProtocols; /// For each type in \p directlyInherited, classify the protocols it refers to /// as included for printing or not, and record them in the appropriate /// vectors. void recordProtocols(ArrayRef directlyInherited) { for (TypeLoc inherited : directlyInherited) { Type inheritedTy = inherited.getType(); if (!inheritedTy || !inheritedTy->isExistentialType()) continue; bool canPrintNormally = isPublicOrUsableFromInline(inheritedTy); SmallVectorImpl &whichProtocols = canPrintNormally ? IncludedProtocols : ExtraProtocols; ExistentialLayout layout = inheritedTy->getExistentialLayout(); for (ProtocolType *protoTy : layout.getProtocols()) whichProtocols.push_back(protoTy->getDecl()); // FIXME: This ignores layout constraints, but currently we don't support // any of those besides 'AnyObject'. } } /// For each type in \p directlyInherited, record any protocols that we would /// have printed in ConditionalConformanceProtocols. void recordConditionalConformances(ArrayRef directlyInherited) { for (TypeLoc inherited : directlyInherited) { Type inheritedTy = inherited.getType(); if (!inheritedTy || !inheritedTy->isExistentialType()) continue; ExistentialLayout layout = inheritedTy->getExistentialLayout(); for (ProtocolType *protoTy : layout.getProtocols()) if (isPublicOrUsableFromInline(protoTy)) ConditionalConformanceProtocols.push_back(protoTy); // FIXME: This ignores layout constraints, but currently we don't support // any of those besides 'AnyObject'. } } public: using PerTypeMap = llvm::MapVector; /// Given that we're about to print \p D, record its protocols in \p map. /// /// \sa recordProtocols static void collectProtocols(PerTypeMap &map, const Decl *D) { ArrayRef directlyInherited; const NominalTypeDecl *nominal; const IterableDeclContext *memberContext; if ((nominal = dyn_cast(D))) { directlyInherited = nominal->getInherited(); memberContext = nominal; } else if (auto *extension = dyn_cast(D)) { if (extension->isConstrainedExtension()) { // Conditional conformances never apply to inherited protocols, nor // can they provide unconditional conformances that might be used in // other extensions. return; } nominal = extension->getExtendedNominal(); directlyInherited = extension->getInherited(); memberContext = extension; } else { return; } if (!isPublicOrUsableFromInline(nominal)) return; map[nominal].recordProtocols(directlyInherited); // Recurse to find any nested types. for (const Decl *member : memberContext->getMembers()) collectProtocols(map, member); } /// If \p D is an extension providing conditional conformances, record those /// in \p map. /// /// \sa recordConditionalConformances static void collectSkippedConditionalConformances(PerTypeMap &map, const Decl *D) { auto *extension = dyn_cast(D); if (!extension || !extension->isConstrainedExtension()) return; const NominalTypeDecl *nominal = extension->getExtendedNominal(); if (!isPublicOrUsableFromInline(nominal)) return; map[nominal].recordConditionalConformances(extension->getInherited()); // No recursion here because extensions are never nested. } /// Returns true if the conformance of \p nominal to \p proto is declared in /// module \p M. static bool conformanceDeclaredInModule(ModuleDecl *M, const NominalTypeDecl *nominal, ProtocolDecl *proto) { SmallVector conformances; nominal->lookupConformance(M, proto, conformances); return llvm::all_of(conformances, [M](const ProtocolConformance *conformance) -> bool { return M == conformance->getDeclContext()->getParentModule(); }); } /// If there were any public protocols that need to be printed (i.e. they /// weren't conformed to explicitly or inherited by another printed protocol), /// do so now by printing a dummy extension on \p nominal to \p out. void printSynthesizedExtensionIfNeeded(raw_ostream &out, const PrintOptions &printOptions, ModuleDecl *M, const NominalTypeDecl *nominal) const { if (ExtraProtocols.empty()) return; SmallPtrSet handledProtocols; // First record all protocols that have already been handled. for (ProtocolDecl *proto : IncludedProtocols) { proto->walkInheritedProtocols( [&handledProtocols](ProtocolDecl *inherited) -> TypeWalker::Action { handledProtocols.insert(inherited); return TypeWalker::Action::Continue; }); } // Then walk the remaining ones, and see what we need to print. // Note: We could do this in one pass, but the logic is easier to // understand if we build up the list and then print it, even if it takes // a bit more memory. SmallVector protocolsToPrint; for (ProtocolDecl *proto : ExtraProtocols) { proto->walkInheritedProtocols( [&](ProtocolDecl *inherited) -> TypeWalker::Action { if (!handledProtocols.insert(inherited).second) return TypeWalker::Action::SkipChildren; if (isPublicOrUsableFromInline(inherited) && conformanceDeclaredInModule(M, nominal, inherited)) { protocolsToPrint.push_back(inherited); return TypeWalker::Action::SkipChildren; } return TypeWalker::Action::Continue; }); } if (protocolsToPrint.empty()) return; out << "extension "; nominal->getDeclaredType().print(out, printOptions); out << " : "; swift::interleave(protocolsToPrint, [&out, &printOptions](ProtocolDecl *proto) { proto->getDeclaredType()->print(out, printOptions); }, [&out] { out << ", "; }); out << " {}\n"; } /// If there were any conditional conformances that couldn't be printed, /// make a dummy extension that conforms to all of them, constrained by a /// fake protocol. bool printInaccessibleConformanceExtensionIfNeeded( raw_ostream &out, const PrintOptions &printOptions, const NominalTypeDecl *nominal) const { if (ConditionalConformanceProtocols.empty()) return false; assert(nominal->isGenericContext()); out << "extension "; nominal->getDeclaredType().print(out, printOptions); out << " : "; swift::interleave(ConditionalConformanceProtocols, [&out, &printOptions](const ProtocolType *protoTy) { protoTy->print(out, printOptions); }, [&out] { out << ", "; }); out << " where " << nominal->getGenericSignature()->getGenericParams().front()->getName() << " : " << DummyProtocolName << " {}\n"; return true; } /// Print a fake protocol declaration for use by /// #printInaccessibleConformanceExtensionIfNeeded. static void printDummyProtocolDeclaration(raw_ostream &out) { out << "\n@usableFromInline\ninternal protocol " << DummyProtocolName << " {}\n"; } }; const StringLiteral InheritedProtocolCollector::DummyProtocolName = "_ConstraintThatIsNotPartOfTheAPIOfThisLibrary"; } // end anonymous namespace bool swift::emitParseableInterface(raw_ostream &out, ParseableInterfaceOptions const &Opts, ModuleDecl *M) { assert(M); printToolVersionAndFlagsComment(out, Opts, M); printImports(out, M); const PrintOptions printOptions = PrintOptions::printParseableInterfaceFile(); InheritedProtocolCollector::PerTypeMap inheritedProtocolMap; SmallVector topLevelDecls; M->getTopLevelDecls(topLevelDecls); for (const Decl *D : topLevelDecls) { if (!D->shouldPrintInContext(printOptions) || !printOptions.CurrentPrintabilityChecker->shouldPrint(D, printOptions)){ InheritedProtocolCollector::collectSkippedConditionalConformances( inheritedProtocolMap, D); continue; } D->print(out, printOptions); out << "\n"; InheritedProtocolCollector::collectProtocols(inheritedProtocolMap, D); } // Print dummy extensions for any protocols that were indirectly conformed to. bool needDummyProtocolDeclaration = false; for (const auto &nominalAndCollector : inheritedProtocolMap) { const NominalTypeDecl *nominal = nominalAndCollector.first; const InheritedProtocolCollector &collector = nominalAndCollector.second; collector.printSynthesizedExtensionIfNeeded(out, printOptions, M, nominal); needDummyProtocolDeclaration |= collector.printInaccessibleConformanceExtensionIfNeeded(out, printOptions, nominal); } if (needDummyProtocolDeclaration) InheritedProtocolCollector::printDummyProtocolDeclaration(out); return false; }