Skip to content

Commit 1eb147d

Browse files
stereotype441commit-bot@chromium.org
authored andcommitted
Flow analysis: add infrastructure for tracking SSA nodes.
This is the first step in a sequence of CLs to support type promotion based on local boolean variables (dart-lang/language#1274). To do this, we will need a way to reliably tell whether a variable's value has changed from one point in program execution to another, and to associate additional information with a particular value of a variable. We'll accomplish both of these tasks by associating each variable with a pointer to an "SSA node". This pointer is updated to point to a fresh node whenever the variable is written to, or two different possible values come together at a control flow join. Note that when a variable is write captured, it becomes impossible to track when/if its value might change Previously we tracked this using a `writeCaptured` boolean; now we track it by setting the SSA node pointer to `null`. This CL just lays the groundwork infrastructure and unit tests it; there is no user-visible change. Bug: dart-lang/language#1274 Change-Id: Id729390655c9371cba264816b418f6c0463e1758 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/176180 Reviewed-by: Konstantin Shcheglov <[email protected]> Commit-Queue: Paul Berry <[email protected]>
1 parent 222f3dc commit 1eb147d

File tree

4 files changed

+582
-60
lines changed

4 files changed

+582
-60
lines changed

pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart

Lines changed: 79 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,12 @@ abstract class FlowAnalysis<Node extends Object, Statement extends Node,
651651
/// is currently promoted. Otherwise returns `null`.
652652
Type? promotedType(Variable variable);
653653

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+
654660
/// Call this method just before visiting one of the cases in the body of a
655661
/// switch statement. See [switchStatement_expressionEnd] for details.
656662
///
@@ -1138,6 +1144,13 @@ class FlowAnalysisDebug<Node extends Object, Statement extends Node,
11381144
isQuery: true);
11391145
}
11401146

1147+
@override
1148+
SsaNode? ssaNodeForTesting(Variable variable) {
1149+
return _wrap('ssaNodeForTesting($variable)',
1150+
() => _wrapped.ssaNodeForTesting(variable),
1151+
isQuery: true);
1152+
}
1153+
11411154
@override
11421155
void switchStatement_beginCase(bool hasLabel, Node node) {
11431156
_wrap('switchStatement_beginCase($hasLabel, $node)',
@@ -1354,7 +1367,7 @@ class FlowModel<Variable extends Object, Type extends Object> {
13541367
(newVariableInfo ??=
13551368
new Map<Variable, VariableModel<Variable, Type>>.from(
13561369
variableInfo))[variable] = new VariableModel<Variable, Type>(
1357-
null, const [], false, false, true);
1370+
null, const [], false, false, null);
13581371
} else if (!info.writeCaptured) {
13591372
(newVariableInfo ??=
13601373
new Map<Variable, VariableModel<Variable, Type>>.from(
@@ -1685,7 +1698,7 @@ class FlowModel<Variable extends Object, Type extends Object> {
16851698
: _updateVariableInfo(
16861699
variable,
16871700
new VariableModel<Variable, Type>(newPromotedTypes, newTested,
1688-
info.assigned, info.unassigned, info.writeCaptured),
1701+
info.assigned, info.unassigned, info.ssaNode),
16891702
reachable: newReachable);
16901703
}
16911704

@@ -1949,6 +1962,29 @@ class Reachability {
19491962
}
19501963
}
19511964

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+
19521988
/// Enum representing the different classifications of types that can be
19531989
/// returned by [TypeOperations.classifyType].
19541990
enum TypeClassification {
@@ -2043,11 +2079,17 @@ class VariableModel<Variable extends Object, Type extends Object> {
20432079
/// Indicates whether the variable is unassigned.
20442080
final bool unassigned;
20452081

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;
20482090

20492091
VariableModel(this.promotedTypes, this.tested, this.assigned, this.unassigned,
2050-
this.writeCaptured) {
2092+
this.ssaNode) {
20512093
assert(!(assigned && unassigned),
20522094
"Can't be both definitely assigned and unassigned");
20532095
assert(promotedTypes == null || promotedTypes!.isNotEmpty);
@@ -2065,16 +2107,19 @@ class VariableModel<Variable extends Object, Type extends Object> {
20652107
: promotedTypes = null,
20662108
tested = const [],
20672109
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;
20692114

20702115
/// Returns a new [VariableModel] in which any promotions present have been
20712116
/// 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.
20722120
VariableModel<Variable, Type> discardPromotionsAndMarkNotUnassigned() {
2073-
if (promotedTypes == null && !unassigned) {
2074-
return this;
2075-
}
20762121
return new VariableModel<Variable, Type>(
2077-
null, tested, assigned, false, writeCaptured);
2122+
null, tested, assigned, false, writeCaptured ? null : new SsaNode());
20782123
}
20792124

20802125
/// 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> {
21412186
}
21422187
}
21432188
return _identicalOrNew(this, otherModel, newPromotedTypes, tested,
2144-
newAssigned, newUnassigned, newWriteCaptured);
2189+
newAssigned, newUnassigned, newWriteCaptured ? null : ssaNode);
21452190
}
21462191

21472192
@override
@@ -2171,7 +2216,7 @@ class VariableModel<Variable extends Object, Type extends Object> {
21712216
TypeOperations<Variable, Type> typeOperations) {
21722217
if (writeCaptured) {
21732218
return new VariableModel<Variable, Type>(
2174-
promotedTypes, tested, true, false, writeCaptured);
2219+
promotedTypes, tested, true, false, null);
21752220
}
21762221

21772222
List<Type>? newPromotedTypes = _demoteViaAssignment(
@@ -2182,7 +2227,10 @@ class VariableModel<Variable extends Object, Type extends Object> {
21822227
Type declaredType = typeOperations.variableType(variable);
21832228
newPromotedTypes = _tryPromoteToTypeOfInterest(
21842229
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+
}
21862234

21872235
List<Type> newTested;
21882236
if (newPromotedTypes == null && promotedTypes != null) {
@@ -2192,14 +2240,14 @@ class VariableModel<Variable extends Object, Type extends Object> {
21922240
}
21932241

21942242
return new VariableModel<Variable, Type>(
2195-
newPromotedTypes, newTested, true, false, writeCaptured);
2243+
newPromotedTypes, newTested, true, false, new SsaNode());
21962244
}
21972245

21982246
/// Returns a new [VariableModel] reflecting the fact that the variable has
21992247
/// been write-captured.
22002248
VariableModel<Variable, Type> writeCapture() {
22012249
return new VariableModel<Variable, Type>(
2202-
null, const [], assigned, false, true);
2250+
null, const [], assigned, false, null);
22032251
}
22042252

22052253
List<Type>? _demoteViaAssignment(
@@ -2356,7 +2404,7 @@ class VariableModel<Variable extends Object, Type extends Object> {
23562404
List<Type> newTested = joinTested(tested, model.tested, typeOperations);
23572405
if (identical(newTested, model.tested)) return model;
23582406
return new VariableModel<Variable, Type>(model.promotedTypes, newTested,
2359-
model.assigned, model.unassigned, model.writeCaptured);
2407+
model.assigned, model.unassigned, model.ssaNode);
23602408
}
23612409

23622410
/// Joins two variable models. See [FlowModel.join] for details.
@@ -2375,8 +2423,13 @@ class VariableModel<Variable extends Object, Type extends Object> {
23752423
List<Type> newTested = newWriteCaptured
23762424
? const []
23772425
: joinTested(first.tested, second.tested, typeOperations);
2426+
SsaNode? newSsaNode = newWriteCaptured
2427+
? null
2428+
: first.ssaNode == second.ssaNode
2429+
? first.ssaNode
2430+
: new SsaNode();
23782431
return _identicalOrNew(first, second, newPromotedTypes, newTested,
2379-
newAssigned, newUnassigned, newWriteCaptured);
2432+
newAssigned, newUnassigned, newWriteCaptured ? null : newSsaNode);
23802433
}
23812434

23822435
/// Performs the portion of the "join" algorithm that applies to promotion
@@ -2487,22 +2540,22 @@ class VariableModel<Variable extends Object, Type extends Object> {
24872540
List<Type> newTested,
24882541
bool newAssigned,
24892542
bool newUnassigned,
2490-
bool newWriteCaptured) {
2543+
SsaNode? newSsaNode) {
24912544
if (identical(first.promotedTypes, newPromotedTypes) &&
24922545
identical(first.tested, newTested) &&
24932546
first.assigned == newAssigned &&
24942547
first.unassigned == newUnassigned &&
2495-
first.writeCaptured == newWriteCaptured) {
2548+
first.ssaNode == newSsaNode) {
24962549
return first;
24972550
} else if (identical(second.promotedTypes, newPromotedTypes) &&
24982551
identical(second.tested, newTested) &&
24992552
second.assigned == newAssigned &&
25002553
second.unassigned == newUnassigned &&
2501-
second.writeCaptured == newWriteCaptured) {
2554+
second.ssaNode == newSsaNode) {
25022555
return second;
25032556
} 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);
25062559
}
25072560
}
25082561

@@ -3137,6 +3190,10 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
31373190
return _current.infoFor(variable).promotedTypes?.last;
31383191
}
31393192

3193+
@override
3194+
SsaNode? ssaNodeForTesting(Variable variable) =>
3195+
_current.variableInfo[variable]?.ssaNode;
3196+
31403197
@override
31413198
void switchStatement_beginCase(bool hasLabel, Node node) {
31423199
AssignedVariablesNodeInfo<Variable> info =

pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,14 @@ Statement checkUnassigned(Var variable, bool expectedUnassignedState) =>
6767
Statement continue_(BranchTargetPlaceholder branchTargetPlaceholder) =>
6868
new _Continue(branchTargetPlaceholder);
6969

70-
Statement declare(Var variable, {required bool initialized}) =>
71-
new _Declare(variable, initialized);
70+
Statement declare(Var variable,
71+
{required bool initialized, bool isFinal = false}) =>
72+
new _Declare(
73+
variable, initialized ? expr(variable.type.type) : null, isFinal);
74+
75+
Statement declareInitialized(Var variable, Expression initializer,
76+
{bool isFinal = false}) =>
77+
new _Declare(variable, initializer, isFinal);
7278

7379
Statement do_(List<Statement> body, Expression condition) =>
7480
_Do(body, condition);
@@ -126,6 +132,12 @@ Statement forEachWithVariableSet(
126132
return new _ForEach(variable, iterable, body, false);
127133
}
128134

135+
/// Creates a [Statement] that, when analyzed, will cause [callback] to be
136+
/// passed an [SsaNodeHarness] allowing the test to examine the values of
137+
/// variables' SSA nodes.
138+
Statement getSsaNodes(void Function(SsaNodeHarness) callback) =>
139+
new _GetSsaNodes(callback);
140+
129141
Statement if_(Expression condition, List<Statement> ifTrue,
130142
[List<Statement>? ifFalse]) =>
131143
new _If(condition, ifTrue, ifFalse);
@@ -258,6 +270,8 @@ abstract class Expression implements _Visitable<Type> {
258270
/// needed for testing.
259271
class Harness extends TypeOperations<Var, Type> {
260272
static const Map<String, bool> _coreSubtypes = const {
273+
'bool <: int': false,
274+
'bool <: Object': true,
261275
'double <: Object': true,
262276
'double <: num': true,
263277
'double <: num?': true,
@@ -326,6 +340,7 @@ class Harness extends TypeOperations<Var, Type> {
326340
'String <: int': false,
327341
'String <: int?': false,
328342
'String <: num?': false,
343+
'String <: Object': true,
329344
'String <: Object?': true,
330345
};
331346

@@ -335,7 +350,9 @@ class Harness extends TypeOperations<Var, Type> {
335350
'Object? - num?': Type('Object'),
336351
'Object? - Object?': Type('Never?'),
337352
'Object? - String': Type('Object?'),
353+
'Object - bool': Type('Object'),
338354
'Object - int': Type('Object'),
355+
'Object - String': Type('Object'),
339356
'int - Object': Type('Never'),
340357
'int - String': Type('int'),
341358
'int - int': Type('Never'),
@@ -489,6 +506,17 @@ class Node {
489506
Node._();
490507
}
491508

509+
/// Helper class allowing tests to examine the values of variables' SSA nodes.
510+
class SsaNodeHarness {
511+
final FlowAnalysis<Node, Statement, Expression, Var, Type> _flow;
512+
513+
SsaNodeHarness(this._flow);
514+
515+
/// Gets the SSA node associated with [variable] at the current point in
516+
/// control flow, or `null` if the variable has been write captured.
517+
SsaNode? operator [](Var variable) => _flow.ssaNodeForTesting(variable);
518+
}
519+
492520
/// Representation of a statement in the pseudo-Dart language used for flow
493521
/// analysis testing.
494522
abstract class Statement extends Node implements _Visitable<void> {
@@ -812,20 +840,33 @@ class _Continue extends Statement {
812840

813841
class _Declare extends Statement {
814842
final Var variable;
815-
final bool initialized;
843+
final Expression? initializer;
844+
final bool isFinal;
816845

817-
_Declare(this.variable, this.initialized) : super._();
846+
_Declare(this.variable, this.initializer, this.isFinal) : super._();
818847

819848
@override
820-
String toString() => '$variable${initialized ? ' = ...' : ''};';
849+
String toString() {
850+
var finalPart = isFinal ? 'final ' : '';
851+
var initializerPart = initializer != null ? ' = $initializer' : '';
852+
return '$finalPart$variable${initializerPart};';
853+
}
821854

822855
@override
823-
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
856+
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
857+
initializer?._preVisit(assignedVariables);
858+
}
824859

825860
@override
826861
void _visit(
827862
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
828-
flow.declare(variable, initialized);
863+
var initializer = this.initializer;
864+
if (initializer == null) {
865+
flow.declare(variable, false);
866+
} else {
867+
initializer._visit(h, flow);
868+
flow.declare(variable, true);
869+
}
829870
}
830871
}
831872

@@ -1006,6 +1047,21 @@ class _ForEach extends Statement {
10061047
}
10071048
}
10081049

1050+
class _GetSsaNodes extends Statement {
1051+
final void Function(SsaNodeHarness) callback;
1052+
1053+
_GetSsaNodes(this.callback) : super._();
1054+
1055+
@override
1056+
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
1057+
1058+
@override
1059+
void _visit(
1060+
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
1061+
callback(SsaNodeHarness(flow));
1062+
}
1063+
}
1064+
10091065
class _If extends Statement {
10101066
final Expression condition;
10111067
final List<Statement> ifTrue;

0 commit comments

Comments
 (0)