diff --git a/include/swift/Frontend/FrontendInputsAndOutputs.h b/include/swift/Frontend/FrontendInputsAndOutputs.h index 8da716bdfaa..dd7accfbc56 100644 --- a/include/swift/Frontend/FrontendInputsAndOutputs.h +++ b/include/swift/Frontend/FrontendInputsAndOutputs.h @@ -173,7 +173,7 @@ public: private: friend class ArgsToFrontendOptionsConverter; - + friend class TextualInterfaceModuleLoader; void setMainAndSupplementaryOutputs( ArrayRef outputFiles, ArrayRef supplementaryOutputs); diff --git a/include/swift/Frontend/ParseableInterfaceSupport.h b/include/swift/Frontend/ParseableInterfaceSupport.h index 6195fbee20c..aa8ed8712bd 100644 --- a/include/swift/Frontend/ParseableInterfaceSupport.h +++ b/include/swift/Frontend/ParseableInterfaceSupport.h @@ -14,6 +14,7 @@ #define SWIFT_FRONTEND_PARSEABLEINTERFACESUPPORT_H #include "swift/Basic/LLVM.h" +#include "swift/Serialization/SerializedModuleLoader.h" #include "llvm/Support/Regex.h" namespace swift { @@ -48,6 +49,42 @@ bool emitParseableInterface(raw_ostream &out, TextualInterfaceOptions const &Opts, ModuleDecl *M); + +/// A ModuleLoader that runs a subordinate \c CompilerInvocation and \c +/// CompilerInstance to convert .swiftinterface files to .swiftmodule +/// files on the fly, caching the resulting .swiftmodules in the module cache +/// directory, and loading the serialized .swiftmodules from there. +class TextualInterfaceModuleLoader : public SerializedModuleLoaderBase { + explicit TextualInterfaceModuleLoader(ASTContext &ctx, StringRef cacheDir, + DependencyTracker *tracker) + : SerializedModuleLoaderBase(ctx, tracker), + CacheDir(cacheDir) + {} + + std::string CacheDir; + + void + configureSubInvocationAndOutputPath(CompilerInvocation &SubInvocation, + StringRef InPath, + llvm::SmallString<128> &OutPath); + + std::error_code + openModuleFiles(StringRef DirName, StringRef ModuleFilename, + StringRef ModuleDocFilename, + std::unique_ptr *ModuleBuffer, + std::unique_ptr *ModuleDocBuffer, + llvm::SmallVectorImpl &Scratch) override; + +public: + static std::unique_ptr + create(ASTContext &ctx, StringRef cacheDir, + DependencyTracker *tracker = nullptr) { + return std::unique_ptr( + new TextualInterfaceModuleLoader(ctx, cacheDir, tracker)); + } +}; + + } // end namespace swift #endif diff --git a/lib/Frontend/CMakeLists.txt b/lib/Frontend/CMakeLists.txt index b1701ae80c6..5a79eaabf80 100644 --- a/lib/Frontend/CMakeLists.txt +++ b/lib/Frontend/CMakeLists.txt @@ -17,6 +17,7 @@ add_swift_library(swiftFrontend STATIC swiftMigrator swiftOption swiftParseSIL + swiftSILOptimizer swiftSema swiftSerialization) diff --git a/lib/Frontend/ParseableInterfaceSupport.cpp b/lib/Frontend/ParseableInterfaceSupport.cpp index 8455993b689..75f6452b1a9 100644 --- a/lib/Frontend/ParseableInterfaceSupport.cpp +++ b/lib/Frontend/ParseableInterfaceSupport.cpp @@ -10,19 +10,218 @@ // //===----------------------------------------------------------------------===// +#define DEBUG_TYPE "textual-module-interface" #include "swift/AST/ASTContext.h" #include "swift/AST/Decl.h" #include "swift/AST/DiagnosticsFrontend.h" #include "swift/AST/Module.h" +#include "swift/Frontend/Frontend.h" #include "swift/Frontend/ParseableInterfaceSupport.h" +#include "swift/SILOptimizer/PassManager/Passes.h" +#include "swift/Serialization/SerializationOptions.h" #include "clang/Basic/Module.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; #define SWIFT_TOOLS_VERSION_KEY "swift-tools-version" #define SWIFT_MODULE_FLAGS_KEY "swift-module-flags" +static bool +extractSwiftInterfaceVersionAndArgs(DiagnosticEngine &Diags, + clang::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(SourceLoc(), diag::error_open_input_file, + SwiftInterfacePathIn, FileOrError.getError().message()); + return true; + } + auto SB = FileOrError.get()->getBuffer(); + auto VersRe = getSwiftInterfaceToolsVersionRegex(); + auto FlagRe = getSwiftInterfaceModuleFlagsRegex(); + SmallVector VersMatches, FlagMatches; + if (!VersRe.match(SB, &VersMatches)) { + Diags.diagnose(SourceLoc(), + diag::error_extracting_version_from_textual_interface); + return true; + } + if (!FlagRe.match(SB, &FlagMatches)) { + Diags.diagnose(SourceLoc(), + diag::error_extracting_flags_from_textual_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; +} + +void +TextualInterfaceModuleLoader::configureSubInvocationAndOutputPath( + CompilerInvocation &SubInvocation, + StringRef InPath, + llvm::SmallString<128> &OutPath) { + + 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); + + // Calculate an output filename based on the SubInvocation hash, and + // wire up the SubInvocation's InputsAndOutputs to contain both + // input and output filenames. + OutPath = CacheDir; + llvm::sys::path::append(OutPath, llvm::sys::path::stem(InPath)); + OutPath.append("-"); + OutPath.append(SubInvocation.getPCHHash()); + OutPath.append("."); + auto Ext = file_types::getExtension(file_types::TY_SwiftModuleFile); + OutPath.append(Ext); + + auto &FEOpts = SubInvocation.getFrontendOptions(); + FEOpts.RequestedAction = FrontendOptions::ActionType::EmitModuleOnly; + FEOpts.InputsAndOutputs.addPrimaryInputFile(InPath); + FEOpts.InputsAndOutputs.setMainAndSupplementaryOutputs( + {OutPath.str()}, {SupplementaryOutputPaths()}); +} + +// FIXME: this needs to be a more extensive up-to-date check. +static bool +swiftModuleIsUpToDate(clang::vfs::FileSystem &FS, + StringRef InPath, StringRef OutPath) { + if (FS.exists(OutPath)) { + auto InStatus = FS.status(InPath); + auto OutStatus = FS.status(OutPath); + if (InStatus && OutStatus) { + return InStatus.get().getLastModificationTime() <= + OutStatus.get().getLastModificationTime(); + } + } + return false; +} + +static bool buildSwiftModuleFromSwiftInterface( + clang::vfs::FileSystem &FS, DiagnosticEngine &Diags, + CompilerInvocation &SubInvocation, StringRef InPath, StringRef OutPath) { + bool SubError = false; + bool RunSuccess = llvm::CrashRecoveryContext().RunSafelyOnThread([&] { + + llvm::BumpPtrAllocator SubArgsAlloc; + llvm::StringSaver SubArgSaver(SubArgsAlloc); + SmallVector SubArgs; + swift::version::Version Vers; + if (extractSwiftInterfaceVersionAndArgs(Diags, FS, InPath, Vers, + SubArgSaver, SubArgs)) { + SubError = true; + return; + } + + if (SubInvocation.parseArgs(SubArgs, Diags)) { + SubError = true; + return; + } + + // 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\n"); + CompilerInstance SubInstance; + if (SubInstance.setup(SubInvocation)) { + SubError = true; + return; + } + + LLVM_DEBUG(llvm::dbgs() << "Performing sema\n"); + SubInstance.performSema(); + if (SubInstance.getASTContext().hadError()) { + SubError = true; + return; + } + + auto Mod = SubInstance.getMainModule(); + auto SILMod = SubInstance.takeSILModule(); + if (SILMod) { + LLVM_DEBUG(llvm::dbgs() << "Running SIL diagnostic passes\n"); + if (runSILDiagnosticPasses(*SILMod)) { + SubError = true; + return; + } + } + + LLVM_DEBUG(llvm::dbgs() << "Serializing " << OutPath << "\n"); + SerializationOptions serializationOpts; + std::string OutPathStr = OutPath; + serializationOpts.OutputPath = OutPathStr.c_str(); + serializationOpts.SerializeAllSIL = true; + serialize(Mod, serializationOpts, SILMod.get()); + SubError = Diags.hadAnyError(); + }); + return !RunSuccess || SubError; +} + +/// 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 TextualInterfaceModuleLoader::openModuleFiles( + StringRef DirName, StringRef ModuleFilename, StringRef ModuleDocFilename, + std::unique_ptr *ModuleBuffer, + std::unique_ptr *ModuleDocBuffer, + llvm::SmallVectorImpl &Scratch) { + + auto &FS = *Ctx.SourceMgr.getFileSystem(); + auto &Diags = Ctx.Diags; + llvm::SmallString<128> InPath, OutPath; + + // First check to see if the .swiftinterface exists at all. Bail if not. + InPath = DirName; + llvm::sys::path::append(InPath, ModuleFilename); + auto Ext = file_types::getExtension(file_types::TY_SwiftModuleInterfaceFile); + llvm::sys::path::replace_extension(InPath, Ext); + if (!FS.exists(InPath)) + return std::make_error_code(std::errc::no_such_file_or_directory); + + // Set up a _potential_ sub-invocation to consume the .swiftinterface and emit + // the .swiftmodule. + CompilerInvocation SubInvocation; + configureSubInvocationAndOutputPath(SubInvocation, InPath, OutPath); + + // Evaluate if we need to run this sub-invocation, and if so run it. + if (!swiftModuleIsUpToDate(FS, InPath, OutPath)) { + if (buildSwiftModuleFromSwiftInterface(FS, Diags, SubInvocation, InPath, + OutPath)) + 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"); + auto ErrorCode = SerializedModuleLoaderBase::openModuleFiles( + CacheDir, llvm::sys::path::filename(OutPath), ModuleDocFilename, + ModuleBuffer, ModuleDocBuffer, Scratch); + LLVM_DEBUG(llvm::dbgs() << "Loaded " << OutPath + << " via normal module loader with error: " + << ErrorCode.message() << "\n"); + return ErrorCode; +} + /// Diagnose any scoped imports in \p imports, i.e. those with a non-empty /// access path. These are not yet supported by textual interfaces, since the /// information about the declaration kind is not preserved through the binary