[vm/kernel/aot] Fix infinite looping in TFA (ensure convergence of analysis)
Change-Id: If4fe0d6522271880b4bf8d0957ca07ef1f038a90
Reviewed-on: https://dart-review.googlesource.com/53525
Reviewed-by: Zach Anderson <zra@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
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 99a885c..71ee83e 100644
--- a/pkg/vm/lib/transformations/type_flow/analysis.dart
+++ b/pkg/vm/lib/transformations/type_flow/analysis.dart
@@ -7,6 +7,7 @@
import 'dart:collection';
import 'dart:core' hide Type;
+import 'dart:math' show max;
import 'package:kernel/ast.dart' hide Statement, StatementVisitor;
import 'package:kernel/class_hierarchy.dart' show ClosedWorldClassHierarchy;
@@ -55,6 +56,12 @@
void invalidateDependentInvocations(_WorkList workList) {
if (_dependentInvocations != null) {
+ if (kPrintTrace) {
+ tracePrint(' - CHANGED: $this');
+ for (var di in _dependentInvocations) {
+ tracePrint(' - invalidating $di');
+ }
+ }
_dependentInvocations.forEach(workList.invalidateInvocation);
}
}
@@ -77,6 +84,13 @@
/// result or not (to avoid invalidation of callers if result hasn't changed).
Type invalidatedResult;
+ /// Number of times result of this invocation was invalidated.
+ int invalidationCounter = 0;
+
+ /// If an invocation is invalidated more than [invalidationLimit] times,
+ /// its result is saturated in order to guarantee convergence.
+ static const int invalidationLimit = 1000;
+
Type process(TypeFlowAnalysis typeFlowAnalysis);
_Invocation(this.selector, this.args);
@@ -320,6 +334,9 @@
.getInvocation(directSelector, directArgs);
type = typeFlowAnalysis.workList.processInvocation(directInvocation);
+ if (kPrintTrace) {
+ tracePrint('Dispatch: $directInvocation, result: $type');
+ }
// Result of this invocation depends on the results of direct
// invocations corresponding to each target.
@@ -672,7 +689,9 @@
newValue.intersection(staticType, typeFlowAnalysis.hierarchyCache),
typeFlowAnalysis.hierarchyCache);
if (newType != value) {
- tracePrint("Set field $field value $newType");
+ if (kPrintTrace) {
+ tracePrint("Set field $field value $newType");
+ }
invalidateDependentInvocations(typeFlowAnalysis.workList);
value = newType;
}
@@ -866,7 +885,9 @@
@override
Type specializeTypeCone(DartType base) {
- tracePrint("specializeTypeCone for $base");
+ if (kPrintTrace) {
+ tracePrint("specializeTypeCone for $base");
+ }
Statistics.typeConeSpecializations++;
// TODO(alexmarkov): handle function types properly
@@ -1009,7 +1030,7 @@
callStack.add(invocation);
pending.remove(invocation);
- final Type result = invocation.process(_typeFlowAnalysis);
+ Type result = invocation.process(_typeFlowAnalysis);
assertx(result != null);
invocation.result = result;
@@ -1017,6 +1038,21 @@
if (invocation.invalidatedResult != null) {
if (invocation.invalidatedResult != result) {
invocation.invalidateDependentInvocations(this);
+
+ invocation.invalidationCounter++;
+ Statistics.maxInvalidationsPerInvocation = max(
+ Statistics.maxInvalidationsPerInvocation,
+ invocation.invalidationCounter);
+ // In rare cases, loops in dependencies and approximation of
+ // recursive invocations may cause infinite bouncing of result
+ // types. To prevent infinite looping and guarantee convergence of
+ // the analysis, result is saturated after invocation is invalidated
+ // at least [_Invocation.invalidationLimit] times.
+ if (invocation.invalidationCounter > _Invocation.invalidationLimit) {
+ result = result.union(
+ invocation.invalidatedResult, _typeFlowAnalysis.hierarchyCache);
+ invocation.result = result;
+ }
}
invocation.invalidatedResult = null;
}
@@ -1034,14 +1070,20 @@
processing.remove(invocation);
Statistics.invocationsProcessed++;
+ if (kPrintTrace) {
+ tracePrint('END PROCESSING $invocation, RESULT $result');
+ }
return result;
} else {
// Recursive invocation, approximate with static type.
Statistics.recursiveInvocationsApproximated++;
final staticType =
new Type.fromStatic(invocation.selector.staticReturnType);
- tracePrint(
- "Approximated recursive invocation with static type $staticType");
+ if (kPrintTrace) {
+ tracePrint(
+ "Approximated recursive invocation with static type $staticType");
+ tracePrint('END PROCESSING $invocation, RESULT $staticType');
+ }
return staticType;
}
}
@@ -1145,7 +1187,9 @@
@override
void addRawCall(Selector selector) {
- debugPrint("ADD RAW CALL: $selector");
+ if (kPrintDebug) {
+ debugPrint("ADD RAW CALL: $selector");
+ }
assertx(selector is! DynamicSelector); // TODO(alexmarkov)
applyCall(null, selector, summaryCollector.rawArguments(selector),
@@ -1154,7 +1198,9 @@
@override
ConcreteType addAllocatedClass(Class c) {
- debugPrint("ADD ALLOCATED CLASS: $c");
+ if (kPrintDebug) {
+ debugPrint("ADD ALLOCATED CLASS: $c");
+ }
return hierarchyCache.addAllocatedClass(c);
}
}
diff --git a/pkg/vm/lib/transformations/type_flow/utils.dart b/pkg/vm/lib/transformations/type_flow/utils.dart
index 95e4467..de0d7a0 100644
--- a/pkg/vm/lib/transformations/type_flow/utils.dart
+++ b/pkg/vm/lib/transformations/type_flow/utils.dart
@@ -75,6 +75,7 @@
static int invocationsProcessed = 0;
static int usedCachedResultsOfInvocations = 0;
static int invocationsInvalidated = 0;
+ static int maxInvalidationsPerInvocation = 0;
static int recursiveInvocationsApproximated = 0;
static int typeConeSpecializations = 0;
static int classesDropped = 0;
@@ -93,6 +94,7 @@
invocationsProcessed = 0;
usedCachedResultsOfInvocations = 0;
invocationsInvalidated = 0;
+ maxInvalidationsPerInvocation = 0;
recursiveInvocationsApproximated = 0;
typeConeSpecializations = 0;
classesDropped = 0;
@@ -112,6 +114,7 @@
${invocationsProcessed} invocations processed
${usedCachedResultsOfInvocations} times cached result of invocation is used
${invocationsInvalidated} invocations invalidated
+ ${maxInvalidationsPerInvocation} maximum invalidations per invocation
${recursiveInvocationsApproximated} recursive invocations approximated
${typeConeSpecializations} type cones specialized
${classesDropped} classes dropped
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_cycle.dart b/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_cycle.dart
new file mode 100644
index 0000000..da1f458
--- /dev/null
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_cycle.dart
@@ -0,0 +1,122 @@
+// 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.
+
+// This test carefully reproduces subtle situation when TFA fails to converge
+// unless result types are saturated.
+// * There is a loop in dependencies (or even multiple loops).
+// * There is a recursive invocation which may result in a type approximation.
+// * Invalidation of results of certain invocations cause types to bounce
+// between approximated and non-approximated results, causing more
+// self-induced invalidations.
+//
+
+class StreamSubscription {}
+
+class _BufferingStreamSubscription extends StreamSubscription {}
+
+class _BroadcastSubscription extends StreamSubscription {}
+
+abstract class Stream {
+ StreamSubscription foobar(void onData(event), {Function onError});
+}
+
+abstract class _StreamImpl extends Stream {
+ StreamSubscription foobar(void onData(data), {Function onError}) {
+ return _createSubscription();
+ }
+
+ StreamSubscription _createSubscription() {
+ return new _BufferingStreamSubscription();
+ }
+}
+
+class _ControllerStream extends _StreamImpl {
+ StreamSubscription _createSubscription() {
+ return new _BroadcastSubscription();
+ }
+}
+
+class _GeneratedStreamImpl extends _StreamImpl {}
+
+class StreamView extends Stream {
+ final Stream _stream;
+
+ StreamView(Stream stream) : _stream = stream;
+
+ StreamSubscription foobar(void onData(value), {Function onError}) {
+ return _stream.foobar(onData, onError: onError);
+ }
+}
+
+class ByteStream extends StreamView {
+ ByteStream(Stream stream) : super(stream);
+
+ super_foobar1(void onData(value)) {
+ super.foobar(onData);
+ }
+
+ super_foobar2(void onData(value)) {
+ super.foobar(onData);
+ }
+
+ super_foobar3({void onData(value), Function onError}) {
+ super.foobar(onData, onError: onError);
+ }
+
+ Stream get super_stream => super._stream;
+}
+
+class _HandleErrorStream extends Stream {
+ StreamSubscription foobar(void onData(event), {Function onError}) {
+ return new _BufferingStreamSubscription();
+ }
+}
+
+void round0() {
+ new ByteStream(new ByteStream(new _GeneratedStreamImpl()));
+}
+
+void round1({void onData(value)}) {
+ var x = new ByteStream(new ByteStream(new _GeneratedStreamImpl()));
+ x.super_foobar1(onData);
+}
+
+void round2({void onData(value), Function onError}) {
+ new _ControllerStream();
+ Stream x = new _GeneratedStreamImpl();
+ x = new ByteStream(x);
+ x.foobar(onData, onError: onError);
+}
+
+void round3({void onData(value), Function onError}) {
+ Stream x = new _GeneratedStreamImpl();
+ x = new ByteStream(x);
+ x = new _ControllerStream();
+ x.foobar(onData, onError: onError);
+}
+
+void round4({void onData(value)}) {
+ var x = new ByteStream(new _ControllerStream());
+ var y = x.super_stream;
+ var z = x._stream;
+ if (y == z) {
+ x.super_foobar2(onData);
+ }
+}
+
+void round5() {
+ var x = new ByteStream(new _GeneratedStreamImpl());
+ new _HandleErrorStream();
+ x.super_foobar3();
+}
+
+main(List<String> args) {
+ new _GeneratedStreamImpl();
+ round0();
+ round1();
+ round2();
+ round3();
+ round4();
+ round5();
+}
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_cycle.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_cycle.dart.expect
new file mode 100644
index 0000000..fd8c263
--- /dev/null
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_cycle.dart.expect
@@ -0,0 +1,120 @@
+library #lib;
+import self as self;
+import "dart:core" as core;
+
+abstract class StreamSubscription extends core::Object {
+ synthetic constructor •() → void
+ : super core::Object::•()
+ ;
+}
+class _BufferingStreamSubscription extends self::StreamSubscription {
+ synthetic constructor •() → void
+ : super self::StreamSubscription::•()
+ ;
+}
+class _BroadcastSubscription extends self::StreamSubscription {
+ synthetic constructor •() → void
+ : super self::StreamSubscription::•()
+ ;
+}
+abstract class Stream extends core::Object {
+ synthetic constructor •() → void
+ : super core::Object::•()
+ ;
+ abstract method foobar((dynamic) → void onData, {core::Function onError = null}) → self::StreamSubscription;
+}
+abstract class _StreamImpl extends self::Stream {
+ synthetic constructor •() → void
+ : super self::Stream::•()
+ ;
+ method foobar([@vm.inferred-type.metadata=dart.core::Null?] (dynamic) → void onData, {[@vm.inferred-type.metadata=dart.core::Null?] core::Function onError = null}) → self::StreamSubscription {
+ return [@vm.inferred-type.metadata=!] this.{self::_StreamImpl::_createSubscription}();
+ }
+ method _createSubscription() → self::StreamSubscription {
+ return new self::_BufferingStreamSubscription::•();
+ }
+}
+class _ControllerStream extends self::_StreamImpl {
+ synthetic constructor •() → void
+ : super self::_StreamImpl::•()
+ ;
+ method _createSubscription() → self::StreamSubscription {
+ return new self::_BroadcastSubscription::•();
+ }
+}
+class _GeneratedStreamImpl extends self::_StreamImpl {
+ synthetic constructor •() → void
+ : super self::_StreamImpl::•()
+ ;
+}
+abstract class StreamView extends self::Stream {
+[@vm.inferred-type.metadata=!] final field self::Stream _stream;
+ constructor •([@vm.inferred-type.metadata=!] self::Stream stream) → void
+ : self::StreamView::_stream = stream, super self::Stream::•()
+ ;
+ method foobar([@vm.inferred-type.metadata=dart.core::Null?] (dynamic) → void onData, {[@vm.inferred-type.metadata=dart.core::Null?] core::Function onError = null}) → self::StreamSubscription {
+ return [@vm.direct-call.metadata=#lib::StreamView::_stream] [@vm.inferred-type.metadata=!] this.{self::StreamView::_stream}.{self::Stream::foobar}(onData, onError: onError);
+ }
+}
+class ByteStream extends self::StreamView {
+ constructor •([@vm.inferred-type.metadata=!] self::Stream stream) → void
+ : super self::StreamView::•(stream)
+ ;
+ method super_foobar1([@vm.inferred-type.metadata=dart.core::Null?] (dynamic) → void onData) → dynamic {
+ super.{self::StreamView::foobar}(onData);
+ }
+ method super_foobar2([@vm.inferred-type.metadata=dart.core::Null?] (dynamic) → void onData) → dynamic {
+ super.{self::StreamView::foobar}(onData);
+ }
+ method super_foobar3({[@vm.inferred-type.metadata=dart.core::Null?] (dynamic) → void onData = null, [@vm.inferred-type.metadata=dart.core::Null?] core::Function onError = null}) → dynamic {
+ super.{self::StreamView::foobar}(onData, onError: onError);
+ }
+ get super_stream() → self::Stream
+ return [@vm.inferred-type.metadata=!] super.{self::StreamView::_stream};
+}
+class _HandleErrorStream extends self::Stream {
+ synthetic constructor •() → void
+ : super self::Stream::•()
+ ;
+}
+static method round0() → void {
+ new self::ByteStream::•(new self::ByteStream::•(new self::_GeneratedStreamImpl::•()));
+}
+static method round1({[@vm.inferred-type.metadata=dart.core::Null?] (dynamic) → void onData = null}) → void {
+ self::ByteStream x = new self::ByteStream::•(new self::ByteStream::•(new self::_GeneratedStreamImpl::•()));
+ [@vm.direct-call.metadata=#lib::ByteStream::super_foobar1] x.{self::ByteStream::super_foobar1}(onData);
+}
+static method round2({[@vm.inferred-type.metadata=dart.core::Null?] (dynamic) → void onData = null, [@vm.inferred-type.metadata=dart.core::Null?] core::Function onError = null}) → void {
+ new self::_ControllerStream::•();
+ self::Stream x = new self::_GeneratedStreamImpl::•();
+ x = new self::ByteStream::•(x);
+ x.{self::Stream::foobar}(onData, onError: onError);
+}
+static method round3({[@vm.inferred-type.metadata=dart.core::Null?] (dynamic) → void onData = null, [@vm.inferred-type.metadata=dart.core::Null?] core::Function onError = null}) → void {
+ self::Stream x = new self::_GeneratedStreamImpl::•();
+ x = new self::ByteStream::•(x);
+ x = new self::_ControllerStream::•();
+ x.{self::Stream::foobar}(onData, onError: onError);
+}
+static method round4({[@vm.inferred-type.metadata=dart.core::Null?] (dynamic) → void onData = null}) → void {
+ self::ByteStream x = new self::ByteStream::•(new self::_ControllerStream::•());
+ self::Stream y = [@vm.direct-call.metadata=#lib::ByteStream::super_stream] [@vm.inferred-type.metadata=!] x.{self::ByteStream::super_stream};
+ self::Stream z = [@vm.direct-call.metadata=#lib::StreamView::_stream] [@vm.inferred-type.metadata=!] x.{self::StreamView::_stream};
+ if([@vm.direct-call.metadata=dart.core::Object::==] [@vm.inferred-type.metadata=dart.core::bool] y.{core::Object::==}(z)) {
+ [@vm.direct-call.metadata=#lib::ByteStream::super_foobar2] x.{self::ByteStream::super_foobar2}(onData);
+ }
+}
+static method round5() → void {
+ self::ByteStream x = new self::ByteStream::•(new self::_GeneratedStreamImpl::•());
+ new self::_HandleErrorStream::•();
+ [@vm.direct-call.metadata=#lib::ByteStream::super_foobar3] x.{self::ByteStream::super_foobar3}();
+}
+static method main(core::List<core::String> args) → dynamic {
+ new self::_GeneratedStreamImpl::•();
+ self::round0();
+ self::round1();
+ self::round2();
+ self::round3();
+ self::round4();
+ self::round5();
+}