diff --git a/pkg/vm/lib/transformations/type_flow/analysis.dart b/pkg/vm/lib/transformations/type_flow/analysis.dart index ad501558ede..c4b0cafbf9b 100644 --- a/pkg/vm/lib/transformations/type_flow/analysis.dart +++ b/pkg/vm/lib/transformations/type_flow/analysis.dart @@ -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 targets = new Set(); @@ -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 _summaries = {}; final Map _fieldValues = {}; + final Map _sharedCapturedVariables = + {}; final Set _tearOffTaken = new Set(); final Set _methodsAndSettersCalledDynamically = new Set(); final Set _gettersCalledDynamically = new Set(); @@ -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? 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); } diff --git a/pkg/vm/lib/transformations/type_flow/summary.dart b/pkg/vm/lib/transformations/type_flow/summary.dart index 559e49487af..94733bcdd2b 100644 --- a/pkg/vm/lib/transformations/type_flow/summary.dart +++ b/pkg/vm/lib/transformations/type_flow/summary.dart @@ -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 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 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 { diff --git a/pkg/vm/lib/transformations/type_flow/summary_collector.dart b/pkg/vm/lib/transformations/type_flow/summary_collector.dart index 1f9d989eb8b..5cf0f42a2e5 100644 --- a/pkg/vm/lib/transformations/type_flow/summary_collector.dart +++ b/pkg/vm/lib/transformations/type_flow/summary_collector.dart @@ -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 { final TypesBuilder _typesBuilder; final NativeCodeOracle _nativeCodeOracle; final GenericInterfacesInfo _genericInterfacesInfo; + final SharedVariableBuilder _sharedVariableBuilder; final ProtobufHandler? _protobufHandler; final Map callSites = {}; @@ -483,17 +499,20 @@ class SummaryCollector extends RecursiveResultVisitor { // (e.g. after return or throw). List _variableValues = const []; - // 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 _variableCells = const []; + // + // If _aggregateVariable[i], then all values are accumulated + // and _variableValues[i] should not be changed. + List _aggregateVariable = const []; + + // Cached unconditional reads of captured variables + // (can be reused to avoid repetitive reads). + Map? _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 { 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 { _variablesInfo = new _VariablesInfoCollector(member); _variableValues = new List.filled(_variablesInfo.numVariables, null); - _variableCells = new List.filled(_variablesInfo.numVariables, null); + _aggregateVariable = + new List.filled(_variablesInfo.numVariables, false); + _capturedVariableReads = null; _variableVersions = new List.filled(_variablesInfo.numVariables, 0); _jumpHandlers = null; _returnValue = null; @@ -895,24 +917,63 @@ class SummaryCollector extends RecursiveResultVisitor { 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 _cloneVariableValues(List values) => new List.from(values); @@ -920,7 +981,7 @@ class SummaryCollector extends RecursiveResultVisitor { final values = new List.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 { 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 { final List joins = new List.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 { // 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 { } /// Stops accumulating values in [joins] by removing them from - /// _variableCells. - void _restoreVariableCellsAfterTry(List joins) { + /// _aggregateVariable. + void _restoreModifiedVariablesAfterTry(List 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 { 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 { 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 { 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 []; // Should not be used. @@ -1423,7 +1485,7 @@ class SummaryCollector extends RecursiveResultVisitor { Args([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 { _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 { 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 { 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 { 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 { @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 { 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 { final stateDuringTry = _cloneVariableValues(_variableValues); final conditionOnEntry = _currentCondition; _visitWithoutResult(node.body); - _restoreVariableCellsAfterTry(joins); + _restoreModifiedVariablesAfterTry(joins); final conditionAfterTry = _currentCondition; _jumpHandlers = outerJumpHandlers; diff --git a/pkg/vm/test/transformations/type_flow/summary_collector_test.dart b/pkg/vm/test/transformations/type_flow/summary_collector_test.dart index ab009ecb39a..03e74064fc5 100644 --- a/pkg/vm/test/transformations/type_flow/summary_collector_test.dart +++ b/pkg/vm/test/transformations/type_flow/summary_collector_test.dart @@ -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 _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); } diff --git a/pkg/vm/testcases/transformations/type_flow/summary_collector/control_flow.dart.expect b/pkg/vm/testcases/transformations/type_flow/summary_collector/control_flow.dart.expect index f4f0edd820a..528987f29bc 100644 --- a/pkg/vm/testcases/transformations/type_flow/summary_collector/control_flow.dart.expect +++ b/pkg/vm/testcases/transformations/type_flow/summary_collector/control_flow.dart.expect @@ -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))