@@ -651,6 +651,12 @@ abstract class FlowAnalysis<Node extends Object, Statement extends Node,
651
651
/// is currently promoted. Otherwise returns `null` .
652
652
Type ? promotedType (Variable variable);
653
653
654
+ /// Retrieves the SSA node associated with [variable] , or `null` if [variable]
655
+ /// is not associated with an SSA node because it is write captured. For
656
+ /// testing only.
657
+ @visibleForTesting
658
+ SsaNode ? ssaNodeForTesting (Variable variable);
659
+
654
660
/// Call this method just before visiting one of the cases in the body of a
655
661
/// switch statement. See [switchStatement_expressionEnd] for details.
656
662
///
@@ -1138,6 +1144,13 @@ class FlowAnalysisDebug<Node extends Object, Statement extends Node,
1138
1144
isQuery: true );
1139
1145
}
1140
1146
1147
+ @override
1148
+ SsaNode ? ssaNodeForTesting (Variable variable) {
1149
+ return _wrap ('ssaNodeForTesting($variable )' ,
1150
+ () => _wrapped.ssaNodeForTesting (variable),
1151
+ isQuery: true );
1152
+ }
1153
+
1141
1154
@override
1142
1155
void switchStatement_beginCase (bool hasLabel, Node node) {
1143
1156
_wrap ('switchStatement_beginCase($hasLabel , $node )' ,
@@ -1354,7 +1367,7 @@ class FlowModel<Variable extends Object, Type extends Object> {
1354
1367
(newVariableInfo ?? =
1355
1368
new Map <Variable , VariableModel <Variable , Type >>.from (
1356
1369
variableInfo))[variable] = new VariableModel <Variable , Type >(
1357
- null , const [], false , false , true );
1370
+ null , const [], false , false , null );
1358
1371
} else if (! info.writeCaptured) {
1359
1372
(newVariableInfo ?? =
1360
1373
new Map <Variable , VariableModel <Variable , Type >>.from (
@@ -1685,7 +1698,7 @@ class FlowModel<Variable extends Object, Type extends Object> {
1685
1698
: _updateVariableInfo (
1686
1699
variable,
1687
1700
new VariableModel <Variable , Type >(newPromotedTypes, newTested,
1688
- info.assigned, info.unassigned, info.writeCaptured ),
1701
+ info.assigned, info.unassigned, info.ssaNode ),
1689
1702
reachable: newReachable);
1690
1703
}
1691
1704
@@ -1949,6 +1962,29 @@ class Reachability {
1949
1962
}
1950
1963
}
1951
1964
1965
+ /// Data structure representing a unique value that a variable might take on
1966
+ /// during execution of the code being analyzed. SSA nodes are immutable (so
1967
+ /// they can be safety shared among data structures) and have identity (so that
1968
+ /// it is possible to tell whether one SSA node is the same as another).
1969
+ ///
1970
+ /// This is similar to the nodes used in traditional single assignment analysis
1971
+ /// (https://en.wikipedia.org/wiki/Static_single_assignment_form) except that it
1972
+ /// does not store a complete IR of the code being analyzed.
1973
+ @visibleForTesting
1974
+ class SsaNode {
1975
+ /// Expando mapping SSA nodes to debug ids. Only used by `toString` .
1976
+ static final Expando <int > _debugIds = new Expando <int >();
1977
+
1978
+ static int _nextDebugId = 0 ;
1979
+
1980
+ @override
1981
+ String toString () {
1982
+ SsaNode self = this ; // Work around #44475
1983
+ int id = _debugIds[self] ?? = _nextDebugId++ ;
1984
+ return 'ssa$id ' ;
1985
+ }
1986
+ }
1987
+
1952
1988
/// Enum representing the different classifications of types that can be
1953
1989
/// returned by [TypeOperations.classifyType] .
1954
1990
enum TypeClassification {
@@ -2043,11 +2079,17 @@ class VariableModel<Variable extends Object, Type extends Object> {
2043
2079
/// Indicates whether the variable is unassigned.
2044
2080
final bool unassigned;
2045
2081
2046
- /// Indicates whether the variable has been write captured.
2047
- final bool writeCaptured;
2082
+ /// SSA node associated with this variable. Every time the variable's value
2083
+ /// potentially changes (either through an explicit write or a join with a
2084
+ /// control flow path that contains a write), this field is updated to point
2085
+ /// to a fresh node. Thus, it can be used to detect whether a variable's
2086
+ /// value has changed since a time in the past.
2087
+ ///
2088
+ /// `null` if the variable has been write captured.
2089
+ final SsaNode ? ssaNode;
2048
2090
2049
2091
VariableModel (this .promotedTypes, this .tested, this .assigned, this .unassigned,
2050
- this .writeCaptured ) {
2092
+ this .ssaNode ) {
2051
2093
assert (! (assigned && unassigned),
2052
2094
"Can't be both definitely assigned and unassigned" );
2053
2095
assert (promotedTypes == null || promotedTypes! .isNotEmpty);
@@ -2065,16 +2107,19 @@ class VariableModel<Variable extends Object, Type extends Object> {
2065
2107
: promotedTypes = null ,
2066
2108
tested = const [],
2067
2109
unassigned = ! assigned,
2068
- writeCaptured = false ;
2110
+ ssaNode = new SsaNode ();
2111
+
2112
+ /// Indicates whether the variable has been write captured.
2113
+ bool get writeCaptured => ssaNode == null ;
2069
2114
2070
2115
/// Returns a new [VariableModel] in which any promotions present have been
2071
2116
/// dropped, and the variable has been marked as "not unassigned".
2117
+ ///
2118
+ /// Used by [conservativeJoin] to update the state of variables at the top of
2119
+ /// loops whose bodies write to them.
2072
2120
VariableModel <Variable , Type > discardPromotionsAndMarkNotUnassigned () {
2073
- if (promotedTypes == null && ! unassigned) {
2074
- return this ;
2075
- }
2076
2121
return new VariableModel <Variable , Type >(
2077
- null , tested, assigned, false , writeCaptured);
2122
+ null , tested, assigned, false , writeCaptured ? null : new SsaNode () );
2078
2123
}
2079
2124
2080
2125
/// Returns an updated model reflect a control path that is known to have
@@ -2141,7 +2186,7 @@ class VariableModel<Variable extends Object, Type extends Object> {
2141
2186
}
2142
2187
}
2143
2188
return _identicalOrNew (this , otherModel, newPromotedTypes, tested,
2144
- newAssigned, newUnassigned, newWriteCaptured);
2189
+ newAssigned, newUnassigned, newWriteCaptured ? null : ssaNode );
2145
2190
}
2146
2191
2147
2192
@override
@@ -2171,7 +2216,7 @@ class VariableModel<Variable extends Object, Type extends Object> {
2171
2216
TypeOperations <Variable , Type > typeOperations) {
2172
2217
if (writeCaptured) {
2173
2218
return new VariableModel <Variable , Type >(
2174
- promotedTypes, tested, true , false , writeCaptured );
2219
+ promotedTypes, tested, true , false , null );
2175
2220
}
2176
2221
2177
2222
List <Type >? newPromotedTypes = _demoteViaAssignment (
@@ -2182,7 +2227,10 @@ class VariableModel<Variable extends Object, Type extends Object> {
2182
2227
Type declaredType = typeOperations.variableType (variable);
2183
2228
newPromotedTypes = _tryPromoteToTypeOfInterest (
2184
2229
typeOperations, declaredType, newPromotedTypes, writtenType);
2185
- if (identical (promotedTypes, newPromotedTypes) && assigned) return this ;
2230
+ if (identical (promotedTypes, newPromotedTypes) && assigned) {
2231
+ return new VariableModel <Variable , Type >(
2232
+ promotedTypes, tested, assigned, unassigned, new SsaNode ());
2233
+ }
2186
2234
2187
2235
List <Type > newTested;
2188
2236
if (newPromotedTypes == null && promotedTypes != null ) {
@@ -2192,14 +2240,14 @@ class VariableModel<Variable extends Object, Type extends Object> {
2192
2240
}
2193
2241
2194
2242
return new VariableModel <Variable , Type >(
2195
- newPromotedTypes, newTested, true , false , writeCaptured );
2243
+ newPromotedTypes, newTested, true , false , new SsaNode () );
2196
2244
}
2197
2245
2198
2246
/// Returns a new [VariableModel] reflecting the fact that the variable has
2199
2247
/// been write-captured.
2200
2248
VariableModel <Variable , Type > writeCapture () {
2201
2249
return new VariableModel <Variable , Type >(
2202
- null , const [], assigned, false , true );
2250
+ null , const [], assigned, false , null );
2203
2251
}
2204
2252
2205
2253
List <Type >? _demoteViaAssignment (
@@ -2356,7 +2404,7 @@ class VariableModel<Variable extends Object, Type extends Object> {
2356
2404
List <Type > newTested = joinTested (tested, model.tested, typeOperations);
2357
2405
if (identical (newTested, model.tested)) return model;
2358
2406
return new VariableModel <Variable , Type >(model.promotedTypes, newTested,
2359
- model.assigned, model.unassigned, model.writeCaptured );
2407
+ model.assigned, model.unassigned, model.ssaNode );
2360
2408
}
2361
2409
2362
2410
/// Joins two variable models. See [FlowModel.join] for details.
@@ -2375,8 +2423,13 @@ class VariableModel<Variable extends Object, Type extends Object> {
2375
2423
List <Type > newTested = newWriteCaptured
2376
2424
? const []
2377
2425
: joinTested (first.tested, second.tested, typeOperations);
2426
+ SsaNode ? newSsaNode = newWriteCaptured
2427
+ ? null
2428
+ : first.ssaNode == second.ssaNode
2429
+ ? first.ssaNode
2430
+ : new SsaNode ();
2378
2431
return _identicalOrNew (first, second, newPromotedTypes, newTested,
2379
- newAssigned, newUnassigned, newWriteCaptured);
2432
+ newAssigned, newUnassigned, newWriteCaptured ? null : newSsaNode );
2380
2433
}
2381
2434
2382
2435
/// Performs the portion of the "join" algorithm that applies to promotion
@@ -2487,22 +2540,22 @@ class VariableModel<Variable extends Object, Type extends Object> {
2487
2540
List <Type > newTested,
2488
2541
bool newAssigned,
2489
2542
bool newUnassigned,
2490
- bool newWriteCaptured ) {
2543
+ SsaNode ? newSsaNode ) {
2491
2544
if (identical (first.promotedTypes, newPromotedTypes) &&
2492
2545
identical (first.tested, newTested) &&
2493
2546
first.assigned == newAssigned &&
2494
2547
first.unassigned == newUnassigned &&
2495
- first.writeCaptured == newWriteCaptured ) {
2548
+ first.ssaNode == newSsaNode ) {
2496
2549
return first;
2497
2550
} else if (identical (second.promotedTypes, newPromotedTypes) &&
2498
2551
identical (second.tested, newTested) &&
2499
2552
second.assigned == newAssigned &&
2500
2553
second.unassigned == newUnassigned &&
2501
- second.writeCaptured == newWriteCaptured ) {
2554
+ second.ssaNode == newSsaNode ) {
2502
2555
return second;
2503
2556
} else {
2504
- return new VariableModel <Variable , Type >(newPromotedTypes, newTested,
2505
- newAssigned, newUnassigned, newWriteCaptured );
2557
+ return new VariableModel <Variable , Type >(
2558
+ newPromotedTypes, newTested, newAssigned, newUnassigned, newSsaNode );
2506
2559
}
2507
2560
}
2508
2561
@@ -3137,6 +3190,10 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
3137
3190
return _current.infoFor (variable).promotedTypes? .last;
3138
3191
}
3139
3192
3193
+ @override
3194
+ SsaNode ? ssaNodeForTesting (Variable variable) =>
3195
+ _current.variableInfo[variable]? .ssaNode;
3196
+
3140
3197
@override
3141
3198
void switchStatement_beginCase (bool hasLabel, Node node) {
3142
3199
AssignedVariablesNodeInfo <Variable > info =
0 commit comments