Skip to content

Commit

Permalink
Flow analysis: add infrastructure for tracking SSA nodes.
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
stereotype441 authored and [email protected] committed Dec 15, 2020
1 parent 222f3dc commit 1eb147d
Show file tree
Hide file tree
Showing 4 changed files with 582 additions and 60 deletions.
101 changes: 79 additions & 22 deletions pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,12 @@ abstract class FlowAnalysis<Node extends Object, Statement extends Node,
/// is currently promoted. Otherwise returns `null`.
Type? promotedType(Variable variable);

/// Retrieves the SSA node associated with [variable], or `null` if [variable]
/// is not associated with an SSA node because it is write captured. For
/// testing only.
@visibleForTesting
SsaNode? ssaNodeForTesting(Variable variable);

/// Call this method just before visiting one of the cases in the body of a
/// switch statement. See [switchStatement_expressionEnd] for details.
///
Expand Down Expand Up @@ -1138,6 +1144,13 @@ class FlowAnalysisDebug<Node extends Object, Statement extends Node,
isQuery: true);
}

@override
SsaNode? ssaNodeForTesting(Variable variable) {
return _wrap('ssaNodeForTesting($variable)',
() => _wrapped.ssaNodeForTesting(variable),
isQuery: true);
}

@override
void switchStatement_beginCase(bool hasLabel, Node node) {
_wrap('switchStatement_beginCase($hasLabel, $node)',
Expand Down Expand Up @@ -1354,7 +1367,7 @@ class FlowModel<Variable extends Object, Type extends Object> {
(newVariableInfo ??=
new Map<Variable, VariableModel<Variable, Type>>.from(
variableInfo))[variable] = new VariableModel<Variable, Type>(
null, const [], false, false, true);
null, const [], false, false, null);
} else if (!info.writeCaptured) {
(newVariableInfo ??=
new Map<Variable, VariableModel<Variable, Type>>.from(
Expand Down Expand Up @@ -1685,7 +1698,7 @@ class FlowModel<Variable extends Object, Type extends Object> {
: _updateVariableInfo(
variable,
new VariableModel<Variable, Type>(newPromotedTypes, newTested,
info.assigned, info.unassigned, info.writeCaptured),
info.assigned, info.unassigned, info.ssaNode),
reachable: newReachable);
}

Expand Down Expand Up @@ -1949,6 +1962,29 @@ class Reachability {
}
}

/// Data structure representing a unique value that a variable might take on
/// during execution of the code being analyzed. SSA nodes are immutable (so
/// they can be safety shared among data structures) and have identity (so that
/// it is possible to tell whether one SSA node is the same as another).
///
/// This is similar to the nodes used in traditional single assignment analysis
/// (https://en.wikipedia.org/wiki/Static_single_assignment_form) except that it
/// does not store a complete IR of the code being analyzed.
@visibleForTesting
class SsaNode {
/// Expando mapping SSA nodes to debug ids. Only used by `toString`.
static final Expando<int> _debugIds = new Expando<int>();

static int _nextDebugId = 0;

@override
String toString() {
SsaNode self = this; // Work around #44475
int id = _debugIds[self] ??= _nextDebugId++;
return 'ssa$id';
}
}

/// Enum representing the different classifications of types that can be
/// returned by [TypeOperations.classifyType].
enum TypeClassification {
Expand Down Expand Up @@ -2043,11 +2079,17 @@ class VariableModel<Variable extends Object, Type extends Object> {
/// Indicates whether the variable is unassigned.
final bool unassigned;

/// Indicates whether the variable has been write captured.
final bool writeCaptured;
/// SSA node associated with this variable. Every time the variable's value
/// potentially changes (either through an explicit write or a join with a
/// control flow path that contains a write), this field is updated to point
/// to a fresh node. Thus, it can be used to detect whether a variable's
/// value has changed since a time in the past.
///
/// `null` if the variable has been write captured.
final SsaNode? ssaNode;

VariableModel(this.promotedTypes, this.tested, this.assigned, this.unassigned,
this.writeCaptured) {
this.ssaNode) {
assert(!(assigned && unassigned),
"Can't be both definitely assigned and unassigned");
assert(promotedTypes == null || promotedTypes!.isNotEmpty);
Expand All @@ -2065,16 +2107,19 @@ class VariableModel<Variable extends Object, Type extends Object> {
: promotedTypes = null,
tested = const [],
unassigned = !assigned,
writeCaptured = false;
ssaNode = new SsaNode();

/// Indicates whether the variable has been write captured.
bool get writeCaptured => ssaNode == null;

/// Returns a new [VariableModel] in which any promotions present have been
/// dropped, and the variable has been marked as "not unassigned".
///
/// Used by [conservativeJoin] to update the state of variables at the top of
/// loops whose bodies write to them.
VariableModel<Variable, Type> discardPromotionsAndMarkNotUnassigned() {
if (promotedTypes == null && !unassigned) {
return this;
}
return new VariableModel<Variable, Type>(
null, tested, assigned, false, writeCaptured);
null, tested, assigned, false, writeCaptured ? null : new SsaNode());
}

/// Returns an updated model reflect a control path that is known to have
Expand Down Expand Up @@ -2141,7 +2186,7 @@ class VariableModel<Variable extends Object, Type extends Object> {
}
}
return _identicalOrNew(this, otherModel, newPromotedTypes, tested,
newAssigned, newUnassigned, newWriteCaptured);
newAssigned, newUnassigned, newWriteCaptured ? null : ssaNode);
}

@override
Expand Down Expand Up @@ -2171,7 +2216,7 @@ class VariableModel<Variable extends Object, Type extends Object> {
TypeOperations<Variable, Type> typeOperations) {
if (writeCaptured) {
return new VariableModel<Variable, Type>(
promotedTypes, tested, true, false, writeCaptured);
promotedTypes, tested, true, false, null);
}

List<Type>? newPromotedTypes = _demoteViaAssignment(
Expand All @@ -2182,7 +2227,10 @@ class VariableModel<Variable extends Object, Type extends Object> {
Type declaredType = typeOperations.variableType(variable);
newPromotedTypes = _tryPromoteToTypeOfInterest(
typeOperations, declaredType, newPromotedTypes, writtenType);
if (identical(promotedTypes, newPromotedTypes) && assigned) return this;
if (identical(promotedTypes, newPromotedTypes) && assigned) {
return new VariableModel<Variable, Type>(
promotedTypes, tested, assigned, unassigned, new SsaNode());
}

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

return new VariableModel<Variable, Type>(
newPromotedTypes, newTested, true, false, writeCaptured);
newPromotedTypes, newTested, true, false, new SsaNode());
}

/// Returns a new [VariableModel] reflecting the fact that the variable has
/// been write-captured.
VariableModel<Variable, Type> writeCapture() {
return new VariableModel<Variable, Type>(
null, const [], assigned, false, true);
null, const [], assigned, false, null);
}

List<Type>? _demoteViaAssignment(
Expand Down Expand Up @@ -2356,7 +2404,7 @@ class VariableModel<Variable extends Object, Type extends Object> {
List<Type> newTested = joinTested(tested, model.tested, typeOperations);
if (identical(newTested, model.tested)) return model;
return new VariableModel<Variable, Type>(model.promotedTypes, newTested,
model.assigned, model.unassigned, model.writeCaptured);
model.assigned, model.unassigned, model.ssaNode);
}

/// Joins two variable models. See [FlowModel.join] for details.
Expand All @@ -2375,8 +2423,13 @@ class VariableModel<Variable extends Object, Type extends Object> {
List<Type> newTested = newWriteCaptured
? const []
: joinTested(first.tested, second.tested, typeOperations);
SsaNode? newSsaNode = newWriteCaptured
? null
: first.ssaNode == second.ssaNode
? first.ssaNode
: new SsaNode();
return _identicalOrNew(first, second, newPromotedTypes, newTested,
newAssigned, newUnassigned, newWriteCaptured);
newAssigned, newUnassigned, newWriteCaptured ? null : newSsaNode);
}

/// Performs the portion of the "join" algorithm that applies to promotion
Expand Down Expand Up @@ -2487,22 +2540,22 @@ class VariableModel<Variable extends Object, Type extends Object> {
List<Type> newTested,
bool newAssigned,
bool newUnassigned,
bool newWriteCaptured) {
SsaNode? newSsaNode) {
if (identical(first.promotedTypes, newPromotedTypes) &&
identical(first.tested, newTested) &&
first.assigned == newAssigned &&
first.unassigned == newUnassigned &&
first.writeCaptured == newWriteCaptured) {
first.ssaNode == newSsaNode) {
return first;
} else if (identical(second.promotedTypes, newPromotedTypes) &&
identical(second.tested, newTested) &&
second.assigned == newAssigned &&
second.unassigned == newUnassigned &&
second.writeCaptured == newWriteCaptured) {
second.ssaNode == newSsaNode) {
return second;
} else {
return new VariableModel<Variable, Type>(newPromotedTypes, newTested,
newAssigned, newUnassigned, newWriteCaptured);
return new VariableModel<Variable, Type>(
newPromotedTypes, newTested, newAssigned, newUnassigned, newSsaNode);
}
}

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

@override
SsaNode? ssaNodeForTesting(Variable variable) =>
_current.variableInfo[variable]?.ssaNode;

@override
void switchStatement_beginCase(bool hasLabel, Node node) {
AssignedVariablesNodeInfo<Variable> info =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,14 @@ Statement checkUnassigned(Var variable, bool expectedUnassignedState) =>
Statement continue_(BranchTargetPlaceholder branchTargetPlaceholder) =>
new _Continue(branchTargetPlaceholder);

Statement declare(Var variable, {required bool initialized}) =>
new _Declare(variable, initialized);
Statement declare(Var variable,
{required bool initialized, bool isFinal = false}) =>
new _Declare(
variable, initialized ? expr(variable.type.type) : null, isFinal);

Statement declareInitialized(Var variable, Expression initializer,
{bool isFinal = false}) =>
new _Declare(variable, initializer, isFinal);

Statement do_(List<Statement> body, Expression condition) =>
_Do(body, condition);
Expand Down Expand Up @@ -126,6 +132,12 @@ Statement forEachWithVariableSet(
return new _ForEach(variable, iterable, body, false);
}

/// Creates a [Statement] that, when analyzed, will cause [callback] to be
/// passed an [SsaNodeHarness] allowing the test to examine the values of
/// variables' SSA nodes.
Statement getSsaNodes(void Function(SsaNodeHarness) callback) =>
new _GetSsaNodes(callback);

Statement if_(Expression condition, List<Statement> ifTrue,
[List<Statement>? ifFalse]) =>
new _If(condition, ifTrue, ifFalse);
Expand Down Expand Up @@ -258,6 +270,8 @@ abstract class Expression implements _Visitable<Type> {
/// needed for testing.
class Harness extends TypeOperations<Var, Type> {
static const Map<String, bool> _coreSubtypes = const {
'bool <: int': false,
'bool <: Object': true,
'double <: Object': true,
'double <: num': true,
'double <: num?': true,
Expand Down Expand Up @@ -326,6 +340,7 @@ class Harness extends TypeOperations<Var, Type> {
'String <: int': false,
'String <: int?': false,
'String <: num?': false,
'String <: Object': true,
'String <: Object?': true,
};

Expand All @@ -335,7 +350,9 @@ class Harness extends TypeOperations<Var, Type> {
'Object? - num?': Type('Object'),
'Object? - Object?': Type('Never?'),
'Object? - String': Type('Object?'),
'Object - bool': Type('Object'),
'Object - int': Type('Object'),
'Object - String': Type('Object'),
'int - Object': Type('Never'),
'int - String': Type('int'),
'int - int': Type('Never'),
Expand Down Expand Up @@ -489,6 +506,17 @@ class Node {
Node._();
}

/// Helper class allowing tests to examine the values of variables' SSA nodes.
class SsaNodeHarness {
final FlowAnalysis<Node, Statement, Expression, Var, Type> _flow;

SsaNodeHarness(this._flow);

/// Gets the SSA node associated with [variable] at the current point in
/// control flow, or `null` if the variable has been write captured.
SsaNode? operator [](Var variable) => _flow.ssaNodeForTesting(variable);
}

/// Representation of a statement in the pseudo-Dart language used for flow
/// analysis testing.
abstract class Statement extends Node implements _Visitable<void> {
Expand Down Expand Up @@ -812,20 +840,33 @@ class _Continue extends Statement {

class _Declare extends Statement {
final Var variable;
final bool initialized;
final Expression? initializer;
final bool isFinal;

_Declare(this.variable, this.initialized) : super._();
_Declare(this.variable, this.initializer, this.isFinal) : super._();

@override
String toString() => '$variable${initialized ? ' = ...' : ''};';
String toString() {
var finalPart = isFinal ? 'final ' : '';
var initializerPart = initializer != null ? ' = $initializer' : '';
return '$finalPart$variable${initializerPart};';
}

@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
initializer?._preVisit(assignedVariables);
}

@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.declare(variable, initialized);
var initializer = this.initializer;
if (initializer == null) {
flow.declare(variable, false);
} else {
initializer._visit(h, flow);
flow.declare(variable, true);
}
}
}

Expand Down Expand Up @@ -1006,6 +1047,21 @@ class _ForEach extends Statement {
}
}

class _GetSsaNodes extends Statement {
final void Function(SsaNodeHarness) callback;

_GetSsaNodes(this.callback) : super._();

@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}

@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
callback(SsaNodeHarness(flow));
}
}

class _If extends Statement {
final Expression condition;
final List<Statement> ifTrue;
Expand Down
Loading

0 comments on commit 1eb147d

Please sign in to comment.