[vm/aot/tfa] Share captured local variables among invocations

This is a step towards detaching bodies of closures/local
functions from the enclosing functions in TFA, so closures
could be analyzed separately.

Also, this change allows TFA to accumulate types of captured local
variables among all invocations.

On a large Flutter app, this change doesn't cause noticeable compilation
time or snapshot size changes.

Issue: https://github.com/dart-lang/sdk/issues/39692
Issue: https://github.com/dart-lang/sdk/issues/51102

TEST=pkg/vm/testcases/transformations/type_flow/summary_collector/control_flow.dart

Cq-Include-Trybots: luci.dart.try:vm-aot-linux-release-x64-try,vm-aot-mac-product-arm64-try
Change-Id: I785b15656df559a8cc80fcceea196b480ba7a91a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/318021
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Alexander Markov 2023-08-08 16:35:10 +00:00 committed by Commit Queue
parent 67d752aa12
commit fe7778291b
5 changed files with 240 additions and 49 deletions

View file

@ -983,6 +983,39 @@ class _FieldValue extends _DependencyTracker {
String toString() => "_FieldValue $field => $value";
}
class _SharedVariableImpl extends _DependencyTracker implements SharedVariable {
final String name;
Type value = emptyType;
_SharedVariableImpl(VariableDeclaration decl) : name = decl.name ?? '__tmp';
@override
Type getValue(
TypeHierarchy typeHierarchy, covariant TypeFlowAnalysis callHandler) {
addDependentInvocation(callHandler.currentInvocation);
return value;
}
@override
void setValue(
Type newValue, TypeHierarchy hierarchy, CallHandler callHandler) {
// Make sure type cones are specialized before putting them into shared
// variables, in order to ensure that dependency is established between
// cone's base type and corresponding invocation accessing variable.
newValue = value.union(newValue, hierarchy).specialize(hierarchy);
assert(newValue.isSpecialized);
if (newValue != value) {
invalidateDependentInvocations(
(callHandler as TypeFlowAnalysis).workList);
value = newValue;
}
}
@override
String toString() => name;
}
class _DynamicTargetSet extends _DependencyTracker {
final DynamicSelector selector;
final Set<Member> targets = new Set<Member>();
@ -1552,7 +1585,8 @@ class _WorkList {
}
}
class TypeFlowAnalysis implements EntryPointsListener, CallHandler {
class TypeFlowAnalysis
implements EntryPointsListener, CallHandler, SharedVariableBuilder {
final Target target;
final TypeEnvironment environment;
final CoreTypes coreTypes;
@ -1568,6 +1602,8 @@ class TypeFlowAnalysis implements EntryPointsListener, CallHandler {
final Map<Member, Summary> _summaries = <Member, Summary>{};
final Map<Field, _FieldValue> _fieldValues = <Field, _FieldValue>{};
final Map<VariableDeclaration, _SharedVariableImpl> _sharedCapturedVariables =
{};
final Set<Member> _tearOffTaken = new Set<Member>();
final Set<Member> _methodsAndSettersCalledDynamically = new Set<Member>();
final Set<Member> _gettersCalledDynamically = new Set<Member>();
@ -1597,6 +1633,7 @@ class TypeFlowAnalysis implements EntryPointsListener, CallHandler {
hierarchyCache,
nativeCodeOracle,
hierarchyCache,
this,
protobufHandler);
_invocationsCache = new _InvocationsCache(this);
workList = new _WorkList(this);
@ -1693,6 +1730,9 @@ class TypeFlowAnalysis implements EntryPointsListener, CallHandler {
Type? fieldType(Field field) => _fieldValues[field]?.value;
Type? capturedVariableType(VariableDeclaration v) =>
_sharedCapturedVariables[v]?.value;
Args<Type>? argumentTypes(Member member) => _summaries[member]?.argumentTypes;
Type? argumentType(Member member, VariableDeclaration memberParam) {
@ -1825,4 +1865,10 @@ class TypeFlowAnalysis implements EntryPointsListener, CallHandler {
void recordTearOff(Member target) {
_tearOffTaken.add(target);
}
/// ---- Implementation of [SharedVariableBuilder] interface. ----
@override
SharedVariable getSharedVariable(VariableDeclaration variable) =>
_sharedCapturedVariables[variable] ??= _SharedVariableImpl(variable);
}

View file

@ -68,6 +68,8 @@ abstract class StatementVisitor {
void visitTypeCheck(TypeCheck expr);
void visitUnaryOperation(UnaryOperation expr);
void visitBinaryOperation(BinaryOperation expr);
void visitReadVariable(ReadVariable expr);
void visitWriteVariable(WriteVariable expr);
}
/// Input parameter of the summary.
@ -889,6 +891,60 @@ class BinaryOperation extends Statement {
}
}
/// Box holding the value of a variable, shared among multiple summaries.
/// Used to represent captured variables.
abstract class SharedVariable {
Type getValue(TypeHierarchy typeHierarchy, CallHandler callHandler);
void setValue(
Type newValue, TypeHierarchy typeHierarchy, CallHandler callHandler);
}
abstract class SharedVariableBuilder {
/// Returns [SharedVariable] representing captured [variable].
SharedVariable getSharedVariable(VariableDeclaration variable);
}
/// Reads value from [variable].
class ReadVariable extends Statement {
final SharedVariable variable;
ReadVariable(this.variable);
@override
void accept(StatementVisitor visitor) => visitor.visitReadVariable(this);
@override
String dump() => "$label = read $variable$_conditionSuffix";
@override
Type apply(List<Type?> computedTypes, TypeHierarchy typeHierarchy,
CallHandler callHandler) {
return variable.getValue(typeHierarchy, callHandler);
}
}
/// Writes value [arg] to [variable].
class WriteVariable extends Statement {
final SharedVariable variable;
TypeExpr arg;
WriteVariable(this.variable, this.arg);
@override
void accept(StatementVisitor visitor) => visitor.visitWriteVariable(this);
@override
String dump() => "write $variable = $arg$_conditionSuffix";
@override
Type apply(List<Type?> computedTypes, TypeHierarchy typeHierarchy,
CallHandler callHandler) {
variable.setValue(
arg.getComputedType(computedTypes), typeHierarchy, callHandler);
return emptyType;
}
}
/// Summary is a linear sequence of statements representing a type flow in
/// one member, function or initializer.
class Summary {

View file

@ -71,7 +71,10 @@ class _SummaryNormalizer implements StatementVisitor {
}
for (final st in statements) {
if (st is Call || st is TypeCheck || st is NarrowNotNull) {
if (st is Call ||
st is TypeCheck ||
st is NarrowNotNull ||
st is WriteVariable) {
_normalizeExpr(st, false);
} else if (st is Use) {
_normalizeExpr(st.arg, true);
@ -252,6 +255,18 @@ class _SummaryNormalizer implements StatementVisitor {
if (_inLoop) return;
expr.arg2 = _normalizeExpr(expr.arg2, true);
}
@override
void visitReadVariable(ReadVariable expr) {
visitStatement(expr);
}
@override
void visitWriteVariable(WriteVariable expr) {
visitStatement(expr);
if (_inLoop) return;
expr.arg = _normalizeExpr(expr.arg, true);
}
}
/// Collects sets of captured variables, as well as variables
@ -465,6 +480,7 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
final TypesBuilder _typesBuilder;
final NativeCodeOracle _nativeCodeOracle;
final GenericInterfacesInfo _genericInterfacesInfo;
final SharedVariableBuilder _sharedVariableBuilder;
final ProtobufHandler? _protobufHandler;
final Map<TreeNode, Call> callSites = <TreeNode, Call>{};
@ -483,17 +499,20 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
// (e.g. after return or throw).
List<TypeExpr?> _variableValues = const <TypeExpr?>[];
// Contains Joins which accumulate all values of certain variables.
// Used only when all variable values should be merged regardless of control
// flow. Such accumulating joins are used for
// 1. captured variables, as closures may potentially see any value;
// Flags indicating that all values of variables should be merged
// regardless of control flow. Such aggregated values are used for
// 1. shared captured variables, as closures may potentially see any value;
// 2. variables modified inside try blocks (while in the try block), as
// catch can potentially see any value assigned to a variable inside try
// block.
// If _variableCells[i] != null, then all values are accumulated in the
// _variableCells[i]. _variableValues[i] does not change and remains equal
// to _variableCells[i].
List<Join?> _variableCells = const <Join?>[];
//
// If _aggregateVariable[i], then all values are accumulated
// and _variableValues[i] should not be changed.
List<bool> _aggregateVariable = const <bool>[];
// Cached unconditional reads of captured variables
// (can be reused to avoid repetitive reads).
Map<VariableDeclaration, ReadVariable>? _capturedVariableReads;
// Counts number of Joins inserted for each variable. Only used to set
// readable names for such joins (foo_0, foo_1 etc.)
@ -522,6 +541,7 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
this._typesBuilder,
this._nativeCodeOracle,
this._genericInterfacesInfo,
this._sharedVariableBuilder,
this._protobufHandler) {
constantAllocationCollector = new ConstantAllocationCollector(this);
_nullMethodsAndGetters.addAll(getSelectors(
@ -545,7 +565,9 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
_variablesInfo = new _VariablesInfoCollector(member);
_variableValues =
new List<TypeExpr?>.filled(_variablesInfo.numVariables, null);
_variableCells = new List<Join?>.filled(_variablesInfo.numVariables, null);
_aggregateVariable =
new List<bool>.filled(_variablesInfo.numVariables, false);
_capturedVariableReads = null;
_variableVersions = new List<int>.filled(_variablesInfo.numVariables, 0);
_jumpHandlers = null;
_returnValue = null;
@ -895,24 +917,63 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
assert(_variablesInfo.varDeclarations[varIndex] == decl);
assert(_variableValues[varIndex] == null);
if (_variablesInfo.isCaptured(decl)) {
final join = _makeJoin(varIndex, initialValue);
_variableCells[varIndex] = join;
_variableValues[varIndex] = join;
assert(!_aggregateVariable[varIndex]);
_aggregateVariable[varIndex] = true;
if (initialValue is! EmptyType) {
_writeVariable(decl, initialValue);
}
} else {
_variableValues[varIndex] = initialValue;
}
}
void _writeVariable(VariableDeclaration variable, TypeExpr value) {
if (_variablesInfo.isCaptured(variable)) {
assert(_aggregateVariable[_variablesInfo.varIndex[variable]!]);
final sharedVar = _sharedVariableBuilder.getSharedVariable(variable);
final write = WriteVariable(sharedVar, value)
..condition = _currentCondition;
_summary.add(write);
return;
}
final int varIndex = _variablesInfo.varIndex[variable]!;
final Join? join = _variableCells[varIndex];
if (join != null) {
if (_aggregateVariable[varIndex]) {
final Join join = _variableValues[varIndex] as Join;
_addValueToJoin(join, value);
} else {
_variableValues[varIndex] = value;
}
}
TypeExpr _readVariable(VariableDeclaration variable, TreeNode node) {
if (_variablesInfo.isCaptured(variable)) {
assert(_aggregateVariable[_variablesInfo.varIndex[variable]!]);
final cachedRead = _capturedVariableReads?[variable];
if (cachedRead != null) {
assert(cachedRead.variable ==
_sharedVariableBuilder.getSharedVariable(variable) &&
cachedRead.condition == null);
if (_currentCondition == null) {
return cachedRead;
} else {
return _makeUnaryOperation(UnaryOp.Move, cachedRead);
}
}
final sharedVar = _sharedVariableBuilder.getSharedVariable(variable);
final read = ReadVariable(sharedVar)..condition = _currentCondition;
_summary.add(read);
if (_currentCondition == null) {
(_capturedVariableReads ??= {})[variable] = read;
}
return read;
}
final v = _variableValues[_variablesInfo.varIndex[variable]!];
if (v == null) {
throw 'Unable to find variable ${variable} at ${node.location}';
}
return v;
}
List<TypeExpr?> _cloneVariableValues(List<TypeExpr?> values) =>
new List<TypeExpr?>.from(values);
@ -920,7 +981,7 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
final values =
new List<TypeExpr?>.filled(_variablesInfo.numVariables, null);
for (int i = 0; i < values.length; ++i) {
if (_variableCells[i] != null) {
if (_aggregateVariable[i]) {
values[i] = _variableValues[i];
} else if (_variableValues[i] != null) {
values[i] = emptyType;
@ -977,6 +1038,7 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
srcValue.condition == _currentCondition)) {
dst[i] = srcValue;
} else {
assert(!_aggregateVariable[i]);
final Join join = _makeJoin(i, dstValue);
join.values.add(srcValue);
dst[i] = join;
@ -1005,9 +1067,7 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
final List<Join?> joins =
new List<Join?>.filled(_variablesInfo.numVariables, null);
for (var i in _variablesInfo.getModifiedVariables(node)) {
if (_variableCells[i] != null) {
assert(_variableCells[i] == _variableValues[i]);
} else {
if (!_aggregateVariable[i]) {
final join = _makeJoin(i, _variableValues[i]!);
joins[i] = join;
_variableValues[i] = join;
@ -1015,7 +1075,7 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
// Inside try blocks all values of modified variables are merged,
// as catch can potentially see any value (in case exception
// is thrown after each assignment).
_variableCells[i] = join;
_aggregateVariable[i] = true;
}
}
}
@ -1023,12 +1083,11 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
}
/// Stops accumulating values in [joins] by removing them from
/// _variableCells.
void _restoreVariableCellsAfterTry(List<Join?> joins) {
/// _aggregateVariable.
void _restoreModifiedVariablesAfterTry(List<Join?> joins) {
for (int i = 0; i < joins.length; ++i) {
if (joins[i] != null) {
assert(_variableCells[i] == joins[i]);
_variableCells[i] = null;
_aggregateVariable[i] = false;
}
}
}
@ -1206,7 +1265,10 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
void _addUse(TypeExpr arg) {
if (arg is Narrow) {
_addUse(arg.arg);
} else if (arg is Join || arg is Call || arg is TypeCheck) {
} else if (arg is Join ||
arg is Call ||
arg is TypeCheck ||
arg is ReadVariable) {
_summary.add(new Use(arg));
} else if (arg is UnaryOperation) {
_addUse(arg.arg);
@ -1388,7 +1450,7 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
final variableGet =
(node is AsExpression ? node.operand : node) as VariableGet;
final int varIndex = _variablesInfo.varIndex[variableGet.variable]!;
if (_variableCells[varIndex] == null) {
if (!_aggregateVariable[varIndex]) {
trueState[varIndex] = _boolTrue;
falseState[varIndex] = _boolFalse;
}
@ -1409,7 +1471,7 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
final result = _visit(node);
_addUse(result);
final int varIndex = _variablesInfo.varIndex[lhs.variable]!;
if (_variableCells[varIndex] == null) {
if (!_aggregateVariable[varIndex]) {
trueState[varIndex] = _visit(rhs);
}
_variableValues = const <TypeExpr?>[]; // Should not be used.
@ -1423,7 +1485,7 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
Args<TypeExpr>([expr, _nullType]));
final narrowedNotNull = _makeNarrowNotNull(node, expr);
final int varIndex = _variablesInfo.varIndex[lhs.variable]!;
if (_variableCells[varIndex] == null) {
if (!_aggregateVariable[varIndex]) {
trueState[varIndex] = _nullType;
falseState[varIndex] = narrowedNotNull;
}
@ -1437,7 +1499,7 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
_typeCheck(_visit(operand), node.type, node, SubtypeTestKind.IsTest);
isTests[node] = typeCheck;
final int varIndex = _variablesInfo.varIndex[operand.variable]!;
if (_variableCells[varIndex] == null) {
if (!_aggregateVariable[varIndex]) {
trueState[varIndex] = typeCheck;
}
final result = _makeUnaryOperation(
@ -1460,7 +1522,7 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
final nullSelectors = isSetter ? _nullSetters : _nullMethodsAndGetters;
if (!nullSelectors.contains(selector)) {
final int varIndex = _variablesInfo.varIndex[receiverNode.variable]!;
if (_variableCells[varIndex] == null) {
if (!_aggregateVariable[varIndex]) {
_variableValues[varIndex] =
_makeNarrow(receiverValue, anyInstanceType);
}
@ -1483,7 +1545,7 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
explicitCasts[node] = result;
if (operandNode is VariableGet) {
final int varIndex = _variablesInfo.varIndex[operandNode.variable]!;
if (_variableCells[varIndex] == null) {
if (!_aggregateVariable[varIndex]) {
_variableValues[varIndex] = result;
}
}
@ -1496,7 +1558,7 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
final TypeExpr result = _makeNarrowNotNull(node, _visit(operandNode));
if (operandNode is VariableGet) {
final int varIndex = _variablesInfo.varIndex[operandNode.variable]!;
if (_variableCells[varIndex] == null) {
if (!_aggregateVariable[varIndex]) {
_variableValues[varIndex] = result;
}
}
@ -2032,11 +2094,7 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
@override
TypeExpr visitVariableGet(VariableGet node) {
final v = _variableValues[_variablesInfo.varIndex[node.variable]!];
if (v == null) {
throw 'Unable to find variable ${node.variable} at ${node.location}';
}
return v;
return _readVariable(node.variable, node);
}
@override
@ -2281,7 +2339,7 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
final stateDuringTry = _cloneVariableValues(_variableValues);
final conditionOnEntry = _currentCondition;
_visitWithoutResult(node.body);
_restoreVariableCellsAfterTry(joins);
_restoreModifiedVariablesAfterTry(joins);
for (var catchClause in node.catches) {
final conditionAfterTry = _currentCondition;
final stateAfterTry = _variableValues;
@ -2318,7 +2376,7 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
final stateDuringTry = _cloneVariableValues(_variableValues);
final conditionOnEntry = _currentCondition;
_visitWithoutResult(node.body);
_restoreVariableCellsAfterTry(joins);
_restoreModifiedVariablesAfterTry(joins);
final conditionAfterTry = _currentCondition;
_jumpHandlers = outerJumpHandlers;

View file

@ -18,6 +18,7 @@ import 'package:vm/transformations/pragma.dart'
import 'package:vm/transformations/type_flow/analysis.dart';
import 'package:vm/transformations/type_flow/calls.dart';
import 'package:vm/transformations/type_flow/native_code.dart';
import 'package:vm/transformations/type_flow/summary.dart';
import 'package:vm/transformations/type_flow/summary_collector.dart';
import 'package:vm/transformations/type_flow/types.dart';
@ -71,6 +72,31 @@ class FakeEntryPointsListener implements EntryPointsListener {
void recordTearOff(Member target) {}
}
class FakeSharedVariable implements SharedVariable {
final VariableDeclaration decl;
FakeSharedVariable(this.decl);
@override
Type getValue(TypeHierarchy typeHierarchy, CallHandler callHandler) =>
throw 'Not implemented';
@override
void setValue(Type newValue, TypeHierarchy typeHierarchy,
CallHandler callHandler) =>
throw 'Not implemented';
@override
String toString() => decl.name ?? '__tmp';
}
class FakeSharedVariableBuilder implements SharedVariableBuilder {
final Map<VariableDeclaration, SharedVariable> _sharedVariables = {};
@override
SharedVariable getSharedVariable(VariableDeclaration variable) =>
_sharedVariables[variable] ??= FakeSharedVariable(variable);
}
class PrintSummaries extends RecursiveVisitor {
late SummaryCollector _summaryCollector;
final StringBuffer _buf = new StringBuffer();
@ -87,6 +113,7 @@ class PrintSummaries extends RecursiveVisitor {
typesBuilder,
NativeCodeOracle(coreTypes.index, annotationParser),
GenericInterfacesInfoImpl(coreTypes, hierarchy),
FakeSharedVariableBuilder(),
/*_protobufHandler=*/ null);
}

View file

@ -283,19 +283,23 @@ t8 = _Call direct [#lib::baz] (x_0)
RESULT: x_0
------------ closure1 ------------
t0* = _Call direct [#lib::C1.] (_T (#lib::C1))
t1* = _Call direct [#lib::C2.] (_T (#lib::C2))
x_0 = _Join [dynamic] (t0, t1)
t3 = _Call direct [#lib::foo] (x_0)
t4 = _Call direct [#lib::bar] (x_0)
write x = t0
t2 = read x
t3 = _Call direct [#lib::foo] (t2)
t4 = _Call direct [#lib::bar] (t2)
t5 = _Call direct [#lib::foo] (_T (dart.core::Function)+?)
t6* = _Call direct [#lib::C2.] (_T (#lib::C2))
write x = t6
RESULT: _T {}?
------------ closure2 ------------
t0* = _Call direct [#lib::C1.] (_T (#lib::C1))
t1* = _Call direct [#lib::C2.] (_T (#lib::C2))
x_0 = _Join [dynamic] (t0, t1)
t3 = _Call direct [#lib::foo] (x_0)
t4 = _Call direct [#lib::foo] (_T (dart.core::Function)+?)
RESULT: x_0
write x = t0
t2 = read x
t3 = _Call direct [#lib::foo] (t2)
t4* = _Call direct [#lib::C2.] (_T (#lib::C2))
write x = t4
t6 = _Call direct [#lib::foo] (_T (dart.core::Function)+?)
RESULT: t2
------------ switch1 ------------
%selector = _Parameter #0 [_T (dart.core::int)+?]
t1* = _Call direct [#lib::C1.] (_T (#lib::C1))