1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
pub
use ;
pub use ;
use DataFlowSolver;
/// Indicates whether the control enters, exits, or skips over the callee (in the case of
/// external functions).
/// [DataFlowAnalysis] is the base trait for all data-flow analyses.
///
/// In general, a data-flow analysis, is expected to visit the IR rooted at some operation, and in
/// the process, define state that represents its results, invoking transfer functions on that state
/// at each program point. In addition to its own state, it may also consume the states of other
/// analyses which it either requires, or may optimistically benefit from. In the process, a
/// dependency graph is established between the analyses being applied.
///
/// In classical data-flow analysis, the dependency graph is static and each analysis defines the
/// transfer functions between its input states and output states. In the data-flow framework
/// implemented here however, things work a bit differently:
///
/// * The set of analyses, and the dependencies between them, is dynamic and implicit.
/// * Multiple analyses can share the same state, so the transfer functions are defined as part of
/// the [AnalysisState] implementation, not the [DataFlowAnalysis] that consumes it. The analysis
/// is responsible for invoking the appropriate transfer function at each program point, but it
/// does not need to know how it is implemented.
///
/// Generally, when an analysis queries an uninitialized state, it is expected to "bail out", i.e.,
/// not provide any updates. When the value is initialized, the solver will re-invoke the analysis.
/// However, if the solver exhausts its worklist, and there are still uninitialized states, the
/// solver "nudges" the analyses by default-initializing those states.
/// This trait represents a type which is can be constructed into an instance of [DataFlowAnalysis]
/// by the [DataFlowSolver], by constructing an instance of its corresponding [AnalysisStrategy]
/// with an instance of the type. The strategy is responsible for adapting the specific semantics
/// of the analysis to the abstract [DataFlowAnalysis] interface.
///
/// There are two primary ways of categorizing analysis:
///
/// * dense vs sparse - dictates whether analysis states are anchored to program points (dense) or
/// values (sparse). Sparse analyses are referred to as such because SSA values can only have a
/// single definition, so the analysis state only has to be anchored to a single location per
/// value. Dense analyses on the other hand, must attach analysis state to every program point
/// (operation and block) visited by the analysis.
///
/// * forward vs backward - dictates whether analysis state is propagated forward (from the entry
/// point of a region to the exits of the region) or backward (from the exits of a region, to
/// the entry point). A forward analysis follows the CFG top-down, while a backward analysis
/// visits the CFG bottom-up.
///
/// As a result, there are four unique permutations of analysis possible, and each have different
/// semantics from the others, requiring separate traits. This trait allows loading analyses into
/// the [DataFlowSolver] without having to know the specific details of how that analysis is
/// implemented. Instead, the author of the analysis implements the specific analysis trait that
/// corresponds to the semantics it wants, and then implements this trait to specify the concrete
/// type that understands how to run that type of analysis in the context of our data-flow analysis
/// framework.
///
/// This must be separate from [DataFlowAnalysis] as the underlying type may not implement the
/// [DataFlowAnalysis] trait itself, only doing so once combined with the specified strategy.
/// However, it is expected that all concrete implementations of [DataFlowAnalysis] implement this
/// trait, enabling it to be loaded into the solver via [DataFlowSolver::load].
/// This trait represents a type that adapts some primitive analysis `T` to the abstract
/// [DataFlowAnalysis] interface.
///
/// It is intended to be used in conjunction with [BuildableDataFlowAnalysis]. See the documentation
/// of that trait for more details on how these work together and why they exist.
/// A marker trait for abstracting/specializing over the abstract kind of an analysis: dense or
/// sparse.
///
/// This trait is sealed as there are only two supported kinds.
pub use ;