From adacecb47a4809eef50e5de6185c5d30586b5742 Mon Sep 17 00:00:00 2001 From: Robert Widmann Date: Wed, 11 Nov 2020 14:17:00 -0800 Subject: [PATCH 1/4] [NFC] Split Make-Style Dependency Emission from FrontendTool --- lib/FrontendTool/CMakeLists.txt | 1 + lib/FrontendTool/Dependencies.h | 31 +++++ lib/FrontendTool/FrontendTool.cpp | 124 +----------------- lib/FrontendTool/MakeStyleDependencies.cpp | 144 +++++++++++++++++++++ 4 files changed, 178 insertions(+), 122 deletions(-) create mode 100644 lib/FrontendTool/Dependencies.h create mode 100644 lib/FrontendTool/MakeStyleDependencies.cpp diff --git a/lib/FrontendTool/CMakeLists.txt b/lib/FrontendTool/CMakeLists.txt index 26b37f661b82b..210bb45b47674 100644 --- a/lib/FrontendTool/CMakeLists.txt +++ b/lib/FrontendTool/CMakeLists.txt @@ -2,6 +2,7 @@ set_swift_llvm_is_available() add_swift_host_library(swiftFrontendTool STATIC FrontendTool.cpp ImportedModules.cpp + MakeStyleDependencies.cpp ScanDependencies.cpp TBD.cpp) add_dependencies(swiftFrontendTool diff --git a/lib/FrontendTool/Dependencies.h b/lib/FrontendTool/Dependencies.h new file mode 100644 index 0000000000000..24e55ef1cba71 --- /dev/null +++ b/lib/FrontendTool/Dependencies.h @@ -0,0 +1,31 @@ +//===--- Dependencies.h -- Unified header for dependnecy tracing utilies --===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2020 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_FRONTENDTOOL_DEPENDENCIES_H +#define SWIFT_FRONTENDTOOL_DEPENDENCIES_H + +namespace swift { + +class ASTContext; +class DependencyTracker; +class DiagnosticEngine; +class FrontendOptions; +class InputFile; +class ModuleDecl; + +bool emitMakeDependenciesIfNeeded(DiagnosticEngine &diags, + DependencyTracker *depTracker, + const FrontendOptions &opts, + const InputFile &input); +} // end namespace swift + +#endif diff --git a/lib/FrontendTool/FrontendTool.cpp b/lib/FrontendTool/FrontendTool.cpp index a57b6d69e1be8..501bfcdf7cd7c 100644 --- a/lib/FrontendTool/FrontendTool.cpp +++ b/lib/FrontendTool/FrontendTool.cpp @@ -21,7 +21,7 @@ //===----------------------------------------------------------------------===// #include "swift/FrontendTool/FrontendTool.h" -#include "ImportedModules.h" +#include "Dependencies.h" #include "ScanDependencies.h" #include "TBD.h" @@ -107,132 +107,12 @@ static std::string displayName(StringRef MainExecutablePath) { return Name; } -StringRef -swift::frontend::utils::escapeForMake(StringRef raw, - llvm::SmallVectorImpl &buffer) { - buffer.clear(); - - // The escaping rules for GNU make are complicated due to the various - // subsitutions and use of the tab in the leading position for recipes. - // Various symbols have significance in different contexts. It is not - // possible to correctly quote all characters in Make (as of 3.7). Match - // gcc and clang's behaviour for the escaping which covers only a subset of - // characters. - for (unsigned I = 0, E = raw.size(); I != E; ++I) { - switch (raw[I]) { - case '#': // Handle '#' the broken GCC way - buffer.push_back('\\'); - break; - - case ' ': - for (unsigned J = I; J && raw[J - 1] == '\\'; --J) - buffer.push_back('\\'); - buffer.push_back('\\'); - break; - - case '$': // $ is escaped by $ - buffer.push_back('$'); - break; - } - buffer.push_back(raw[I]); - } - buffer.push_back('\0'); - - return buffer.data(); -} - -/// This sorting function is used to stabilize the order in which dependencies -/// are emitted into \c .d files that are consumed by external build systems. -/// This serves to eliminate order as a source of non-determinism in these -/// outputs. -/// -/// The exact sorting predicate is not important. Currently, it is a -/// lexicographic comparison that reverses the provided strings before applying -/// the sorting predicate. This has the benefit of being somewhat -/// invariant with respect to the installation location of various system -/// components. e.g. on two systems, the same file identified by two different -/// paths differing only in their relative install location such as -/// -/// /Applications/MyXcode.app/Path/To/A/Framework/In/The/SDK/Header.h -/// /Applications/Xcodes/AnotherXcode.app/Path/To/A/Framework/In/The/SDK/Header.h -/// -/// should appear in roughly the same order relative to other paths. Ultimately, -/// this makes it easier to test the contents of the emitted files with tools -/// like FileCheck. -static std::vector -reversePathSortedFilenames(const ArrayRef elts) { - std::vector tmp(elts.begin(), elts.end()); - std::sort(tmp.begin(), tmp.end(), [](const std::string &a, - const std::string &b) -> bool { - return std::lexicographical_compare(a.rbegin(), a.rend(), - b.rbegin(), b.rend()); - }); - return tmp; -} - -/// Emits a Make-style dependencies file. -static bool emitMakeDependenciesIfNeeded(DiagnosticEngine &diags, - DependencyTracker *depTracker, - const FrontendOptions &opts, - const InputFile &input) { - const std::string &dependenciesFilePath = input.dependenciesFilePath(); - if (dependenciesFilePath.empty()) - return false; - - std::error_code EC; - llvm::raw_fd_ostream out(dependenciesFilePath, EC, llvm::sys::fs::F_None); - - if (out.has_error() || EC) { - diags.diagnose(SourceLoc(), diag::error_opening_output, - dependenciesFilePath, EC.message()); - out.clear_error(); - return true; - } - - llvm::SmallString<256> buffer; - - // collect everything in memory to avoid redundant work - // when there are multiple targets - std::string dependencyString; - - // First include all other files in the module. Make-style dependencies - // need to be conservative! - auto inputPaths = - reversePathSortedFilenames(opts.InputsAndOutputs.getInputFilenames()); - for (auto const &path : inputPaths) { - dependencyString.push_back(' '); - dependencyString.append(frontend::utils::escapeForMake(path, buffer).str()); - } - // Then print dependencies we've picked up during compilation. - auto dependencyPaths = - reversePathSortedFilenames(depTracker->getDependencies()); - for (auto const &path : dependencyPaths) { - dependencyString.push_back(' '); - dependencyString.append(frontend::utils::escapeForMake(path, buffer).str()); - } - auto incrementalDependencyPaths = - reversePathSortedFilenames(depTracker->getIncrementalDependencies()); - for (auto const &path : incrementalDependencyPaths) { - dependencyString.push_back(' '); - dependencyString.append(frontend::utils::escapeForMake(path, buffer).str()); - } - - // FIXME: Xcode can't currently handle multiple targets in a single - // dependency line. - opts.forAllOutputPaths(input, [&](const StringRef targetName) { - auto targetNameEscaped = frontend::utils::escapeForMake(targetName, buffer); - out << targetNameEscaped << " :" << dependencyString << '\n'; - }); - - return false; -} - static void emitMakeDependenciesIfNeeded(DiagnosticEngine &diags, DependencyTracker *depTracker, const FrontendOptions &opts) { opts.InputsAndOutputs.forEachInputProducingSupplementaryOutput( [&](const InputFile &f) -> bool { - return emitMakeDependenciesIfNeeded(diags, depTracker, opts, f); + return swift::emitMakeDependenciesIfNeeded(diags, depTracker, opts, f); }); } diff --git a/lib/FrontendTool/MakeStyleDependencies.cpp b/lib/FrontendTool/MakeStyleDependencies.cpp new file mode 100644 index 0000000000000..f3e4d77a213ef --- /dev/null +++ b/lib/FrontendTool/MakeStyleDependencies.cpp @@ -0,0 +1,144 @@ +//===--- MakeStyleDependencies.cpp -- Emit make-style dependencies --------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#include "Dependencies.h" +#include "swift/AST/DiagnosticEngine.h" +#include "swift/AST/DiagnosticsFrontend.h" +#include "swift/AST/ModuleLoader.h" +#include "swift/Frontend/FrontendOptions.h" +#include "swift/Frontend/InputFile.h" +#include "swift/FrontendTool/FrontendTool.h" + +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" + +using namespace swift; + +StringRef +swift::frontend::utils::escapeForMake(StringRef raw, + llvm::SmallVectorImpl &buffer) { + buffer.clear(); + + // The escaping rules for GNU make are complicated due to the various + // subsitutions and use of the tab in the leading position for recipes. + // Various symbols have significance in different contexts. It is not + // possible to correctly quote all characters in Make (as of 3.7). Match + // gcc and clang's behaviour for the escaping which covers only a subset of + // characters. + for (unsigned I = 0, E = raw.size(); I != E; ++I) { + switch (raw[I]) { + case '#': // Handle '#' the broken GCC way + buffer.push_back('\\'); + break; + + case ' ': + for (unsigned J = I; J && raw[J - 1] == '\\'; --J) + buffer.push_back('\\'); + buffer.push_back('\\'); + break; + + case '$': // $ is escaped by $ + buffer.push_back('$'); + break; + } + buffer.push_back(raw[I]); + } + buffer.push_back('\0'); + + return buffer.data(); +} + +/// This sorting function is used to stabilize the order in which dependencies +/// are emitted into \c .d files that are consumed by external build systems. +/// This serves to eliminate order as a source of non-determinism in these +/// outputs. +/// +/// The exact sorting predicate is not important. Currently, it is a +/// lexicographic comparison that reverses the provided strings before applying +/// the sorting predicate. This has the benefit of being somewhat +/// invariant with respect to the installation location of various system +/// components. e.g. on two systems, the same file identified by two different +/// paths differing only in their relative install location such as +/// +/// /Applications/MyXcode.app/Path/To/A/Framework/In/The/SDK/Header.h +/// /Applications/Xcodes/AnotherXcode.app/Path/To/A/Framework/In/The/SDK/Header.h +/// +/// should appear in roughly the same order relative to other paths. Ultimately, +/// this makes it easier to test the contents of the emitted files with tools +/// like FileCheck. +static std::vector +reversePathSortedFilenames(const ArrayRef elts) { + std::vector tmp(elts.begin(), elts.end()); + std::sort(tmp.begin(), tmp.end(), + [](const std::string &a, const std::string &b) -> bool { + return std::lexicographical_compare(a.rbegin(), a.rend(), + b.rbegin(), b.rend()); + }); + return tmp; +} + +/// Emits a Make-style dependencies file. +bool swift::emitMakeDependenciesIfNeeded(DiagnosticEngine &diags, + DependencyTracker *depTracker, + const FrontendOptions &opts, + const InputFile &input) { + const std::string &dependenciesFilePath = input.dependenciesFilePath(); + if (dependenciesFilePath.empty()) + return false; + + std::error_code EC; + llvm::raw_fd_ostream out(dependenciesFilePath, EC, llvm::sys::fs::F_None); + + if (out.has_error() || EC) { + diags.diagnose(SourceLoc(), diag::error_opening_output, + dependenciesFilePath, EC.message()); + out.clear_error(); + return true; + } + + llvm::SmallString<256> buffer; + + // collect everything in memory to avoid redundant work + // when there are multiple targets + std::string dependencyString; + + // First include all other files in the module. Make-style dependencies + // need to be conservative! + auto inputPaths = + reversePathSortedFilenames(opts.InputsAndOutputs.getInputFilenames()); + for (auto const &path : inputPaths) { + dependencyString.push_back(' '); + dependencyString.append(frontend::utils::escapeForMake(path, buffer).str()); + } + // Then print dependencies we've picked up during compilation. + auto dependencyPaths = + reversePathSortedFilenames(depTracker->getDependencies()); + for (auto const &path : dependencyPaths) { + dependencyString.push_back(' '); + dependencyString.append(frontend::utils::escapeForMake(path, buffer).str()); + } + auto incrementalDependencyPaths = + reversePathSortedFilenames(depTracker->getIncrementalDependencies()); + for (auto const &path : incrementalDependencyPaths) { + dependencyString.push_back(' '); + dependencyString.append(frontend::utils::escapeForMake(path, buffer).str()); + } + + // FIXME: Xcode can't currently handle multiple targets in a single + // dependency line. + opts.forAllOutputPaths(input, [&](const StringRef targetName) { + auto targetNameEscaped = frontend::utils::escapeForMake(targetName, buffer); + out << targetNameEscaped << " :" << dependencyString << '\n'; + }); + + return false; +} From 17143cb9c3d936a3e9567074c8fd0c3da34c1af6 Mon Sep 17 00:00:00 2001 From: Robert Widmann Date: Wed, 11 Nov 2020 14:17:36 -0800 Subject: [PATCH 2/4] [NFC] Split ModuleTrace Infrastructure out of FrontendTool --- lib/FrontendTool/CMakeLists.txt | 1 + lib/FrontendTool/Dependencies.h | 7 + lib/FrontendTool/FrontendTool.cpp | 744 +----------------------- lib/FrontendTool/ImportedModules.cpp | 2 +- lib/FrontendTool/ImportedModules.h | 26 - lib/FrontendTool/LoadedModuleTrace.cpp | 764 +++++++++++++++++++++++++ lib/FrontendTool/ScanDependencies.cpp | 3 +- 7 files changed, 777 insertions(+), 770 deletions(-) delete mode 100644 lib/FrontendTool/ImportedModules.h create mode 100644 lib/FrontendTool/LoadedModuleTrace.cpp diff --git a/lib/FrontendTool/CMakeLists.txt b/lib/FrontendTool/CMakeLists.txt index 210bb45b47674..67e725145dedb 100644 --- a/lib/FrontendTool/CMakeLists.txt +++ b/lib/FrontendTool/CMakeLists.txt @@ -2,6 +2,7 @@ set_swift_llvm_is_available() add_swift_host_library(swiftFrontendTool STATIC FrontendTool.cpp ImportedModules.cpp + LoadedModuleTrace.cpp MakeStyleDependencies.cpp ScanDependencies.cpp TBD.cpp) diff --git a/lib/FrontendTool/Dependencies.h b/lib/FrontendTool/Dependencies.h index 24e55ef1cba71..b6ad92fb44467 100644 --- a/lib/FrontendTool/Dependencies.h +++ b/lib/FrontendTool/Dependencies.h @@ -22,10 +22,17 @@ class FrontendOptions; class InputFile; class ModuleDecl; +/// Emit the names of the modules imported by \c mainModule. +bool emitImportedModules(ModuleDecl *mainModule, const FrontendOptions &opts); bool emitMakeDependenciesIfNeeded(DiagnosticEngine &diags, DependencyTracker *depTracker, const FrontendOptions &opts, const InputFile &input); +bool emitLoadedModuleTraceIfNeeded(ModuleDecl *mainModule, + DependencyTracker *depTracker, + const FrontendOptions &opts, + const InputFile &input); + } // end namespace swift #endif diff --git a/lib/FrontendTool/FrontendTool.cpp b/lib/FrontendTool/FrontendTool.cpp index 501bfcdf7cd7c..e67962af25023 100644 --- a/lib/FrontendTool/FrontendTool.cpp +++ b/lib/FrontendTool/FrontendTool.cpp @@ -93,12 +93,6 @@ #include #include -#if !defined(_MSC_VER) && !defined(__MINGW32__) -#include -#else -#include -#endif - using namespace swift; static std::string displayName(StringRef MainExecutablePath) { @@ -116,748 +110,14 @@ static void emitMakeDependenciesIfNeeded(DiagnosticEngine &diags, }); } -// MARK: - Module Trace - -namespace { -struct SwiftModuleTraceInfo { - Identifier Name; - std::string Path; - bool IsImportedDirectly; - bool SupportsLibraryEvolution; -}; - -struct LoadedModuleTraceFormat { - static const unsigned CurrentVersion = 2; - unsigned Version; - Identifier Name; - std::string Arch; - std::vector SwiftModules; -}; -} - -namespace swift { -namespace json { -template <> struct ObjectTraits { - static void mapping(Output &out, SwiftModuleTraceInfo &contents) { - StringRef name = contents.Name.str(); - out.mapRequired("name", name); - out.mapRequired("path", contents.Path); - out.mapRequired("isImportedDirectly", contents.IsImportedDirectly); - out.mapRequired("supportsLibraryEvolution", - contents.SupportsLibraryEvolution); - } -}; - -// Version notes: -// 1. Keys: name, arch, swiftmodules -// 2. New keys: version, swiftmodulesDetailedInfo -template <> struct ObjectTraits { - static void mapping(Output &out, LoadedModuleTraceFormat &contents) { - out.mapRequired("version", contents.Version); - - StringRef name = contents.Name.str(); - out.mapRequired("name", name); - - out.mapRequired("arch", contents.Arch); - - // The 'swiftmodules' key is kept for backwards compatibility. - std::vector moduleNames; - for (auto &m : contents.SwiftModules) - moduleNames.push_back(m.Path); - out.mapRequired("swiftmodules", moduleNames); - - out.mapRequired("swiftmodulesDetailedInfo", contents.SwiftModules); - } -}; -} -} - -static bool isClangOverlayOf(ModuleDecl *potentialOverlay, - ModuleDecl *potentialUnderlying) { - return !potentialOverlay->isNonSwiftModule() - && potentialUnderlying->isNonSwiftModule() - && potentialOverlay->getName() == potentialUnderlying->getName(); -} - -// TODO: Delete this once changes from https://reviews.llvm.org/D83449 land on -// apple/llvm-project's swift/main branch. -template -static bool contains(const SetLike &setLike, Item item) { - return setLike.find(item) != setLike.end(); -} - -/// Get a set of modules imported by \p module. -/// -/// By default, all imports are included. -static void getImmediateImports( - ModuleDecl *module, - SmallPtrSetImpl &imports, - ModuleDecl::ImportFilter importFilter = { - ModuleDecl::ImportFilterKind::Exported, - ModuleDecl::ImportFilterKind::Default, - ModuleDecl::ImportFilterKind::ImplementationOnly, - ModuleDecl::ImportFilterKind::SPIAccessControl, - ModuleDecl::ImportFilterKind::ShadowedByCrossImportOverlay - }) { - SmallVector importList; - module->getImportedModules(importList, importFilter); - - for (ImportedModule &import : importList) - imports.insert(import.importedModule); -} - -namespace { -/// Helper type for computing (approximate) information about ABI-dependencies. -/// -/// This misses out on details such as typealiases and more. -/// See the "isImportedDirectly" field above for more details. -class ABIDependencyEvaluator { - /// Map of ABIs exported by a particular module, excluding itself. - /// - /// For example, consider (primed letters represent Clang modules): - /// \code - /// - A is @_exported-imported by B - /// - B is #imported by C' (via a compiler-generated umbrella header) - /// - C' is @_exported-imported by C (Swift overlay) - /// - D' is #imported by E' - /// - D' is @_exported-imported by D (Swift overlay) - /// - E' is @_exported-imported by E (Swift overlay) - /// \endcode - /// - /// Then the \c abiExportMap will be - /// \code - /// { A: {}, B: {A}, C: {B}, C': {B}, D: {}, D': {}, E: {D}, E': {D'} } - /// \endcode - /// - /// \b WARNING: Use \c reexposeImportedABI instead of inserting directly. - llvm::DenseMap> abiExportMap; - - /// Stack for depth-first traversal. - SmallVector searchStack; - - llvm::DenseSet visited; - - /// Helper function to handle invariant violations as crashes in debug mode. - void crashOnInvariantViolation( - llvm::function_ref f) const; - - /// Computes the ABI exports for \p importedModule and adds them to - /// \p module's ABI exports. - /// - /// If \p includeImportedModule is true, also adds \p importedModule to - /// \p module's ABI exports. - /// - /// Correct way to add entries to \c abiExportMap. - void reexposeImportedABI(ModuleDecl *module, ModuleDecl *importedModule, - bool includeImportedModule = true); - - /// Check if a Swift module is an overlay for some Clang module. - /// - /// FIXME: Delete this hack once SR-13363 is fixed and ModuleDecl has the - /// right API which we can use directly. - bool isOverlayOfClangModule(ModuleDecl *swiftModule); - - /// Check for cases where we have a fake cycle through an overlay. - /// - /// Sometimes, we have fake cycles in the import graph due to the Clang - /// importer injecting overlays between Clang modules. These don't represent - /// an actual cycle in the build, so we should ignore them. - /// - /// We check this lazily after detecting a cycle because it is difficult to - /// determine at the point where we see the overlay whether it was incorrectly - /// injected by the Clang importer or whether any of its imports will - /// eventually lead to a cycle. - /// - /// For more details, see [NOTE: ABIDependencyEvaluator-fake-cycle-detection] - /// - /// \param startOfCycle A pointer to the element of \c searchStack where - /// the module \em first appeared. - /// - /// \pre The module on top of \c searchStack is the same module as - /// *startOfCycle. - /// - /// \pre searchStack.begin() <= startOfCycle < searchStack.end() - bool isFakeCycleThroughOverlay(ModuleDecl **startOfCycle); - - /// Recursive step in computing ABI dependencies. - /// - /// Use this method instead of using the \c forClangModule/\c forSwiftModule - /// methods. - void computeABIDependenciesForModule(ModuleDecl *module); - void computeABIDependenciesForSwiftModule(ModuleDecl *module); - void computeABIDependenciesForClangModule(ModuleDecl *module); - - static void printModule(const ModuleDecl *module, llvm::raw_ostream &os); - - template - static void printModuleSet(const SetLike &set, llvm::raw_ostream &os); - -public: - ABIDependencyEvaluator() = default; - ABIDependencyEvaluator(const ABIDependencyEvaluator &) = delete; - ABIDependencyEvaluator(ABIDependencyEvaluator &&) = default; - - void getABIDependenciesForSwiftModule( - ModuleDecl *module, SmallPtrSetImpl &abiDependencies); - - void printABIExportMap(llvm::raw_ostream &os) const; -}; -} // end anonymous namespace - -// See [NOTE: Bailing-vs-crashing-in-trace-emission]. -// TODO: Use PrettyStackTrace instead? -void ABIDependencyEvaluator::crashOnInvariantViolation( - llvm::function_ref f) const { -#ifndef NDEBUG - std::string msg; - llvm::raw_string_ostream os(msg); - os << "error: invariant violation: "; - f(os); - llvm::report_fatal_error(os.str()); -#endif -} - -// [NOTE: Trace-Clang-submodule-complexity] -// -// A Clang module may have zero or more submodules. In practice, when traversing -// the imports of a module, we observe that different submodules of the same -// top-level module (almost) freely import each other. Despite this, we still -// need to conceptually traverse the tree formed by the submodule relationship -// (with the top-level module being the root). -// -// This needs to be taken care of in two ways: -// 1. We need to make sure we only go towards the leaves. It's okay if we "jump" -// branches, so long as we don't try to visit an ancestor when one of its -// descendants is still on the traversal stack, so that we don't end up with -// arbitrarily complex intra-module cycles. -// See also: [NOTE: Intra-module-leafwards-traversal]. -// 2. When adding entries to the ABI export map, we need to avoid marking -// dependencies within the same top-level module. This step is needed in -// addition to step 1 to avoid creating cycles like -// Overlay -> Underlying -> Submodule -> Overlay. - -void ABIDependencyEvaluator::reexposeImportedABI( - ModuleDecl *module, ModuleDecl *importedModule, - bool includeImportedModule) { - if (module == importedModule) { - crashOnInvariantViolation([&](llvm::raw_string_ostream &os) { - os << "module "; printModule(module, os); os << " imports itself!\n"; - }); - return; - } - - auto addToABIExportMap = [this](ModuleDecl *module, ModuleDecl *reexport) { - if (module == reexport) { - crashOnInvariantViolation([&](llvm::raw_string_ostream &os){ - os << "expected module "; printModule(reexport, os); - os << " to not re-export itself\n"; - }); - return; - } - if (reexport->isNonSwiftModule() - && module->isNonSwiftModule() - && module->getTopLevelModule() == reexport->getTopLevelModule()) { - // Dependencies within the same top-level Clang module are not useful. - // See also: [NOTE: Trace-Clang-submodule-complexity]. - return; - } - - // We only care about dependencies across top-level modules and we want to - // avoid exploding abiExportMap with submodules. So we only insert entries - // after calling getTopLevelModule(). - - if (::isClangOverlayOf(module, reexport)) { - // For overlays, we need to have a dependency on the underlying module. - // Otherwise, we might accidentally create a Swift -> Swift cycle. - abiExportMap[module].insert( - reexport->getTopLevelModule(/*preferOverlay*/false)); - return; - } - abiExportMap[module].insert( - reexport->getTopLevelModule(/*preferOverlay*/true)); - }; - - computeABIDependenciesForModule(importedModule); - if (includeImportedModule) { - addToABIExportMap(module, importedModule); - } - // Force creation of default value if missing. This prevents abiExportMap from - // growing (and moving) when calling addToABIExportMap. If abiExportMap gets - // moved, then abiExportMap[importedModule] will be moved, forcing us to - // create a defensive copy to avoid iterator invalidation on move. - (void)abiExportMap[module]; - for (auto reexportedModule: abiExportMap[importedModule]) - addToABIExportMap(module, reexportedModule); -} - -bool ABIDependencyEvaluator::isOverlayOfClangModule(ModuleDecl *swiftModule) { - assert(!swiftModule->isNonSwiftModule()); - - llvm::SmallPtrSet importList; - ::getImmediateImports(swiftModule, importList, - {ModuleDecl::ImportFilterKind::Exported}); - bool isOverlay = - llvm::any_of(importList, [&](ModuleDecl *importedModule) -> bool { - return isClangOverlayOf(swiftModule, importedModule); - }); - return isOverlay; -} - -// [NOTE: ABIDependencyEvaluator-fake-cycle-detection] -// -// First, let's consider a concrete example. -// - In Clang-land, ToyKit #imports CoreDoll. -// - The Swift overlay for CoreDoll imports both CoreDoll and ToyKit. -// Importing ToyKit from CoreDoll's overlay informally violates the layering -// of frameworks, but it doesn't actually create any cycles in the build -// dependencies. -// ┌───────────────────────────┐ -// ┌───│ CoreDoll.swiftmodule │ -// │ └───────────────────────────┘ -// │ │ -// import ToyKit @_exported import CoreDoll -// │ │ -// │ │ -// ▼ │ -// ┌──────────────────────────┐ │ -// │ ToyKit (ToyKit/ToyKit.h) │ │ -// └──────────────────────────┘ │ -// │ │ -// #import │ -// │ │ -// ▼ │ -// ┌──────────────────────────────┐ │ -// │CoreDoll (CoreDoll/CoreDoll.h)│◀──┘ -// └──────────────────────────────┘ -// -// Say we are trying to build a Swift module that imports ToyKit. Due to how -// module loading works, the Clang importer inserts the CoreDoll overlay -// between the ToyKit and CoreDoll Clang modules, creating a cycle in the -// import graph. -// -// ┌──────────────────────────┐ -// │ ToyKit (ToyKit/ToyKit.h) │◀──────────┐ -// └──────────────────────────┘ │ -// │ │ -// #import import ToyKit -// │ │ -// ▼ │ -// ┌────────────────────────────┐ │ -// │ CoreDoll.swiftmodule │─────────┘ -// └────────────────────────────┘ -// │ -// @_exported import CoreDoll -// │ -// ▼ -// ┌──────────────────────────────┐ -// │CoreDoll (CoreDoll/CoreDoll.h)│ -// └──────────────────────────────┘ -// -// This means that, at some point, searchStack will look like: -// -// [others] → ToyKit → CoreDoll (overlay) → ToyKit -// -// In the general case, there may be arbitrarily many modules in the cycle, -// including submodules. -// -// [others] → ToyKit → [others] → CoreDoll (overlay) → [others] → ToyKit -// -// where "[others]" indicates 0 or more modules of any kind. -// -// To detect this, we check that the start of the cycle is a Clang module and -// that there is at least one overlay between it and its recurrence at the end -// of the searchStack. If so, we assume we have detected a benign cycle which -// can be safely ignored. - -bool ABIDependencyEvaluator::isFakeCycleThroughOverlay( - ModuleDecl **startOfCycle) { - assert(startOfCycle >= searchStack.begin() && - startOfCycle < searchStack.end() && - "startOfCycleIter points to an element in searchStack"); - // The startOfCycle module must be a Clang module. - if (!(*startOfCycle)->isNonSwiftModule()) - return false; - // Next, we must have zero or more modules followed by a Swift overlay for a - // Clang module. - return std::any_of(startOfCycle + 1, searchStack.end(), - [this](ModuleDecl *module) { - return !module->isNonSwiftModule() && - isOverlayOfClangModule(module); - }); -} - -void ABIDependencyEvaluator::computeABIDependenciesForModule( - ModuleDecl *module) { - auto moduleIter = llvm::find(searchStack, module); - if (moduleIter != searchStack.end()) { - if (isFakeCycleThroughOverlay(moduleIter)) - return; - crashOnInvariantViolation([&](llvm::raw_string_ostream &os) { - os << "unexpected cycle in import graph!\n"; - for (auto m: searchStack) { - printModule(m, os); - if (!m->isNonSwiftModule()) { - os << " (isOverlay = " << isOverlayOfClangModule(m) << ")"; - } - os << "\ndepends on "; - } - printModule(module, os); os << '\n'; - }); - return; - } - if (::contains(visited, module)) - return; - searchStack.push_back(module); - if (module->isNonSwiftModule()) - computeABIDependenciesForClangModule(module); - else - computeABIDependenciesForSwiftModule(module); - searchStack.pop_back(); - visited.insert(module); -} - -void ABIDependencyEvaluator::computeABIDependenciesForSwiftModule( - ModuleDecl *module) { - SmallPtrSet allImports; - ::getImmediateImports(module, allImports); - for (auto import: allImports) { - computeABIDependenciesForModule(import); - if (::isClangOverlayOf(module, import)) { - reexposeImportedABI(module, import, - /*includeImportedModule=*/false); - } - } - - SmallPtrSet reexportedImports; - ::getImmediateImports(module, reexportedImports, - {ModuleDecl::ImportFilterKind::Exported}); - for (auto reexportedImport: reexportedImports) { - reexposeImportedABI(module, reexportedImport); - } -} - -void ABIDependencyEvaluator::computeABIDependenciesForClangModule( - ModuleDecl *module) { - SmallPtrSet imports; - ::getImmediateImports(module, imports); - for (auto import: imports) { - // There are three cases here which can potentially create cycles: - // - // 1. Clang modules importing the stdlib. - // See [NOTE: Pure-Clang-modules-privately-import-stdlib]. - // 2. Overlay S @_exported-imports underlying module S' and another Clang - // module C'. C' (transitively) #imports S' but it gets treated as if - // C' imports S. This creates a cycle: S -> C' -> ... -> S. - // In practice, this case is hit for - // Darwin (Swift) -> SwiftOverlayShims (Clang) -> Darwin (Swift). - // We may also hit this in a slightly different direction, in case - // the module directly imports SwiftOverlayShims: - // SwiftOverlayShims -> Darwin (Swift) -> SwiftOverlayShims - // The latter is handled later by isFakeCycleThroughOverlay. - // 3. [NOTE: Intra-module-leafwards-traversal] - // Cycles within the same top-level module. - // These don't matter for us, since we only care about the dependency - // graph at the granularity of top-level modules. So we ignore these - // by only considering parent -> submodule dependencies. - // See also [NOTE: Trace-Clang-submodule-complexity]. - if (import->isStdlibModule()) { - continue; - } - if (!import->isNonSwiftModule() && isOverlayOfClangModule(import) && - llvm::find(searchStack, import) != searchStack.end()) { - continue; - } - if (import->isNonSwiftModule() - && module->getTopLevelModule() == import->getTopLevelModule() - && (module == import - || !import->findUnderlyingClangModule() - ->isSubModuleOf(module->findUnderlyingClangModule()))) { - continue; - } - computeABIDependenciesForModule(import); - reexposeImportedABI(module, import); - } -} - -void ABIDependencyEvaluator::getABIDependenciesForSwiftModule( - ModuleDecl *module, SmallPtrSetImpl &abiDependencies) { - computeABIDependenciesForModule(module); - SmallPtrSet allImports; - ::getImmediateImports(module, allImports); - for (auto directDependency: allImports) { - abiDependencies.insert(directDependency); - for (auto exposedDependency: abiExportMap[directDependency]) { - abiDependencies.insert(exposedDependency); - } - } -} - -void ABIDependencyEvaluator::printModule( - const ModuleDecl *module, llvm::raw_ostream &os) { - module->getReverseFullModuleName().printForward(os); - os << (module->isNonSwiftModule() ? " (Clang)" : " (Swift)"); - os << " @ " << llvm::format("0x%llx", reinterpret_cast(module)); -} - -template -void ABIDependencyEvaluator::printModuleSet( - const SetLike &set, llvm::raw_ostream &os) { - os << "{ "; - for (auto module: set) { - printModule(module, os); os << ", "; - } - os << "}"; -} - -void ABIDependencyEvaluator::printABIExportMap(llvm::raw_ostream &os) const { - os << "ABI Export Map {{\n"; - for (auto &entry: abiExportMap) { - printModule(entry.first, os); os << " : "; - printModuleSet(entry.second, os); - os << "\n"; - } - os << "}}\n"; -} - -/// Compute the per-module information to be recorded in the trace file. -// -// The most interesting/tricky thing here is _which_ paths get recorded in -// the trace file as dependencies. It depends on how the module was synthesized. -// The key points are: -// -// 1. Paths to swiftmodules in the module cache or in the prebuilt cache are not -// recorded - Precondition: the corresponding path to the swiftinterface must -// already be present as a key in pathToModuleDecl. -// 2. swiftmodules next to a swiftinterface are saved if they are up-to-date. -// -// FIXME: Use the VFS instead of handling paths directly. We are particularly -// sloppy about handling relative paths in the dependency tracker. -static void computeSwiftModuleTraceInfo( - const SmallPtrSetImpl &abiDependencies, - const llvm::DenseMap &pathToModuleDecl, - const DependencyTracker &depTracker, - StringRef prebuiltCachePath, - std::vector &traceInfo) { - - SmallString<256> buffer; - - std::string errMsg; - llvm::raw_string_ostream err(errMsg); - - // FIXME: Use PrettyStackTrace instead. - auto errorUnexpectedPath = - [&pathToModuleDecl](llvm::raw_string_ostream &errStream) { - errStream << "The module <-> path mapping we have is:\n"; - for (auto &m: pathToModuleDecl) - errStream << m.second->getName() << " <-> " << m.first << '\n'; - llvm::report_fatal_error(errStream.str()); - }; - - using namespace llvm::sys; - - auto computeAdjacentInterfacePath = [](SmallVectorImpl &modPath) { - auto swiftInterfaceExt = - file_types::getExtension(file_types::TY_SwiftModuleInterfaceFile); - path::replace_extension(modPath, swiftInterfaceExt); - }; - - for (auto &depPath : depTracker.getDependencies()) { - - // Decide if this is a swiftmodule based on the extension of the raw - // dependency path, as the true file may have a different one. - // For example, this might happen when the canonicalized path points to - // a Content Addressed Storage (CAS) location. - auto moduleFileType = - file_types::lookupTypeForExtension(path::extension(depPath)); - auto isSwiftmodule = - moduleFileType == file_types::TY_SwiftModuleFile; - auto isSwiftinterface = - moduleFileType == file_types::TY_SwiftModuleInterfaceFile; - - if (!(isSwiftmodule || isSwiftinterface)) - continue; - - auto dep = pathToModuleDecl.find(depPath); - if (dep != pathToModuleDecl.end()) { - // Great, we recognize the path! Check if the file is still around. - - ModuleDecl *depMod = dep->second; - if(depMod->isResilient() && !isSwiftinterface) { - // FIXME: Ideally, we would check that the swiftmodule has a - // swiftinterface next to it. Tracked by rdar://problem/56351399. - } - - // FIXME: Better error handling - StringRef realDepPath - = fs::real_path(depPath, buffer, /*expand_tile*/true) - ? StringRef(depPath) // Couldn't find the canonical path, assume - // this is good enough. - : buffer.str(); - - bool isImportedDirectly = ::contains(abiDependencies, depMod); - - traceInfo.push_back( - {/*Name=*/ - depMod->getName(), - /*Path=*/ - realDepPath.str(), - // TODO: There is an edge case which is not handled here. - // When we build a framework using -import-underlying-module, or an - // app/test using -import-objc-header, we should look at the direct - // imports of the bridging modules, and mark those as our direct - // imports. - // TODO: Add negative test cases for the comment above. - // TODO: Describe precise semantics of "isImportedDirectly". - /*IsImportedDirectly=*/ - isImportedDirectly, - /*SupportsLibraryEvolution=*/ - depMod->isResilient()}); - buffer.clear(); - - continue; - } - - // If the depTracker had an interface, that means that we must've - // built a swiftmodule from that interface, so we should have that - // filename available. - if (isSwiftinterface) { - err << "Unexpected path for swiftinterface file:\n" << depPath << "\n"; - errorUnexpectedPath(err); - } - - // Skip cached modules in the prebuilt cache. We will add the corresponding - // swiftinterface from the SDK directly, but this isn't checked. :-/ - // - // FIXME: This is incorrect if both paths are not relative w.r.t. to the - // same root. - if (StringRef(depPath).startswith(prebuiltCachePath)) - continue; - - // If we have a swiftmodule next to an interface, that interface path will - // be saved (not checked), so don't save the path to this swiftmodule. - SmallString<256> moduleAdjacentInterfacePath(depPath); - computeAdjacentInterfacePath(moduleAdjacentInterfacePath); - if (::contains(pathToModuleDecl, moduleAdjacentInterfacePath)) - continue; - - // FIXME: The behavior of fs::exists for relative paths is undocumented. - // Use something else instead? - if (fs::exists(moduleAdjacentInterfacePath)) { - // This should be an error but it is not because of funkiness around - // compatible modules such as us having both armv7s.swiftinterface - // and armv7.swiftinterface in the dependency tracker. - continue; - } - buffer.clear(); - - // We might land here when we have a arm.swiftmodule in the cache path - // which added a dependency on a arm.swiftinterface (which was not loaded). - } - - // Almost a re-implementation of reversePathSortedFilenames :(. - std::sort( - traceInfo.begin(), traceInfo.end(), - [](const SwiftModuleTraceInfo &m1, const SwiftModuleTraceInfo &m2) -> bool { - return std::lexicographical_compare( - m1.Path.rbegin(), m1.Path.rend(), - m2.Path.rbegin(), m2.Path.rend()); - }); -} - -// [NOTE: Bailing-vs-crashing-in-trace-emission] There are certain edge cases -// in trace emission where an invariant that you think should hold does not hold -// in practice. For example, sometimes we have seen modules without any -// corresponding filename. -// -// Since the trace is a supplementary output for build system consumption, it -// it better to emit it on a best-effort basis instead of crashing and failing -// the build. -// -// Moreover, going forward, it would be nice if trace emission were more robust -// so we could emit the trace on a best-effort basis even if the dependency -// graph is ill-formed, so that the trace can be used as a debugging aid. -static bool emitLoadedModuleTraceIfNeeded(ModuleDecl *mainModule, - DependencyTracker *depTracker, - StringRef prebuiltCachePath, - StringRef loadedModuleTracePath) { - ASTContext &ctxt = mainModule->getASTContext(); - assert(!ctxt.hadError() - && "We should've already exited earlier if there was an error."); - - if (loadedModuleTracePath.empty()) - return false; - std::error_code EC; - llvm::raw_fd_ostream out(loadedModuleTracePath, EC, llvm::sys::fs::F_Append); - - if (out.has_error() || EC) { - ctxt.Diags.diagnose(SourceLoc(), diag::error_opening_output, - loadedModuleTracePath, EC.message()); - out.clear_error(); - return true; - } - - SmallPtrSet abiDependencies; - { - ABIDependencyEvaluator evaluator{}; - evaluator.getABIDependenciesForSwiftModule(mainModule, - abiDependencies); - } - - llvm::DenseMap pathToModuleDecl; - for (const auto &module : ctxt.getLoadedModules()) { - ModuleDecl *loadedDecl = module.second; - if (!loadedDecl) - llvm::report_fatal_error("Expected loaded modules to be non-null."); - if (loadedDecl == mainModule) - continue; - if (loadedDecl->getModuleFilename().empty()) { - // FIXME: rdar://problem/59853077 - // Ideally, this shouldn't happen. As a temporary workaround, avoid - // crashing with a message while we investigate the problem. - llvm::errs() << "WARNING: Module '" << loadedDecl->getName().str() - << "' has an empty filename. This is probably an " - << "invariant violation.\n" - << "Please report it as a compiler bug.\n"; - continue; - } - pathToModuleDecl.insert( - std::make_pair(loadedDecl->getModuleFilename(), loadedDecl)); - } - - std::vector swiftModules; - computeSwiftModuleTraceInfo(abiDependencies, - pathToModuleDecl, *depTracker, - prebuiltCachePath, swiftModules); - - LoadedModuleTraceFormat trace = { - /*version=*/LoadedModuleTraceFormat::CurrentVersion, - /*name=*/mainModule->getName(), - /*arch=*/ctxt.LangOpts.Target.getArchName().str(), swiftModules}; - - // raw_fd_ostream is unbuffered, and we may have multiple processes writing, - // so first write to memory and then dump the buffer to the trace file. - std::string stringBuffer; - { - llvm::raw_string_ostream memoryBuffer(stringBuffer); - json::Output jsonOutput(memoryBuffer, /*UserInfo=*/{}, - /*PrettyPrint=*/false); - json::jsonize(jsonOutput, trace, /*Required=*/true); - } - stringBuffer += "\n"; - out << stringBuffer; - - return true; -} - static void emitLoadedModuleTraceForAllPrimariesIfNeeded(ModuleDecl *mainModule, DependencyTracker *depTracker, const FrontendOptions &opts) { opts.InputsAndOutputs.forEachInputProducingSupplementaryOutput( [&](const InputFile &input) -> bool { - return emitLoadedModuleTraceIfNeeded( - mainModule, depTracker, opts.PrebuiltModuleCachePath, - input.loadedModuleTracePath()); + return emitLoadedModuleTraceIfNeeded(mainModule, depTracker, opts, + input); }); } diff --git a/lib/FrontendTool/ImportedModules.cpp b/lib/FrontendTool/ImportedModules.cpp index 857efcda8f98b..9316af5a09dc1 100644 --- a/lib/FrontendTool/ImportedModules.cpp +++ b/lib/FrontendTool/ImportedModules.cpp @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -#include "ImportedModules.h" +#include "Dependencies.h" #include "swift/AST/ASTContext.h" #include "swift/AST/Decl.h" #include "swift/AST/DiagnosticEngine.h" diff --git a/lib/FrontendTool/ImportedModules.h b/lib/FrontendTool/ImportedModules.h deleted file mode 100644 index 510fa4ccdedde..0000000000000 --- a/lib/FrontendTool/ImportedModules.h +++ /dev/null @@ -1,26 +0,0 @@ -//===--- ImportedModules.h -- generates the list of imported modules ------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -#ifndef SWIFT_FRONTENDTOOL_IMPORTEDMODULES_H -#define SWIFT_FRONTENDTOOL_IMPORTEDMODULES_H - -namespace swift { - -class ASTContext; -class FrontendOptions; -class ModuleDecl; - -/// Emit the names of the modules imported by \c mainModule. -bool emitImportedModules(ModuleDecl *mainModule, const FrontendOptions &opts); -} // end namespace swift - -#endif diff --git a/lib/FrontendTool/LoadedModuleTrace.cpp b/lib/FrontendTool/LoadedModuleTrace.cpp new file mode 100644 index 0000000000000..7f81c982a5efe --- /dev/null +++ b/lib/FrontendTool/LoadedModuleTrace.cpp @@ -0,0 +1,764 @@ +//===--- ModuleTrace.cpp -- Emit a trace of all loaded Swift modules ------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2020 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 "Dependencies.h" +#include "swift/AST/ASTContext.h" +#include "swift/AST/DiagnosticEngine.h" +#include "swift/AST/DiagnosticsFrontend.h" +#include "swift/AST/Module.h" +#include "swift/Basic/FileTypes.h" +#include "swift/Basic/JSONSerialization.h" +#include "swift/Frontend/FrontendOptions.h" + +#include "clang/Basic/Module.h" + +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/YAMLTraits.h" + +#if !defined(_MSC_VER) && !defined(__MINGW32__) +#include +#else +#include +#endif + +using namespace swift; + +namespace { +struct SwiftModuleTraceInfo { + Identifier Name; + std::string Path; + bool IsImportedDirectly; + bool SupportsLibraryEvolution; +}; + +struct LoadedModuleTraceFormat { + static const unsigned CurrentVersion = 2; + unsigned Version; + Identifier Name; + std::string Arch; + std::vector SwiftModules; +}; +} // namespace + +namespace swift { +namespace json { +template <> struct ObjectTraits { + static void mapping(Output &out, SwiftModuleTraceInfo &contents) { + StringRef name = contents.Name.str(); + out.mapRequired("name", name); + out.mapRequired("path", contents.Path); + out.mapRequired("isImportedDirectly", contents.IsImportedDirectly); + out.mapRequired("supportsLibraryEvolution", + contents.SupportsLibraryEvolution); + } +}; + +// Version notes: +// 1. Keys: name, arch, swiftmodules +// 2. New keys: version, swiftmodulesDetailedInfo +template <> struct ObjectTraits { + static void mapping(Output &out, LoadedModuleTraceFormat &contents) { + out.mapRequired("version", contents.Version); + + StringRef name = contents.Name.str(); + out.mapRequired("name", name); + + out.mapRequired("arch", contents.Arch); + + // The 'swiftmodules' key is kept for backwards compatibility. + std::vector moduleNames; + for (auto &m : contents.SwiftModules) + moduleNames.push_back(m.Path); + out.mapRequired("swiftmodules", moduleNames); + + out.mapRequired("swiftmodulesDetailedInfo", contents.SwiftModules); + } +}; +} // namespace json +} // namespace swift + +static bool isClangOverlayOf(ModuleDecl *potentialOverlay, + ModuleDecl *potentialUnderlying) { + return !potentialOverlay->isNonSwiftModule() && + potentialUnderlying->isNonSwiftModule() && + potentialOverlay->getName() == potentialUnderlying->getName(); +} + +// TODO: Delete this once changes from https://reviews.llvm.org/D83449 land on +// apple/llvm-project's swift/main branch. +template +static bool contains(const SetLike &setLike, Item item) { + return setLike.find(item) != setLike.end(); +} + +/// Get a set of modules imported by \p module. +/// +/// By default, all imports are included. +static void getImmediateImports( + ModuleDecl *module, SmallPtrSetImpl &imports, + ModuleDecl::ImportFilter importFilter = { + ModuleDecl::ImportFilterKind::Exported, + ModuleDecl::ImportFilterKind::Default, + ModuleDecl::ImportFilterKind::ImplementationOnly, + ModuleDecl::ImportFilterKind::SPIAccessControl, + ModuleDecl::ImportFilterKind::ShadowedByCrossImportOverlay}) { + SmallVector importList; + module->getImportedModules(importList, importFilter); + + for (ImportedModule &import : importList) + imports.insert(import.importedModule); +} + +namespace { +/// Helper type for computing (approximate) information about ABI-dependencies. +/// +/// This misses out on details such as typealiases and more. +/// See the "isImportedDirectly" field above for more details. +class ABIDependencyEvaluator { + /// Map of ABIs exported by a particular module, excluding itself. + /// + /// For example, consider (primed letters represent Clang modules): + /// \code + /// - A is @_exported-imported by B + /// - B is #imported by C' (via a compiler-generated umbrella header) + /// - C' is @_exported-imported by C (Swift overlay) + /// - D' is #imported by E' + /// - D' is @_exported-imported by D (Swift overlay) + /// - E' is @_exported-imported by E (Swift overlay) + /// \endcode + /// + /// Then the \c abiExportMap will be + /// \code + /// { A: {}, B: {A}, C: {B}, C': {B}, D: {}, D': {}, E: {D}, E': {D'} } + /// \endcode + /// + /// \b WARNING: Use \c reexposeImportedABI instead of inserting directly. + llvm::DenseMap> abiExportMap; + + /// Stack for depth-first traversal. + SmallVector searchStack; + + llvm::DenseSet visited; + + /// Helper function to handle invariant violations as crashes in debug mode. + void crashOnInvariantViolation( + llvm::function_ref f) const; + + /// Computes the ABI exports for \p importedModule and adds them to + /// \p module's ABI exports. + /// + /// If \p includeImportedModule is true, also adds \p importedModule to + /// \p module's ABI exports. + /// + /// Correct way to add entries to \c abiExportMap. + void reexposeImportedABI(ModuleDecl *module, ModuleDecl *importedModule, + bool includeImportedModule = true); + + /// Check if a Swift module is an overlay for some Clang module. + /// + /// FIXME: Delete this hack once SR-13363 is fixed and ModuleDecl has the + /// right API which we can use directly. + bool isOverlayOfClangModule(ModuleDecl *swiftModule); + + /// Check for cases where we have a fake cycle through an overlay. + /// + /// Sometimes, we have fake cycles in the import graph due to the Clang + /// importer injecting overlays between Clang modules. These don't represent + /// an actual cycle in the build, so we should ignore them. + /// + /// We check this lazily after detecting a cycle because it is difficult to + /// determine at the point where we see the overlay whether it was incorrectly + /// injected by the Clang importer or whether any of its imports will + /// eventually lead to a cycle. + /// + /// For more details, see [NOTE: ABIDependencyEvaluator-fake-cycle-detection] + /// + /// \param startOfCycle A pointer to the element of \c searchStack where + /// the module \em first appeared. + /// + /// \pre The module on top of \c searchStack is the same module as + /// *startOfCycle. + /// + /// \pre searchStack.begin() <= startOfCycle < searchStack.end() + bool isFakeCycleThroughOverlay(ModuleDecl **startOfCycle); + + /// Recursive step in computing ABI dependencies. + /// + /// Use this method instead of using the \c forClangModule/\c forSwiftModule + /// methods. + void computeABIDependenciesForModule(ModuleDecl *module); + void computeABIDependenciesForSwiftModule(ModuleDecl *module); + void computeABIDependenciesForClangModule(ModuleDecl *module); + + static void printModule(const ModuleDecl *module, llvm::raw_ostream &os); + + template + static void printModuleSet(const SetLike &set, llvm::raw_ostream &os); + +public: + ABIDependencyEvaluator() = default; + ABIDependencyEvaluator(const ABIDependencyEvaluator &) = delete; + ABIDependencyEvaluator(ABIDependencyEvaluator &&) = default; + + void getABIDependenciesForSwiftModule( + ModuleDecl *module, SmallPtrSetImpl &abiDependencies); + + void printABIExportMap(llvm::raw_ostream &os) const; +}; +} // end anonymous namespace + +// See [NOTE: Bailing-vs-crashing-in-trace-emission]. +// TODO: Use PrettyStackTrace instead? +void ABIDependencyEvaluator::crashOnInvariantViolation( + llvm::function_ref f) const { +#ifndef NDEBUG + std::string msg; + llvm::raw_string_ostream os(msg); + os << "error: invariant violation: "; + f(os); + llvm::report_fatal_error(os.str()); +#endif +} + +// [NOTE: Trace-Clang-submodule-complexity] +// +// A Clang module may have zero or more submodules. In practice, when traversing +// the imports of a module, we observe that different submodules of the same +// top-level module (almost) freely import each other. Despite this, we still +// need to conceptually traverse the tree formed by the submodule relationship +// (with the top-level module being the root). +// +// This needs to be taken care of in two ways: +// 1. We need to make sure we only go towards the leaves. It's okay if we "jump" +// branches, so long as we don't try to visit an ancestor when one of its +// descendants is still on the traversal stack, so that we don't end up with +// arbitrarily complex intra-module cycles. +// See also: [NOTE: Intra-module-leafwards-traversal]. +// 2. When adding entries to the ABI export map, we need to avoid marking +// dependencies within the same top-level module. This step is needed in +// addition to step 1 to avoid creating cycles like +// Overlay -> Underlying -> Submodule -> Overlay. + +void ABIDependencyEvaluator::reexposeImportedABI(ModuleDecl *module, + ModuleDecl *importedModule, + bool includeImportedModule) { + if (module == importedModule) { + crashOnInvariantViolation([&](llvm::raw_string_ostream &os) { + os << "module "; + printModule(module, os); + os << " imports itself!\n"; + }); + return; + } + + auto addToABIExportMap = [this](ModuleDecl *module, ModuleDecl *reexport) { + if (module == reexport) { + crashOnInvariantViolation([&](llvm::raw_string_ostream &os) { + os << "expected module "; + printModule(reexport, os); + os << " to not re-export itself\n"; + }); + return; + } + if (reexport->isNonSwiftModule() && module->isNonSwiftModule() && + module->getTopLevelModule() == reexport->getTopLevelModule()) { + // Dependencies within the same top-level Clang module are not useful. + // See also: [NOTE: Trace-Clang-submodule-complexity]. + return; + } + + // We only care about dependencies across top-level modules and we want to + // avoid exploding abiExportMap with submodules. So we only insert entries + // after calling getTopLevelModule(). + + if (::isClangOverlayOf(module, reexport)) { + // For overlays, we need to have a dependency on the underlying module. + // Otherwise, we might accidentally create a Swift -> Swift cycle. + abiExportMap[module].insert( + reexport->getTopLevelModule(/*preferOverlay*/ false)); + return; + } + abiExportMap[module].insert( + reexport->getTopLevelModule(/*preferOverlay*/ true)); + }; + + computeABIDependenciesForModule(importedModule); + if (includeImportedModule) { + addToABIExportMap(module, importedModule); + } + // Force creation of default value if missing. This prevents abiExportMap from + // growing (and moving) when calling addToABIExportMap. If abiExportMap gets + // moved, then abiExportMap[importedModule] will be moved, forcing us to + // create a defensive copy to avoid iterator invalidation on move. + (void)abiExportMap[module]; + for (auto reexportedModule : abiExportMap[importedModule]) + addToABIExportMap(module, reexportedModule); +} + +bool ABIDependencyEvaluator::isOverlayOfClangModule(ModuleDecl *swiftModule) { + assert(!swiftModule->isNonSwiftModule()); + + llvm::SmallPtrSet importList; + ::getImmediateImports(swiftModule, importList, + {ModuleDecl::ImportFilterKind::Exported}); + bool isOverlay = + llvm::any_of(importList, [&](ModuleDecl *importedModule) -> bool { + return isClangOverlayOf(swiftModule, importedModule); + }); + return isOverlay; +} + +// [NOTE: ABIDependencyEvaluator-fake-cycle-detection] +// +// First, let's consider a concrete example. +// - In Clang-land, ToyKit #imports CoreDoll. +// - The Swift overlay for CoreDoll imports both CoreDoll and ToyKit. +// Importing ToyKit from CoreDoll's overlay informally violates the layering +// of frameworks, but it doesn't actually create any cycles in the build +// dependencies. +// ┌───────────────────────────┐ +// ┌───│ CoreDoll.swiftmodule │ +// │ └───────────────────────────┘ +// │ │ +// import ToyKit @_exported import CoreDoll +// │ │ +// │ │ +// ▼ │ +// ┌──────────────────────────┐ │ +// │ ToyKit (ToyKit/ToyKit.h) │ │ +// └──────────────────────────┘ │ +// │ │ +// #import │ +// │ │ +// ▼ │ +// ┌──────────────────────────────┐ │ +// │CoreDoll (CoreDoll/CoreDoll.h)│◀──┘ +// └──────────────────────────────┘ +// +// Say we are trying to build a Swift module that imports ToyKit. Due to how +// module loading works, the Clang importer inserts the CoreDoll overlay +// between the ToyKit and CoreDoll Clang modules, creating a cycle in the +// import graph. +// +// ┌──────────────────────────┐ +// │ ToyKit (ToyKit/ToyKit.h) │◀──────────┐ +// └──────────────────────────┘ │ +// │ │ +// #import import ToyKit +// │ │ +// ▼ │ +// ┌────────────────────────────┐ │ +// │ CoreDoll.swiftmodule │─────────┘ +// └────────────────────────────┘ +// │ +// @_exported import CoreDoll +// │ +// ▼ +// ┌──────────────────────────────┐ +// │CoreDoll (CoreDoll/CoreDoll.h)│ +// └──────────────────────────────┘ +// +// This means that, at some point, searchStack will look like: +// +// [others] → ToyKit → CoreDoll (overlay) → ToyKit +// +// In the general case, there may be arbitrarily many modules in the cycle, +// including submodules. +// +// [others] → ToyKit → [others] → CoreDoll (overlay) → [others] → ToyKit +// +// where "[others]" indicates 0 or more modules of any kind. +// +// To detect this, we check that the start of the cycle is a Clang module and +// that there is at least one overlay between it and its recurrence at the end +// of the searchStack. If so, we assume we have detected a benign cycle which +// can be safely ignored. + +bool ABIDependencyEvaluator::isFakeCycleThroughOverlay( + ModuleDecl **startOfCycle) { + assert(startOfCycle >= searchStack.begin() && + startOfCycle < searchStack.end() && + "startOfCycleIter points to an element in searchStack"); + // The startOfCycle module must be a Clang module. + if (!(*startOfCycle)->isNonSwiftModule()) + return false; + // Next, we must have zero or more modules followed by a Swift overlay for a + // Clang module. + return std::any_of( + startOfCycle + 1, searchStack.end(), [this](ModuleDecl *module) { + return !module->isNonSwiftModule() && isOverlayOfClangModule(module); + }); +} + +void ABIDependencyEvaluator::computeABIDependenciesForModule( + ModuleDecl *module) { + auto moduleIter = llvm::find(searchStack, module); + if (moduleIter != searchStack.end()) { + if (isFakeCycleThroughOverlay(moduleIter)) + return; + crashOnInvariantViolation([&](llvm::raw_string_ostream &os) { + os << "unexpected cycle in import graph!\n"; + for (auto m : searchStack) { + printModule(m, os); + if (!m->isNonSwiftModule()) { + os << " (isOverlay = " << isOverlayOfClangModule(m) << ")"; + } + os << "\ndepends on "; + } + printModule(module, os); + os << '\n'; + }); + return; + } + if (::contains(visited, module)) + return; + searchStack.push_back(module); + if (module->isNonSwiftModule()) + computeABIDependenciesForClangModule(module); + else + computeABIDependenciesForSwiftModule(module); + searchStack.pop_back(); + visited.insert(module); +} + +void ABIDependencyEvaluator::computeABIDependenciesForSwiftModule( + ModuleDecl *module) { + SmallPtrSet allImports; + ::getImmediateImports(module, allImports); + for (auto import : allImports) { + computeABIDependenciesForModule(import); + if (::isClangOverlayOf(module, import)) { + reexposeImportedABI(module, import, + /*includeImportedModule=*/false); + } + } + + SmallPtrSet reexportedImports; + ::getImmediateImports(module, reexportedImports, + {ModuleDecl::ImportFilterKind::Exported}); + for (auto reexportedImport : reexportedImports) { + reexposeImportedABI(module, reexportedImport); + } +} + +void ABIDependencyEvaluator::computeABIDependenciesForClangModule( + ModuleDecl *module) { + SmallPtrSet imports; + ::getImmediateImports(module, imports); + for (auto import : imports) { + // There are three cases here which can potentially create cycles: + // + // 1. Clang modules importing the stdlib. + // See [NOTE: Pure-Clang-modules-privately-import-stdlib]. + // 2. Overlay S @_exported-imports underlying module S' and another Clang + // module C'. C' (transitively) #imports S' but it gets treated as if + // C' imports S. This creates a cycle: S -> C' -> ... -> S. + // In practice, this case is hit for + // Darwin (Swift) -> SwiftOverlayShims (Clang) -> Darwin (Swift). + // We may also hit this in a slightly different direction, in case + // the module directly imports SwiftOverlayShims: + // SwiftOverlayShims -> Darwin (Swift) -> SwiftOverlayShims + // The latter is handled later by isFakeCycleThroughOverlay. + // 3. [NOTE: Intra-module-leafwards-traversal] + // Cycles within the same top-level module. + // These don't matter for us, since we only care about the dependency + // graph at the granularity of top-level modules. So we ignore these + // by only considering parent -> submodule dependencies. + // See also [NOTE: Trace-Clang-submodule-complexity]. + if (import->isStdlibModule()) { + continue; + } + if (!import->isNonSwiftModule() && isOverlayOfClangModule(import) && + llvm::find(searchStack, import) != searchStack.end()) { + continue; + } + if (import->isNonSwiftModule() && + module->getTopLevelModule() == import->getTopLevelModule() && + (module == import || + !import->findUnderlyingClangModule()->isSubModuleOf( + module->findUnderlyingClangModule()))) { + continue; + } + computeABIDependenciesForModule(import); + reexposeImportedABI(module, import); + } +} + +void ABIDependencyEvaluator::getABIDependenciesForSwiftModule( + ModuleDecl *module, SmallPtrSetImpl &abiDependencies) { + computeABIDependenciesForModule(module); + SmallPtrSet allImports; + ::getImmediateImports(module, allImports); + for (auto directDependency : allImports) { + abiDependencies.insert(directDependency); + for (auto exposedDependency : abiExportMap[directDependency]) { + abiDependencies.insert(exposedDependency); + } + } +} + +void ABIDependencyEvaluator::printModule(const ModuleDecl *module, + llvm::raw_ostream &os) { + module->getReverseFullModuleName().printForward(os); + os << (module->isNonSwiftModule() ? " (Clang)" : " (Swift)"); + os << " @ " << llvm::format("0x%llx", reinterpret_cast(module)); +} + +template +void ABIDependencyEvaluator::printModuleSet(const SetLike &set, + llvm::raw_ostream &os) { + os << "{ "; + for (auto module : set) { + printModule(module, os); + os << ", "; + } + os << "}"; +} + +void ABIDependencyEvaluator::printABIExportMap(llvm::raw_ostream &os) const { + os << "ABI Export Map {{\n"; + for (auto &entry : abiExportMap) { + printModule(entry.first, os); + os << " : "; + printModuleSet(entry.second, os); + os << "\n"; + } + os << "}}\n"; +} + +/// Compute the per-module information to be recorded in the trace file. +// +// The most interesting/tricky thing here is _which_ paths get recorded in +// the trace file as dependencies. It depends on how the module was synthesized. +// The key points are: +// +// 1. Paths to swiftmodules in the module cache or in the prebuilt cache are not +// recorded - Precondition: the corresponding path to the swiftinterface must +// already be present as a key in pathToModuleDecl. +// 2. swiftmodules next to a swiftinterface are saved if they are up-to-date. +// +// FIXME: Use the VFS instead of handling paths directly. We are particularly +// sloppy about handling relative paths in the dependency tracker. +static void computeSwiftModuleTraceInfo( + const SmallPtrSetImpl &abiDependencies, + const llvm::DenseMap &pathToModuleDecl, + const DependencyTracker &depTracker, StringRef prebuiltCachePath, + std::vector &traceInfo) { + + SmallString<256> buffer; + + std::string errMsg; + llvm::raw_string_ostream err(errMsg); + + // FIXME: Use PrettyStackTrace instead. + auto errorUnexpectedPath = + [&pathToModuleDecl](llvm::raw_string_ostream &errStream) { + errStream << "The module <-> path mapping we have is:\n"; + for (auto &m : pathToModuleDecl) + errStream << m.second->getName() << " <-> " << m.first << '\n'; + llvm::report_fatal_error(errStream.str()); + }; + + using namespace llvm::sys; + + auto computeAdjacentInterfacePath = [](SmallVectorImpl &modPath) { + auto swiftInterfaceExt = + file_types::getExtension(file_types::TY_SwiftModuleInterfaceFile); + path::replace_extension(modPath, swiftInterfaceExt); + }; + + for (auto &depPath : depTracker.getDependencies()) { + + // Decide if this is a swiftmodule based on the extension of the raw + // dependency path, as the true file may have a different one. + // For example, this might happen when the canonicalized path points to + // a Content Addressed Storage (CAS) location. + auto moduleFileType = + file_types::lookupTypeForExtension(path::extension(depPath)); + auto isSwiftmodule = moduleFileType == file_types::TY_SwiftModuleFile; + auto isSwiftinterface = + moduleFileType == file_types::TY_SwiftModuleInterfaceFile; + + if (!(isSwiftmodule || isSwiftinterface)) + continue; + + auto dep = pathToModuleDecl.find(depPath); + if (dep != pathToModuleDecl.end()) { + // Great, we recognize the path! Check if the file is still around. + + ModuleDecl *depMod = dep->second; + if (depMod->isResilient() && !isSwiftinterface) { + // FIXME: Ideally, we would check that the swiftmodule has a + // swiftinterface next to it. Tracked by rdar://problem/56351399. + } + + // FIXME: Better error handling + StringRef realDepPath = + fs::real_path(depPath, buffer, /*expand_tile*/ true) + ? StringRef(depPath) // Couldn't find the canonical path, assume + // this is good enough. + : buffer.str(); + + bool isImportedDirectly = ::contains(abiDependencies, depMod); + + traceInfo.push_back( + {/*Name=*/ + depMod->getName(), + /*Path=*/ + realDepPath.str(), + // TODO: There is an edge case which is not handled here. + // When we build a framework using -import-underlying-module, or an + // app/test using -import-objc-header, we should look at the direct + // imports of the bridging modules, and mark those as our direct + // imports. + // TODO: Add negative test cases for the comment above. + // TODO: Describe precise semantics of "isImportedDirectly". + /*IsImportedDirectly=*/ + isImportedDirectly, + /*SupportsLibraryEvolution=*/ + depMod->isResilient()}); + buffer.clear(); + + continue; + } + + // If the depTracker had an interface, that means that we must've + // built a swiftmodule from that interface, so we should have that + // filename available. + if (isSwiftinterface) { + err << "Unexpected path for swiftinterface file:\n" << depPath << "\n"; + errorUnexpectedPath(err); + } + + // Skip cached modules in the prebuilt cache. We will add the corresponding + // swiftinterface from the SDK directly, but this isn't checked. :-/ + // + // FIXME: This is incorrect if both paths are not relative w.r.t. to the + // same root. + if (StringRef(depPath).startswith(prebuiltCachePath)) + continue; + + // If we have a swiftmodule next to an interface, that interface path will + // be saved (not checked), so don't save the path to this swiftmodule. + SmallString<256> moduleAdjacentInterfacePath(depPath); + computeAdjacentInterfacePath(moduleAdjacentInterfacePath); + if (::contains(pathToModuleDecl, moduleAdjacentInterfacePath)) + continue; + + // FIXME: The behavior of fs::exists for relative paths is undocumented. + // Use something else instead? + if (fs::exists(moduleAdjacentInterfacePath)) { + // This should be an error but it is not because of funkiness around + // compatible modules such as us having both armv7s.swiftinterface + // and armv7.swiftinterface in the dependency tracker. + continue; + } + buffer.clear(); + + // We might land here when we have a arm.swiftmodule in the cache path + // which added a dependency on a arm.swiftinterface (which was not loaded). + } + + // Almost a re-implementation of reversePathSortedFilenames :(. + std::sort(traceInfo.begin(), traceInfo.end(), + [](const SwiftModuleTraceInfo &m1, + const SwiftModuleTraceInfo &m2) -> bool { + return std::lexicographical_compare( + m1.Path.rbegin(), m1.Path.rend(), m2.Path.rbegin(), + m2.Path.rend()); + }); +} + +// [NOTE: Bailing-vs-crashing-in-trace-emission] There are certain edge cases +// in trace emission where an invariant that you think should hold does not hold +// in practice. For example, sometimes we have seen modules without any +// corresponding filename. +// +// Since the trace is a supplementary output for build system consumption, it +// it better to emit it on a best-effort basis instead of crashing and failing +// the build. +// +// Moreover, going forward, it would be nice if trace emission were more robust +// so we could emit the trace on a best-effort basis even if the dependency +// graph is ill-formed, so that the trace can be used as a debugging aid. +bool swift::emitLoadedModuleTraceIfNeeded(ModuleDecl *mainModule, + DependencyTracker *depTracker, + const FrontendOptions &opts, + const InputFile &input) { + ASTContext &ctxt = mainModule->getASTContext(); + assert(!ctxt.hadError() && + "We should've already exited earlier if there was an error."); + + StringRef loadedModuleTracePath = input.loadedModuleTracePath(); + if (loadedModuleTracePath.empty()) + return false; + std::error_code EC; + llvm::raw_fd_ostream out(loadedModuleTracePath, EC, llvm::sys::fs::F_Append); + + if (out.has_error() || EC) { + ctxt.Diags.diagnose(SourceLoc(), diag::error_opening_output, + loadedModuleTracePath, EC.message()); + out.clear_error(); + return true; + } + + SmallPtrSet abiDependencies; + { + ABIDependencyEvaluator evaluator{}; + evaluator.getABIDependenciesForSwiftModule(mainModule, abiDependencies); + } + + llvm::DenseMap pathToModuleDecl; + for (const auto &module : ctxt.getLoadedModules()) { + ModuleDecl *loadedDecl = module.second; + if (!loadedDecl) + llvm::report_fatal_error("Expected loaded modules to be non-null."); + if (loadedDecl == mainModule) + continue; + if (loadedDecl->getModuleFilename().empty()) { + // FIXME: rdar://problem/59853077 + // Ideally, this shouldn't happen. As a temporary workaround, avoid + // crashing with a message while we investigate the problem. + llvm::errs() << "WARNING: Module '" << loadedDecl->getName().str() + << "' has an empty filename. This is probably an " + << "invariant violation.\n" + << "Please report it as a compiler bug.\n"; + continue; + } + pathToModuleDecl.insert( + std::make_pair(loadedDecl->getModuleFilename(), loadedDecl)); + } + + std::vector swiftModules; + computeSwiftModuleTraceInfo(abiDependencies, pathToModuleDecl, *depTracker, + opts.PrebuiltModuleCachePath, swiftModules); + + LoadedModuleTraceFormat trace = { + /*version=*/LoadedModuleTraceFormat::CurrentVersion, + /*name=*/mainModule->getName(), + /*arch=*/ctxt.LangOpts.Target.getArchName().str(), swiftModules}; + + // raw_fd_ostream is unbuffered, and we may have multiple processes writing, + // so first write to memory and then dump the buffer to the trace file. + std::string stringBuffer; + { + llvm::raw_string_ostream memoryBuffer(stringBuffer); + json::Output jsonOutput(memoryBuffer, /*UserInfo=*/{}, + /*PrettyPrint=*/false); + json::jsonize(jsonOutput, trace, /*Required=*/true); + } + stringBuffer += "\n"; + out << stringBuffer; + + return true; +} diff --git a/lib/FrontendTool/ScanDependencies.cpp b/lib/FrontendTool/ScanDependencies.cpp index 4968a135ff3c8..4c5443762478b 100644 --- a/lib/FrontendTool/ScanDependencies.cpp +++ b/lib/FrontendTool/ScanDependencies.cpp @@ -2,13 +2,14 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2020 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 "ScanDependencies.h" #include "swift/AST/ASTContext.h" #include "swift/AST/Decl.h" From 13c97a89054125ab8e2b51fe27e3c1ae2dd252f1 Mon Sep 17 00:00:00 2001 From: Robert Widmann Date: Wed, 11 Nov 2020 14:26:25 -0800 Subject: [PATCH 3/4] [NFC] Downgrade CompilerInvocation to FrontendOptions --- lib/FrontendTool/FrontendTool.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/FrontendTool/FrontendTool.cpp b/lib/FrontendTool/FrontendTool.cpp index e67962af25023..2756fd899ad5c 100644 --- a/lib/FrontendTool/FrontendTool.cpp +++ b/lib/FrontendTool/FrontendTool.cpp @@ -461,10 +461,9 @@ static bool compileLLVMIR(CompilerInstance &Instance) { Module.get(), inputsAndOutputs.getSingleOutputFilename()); } -static void verifyGenericSignaturesIfNeeded(const CompilerInvocation &Invocation, +static void verifyGenericSignaturesIfNeeded(const FrontendOptions &opts, ASTContext &Context) { - auto verifyGenericSignaturesInModule = - Invocation.getFrontendOptions().VerifyGenericSignaturesInModule; + auto verifyGenericSignaturesInModule = opts.VerifyGenericSignaturesInModule; if (verifyGenericSignaturesInModule.empty()) return; if (auto module = Context.getModuleByName(verifyGenericSignaturesInModule)) @@ -932,7 +931,7 @@ static void performEndOfPipelineActions(CompilerInstance &Instance) { ctx.verifyAllLoadedModules(); // Verify generic signatures if we've been asked to. - verifyGenericSignaturesIfNeeded(Invocation, ctx); + verifyGenericSignaturesIfNeeded(Invocation.getFrontendOptions(), ctx); } // Emit any additional outputs that we only need for a successful compilation. From 19e6bee3747260beeb7bf7604bf27db737c97732 Mon Sep 17 00:00:00 2001 From: Robert Widmann Date: Wed, 11 Nov 2020 14:31:18 -0800 Subject: [PATCH 4/4] [NFC] Use the Correct const-Qualification in Dependency Code T *const does not prevent logically non-const accesses to the underlying data, it merely indicates that the pointer value itself is const. This modifier can be cast off by a copy, so it's not generally what you want here. Switch to const T * instead. --- include/swift/AST/FineGrainedDependencies.h | 2 +- lib/AST/FrontendSourceFileDepGraphFactory.cpp | 37 ++++++++++--------- lib/AST/FrontendSourceFileDepGraphFactory.h | 12 +++--- lib/FrontendTool/FrontendTool.cpp | 4 +- lib/FrontendTool/LoadedModuleTrace.cpp | 2 +- 5 files changed, 29 insertions(+), 28 deletions(-) diff --git a/include/swift/AST/FineGrainedDependencies.h b/include/swift/AST/FineGrainedDependencies.h index e701ad31cce8a..3d224cd842389 100644 --- a/include/swift/AST/FineGrainedDependencies.h +++ b/include/swift/AST/FineGrainedDependencies.h @@ -351,7 +351,7 @@ class BiIndexedTwoStageMap { /// /// \Note The returned graph should not be escaped from the callback. bool withReferenceDependencies( - llvm::PointerUnion MSF, + llvm::PointerUnion MSF, const DependencyTracker &depTracker, StringRef outputPath, bool alsoEmitDotFile, llvm::function_ref); diff --git a/lib/AST/FrontendSourceFileDepGraphFactory.cpp b/lib/AST/FrontendSourceFileDepGraphFactory.cpp index fe358fbffd7fd..d85b06f92588a 100644 --- a/lib/AST/FrontendSourceFileDepGraphFactory.cpp +++ b/lib/AST/FrontendSourceFileDepGraphFactory.cpp @@ -193,16 +193,16 @@ std::string DependencyKey::computeNameForProvidedEntity< //============================================================================== bool fine_grained_dependencies::withReferenceDependencies( - llvm::PointerUnion MSF, + llvm::PointerUnion MSF, const DependencyTracker &depTracker, StringRef outputPath, bool alsoEmitDotFile, llvm::function_ref cont) { - if (auto *MD = MSF.dyn_cast()) { + if (auto *MD = MSF.dyn_cast()) { SourceFileDepGraph g = ModuleDepGraphFactory(MD, alsoEmitDotFile).construct(); return cont(std::move(g)); } else { - auto *SF = MSF.get(); + auto *SF = MSF.get(); SourceFileDepGraph g = FrontendSourceFileDepGraphFactory( SF, outputPath, depTracker, alsoEmitDotFile) .construct(); @@ -215,22 +215,22 @@ bool fine_grained_dependencies::withReferenceDependencies( //============================================================================== FrontendSourceFileDepGraphFactory::FrontendSourceFileDepGraphFactory( - SourceFile *SF, StringRef outputPath, const DependencyTracker &depTracker, - const bool alsoEmitDotFile) + const SourceFile *SF, StringRef outputPath, + const DependencyTracker &depTracker, const bool alsoEmitDotFile) : AbstractSourceFileDepGraphFactory( - SF->getASTContext().hadError(), - outputPath, getInterfaceHash(SF), alsoEmitDotFile, - SF->getASTContext().Diags), + SF->getASTContext().hadError(), outputPath, getInterfaceHash(SF), + alsoEmitDotFile, SF->getASTContext().Diags), SF(SF), depTracker(depTracker) {} /// Centralize the invariant that the fingerprint of the whole file is the /// interface hash -std::string FrontendSourceFileDepGraphFactory::getFingerprint(SourceFile *SF) { +std::string +FrontendSourceFileDepGraphFactory::getFingerprint(const SourceFile *SF) { return getInterfaceHash(SF); } std::string -FrontendSourceFileDepGraphFactory::getInterfaceHash(SourceFile *SF) { +FrontendSourceFileDepGraphFactory::getInterfaceHash(const SourceFile *SF) { llvm::SmallString<32> interfaceHash; SF->getInterfaceHash(interfaceHash); return interfaceHash.str().str(); @@ -415,7 +415,7 @@ void FrontendSourceFileDepGraphFactory::addAllDefinedDecls() { namespace { /// Extracts uses out of a SourceFile class UsedDeclEnumerator { - SourceFile *SF; + const SourceFile *SF; const DependencyTracker &depTracker; StringRef swiftDeps; @@ -427,7 +427,8 @@ class UsedDeclEnumerator { public: UsedDeclEnumerator( - SourceFile *SF, const DependencyTracker &depTracker, StringRef swiftDeps, + const SourceFile *SF, const DependencyTracker &depTracker, + StringRef swiftDeps, function_ref createDefUse) : SF(SF), depTracker(depTracker), swiftDeps(swiftDeps), @@ -435,8 +436,7 @@ class UsedDeclEnumerator { DeclAspect::interface, swiftDeps)), sourceFileImplementation(DependencyKey::createKeyForWholeSourceFile( DeclAspect::implementation, swiftDeps)), - createDefUse(createDefUse) { - } + createDefUse(createDefUse) {} public: void enumerateAllUses() { @@ -517,10 +517,11 @@ void FrontendSourceFileDepGraphFactory::addAllUsedDecls() { // MARK: ModuleDepGraphFactory //============================================================================== -ModuleDepGraphFactory::ModuleDepGraphFactory(ModuleDecl *Mod, bool emitDot) - : AbstractSourceFileDepGraphFactory( - Mod->getASTContext().hadError(), - Mod->getNameStr(), "0xBADBEEF", emitDot, Mod->getASTContext().Diags), +ModuleDepGraphFactory::ModuleDepGraphFactory(const ModuleDecl *Mod, + bool emitDot) + : AbstractSourceFileDepGraphFactory(Mod->getASTContext().hadError(), + Mod->getNameStr(), "0xBADBEEF", emitDot, + Mod->getASTContext().Diags), Mod(Mod) {} void ModuleDepGraphFactory::addAllDefinedDecls() { diff --git a/lib/AST/FrontendSourceFileDepGraphFactory.h b/lib/AST/FrontendSourceFileDepGraphFactory.h index e0a85a029140b..e97587dac6b9a 100644 --- a/lib/AST/FrontendSourceFileDepGraphFactory.h +++ b/lib/AST/FrontendSourceFileDepGraphFactory.h @@ -23,29 +23,29 @@ namespace fine_grained_dependencies { class FrontendSourceFileDepGraphFactory : public AbstractSourceFileDepGraphFactory { - SourceFile *const SF; + const SourceFile *SF; const DependencyTracker &depTracker; public: - FrontendSourceFileDepGraphFactory(SourceFile *SF, StringRef outputPath, + FrontendSourceFileDepGraphFactory(const SourceFile *SF, StringRef outputPath, const DependencyTracker &depTracker, bool alsoEmitDotFile); ~FrontendSourceFileDepGraphFactory() override = default; private: - static std::string getFingerprint(SourceFile *SF); - static std::string getInterfaceHash(SourceFile *SF); + static std::string getFingerprint(const SourceFile *SF); + static std::string getInterfaceHash(const SourceFile *SF); void addAllDefinedDecls() override; void addAllUsedDecls() override; }; class ModuleDepGraphFactory : public AbstractSourceFileDepGraphFactory { - ModuleDecl *const Mod; + const ModuleDecl *Mod; public: - ModuleDepGraphFactory(ModuleDecl *Mod, bool emitDot); + ModuleDepGraphFactory(const ModuleDecl *Mod, bool emitDot); ~ModuleDepGraphFactory() override = default; diff --git a/lib/FrontendTool/FrontendTool.cpp b/lib/FrontendTool/FrontendTool.cpp index 2756fd899ad5c..ee78f97ace5d9 100644 --- a/lib/FrontendTool/FrontendTool.cpp +++ b/lib/FrontendTool/FrontendTool.cpp @@ -116,8 +116,8 @@ emitLoadedModuleTraceForAllPrimariesIfNeeded(ModuleDecl *mainModule, const FrontendOptions &opts) { opts.InputsAndOutputs.forEachInputProducingSupplementaryOutput( [&](const InputFile &input) -> bool { - return emitLoadedModuleTraceIfNeeded(mainModule, depTracker, opts, - input); + return swift::emitLoadedModuleTraceIfNeeded(mainModule, depTracker, + opts, input); }); } diff --git a/lib/FrontendTool/LoadedModuleTrace.cpp b/lib/FrontendTool/LoadedModuleTrace.cpp index 7f81c982a5efe..01aafe1b4f1ee 100644 --- a/lib/FrontendTool/LoadedModuleTrace.cpp +++ b/lib/FrontendTool/LoadedModuleTrace.cpp @@ -699,7 +699,7 @@ bool swift::emitLoadedModuleTraceIfNeeded(ModuleDecl *mainModule, assert(!ctxt.hadError() && "We should've already exited earlier if there was an error."); - StringRef loadedModuleTracePath = input.loadedModuleTracePath(); + auto loadedModuleTracePath = input.loadedModuleTracePath(); if (loadedModuleTracePath.empty()) return false; std::error_code EC;