Skip to content

Commit d0ac0d4

Browse files
[ModuleTrace] Emit import graph to ease debugging import issues.
This should make it easier to spot cycles in import graphs, as well as funky behavior involving both default/private imports + SPI imports.
1 parent 9799b1b commit d0ac0d4

File tree

11 files changed

+639
-226
lines changed

11 files changed

+639
-226
lines changed

include/swift/AST/Module.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,15 @@ class ModuleDecl : public DeclContext, public TypeDecl {
667667
/// this flag is specified.
668668
ShadowedBySeparateOverlay = 1 << 4
669669
};
670+
671+
static constexpr std::array<ImportFilterKind, 5> AllImportFilterKinds {
672+
ImportFilterKind::Public,
673+
ImportFilterKind::Private,
674+
ImportFilterKind::ImplementationOnly,
675+
ImportFilterKind::SPIAccessControl,
676+
ImportFilterKind::ShadowedBySeparateOverlay,
677+
};
678+
670679
/// \sa getImportedModules
671680
using ImportFilter = OptionSet<ImportFilterKind>;
672681

include/swift/Basic/FileTypes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ TYPE("tbd", TBD, "tbd", "")
7272
// Swift section of the internal wiki.
7373
TYPE("module-trace", ModuleTrace, "trace.json", "")
7474

75+
TYPE("module-import-graph", ModuleImportGraph, "import-graph.dot","")
76+
7577
// Complete dependency information for the given Swift files as JSON.
7678
TYPE("json-dependencies", JSONDependencies, "dependencies.json", "")
7779

include/swift/Basic/GraphViz.h

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
//===--- GraphViz.h - Utilities for outputting graphs -----------*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
///
13+
/// \file
14+
/// Defines data structures for creating transient graphs in order to print
15+
/// them to GraphViz DOT format.
16+
///
17+
//===----------------------------------------------------------------------===//
18+
19+
#ifndef SWIFT_BASIC_GRAPHVIZ_H
20+
#define SWIFT_BASIC_GRAPHVIZ_H
21+
22+
#include "llvm/ADT/DenseMap.h"
23+
#include "llvm/ADT/STLExtras.h"
24+
#include "llvm/Support/raw_ostream.h"
25+
26+
#include <iterator>
27+
#include <string>
28+
#include <utility>
29+
#include <vector>
30+
31+
namespace swift {
32+
// MARK: - MultiGraph
33+
34+
/// A multigraph data structure, with a set of edges connecting pairs of nodes.
35+
///
36+
/// The fields are left public so that you can potentially customize printing
37+
/// directly, instead of relying on the existing customization points.
38+
///
39+
/// \invariant {
40+
/// \code forall n1 n2. nodePairs[{n1, n2}] < edgeSets.size() \endcode
41+
/// }
42+
template <typename Node, typename Edge, typename EdgeSet>
43+
struct MultiGraph {
44+
/// List of nodes stored in the graph.
45+
llvm::DenseMap<std::pair<Node, Node>, size_t> nodePairs;
46+
47+
/// List of edges stored in the graph.
48+
std::vector<EdgeSet> edgeSets;
49+
50+
/// Three way comparison function for nodes used for deterministic output.
51+
llvm::function_ref<int (Node, Node)> compareNodes;
52+
53+
/// Three way comparison function for edges used for deterministic output.
54+
llvm::function_ref<int (Edge, Edge)> compareEdges;
55+
56+
/// Default attributes used for styling nodes.
57+
std::string defaultNodeAttrs =
58+
"shape = \"box\", style = \"rounded\", penwidth = \"2\"";
59+
60+
/// Print the name of a node.
61+
llvm::function_ref<void (Node, llvm::raw_ostream &)> printNodeName;
62+
63+
/// Print the attributes used to style a node, potentially null.
64+
llvm::function_ref<void (Node, llvm::raw_ostream &)> printNodeAttr;
65+
66+
/// Print the label for the edge set between a pair of nodes, optionally
67+
/// customizing it based on the nodes themselves.
68+
llvm::function_ref<
69+
void (Node, Node, const SmallVectorImpl<Edge> &,
70+
llvm::raw_ostream &)> printEdgeSet;
71+
72+
public:
73+
MultiGraph() = default;
74+
MultiGraph(const MultiGraph &) = delete;
75+
MultiGraph(MultiGraph &&) = default;
76+
77+
/// Insert an edge to the set of edges between two nodes.
78+
void updateEdge(Node from, Node to, Edge edge) {
79+
auto it = nodePairs.find(std::pair<Node, Node>{from, to});
80+
if (it != nodePairs.end()) {
81+
edgeSets[it->second].insert(edge);
82+
return;
83+
}
84+
nodePairs.insert({{from, to}, edgeSets.size()});
85+
EdgeSet edgeSet{};
86+
edgeSet.insert(edge);
87+
edgeSets.push_back(edgeSet);
88+
};
89+
90+
/// Output the graph in DOT format to \p os.
91+
void printAsGraphviz(llvm::raw_ostream &os, bool horizontal = false) {
92+
os << "digraph CustomGraph {\n";
93+
os << " node [" << defaultNodeAttrs; os << "];\n";
94+
if (horizontal)
95+
os << " rankdir = \"LR\";\n";
96+
97+
auto copyAndSort = [](const auto &set, auto &vec, auto cmp) {
98+
vec.reserve(set.size());
99+
llvm::copy(set, std::back_inserter(vec));
100+
llvm::sort(vec, [&](auto t1, auto t2) { return cmp(t1, t2) < 0;});
101+
};
102+
103+
// Print the nodes first in case we have custom attributes for nodes.
104+
if (printNodeAttr) {
105+
llvm::DenseSet<Node> nodes{};
106+
for (auto &entry: nodePairs) {
107+
nodes.insert(entry.first.first);
108+
nodes.insert(entry.first.second);
109+
}
110+
std::vector<Node> nodesVec{};
111+
copyAndSort(nodes, nodesVec, compareNodes);
112+
for (auto node: nodes) {
113+
os << " ";
114+
printNodeName(node, os);
115+
printNodeAttr(node, os);
116+
os << ";\n";
117+
}
118+
}
119+
std::vector<std::pair<std::pair<Node, Node>, size_t>> nodePairsVec{};
120+
copyAndSort(nodePairs, nodePairsVec,
121+
[this](auto e1, auto e2) -> int {
122+
auto nodePair1 = e1.first;
123+
auto nodePair2 = e1.first;
124+
auto cmp1 = compareNodes(nodePair1.first, nodePair2.first);
125+
if (cmp1 < 0) return cmp1;
126+
return compareNodes(nodePair1.second, nodePair2.second);
127+
});
128+
SmallVector<Edge, 5> scratchEdges;
129+
for (auto &entry: nodePairsVec) {
130+
auto from = entry.first.first;
131+
auto to = entry.first.second;
132+
auto edgeSetIndex = entry.second;
133+
os << " ";
134+
printNodeName(from, os);
135+
os << " -> ";
136+
printNodeName(to, os);
137+
if (printEdgeSet) {
138+
copyAndSort(edgeSets[edgeSetIndex], scratchEdges, compareEdges);
139+
os << " [label = \"";
140+
printEdgeSet(from, to, scratchEdges, os);
141+
os << "\"]";
142+
}
143+
scratchEdges.clear();
144+
os << ";\n";
145+
}
146+
os << "}\n";
147+
}
148+
};
149+
150+
} // end namespace swift
151+
152+
#endif // SWIFT_BASIC_GRAPHVIZ_H

include/swift/Basic/SupplementaryOutputPaths.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ struct SupplementaryOutputPaths {
126126
/// TARGET. This format is subject to arbitrary change, however.
127127
std::string LoadedModuleTracePath;
128128

129+
/// Path for storing a module import graph in Graphviz DOT format.
130+
std::string ModuleImportGraphPath;
131+
129132
/// The path to which we should output a TBD file.
130133
///
131134
/// "TBD" stands for "text-based dylib". It's a YAML-based format that
@@ -176,7 +179,8 @@ struct SupplementaryOutputPaths {
176179
ModuleDocOutputPath.empty() && DependenciesFilePath.empty() &&
177180
ReferenceDependenciesFilePath.empty() &&
178181
SerializedDiagnosticsPath.empty() && LoadedModuleTracePath.empty() &&
179-
TBDPath.empty() && ModuleInterfaceOutputPath.empty() &&
182+
ModuleImportGraphPath.empty() && TBDPath.empty() &&
183+
ModuleInterfaceOutputPath.empty() &&
180184
ModuleSourceInfoOutputPath.empty() && LdAddCFilePath.empty();
181185
}
182186
};

include/swift/Frontend/InputFile.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ class InputFile {
9090
std::string loadedModuleTracePath() const {
9191
return getPrimarySpecificPaths().SupplementaryOutputs.LoadedModuleTracePath;
9292
}
93+
std::string moduleImportGraphPath() const {
94+
return getPrimarySpecificPaths().SupplementaryOutputs.ModuleImportGraphPath;
95+
}
9396
std::string serializedDiagnosticsPath() const {
9497
return getPrimarySpecificPaths().SupplementaryOutputs
9598
.SerializedDiagnosticsPath;

include/swift/Option/Options.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,13 @@ def emit_loaded_module_trace_path_EQ : Joined<["-"], "emit-loaded-module-trace-p
337337
Flags<[FrontendOption, NoInteractiveOption, ArgumentIsPath,
338338
SupplementaryOutput]>,
339339
Alias<emit_loaded_module_trace_path>;
340+
def emit_module_import_graph_path : Separate<["-"],
341+
"emit-module-import-graph-path">,
342+
Flags<[FrontendOption, NoInteractiveOption, ArgumentIsPath,
343+
SupplementaryOutput]>,
344+
HelpText<"Emit a module import graph in GraphViz DOT format to <path>. The key used is Exp = @_exported, Def = default, IO = @_implementationOnly, SPI = @_spi, SCOO = shadowed by cross-import overlay, (C) = Clang module, (S) = Swift module, Ov = Swift overlay for Clang module, X-Ov = Cross-import overlay.">,
345+
// GraphViz doesn't seem to have a proper way to add a legend to a graph. :(
346+
MetaVarName<"<path>">;
340347
def emit_cross_import_remarks : Flag<["-"], "Rcross-import">,
341348
Flags<[FrontendOption, DoesNotAffectIncrementalBuild]>,
342349
HelpText<"Emit a remark if a cross-import of a module is triggered.">;

lib/Basic/FileTypes.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ bool file_types::isTextual(ID Id) {
7878
case file_types::TY_ImportedModules:
7979
case file_types::TY_TBD:
8080
case file_types::TY_ModuleTrace:
81+
case file_types::TY_ModuleImportGraph:
8182
case file_types::TY_YAMLOptRecord:
8283
case file_types::TY_SwiftModuleInterfaceFile:
8384
case file_types::TY_PrivateSwiftModuleInterfaceFile:
@@ -150,6 +151,7 @@ bool file_types::isAfterLLVM(ID Id) {
150151
case file_types::TY_Remapping:
151152
case file_types::TY_IndexData:
152153
case file_types::TY_ModuleTrace:
154+
case file_types::TY_ModuleImportGraph:
153155
case file_types::TY_YAMLOptRecord:
154156
case file_types::TY_BitstreamOptRecord:
155157
case file_types::TY_SwiftModuleInterfaceFile:
@@ -202,6 +204,7 @@ bool file_types::isPartOfSwiftCompilation(ID Id) {
202204
case file_types::TY_Remapping:
203205
case file_types::TY_IndexData:
204206
case file_types::TY_ModuleTrace:
207+
case file_types::TY_ModuleImportGraph:
205208
case file_types::TY_YAMLOptRecord:
206209
case file_types::TY_BitstreamOptRecord:
207210
case file_types::TY_JSONDependencies:

lib/Driver/Driver.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2000,6 +2000,7 @@ void Driver::buildActions(SmallVectorImpl<const Action *> &TopLevelActions,
20002000
case file_types::TY_PCH:
20012001
case file_types::TY_ImportedModules:
20022002
case file_types::TY_ModuleTrace:
2003+
case file_types::TY_ModuleImportGraph:
20032004
case file_types::TY_YAMLOptRecord:
20042005
case file_types::TY_BitstreamOptRecord:
20052006
case file_types::TY_SwiftModuleInterfaceFile:

lib/Driver/ToolChains.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,7 @@ const char *ToolChain::JobContext::computeFrontendModeForCompile() const {
613613
case file_types::TY_SwiftRanges:
614614
case file_types::TY_CompiledSource:
615615
case file_types::TY_ModuleTrace:
616+
case file_types::TY_ModuleImportGraph:
616617
case file_types::TY_TBD:
617618
case file_types::TY_YAMLOptRecord:
618619
case file_types::TY_BitstreamOptRecord:
@@ -875,6 +876,7 @@ ToolChain::constructInvocation(const BackendJobAction &job,
875876
case file_types::TY_CompiledSource:
876877
case file_types::TY_Remapping:
877878
case file_types::TY_ModuleTrace:
879+
case file_types::TY_ModuleImportGraph:
878880
case file_types::TY_YAMLOptRecord:
879881
case file_types::TY_BitstreamOptRecord:
880882
case file_types::TY_SwiftModuleInterfaceFile:

lib/Frontend/ArgsToFrontendOutputsConverter.cpp

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,8 @@ SupplementaryOutputPathsComputer::getSupplementaryOutputPathsFromArguments()
297297
options::OPT_emit_fixits_path);
298298
auto loadedModuleTrace = getSupplementaryFilenamesFromArguments(
299299
options::OPT_emit_loaded_module_trace_path);
300+
auto moduleImportGraph = getSupplementaryFilenamesFromArguments(
301+
options::OPT_emit_module_import_graph_path);
300302
auto TBD = getSupplementaryFilenamesFromArguments(options::OPT_emit_tbd_path);
301303
auto moduleInterfaceOutput = getSupplementaryFilenamesFromArguments(
302304
options::OPT_emit_module_interface_path);
@@ -310,9 +312,10 @@ SupplementaryOutputPathsComputer::getSupplementaryOutputPathsFromArguments()
310312
options::OPT_emit_module_summary_path);
311313
if (!objCHeaderOutput || !moduleOutput || !moduleDocOutput ||
312314
!dependenciesFile || !referenceDependenciesFile ||
313-
!serializedDiagnostics || !fixItsOutput || !loadedModuleTrace || !TBD ||
314-
!moduleInterfaceOutput || !privateModuleInterfaceOutput ||
315-
!moduleSourceInfoOutput || !ldAddCFileOutput || !moduleSummaryOutput) {
315+
!serializedDiagnostics || !fixItsOutput || !loadedModuleTrace ||
316+
!moduleImportGraph || !TBD || !moduleInterfaceOutput ||
317+
!privateModuleInterfaceOutput || !moduleSourceInfoOutput ||
318+
!ldAddCFileOutput || !moduleSummaryOutput) {
316319
return None;
317320
}
318321
std::vector<SupplementaryOutputPaths> result;
@@ -331,6 +334,7 @@ SupplementaryOutputPathsComputer::getSupplementaryOutputPathsFromArguments()
331334
sop.SerializedDiagnosticsPath = (*serializedDiagnostics)[i];
332335
sop.FixItsOutputPath = (*fixItsOutput)[i];
333336
sop.LoadedModuleTracePath = (*loadedModuleTrace)[i];
337+
sop.ModuleImportGraphPath = (*moduleImportGraph)[i];
334338
sop.TBDPath = (*TBD)[i];
335339
sop.ModuleInterfaceOutputPath = (*moduleInterfaceOutput)[i];
336340
sop.PrivateModuleInterfaceOutputPath = (*privateModuleInterfaceOutput)[i];
@@ -412,6 +416,12 @@ SupplementaryOutputPathsComputer::computeOutputPathsForOneInput(
412416
file_types::TY_ModuleTrace, "",
413417
defaultSupplementaryOutputPathExcludingExtension);
414418

419+
auto moduleImportGraphPath = determineSupplementaryOutputFilename(
420+
OPT_emit_module_import_graph_path,
421+
pathsFromArguments.ModuleImportGraphPath,
422+
file_types::TY_ModuleImportGraph, "",
423+
defaultSupplementaryOutputPathExcludingExtension);
424+
415425
auto tbdPath = determineSupplementaryOutputFilename(
416426
OPT_emit_tbd, pathsFromArguments.TBDPath, file_types::TY_TBD, "",
417427
defaultSupplementaryOutputPathExcludingExtension);
@@ -458,6 +468,7 @@ SupplementaryOutputPathsComputer::computeOutputPathsForOneInput(
458468
sop.SerializedDiagnosticsPath = serializedDiagnosticsPath;
459469
sop.FixItsOutputPath = fixItsOutputPath;
460470
sop.LoadedModuleTracePath = loadedModuleTracePath;
471+
sop.ModuleImportGraphPath = moduleImportGraphPath;
461472
sop.TBDPath = tbdPath;
462473
sop.ModuleInterfaceOutputPath = ModuleInterfaceOutputPath;
463474
sop.PrivateModuleInterfaceOutputPath = PrivateModuleInterfaceOutputPath;
@@ -567,6 +578,7 @@ SupplementaryOutputPathsComputer::readSupplementaryOutputFileMap() const {
567578
options::OPT_emit_swift_ranges_path,
568579
options::OPT_serialize_diagnostics_path,
569580
options::OPT_emit_loaded_module_trace_path,
581+
options::OPT_emit_module_import_graph_path,
570582
options::OPT_emit_module_interface_path,
571583
options::OPT_emit_private_module_interface_path,
572584
options::OPT_emit_module_source_info_path,

0 commit comments

Comments
 (0)