[vm/aot/tfa] Fix corner case in tracking of field values in TFA
The detailed description of the corner case can be found in comment in
_FieldValue.setValue() method (in analysis.dart).
Unfortunately, this bugfix increases analysis time of Flutter gallery
from 10383ms to 11624ms.
Change-Id: I6161fea0747a27d6461e7ff9a1fcedd6b2f91d1e
Reviewed-on: https://dart-review.googlesource.com/66103
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
diff --git a/pkg/vm/lib/transformations/type_flow/analysis.dart b/pkg/vm/lib/transformations/type_flow/analysis.dart
index 4c6eff1..1f4a615 100644
--- a/pkg/vm/lib/transformations/type_flow/analysis.dart
+++ b/pkg/vm/lib/transformations/type_flow/analysis.dart
@@ -731,9 +731,38 @@
}
void setValue(Type newValue, TypeFlowAnalysis typeFlowAnalysis) {
- final Type newType = value.union(
- newValue.intersection(staticType, typeFlowAnalysis.hierarchyCache),
- typeFlowAnalysis.hierarchyCache);
+ // Make sure type cones are specialized before putting them into field
+ // value, in order to ensure that dependency is established between
+ // cone's base type and corresponding field setter.
+ //
+ // This ensures correct invalidation in the following scenario:
+ //
+ // 1) setValue(Cone(X)).
+ // It sets field value to Cone(X).
+ //
+ // 2) setValue(Y).
+ // It calculates Cone(X) U Y, specializing Cone(X).
+ // This establishes class X --> setter(Y) dependency.
+ // If X does not have allocated subclasses, then Cone(X) is specialized
+ // to Empty and the new field value is Y.
+ //
+ // 3) A new allocated subtype is added to X.
+ // This invalidates setter(Y). However, recalculation of setter(Y)
+ // does not yield correct field value, as value calculated on step 1 is
+ // already lost, and repeating setValue(Y) will not change field value.
+ //
+ // The eager specialization of field value ensures that specialization
+ // will happen on step 1 and dependency class X --> setter(Cone(X))
+ // is established.
+ //
+ final hierarchy = typeFlowAnalysis.hierarchyCache;
+ Type newType = value
+ .union(
+ newValue.specialize(hierarchy).intersection(staticType, hierarchy),
+ hierarchy)
+ .specialize(hierarchy);
+ assertx(newType.isSpecialized);
+
if (newType != value) {
if (kPrintTrace) {
tracePrint("Set field $field value $newType");
@@ -887,8 +916,8 @@
@override
bool isSubtype(DartType subType, DartType superType) {
if (kPrintTrace) {
- tracePrint("isSubtype for sub = $subType (${subType
- .runtimeType}), sup = $superType (${superType.runtimeType})");
+ tracePrint("isSubtype for sub = $subType (${subType.runtimeType}),"
+ " sup = $superType (${superType.runtimeType})");
}
if (subType == superType) {
return true;
diff --git a/pkg/vm/lib/transformations/type_flow/summary.dart b/pkg/vm/lib/transformations/type_flow/summary.dart
index e918a6d..f4a99d2 100644
--- a/pkg/vm/lib/transformations/type_flow/summary.dart
+++ b/pkg/vm/lib/transformations/type_flow/summary.dart
@@ -83,7 +83,9 @@
Type get argumentType => _argumentType;
void _observeArgumentType(Type argType, TypeHierarchy typeHierarchy) {
+ assertx(argType.isSpecialized);
_argumentType = _argumentType.union(argType, typeHierarchy);
+ assertx(_argumentType.isSpecialized);
}
}
@@ -183,10 +185,11 @@
if (selector is! DirectSelector) {
_observeReceiverType(argTypes[0]);
}
- final Type result = callHandler.applyCall(
+ Type result = callHandler.applyCall(
this, selector, new Args<Type>(argTypes, names: args.names),
isResultUsed: isResultUsed);
if (isResultUsed) {
+ result = result.specialize(typeHierarchy);
_observeResultType(result, typeHierarchy);
}
return result;
@@ -252,7 +255,9 @@
}
void _observeResultType(Type result, TypeHierarchy typeHierarchy) {
+ assertx(result.isSpecialized);
_resultType = _resultType.union(result, typeHierarchy);
+ assertx(_resultType.isSpecialized);
}
}
@@ -311,36 +316,37 @@
List<Type> types = new List<Type>(_statements.length);
- types.setRange(0, positionalArgCount, args);
-
for (int i = 0; i < positionalArgCount; i++) {
- Parameter param = _statements[i] as Parameter;
- param._observeArgumentType(args[i], typeHierarchy);
- types[i] = args[i].intersection(param.staticType, typeHierarchy);
+ final Parameter param = _statements[i] as Parameter;
+ final argType = args[i].specialize(typeHierarchy);
+ param._observeArgumentType(argType, typeHierarchy);
+ types[i] = argType.intersection(param.staticType, typeHierarchy);
}
for (int i = positionalArgCount; i < positionalParameterCount; i++) {
- Parameter param = _statements[i] as Parameter;
- assertx(param.defaultValue != null);
- param._observeArgumentType(param.defaultValue, typeHierarchy);
- types[i] = param.defaultValue;
+ final Parameter param = _statements[i] as Parameter;
+ final argType = param.defaultValue.specialize(typeHierarchy);
+ param._observeArgumentType(argType, typeHierarchy);
+ types[i] = argType;
}
final argNames = arguments.names;
int argIndex = 0;
for (int i = positionalParameterCount; i < parameterCount; i++) {
- Parameter param = _statements[i] as Parameter;
+ final Parameter param = _statements[i] as Parameter;
assertx(param.defaultValue != null);
if ((argIndex < namedArgCount) && (argNames[argIndex] == param.name)) {
- Type argType = args[positionalArgCount + argIndex];
+ final argType =
+ args[positionalArgCount + argIndex].specialize(typeHierarchy);
argIndex++;
param._observeArgumentType(argType, typeHierarchy);
types[i] = argType.intersection(param.staticType, typeHierarchy);
} else {
assertx((argIndex == namedArgCount) ||
(param.name.compareTo(argNames[argIndex]) < 0));
- param._observeArgumentType(param.defaultValue, typeHierarchy);
- types[i] = param.defaultValue;
+ final argType = param.defaultValue.specialize(typeHierarchy);
+ param._observeArgumentType(argType, typeHierarchy);
+ types[i] = argType;
}
}
assertx(argIndex == namedArgCount);
diff --git a/pkg/vm/lib/transformations/type_flow/types.dart b/pkg/vm/lib/transformations/type_flow/types.dart
index a964682..2100c02 100644
--- a/pkg/vm/lib/transformations/type_flow/types.dart
+++ b/pkg/vm/lib/transformations/type_flow/types.dart
@@ -101,6 +101,12 @@
/// Order of precedence for evaluation of union/intersection.
int get order;
+ /// Returns true iff this type is fully specialized.
+ bool get isSpecialized => true;
+
+ /// Returns specialization of this type using the given [TypeHierarchy].
+ Type specialize(TypeHierarchy typeHierarchy) => this;
+
/// Calculate union of this and [other] types.
Type union(Type other, TypeHierarchy typeHierarchy);
@@ -165,6 +171,14 @@
int get order => TypeOrder.Nullable.index;
@override
+ bool get isSpecialized => baseType.isSpecialized;
+
+ @override
+ Type specialize(TypeHierarchy typeHierarchy) => baseType.isSpecialized
+ ? this
+ : new NullableType(baseType.specialize(typeHierarchy));
+
+ @override
Type union(Type other, TypeHierarchy typeHierarchy) {
if (other.order < this.order) {
return other.union(this, typeHierarchy);
@@ -396,6 +410,13 @@
int get order => TypeOrder.Cone.index;
@override
+ bool get isSpecialized => false;
+
+ @override
+ Type specialize(TypeHierarchy typeHierarchy) =>
+ typeHierarchy.specializeTypeCone(dartType);
+
+ @override
Type union(Type other, TypeHierarchy typeHierarchy) {
if (other.order < this.order) {
return other.union(this, typeHierarchy);
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_set_field2.dart b/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_set_field2.dart
new file mode 100644
index 0000000..12cc624
--- /dev/null
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_set_field2.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+class T1 {
+ T3 go() => new T3();
+}
+
+class T2 {}
+
+class T3 {
+ run() {
+ print('hi');
+ }
+}
+
+class Q<T> {
+ final T result;
+ Q(this.result);
+}
+
+foo1(List<T1> list) {
+ list.map((T1 t1) => new Q<T1>(t1)).first.result.go().run();
+}
+
+Q foo2NewValue() => new Q<T2>(new T2());
+
+foo3NewT1() {
+ new T1();
+}
+
+main(List<String> args) {
+ foo1([]);
+ foo2NewValue();
+ foo3NewT1();
+}
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_set_field2.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_set_field2.dart.expect
new file mode 100644
index 0000000..ac18742
--- /dev/null
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_set_field2.dart.expect
@@ -0,0 +1,43 @@
+library #lib;
+import self as self;
+import "dart:core" as core;
+
+class T1 extends core::Object {
+ synthetic constructor •() → void
+ : super core::Object::•()
+ ;
+ method go() → self::T3
+ return new self::T3::•();
+}
+class T2 extends core::Object {
+ synthetic constructor •() → void
+ : super core::Object::•()
+ ;
+}
+class T3 extends core::Object {
+ synthetic constructor •() → void
+ : super core::Object::•()
+ ;
+ method run() → dynamic {
+ core::print("hi");
+ }
+}
+class Q<T extends core::Object = dynamic> extends core::Object {
+ final field self::Q::T result;
+ constructor •(self::Q::T result) → void
+ : self::Q::result = result, super core::Object::•()
+ ;
+}
+static method foo1(core::List<self::T1> list) → dynamic {
+ [@vm.direct-call.metadata=#lib::T3::run] [@vm.direct-call.metadata=#lib::T1::go??] [@vm.inferred-type.metadata=#lib::T3] [@vm.direct-call.metadata=#lib::Q::result??] [@vm.inferred-type.metadata=!] list.{core::Iterable::map}<self::Q<self::T1>>((self::T1 t1) → self::Q<self::T1> => new self::Q::•<self::T1>(t1)).{core::Iterable::first}.{self::Q::result}.{self::T1::go}().{self::T3::run}();
+}
+static method foo2NewValue() → self::Q<dynamic>
+ return new self::Q::•<self::T2>(new self::T2::•());
+static method foo3NewT1() → dynamic {
+ new self::T1::•();
+}
+static method main(core::List<core::String> args) → dynamic {
+ self::foo1(<self::T1>[]);
+ self::foo2NewValue();
+ self::foo3NewT1();
+}