//===-- ParseableInterfaceModuleLoader.cpp - Loads .swiftinterface files --===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2019 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/Frontend/ParseableInterfaceModuleLoader.h" #include "swift/AST/ASTContext.h" #include "swift/AST/DiagnosticsFrontend.h" #include "swift/AST/DiagnosticsSema.h" #include "swift/AST/FileSystem.h" #include "swift/AST/Module.h" #include "swift/AST/ProtocolConformance.h" #include "swift/Basic/Lazy.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/ModuleFormat.h" #include "swift/Serialization/SerializationOptions.h" #include "swift/Serialization/Validation.h" #include "clang/Basic/Module.h" #include "clang/Lex/Preprocessor.h" #include "clang/Lex/PreprocessorOptions.h" #include "clang/Lex/HeaderSearch.h" #include "clang/Frontend/CompilerInstance.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/Errc.h" #include "llvm/Support/Regex.h" #include "llvm/Support/StringSaver.h" #include "llvm/Support/YAMLTraits.h" using namespace swift; using FileDependency = SerializationOptions::FileDependency; /// 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); } #pragma mark - Forwarding Modules namespace { /// Describes a "forwarding module", that is, a .swiftmodule that's actually /// a YAML file inside, pointing to a the original .swiftmodule but describing /// a different dependency resolution strategy. struct ForwardingModule { /// The path to the original .swiftmodule in the prebuilt cache. std::string underlyingModulePath; /// Describes a set of file-based dependencies with their size and /// modification time stored. This is slightly different from /// \c SerializationOptions::FileDependency, because this type needs to be /// serializable to and from YAML. struct Dependency { std::string path; uint64_t size; uint64_t lastModificationTime; }; std::vector dependencies; unsigned version = 1; ForwardingModule() = default; ForwardingModule(StringRef underlyingModulePath) : underlyingModulePath(underlyingModulePath) {} /// Loads the contents of the forwarding module whose contents lie in /// the provided buffer, and returns a new \c ForwardingModule, or an error /// if the YAML could not be parsed. static llvm::ErrorOr load(const llvm::MemoryBuffer &buf); /// Adds a given dependency to the dependencies list. void addDependency(StringRef path, uint64_t size, uint64_t modTime) { dependencies.push_back({path.str(), size, modTime}); } }; } // end anonymous namespace #pragma mark - YAML Serialization namespace llvm { namespace yaml { template <> struct MappingTraits { static void mapping(IO &io, ForwardingModule::Dependency &dep) { io.mapRequired("mtime", dep.lastModificationTime); io.mapRequired("path", dep.path); io.mapRequired("size", dep.size); } }; template <> struct SequenceElementTraits { static const bool flow = false; }; template <> struct MappingTraits { static void mapping(IO &io, ForwardingModule &module) { io.mapRequired("path", module.underlyingModulePath); io.mapRequired("dependencies", module.dependencies); io.mapRequired("version", module.version); } }; } } // end namespace llvm llvm::ErrorOr ForwardingModule::load(const llvm::MemoryBuffer &buf) { llvm::yaml::Input yamlIn(buf.getBuffer()); ForwardingModule fwd; yamlIn >> fwd; if (yamlIn.error()) return yamlIn.error(); // We only currently support Version 1 of the forwarding module format. if (fwd.version != 1) return std::make_error_code(std::errc::not_supported); return std::move(fwd); } #pragma mark - Module Discovery namespace { /// The result of a search for a module either alongside an interface, in the /// module cache, or in the prebuilt module cache. class DiscoveredModule { /// The kind of module we've found. enum class Kind { /// A module that's either alongside the swiftinterface or in the /// module cache. Normal, /// A module that resides in the prebuilt cache, and has hash-based /// dependencies. Prebuilt, /// A 'forwarded' module. This is a module in the prebuilt cache, but whose /// dependencies live in a forwarding module. /// \sa ForwardingModule. Forwarded }; /// The kind of module that's been discovered. const Kind kind; DiscoveredModule(StringRef path, Kind kind, std::unique_ptr moduleBuffer) : kind(kind), moduleBuffer(std::move(moduleBuffer)), path(path) {} public: /// The contents of the .swiftmodule, if we've read it while validating /// dependencies. std::unique_ptr moduleBuffer; /// The path to the discovered serialized .swiftmodule on disk. const std::string path; /// Creates a \c Normal discovered module. static DiscoveredModule normal(StringRef path, std::unique_ptr moduleBuffer) { return { path, Kind::Normal, std::move(moduleBuffer) }; } /// Creates a \c Prebuilt discovered module. static DiscoveredModule prebuilt( StringRef path, std::unique_ptr moduleBuffer) { return { path, Kind::Prebuilt, std::move(moduleBuffer) }; } /// Creates a \c Forwarded discovered module, whose dependencies have been /// externally validated by a \c ForwardingModule. static DiscoveredModule forwarded( StringRef path, std::unique_ptr moduleBuffer) { return { path, Kind::Forwarded, std::move(moduleBuffer) }; } bool isNormal() const { return kind == Kind::Normal; } bool isPrebuilt() const { return kind == Kind::Prebuilt; } bool isForwarded() const { return kind == Kind::Forwarded; } }; } // end anonymous namespace #pragma mark - Common utilities namespace path = llvm::sys::path; static bool serializedASTLooksValid(const llvm::MemoryBuffer &buf) { auto VI = serialization::validateSerializedAST(buf.getBuffer()); return VI.status == serialization::Status::Valid; } static std::unique_ptr getBufferOfDependency( llvm::vfs::FileSystem &fs, StringRef depPath, StringRef interfacePath, DiagnosticEngine &diags, SourceLoc diagnosticLoc) { auto depBuf = fs.getBufferForFile(depPath, /*FileSize=*/-1, /*RequiresNullTerminator=*/false); if (!depBuf) { diags.diagnose(diagnosticLoc, diag::missing_dependency_of_parseable_module_interface, depPath, interfacePath, depBuf.getError().message()); return nullptr; } return std::move(depBuf.get()); } static Optional getStatusOfDependency( llvm::vfs::FileSystem &fs, StringRef depPath, StringRef interfacePath, DiagnosticEngine &diags, SourceLoc diagnosticLoc) { auto status = fs.status(depPath); if (!status) { diags.diagnose(diagnosticLoc, diag::missing_dependency_of_parseable_module_interface, depPath, interfacePath, status.getError().message()); return None; } return status.get(); } #pragma mark - Module Building /// Builds a parseable module interface into a .swiftmodule at the provided /// output path. /// \note Needs to be in the swift namespace so CompilerInvocation can see it. class swift::ParseableInterfaceBuilder { ASTContext &ctx; llvm::vfs::FileSystem &fs; DiagnosticEngine &diags; const StringRef interfacePath; const StringRef moduleName; const StringRef moduleCachePath; const StringRef prebuiltCachePath; const bool serializeDependencyHashes; const bool trackSystemDependencies; const SourceLoc diagnosticLoc; DependencyTracker *const dependencyTracker; CompilerInvocation subInvocation; void configureSubInvocationInputsAndOutputs(StringRef OutPath) { auto &SubFEOpts = subInvocation.getFrontendOptions(); SubFEOpts.RequestedAction = FrontendOptions::ActionType::EmitModuleOnly; SubFEOpts.InputsAndOutputs.addPrimaryInputFile(interfacePath); SupplementaryOutputPaths SOPs; SOPs.ModuleOutputPath = OutPath.str(); // Pick a primary output path that will cause problems to use. StringRef MainOut = "/"; SubFEOpts.InputsAndOutputs .setMainAndSupplementaryOutputs({MainOut}, {SOPs}); } void configureSubInvocation() { auto &SearchPathOpts = ctx.SearchPathOpts; auto &LangOpts = ctx.LangOpts; // 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(moduleCachePath); subInvocation.getFrontendOptions().PrebuiltModuleCachePath = prebuiltCachePath; subInvocation.getFrontendOptions().TrackSystemDeps = trackSystemDependencies; // 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; // Tell the subinvocation to serialize dependency hashes if asked to do so. auto &frontendOpts = subInvocation.getFrontendOptions(); frontendOpts.SerializeParseableModuleInterfaceDependencyHashes = serializeDependencyHashes; } bool extractSwiftInterfaceVersionAndArgs( swift::version::Version &Vers, llvm::StringSaver &SubArgSaver, SmallVectorImpl &SubArgs) { auto FileOrError = swift::vfs::getFileOrSTDIN(fs, interfacePath); if (!FileOrError) { diags.diagnose(diagnosticLoc, diag::error_open_input_file, interfacePath, 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(diagnosticLoc, diag::error_extracting_version_from_parseable_interface); return true; } if (!FlagRe.match(SB, &FlagMatches)) { diags.diagnose(diagnosticLoc, 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; } /// Populate the provided \p Deps with \c FileDependency entries including: /// /// - 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 cachePath, all of _its_ dependencies, copied /// out to avoid having to do recursive scanning when rechecking this /// dependency in the future. bool collectDepsForSerialization(CompilerInstance &SubInstance, SmallVectorImpl &Deps, bool IsHashBased) { StringRef SDKPath = SubInstance.getASTContext().SearchPathOpts.SDKPath; auto DTDeps = SubInstance.getDependencyTracker()->getDependencies(); SmallVector InitialDepNames(DTDeps.begin(), DTDeps.end()); InitialDepNames.push_back(interfacePath); llvm::StringSet<> AllDepNames; for (auto const &DepName : InitialDepNames) { if (AllDepNames.insert(DepName).second && dependencyTracker) { dependencyTracker->addDependency(DepName, /*isSystem*/false); } /// Lazily load the dependency buffer if we need it. If we're not /// dealing with a hash-based dependencies, and if the dependency is /// not a .swiftmodule, we can avoid opening the buffer. std::unique_ptr DepBuf = nullptr; auto getDepBuf = [&]() -> llvm::MemoryBuffer * { if (DepBuf) return DepBuf.get(); if (auto Buf = getBufferOfDependency(fs, DepName, interfacePath, diags, diagnosticLoc)) { DepBuf = std::move(Buf); return DepBuf.get(); } return nullptr; }; auto Status = getStatusOfDependency(fs, DepName, interfacePath, diags, diagnosticLoc); if (!Status) return true; bool IsSDKRelative = false; StringRef DepNameToStore = DepName; if (SDKPath.size() > 1 && DepName.startswith(SDKPath)) { assert(DepName.size() > SDKPath.size() && "should never depend on a directory"); if (llvm::sys::path::is_separator(DepName[SDKPath.size()])) { // Is the DepName something like ${SDKPath}/foo.h"? DepNameToStore = DepName.substr(SDKPath.size() + 1); IsSDKRelative = true; } else if (llvm::sys::path::is_separator(SDKPath.back())) { // Is the DepName something like "${SDKPath}foo.h", where SDKPath // itself contains a trailing slash? DepNameToStore = DepName.substr(SDKPath.size()); IsSDKRelative = true; } else { // We have something next to an SDK, like "Foo.sdk.h", that's somehow // become a dependency. } } if (IsHashBased) { auto buf = getDepBuf(); if (!buf) return true; uint64_t hash = xxHash64(buf->getBuffer()); Deps.push_back( FileDependency::hashBased(DepNameToStore, IsSDKRelative, Status->getSize(), hash)); } else { uint64_t mtime = Status->getLastModificationTime().time_since_epoch().count(); Deps.push_back( FileDependency::modTimeBased(DepNameToStore, IsSDKRelative, 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)) { auto buf = getDepBuf(); if (!buf) return true; SmallVector SubDeps; auto VI = serialization::validateSerializedAST(buf->getBuffer(), /*ExtendedValidationInfo=*/nullptr, &SubDeps); if (VI.status != serialization::Status::Valid) { diags.diagnose(diagnosticLoc, 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 (dependencyTracker) dependencyTracker->addDependency(SubDep.getPath(), /*IsSystem=*/false); } } } } return false; } public: ParseableInterfaceBuilder(ASTContext &ctx, StringRef interfacePath, StringRef moduleName, StringRef moduleCachePath, StringRef prebuiltCachePath, bool serializeDependencyHashes = false, bool trackSystemDependencies = false, SourceLoc diagnosticLoc = SourceLoc(), DependencyTracker *tracker = nullptr) : ctx(ctx), fs(*ctx.SourceMgr.getFileSystem()), diags(ctx.Diags), interfacePath(interfacePath), moduleName(moduleName), moduleCachePath(moduleCachePath), prebuiltCachePath(prebuiltCachePath), serializeDependencyHashes(serializeDependencyHashes), trackSystemDependencies(trackSystemDependencies), diagnosticLoc(diagnosticLoc), dependencyTracker(tracker) { configureSubInvocation(); } const CompilerInvocation &getSubInvocation() const { return subInvocation; } bool buildSwiftModule(StringRef OutPath, bool ShouldSerializeDeps, std::unique_ptr *ModuleBuffer) { bool SubError = false; bool RunSuccess = llvm::CrashRecoveryContext().RunSafelyOnThread([&] { // Note that we don't assume cachePath is the same as the Clang // module cache path at this point. if (!moduleCachePath.empty()) (void)llvm::sys::fs::create_directory(moduleCachePath); configureSubInvocationInputsAndOutputs(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(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(diagnosticLoc, diag::unsupported_version_of_parseable_interface, interfacePath, 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(diagnosticLoc, 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(FEOpts.TrackSystemDeps); 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(SubInstance, Deps, FEOpts.SerializeParseableModuleInterfaceDependencyHashes)) { SubError = true; return; } if (ShouldSerializeDeps) SerializationOpts.Dependencies = Deps; SILMod->setSerializeSILAction([&]() { // We don't want to serialize module docs in the cache -- they // will be serialized beside the interface file. serializeToBuffers(Mod, SerializationOpts, ModuleBuffer, /*ModuleDocBuffer*/nullptr, 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; } }; #pragma mark - Module Loading namespace { /// Handles the details of loading parseable interfaces as modules, and will /// do the necessary lookup to determine if we should be loading from the /// normal cache, the prebuilt cache, a module adjacent to the interface, or /// a module that we'll build from a parseable interface. class ParseableInterfaceModuleLoaderImpl { using AccessPathElem = std::pair; friend class swift::ParseableInterfaceModuleLoader; ASTContext &ctx; llvm::vfs::FileSystem &fs; DiagnosticEngine &diags; const StringRef modulePath; const std::string interfacePath; const StringRef moduleName; const StringRef prebuiltCacheDir; const StringRef cacheDir; const SourceLoc diagnosticLoc; DependencyTracker *const dependencyTracker; const ModuleLoadingMode loadMode; ParseableInterfaceModuleLoaderImpl( ASTContext &ctx, StringRef modulePath, StringRef interfacePath, StringRef moduleName, StringRef cacheDir, StringRef prebuiltCacheDir, SourceLoc diagLoc, DependencyTracker *dependencyTracker = nullptr, ModuleLoadingMode loadMode = ModuleLoadingMode::PreferSerialized) : ctx(ctx), fs(*ctx.SourceMgr.getFileSystem()), diags(ctx.Diags), modulePath(modulePath), interfacePath(interfacePath), moduleName(moduleName), prebuiltCacheDir(prebuiltCacheDir), cacheDir(cacheDir), diagnosticLoc(diagLoc), dependencyTracker(dependencyTracker), loadMode(loadMode) {} /// 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. std::string getCacheHash(const CompilerInvocation &SubInvocation) { // 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, interfacePath); // 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()); // Whether or not we're tracking system dependencies affects the // invalidation behavior of this cache item. H = hash_combine(H, SubInvocation.getFrontendOptions().TrackSystemDeps); return llvm::APInt(64, H).toString(36, /*Signed=*/false); } /// Calculate an output filename in \p SubInvocation's cache path that /// includes a hash of relevant key data. void computeCachedOutputPath(const CompilerInvocation &SubInvocation, llvm::SmallString<256> &OutPath) { OutPath = SubInvocation.getClangModuleCachePath(); llvm::sys::path::append(OutPath, SubInvocation.getModuleName()); OutPath.append("-"); OutPath.append(getCacheHash(SubInvocation)); OutPath.append("."); auto OutExt = file_types::getExtension(file_types::TY_SwiftModuleFile); OutPath.append(OutExt); } /// Constructs the full path of the dependency \p dep by prepending the SDK /// path if necessary. StringRef getFullDependencyPath(const FileDependency &dep, SmallVectorImpl &scratch) const { if (!dep.isSDKRelative()) return dep.getPath(); StringRef SDKPath = ctx.SearchPathOpts.SDKPath; scratch.assign(SDKPath.begin(), SDKPath.end()); llvm::sys::path::append(scratch, dep.getPath()); return StringRef(scratch.data(), scratch.size()); } // Checks that a dependency read from the cached module is up to date compared // to the interface file it represents. bool dependencyIsUpToDate(const FileDependency &dep, StringRef fullPath) { auto status = getStatusOfDependency(fs, fullPath, interfacePath, diags, diagnosticLoc); if (!status) return false; // If the sizes differ, then we know the file has changed. if (status->getSize() != dep.getSize()) return false; // Otherwise, if this dependency is verified by modification time, check // it vs. the modification time of the file. if (dep.isModificationTimeBased()) { uint64_t mtime = status->getLastModificationTime().time_since_epoch().count(); return mtime == dep.getModificationTime(); } // Slow path: if the dependency is verified by content hash, check it vs. // the hash of the file. auto buf = getBufferOfDependency(fs, fullPath, interfacePath, diags, diagnosticLoc); if (!buf) return false; return xxHash64(buf->getBuffer()) == dep.getContentHash(); } // Check if all the provided file dependencies are up-to-date compared to // what's currently on disk. bool dependenciesAreUpToDate(ArrayRef deps) { SmallString<128> SDKRelativeBuffer; for (auto &in : deps) { StringRef fullPath = getFullDependencyPath(in, SDKRelativeBuffer); if (dependencyTracker) dependencyTracker->addDependency(fullPath, /*isSystem*/in.isSDKRelative()); if (!dependencyIsUpToDate(in, fullPath)) { LLVM_DEBUG(llvm::dbgs() << "Dep " << in.getPath() << " is directly out of date\n"); return false; } LLVM_DEBUG(llvm::dbgs() << "Dep " << in.getPath() << " is up to date\n"); } return true; } // Check that the output .swiftmodule file is at least as new as all the // dependencies it read when it was built last time. bool serializedASTBufferIsUpToDate( const llvm::MemoryBuffer &buf, SmallVectorImpl &allDeps) { LLVM_DEBUG(llvm::dbgs() << "Validating deps of " << modulePath << "\n"); auto validationInfo = serialization::validateSerializedAST( buf.getBuffer(), /*ExtendedValidationInfo=*/nullptr, &allDeps); if (validationInfo.status != serialization::Status::Valid) return false; return dependenciesAreUpToDate(allDeps); } // Check that the output .swiftmodule file is at least as new as all the // dependencies it read when it was built last time. bool swiftModuleIsUpToDate( StringRef modulePath, SmallVectorImpl &AllDeps, std::unique_ptr &moduleBuffer) { auto OutBuf = fs.getBufferForFile(modulePath); if (!OutBuf) return false; moduleBuffer = std::move(*OutBuf); return serializedASTBufferIsUpToDate(*moduleBuffer, AllDeps); } // Check that a "forwarding" .swiftmodule file is at least as new as all the // dependencies it read when it was built last time. Requires that the // forwarding module has been loaded from disk. bool forwardingModuleIsUpToDate( const ForwardingModule &fwd, SmallVectorImpl &deps, std::unique_ptr &moduleBuffer) { // First, make sure the underlying module path exists and is valid. auto modBuf = fs.getBufferForFile(fwd.underlyingModulePath); if (!modBuf || !serializedASTLooksValid(*modBuf.get())) return false; // Next, check the dependencies in the forwarding file. for (auto &dep : fwd.dependencies) { // Forwarding modules expand SDKRelative paths when generated, so are // guaranteed to be absolute. deps.push_back( FileDependency::modTimeBased( dep.path, /*isSDKRelative=*/false, dep.size, dep.lastModificationTime)); } if (!dependenciesAreUpToDate(deps)) return false; moduleBuffer = std::move(*modBuf); return true; } Optional computePrebuiltModulePath(llvm::SmallString<256> &scratch) { namespace path = llvm::sys::path; StringRef sdkPath = ctx.SearchPathOpts.SDKPath; // Check if the interface file comes from the SDK if (sdkPath.empty() || !hasPrefix(path::begin(interfacePath), path::end(interfacePath), path::begin(sdkPath), path::end(sdkPath))) return None; // Assemble the expected path: $PREBUILT_CACHE/Foo.swiftmodule or // $PREBUILT_CACHE/Foo.swiftmodule/arch.swiftmodule. Note that there's no // cache key here. scratch.append(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(interfacePath)); if (path::extension(inParentDirName) == ".swiftmodule") { assert(path::stem(inParentDirName) == moduleName); path::append(scratch, inParentDirName); } path::append(scratch, path::filename(modulePath)); return scratch.str(); } /// Finds the most appropriate .swiftmodule, whose dependencies are up to /// date, that we can load for the provided .swiftinterface file. llvm::ErrorOr discoverUpToDateModuleForInterface( StringRef modulePath, StringRef cachedOutputPath, SmallVectorImpl &deps) { auto notFoundError = std::make_error_code(std::errc::no_such_file_or_directory); // Keep track of whether we should attempt to load a .swiftmodule adjacent // to the .swiftinterface. bool shouldLoadAdjacentModule = true; switch (loadMode) { case ModuleLoadingMode::OnlyParseable: // Always skip both the caches and adjacent modules, and always build the // parseable interface. return notFoundError; case ModuleLoadingMode::PreferParseable: // If we're in the load mode that prefers .swiftinterfaces, specifically // skip the module adjacent to the interface, but use the caches if // they're present. shouldLoadAdjacentModule = false; break; case ModuleLoadingMode::PreferSerialized: // The rest of the function should be covered by this. break; case ModuleLoadingMode::OnlySerialized: llvm_unreachable("parseable module loader should not have been created"); } // First, check the cached module path. Whatever's in this cache represents // the most up-to-date knowledge we have about the module. if (auto cachedBufOrError = fs.getBufferForFile(cachedOutputPath)) { auto buf = std::move(*cachedBufOrError); // Check to see if the module is a serialized AST. If it's not, then we're // probably dealing with a Forwarding Module, which is a YAML file. bool isForwardingModule = !serialization::isSerializedAST(buf->getBuffer()); // If it's a forwarding module, load the YAML file from disk and check // if it's up-to-date. if (isForwardingModule) { if (auto forwardingModule = ForwardingModule::load(*buf)) { std::unique_ptr moduleBuffer; if (forwardingModuleIsUpToDate(*forwardingModule, deps, moduleBuffer)) return DiscoveredModule::forwarded( forwardingModule->underlyingModulePath, std::move(moduleBuffer)); } // Otherwise, check if the AST buffer itself is up to date. } else if (serializedASTBufferIsUpToDate(*buf, deps)) { return DiscoveredModule::normal(cachedOutputPath, std::move(buf)); } } // If we weren't able to open the file for any reason, including it not // existing, keep going. // If we have a prebuilt cache path, check that too if the interface comes // from the SDK. if (!prebuiltCacheDir.empty()) { llvm::SmallString<256> scratch; std::unique_ptr moduleBuffer; auto path = computePrebuiltModulePath(scratch); if (path && swiftModuleIsUpToDate(*path, deps, moduleBuffer)) return DiscoveredModule::prebuilt(*path, std::move(moduleBuffer)); } // Finally, if there's a module adjacent to the .swiftinterface that we can // _likely_ load (it validates OK and is up to date), 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 (!shouldLoadAdjacentModule) return notFoundError; auto adjacentModuleBuffer = fs.getBufferForFile(modulePath); if (adjacentModuleBuffer) { if (serializedASTBufferIsUpToDate(*adjacentModuleBuffer.get(), deps)) return std::make_error_code(std::errc::not_supported); } else if (adjacentModuleBuffer.getError() != notFoundError) { return std::make_error_code(std::errc::not_supported); } // Couldn't find an up-to-date .swiftmodule, will need to build module from // interface. return notFoundError; } /// Writes the "forwarding module" that will forward to a module in the /// prebuilt cache. /// Since forwarding modules track dependencies separately from the module /// they point to, we'll need to grab the up-to-date file status while doing /// this. bool writeForwardingModule(const DiscoveredModule &mod, StringRef outputPath, ArrayRef deps) { assert(mod.isPrebuilt() && "cannot write forwarding file for non-prebuilt module"); ForwardingModule fwd(mod.path); // FIXME: We need to avoid re-statting all these dependencies, otherwise // we may record out-of-date information. auto addDependency = [&](StringRef path) { auto status = fs.status(path); uint64_t mtime = status->getLastModificationTime().time_since_epoch().count(); fwd.addDependency(path, status->getSize(), mtime); }; // Add the prebuilt module as a dependency of the forwarding module. addDependency(fwd.underlyingModulePath); // Add all the dependencies from the prebuilt module. SmallString<128> SDKRelativeBuffer; for (auto dep : deps) { addDependency(getFullDependencyPath(dep, SDKRelativeBuffer)); } return withOutputFile(diags, outputPath, [&](llvm::raw_pwrite_stream &out) { llvm::yaml::Output yamlWriter(out); yamlWriter << fwd; return false; }); } /// Looks up the best module to load for a given interface, and returns a /// buffer of the module's contents. See the main comment in /// \c ParseableInterfaceModuleLoader.h for an explanation of the module /// loading strategy. llvm::ErrorOr> findOrBuildLoadableModule() { // Track system dependencies if the parent tracker is set to do so. // FIXME: This means -track-system-dependencies isn't honored when the // top-level invocation isn't tracking dependencies bool trackSystemDependencies = false; if (dependencyTracker) { auto ClangDependencyTracker = dependencyTracker->getClangCollector(); trackSystemDependencies = ClangDependencyTracker->needSystemDependencies(); } // Set up a builder if we need to build the module. It'll also set up // the subinvocation we'll need to use to compute the cache paths. ParseableInterfaceBuilder builder( ctx, interfacePath, moduleName, cacheDir, prebuiltCacheDir, /*serializeDependencyHashes*/false, trackSystemDependencies, diagnosticLoc, dependencyTracker); auto &subInvocation = builder.getSubInvocation(); // Compute the output path if we're loading or emitting a cached module. llvm::SmallString<256> cachedOutputPath; computeCachedOutputPath(subInvocation, cachedOutputPath); // Try to find the right module for this interface, either alongside it, // in the cache, or in the prebuilt cache. SmallVector allDeps; auto moduleOrErr = discoverUpToDateModuleForInterface(modulePath, cachedOutputPath, allDeps); // If we errored with anything other than 'no such file or directory', // fail this load and let the other module loader diagnose it. if (!moduleOrErr && moduleOrErr.getError() != std::errc::no_such_file_or_directory) return moduleOrErr.getError(); // We discovered a module! Return that module's buffer so we can load it. if (moduleOrErr) { auto module = std::move(moduleOrErr.get()); // If it's prebuilt, use this time to generate a forwarding module. if (module.isPrebuilt()) if (writeForwardingModule(module, cachedOutputPath, allDeps)) return std::make_error_code(std::errc::not_supported); return std::move(module.moduleBuffer); } std::unique_ptr moduleBuffer; // We didn't discover a module corresponding to this interface. Build one. if (builder.buildSwiftModule(cachedOutputPath, /*shouldSerializeDeps*/true, &moduleBuffer)) return std::make_error_code(std::errc::invalid_argument); assert(moduleBuffer && "failed to write module buffer but returned success?"); return std::move(moduleBuffer); } }; } // end anonymous namespace /// 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) { // If running in OnlySerialized mode, ParseableInterfaceModuleLoader // should not have been constructed at all. assert(LoadMode != ModuleLoadingMode::OnlySerialized); auto &fs = *Ctx.SourceMgr.getFileSystem(); llvm::SmallString<256> ModPath, InPath; // 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); // Create an instance of the Impl to do the heavy lifting. ParseableInterfaceModuleLoaderImpl Impl( Ctx, ModPath, InPath, ModuleID.first.str(), CacheDir, PrebuiltCacheDir, ModuleID.second, dependencyTracker, LoadMode); // Ask the impl to find us a module that we can load or give us an error // telling us that we couldn't load it. auto ModuleBufferOrErr = Impl.findOrBuildLoadableModule(); if (!ModuleBufferOrErr) return ModuleBufferOrErr.getError(); if (ModuleBuffer) { *ModuleBuffer = std::move(*ModuleBufferOrErr); } // Delegate back to the serialized module loader to load the module doc. llvm::SmallString<256> DocPath{DirPath}; path::append(DocPath, ModuleDocFilename); auto DocLoadErr = SerializedModuleLoaderBase::openModuleDocFile(ModuleID, DocPath, ModuleDocBuffer); if (DocLoadErr) return DocLoadErr; return std::error_code(); } bool ParseableInterfaceModuleLoader::buildSwiftModuleFromSwiftInterface( ASTContext &Ctx, StringRef CacheDir, StringRef PrebuiltCacheDir, StringRef ModuleName, StringRef InPath, StringRef OutPath, bool SerializeDependencyHashes, bool TrackSystemDependencies) { ParseableInterfaceBuilder builder(Ctx, InPath, ModuleName, CacheDir, PrebuiltCacheDir, SerializeDependencyHashes, TrackSystemDependencies); // FIXME: We really only want to serialize 'important' dependencies here, if // we want to ship the built swiftmodules to another machine. return builder.buildSwiftModule(OutPath, /*shouldSerializeDeps*/true, /*ModuleBuffer*/nullptr); }