Skip to content

Commit

Permalink
[vm/aot/tfa] Improve handling of recursive calls in TFA
Browse files Browse the repository at this point in the history
In general case, TFA approximates results of recursive calls using static
types.

However, if result type of a function does not depend on the flow inside its
body, it cannot change and it can be used in case of recursive calls
instead of a static type.

This improves micro-benchmark from #37455:
Before: 0m11.506s
After: 0m7.324s

Issue: #37455
Change-Id: I967d7add906c8dbd59dbbea1b993e1b4e1733514
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/108500
Commit-Queue: Alexander Markov <[email protected]>
Reviewed-by: Martin Kustermann <[email protected]>
  • Loading branch information
alexmarkov authored and [email protected] committed Jul 10, 2019
1 parent 016061d commit 318a482
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 11 deletions.
55 changes: 44 additions & 11 deletions pkg/vm/lib/transformations/type_flow/analysis.dart
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,14 @@ abstract class _Invocation extends _DependencyTracker
/// its result is saturated in order to guarantee convergence.
static const int invalidationLimit = 1000;

_Invocation(this.selector, this.args);

Type process(TypeFlowAnalysis typeFlowAnalysis);

_Invocation(this.selector, this.args);
/// Returns result of this invocation if its available without
/// further analysis, or `null` if it's not available.
/// Used for recursive calls while this invocation is being processed.
Type get resultForRecursiveInvocation => result;

// Only take selector and args into account as _Invocation objects
// are cached in _InvocationsCache using selector and args as a key.
Expand Down Expand Up @@ -232,9 +237,17 @@ class _DirectInvocation extends _Invocation {
final Member member = selector.member;
if (selector.memberAgreesToCallKind(member)) {
if (_argumentsValid()) {
return typeFlowAnalysis
.getSummary(member)
.apply(args, typeFlowAnalysis.hierarchyCache, typeFlowAnalysis);
final summary = typeFlowAnalysis.getSummary(member);
// If result type is known upfront (doesn't depend on the flow),
// set it eagerly so recursive invocations are able to use it.
final summaryResult = summary.result;
if (summaryResult is Type &&
!typeFlowAnalysis.workList._isPending(this)) {
assertx(result == null || result == summaryResult);
result = summaryResult;
}
return summary.apply(
args, typeFlowAnalysis.hierarchyCache, typeFlowAnalysis);
} else {
assertx(selector.callKind == CallKind.Method);
return _processNoSuchMethod(args.receiver, typeFlowAnalysis);
Expand Down Expand Up @@ -301,6 +314,7 @@ class _DispatchableInvocation extends _Invocation {
bool _isPolymorphic = false;
Set<Call> _callSites; // Populated only if not polymorphic.
Member _monomorphicTarget;
_DirectInvocation _monomorphicDirectInvocation;

@override
set typeChecksNeeded(bool value) {
Expand Down Expand Up @@ -368,6 +382,11 @@ class _DispatchableInvocation extends _Invocation {
final directInvocation = typeFlowAnalysis._invocationsCache
.getInvocation(directSelector, directArgs);

if (!_isPolymorphic) {
assertx(target == _monomorphicTarget);
_monomorphicDirectInvocation = directInvocation;
}

type = typeFlowAnalysis.workList.processInvocation(directInvocation);
if (kPrintTrace) {
tracePrint('Dispatch: $directInvocation, result: $type');
Expand Down Expand Up @@ -561,10 +580,6 @@ class _DispatchableInvocation extends _Invocation {
}

void addCallSite(Call callSite) {
if (selector is DirectSelector) {
return;
}

_notifyCallSite(callSite);
if (!callSite.isPolymorphic) {
(_callSites ??= new Set<Call>()).add(callSite);
Expand All @@ -574,8 +589,6 @@ class _DispatchableInvocation extends _Invocation {
/// Notify call site about changes in polymorphism or checkedness of this
/// invocation.
void _notifyCallSite(Call callSite) {
assert(selector is! DirectSelector);

if (_isPolymorphic) {
callSite.setPolymorphic();
} else {
Expand All @@ -596,6 +609,17 @@ class _DispatchableInvocation extends _Invocation {
_callSites.forEach(_notifyCallSite);
}
}

@override
Type get resultForRecursiveInvocation {
if (result != null) {
return result;
}
if (_monomorphicDirectInvocation != null) {
return _monomorphicDirectInvocation.resultForRecursiveInvocation;
}
return null;
}
}

/// Efficient builder of receiver type.
Expand Down Expand Up @@ -1313,7 +1337,16 @@ class _WorkList {
}
return result;
} else {
// Recursive invocation, approximate with static type.
// Recursive invocation.
final result = invocation.resultForRecursiveInvocation;
if (result != null) {
if (kPrintTrace) {
tracePrint("Already known type for recursive invocation: $result");
tracePrint('END PROCESSING $invocation, RESULT $result');
}
return result;
}
// Fall back to static type.
Statistics.recursiveInvocationsApproximated++;
final staticType =
new Type.fromStatic(invocation.selector.staticReturnType);
Expand Down
14 changes: 14 additions & 0 deletions pkg/vm/lib/transformations/type_flow/summary_collector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,20 @@ class _SummaryNormalizer extends StatementVisitor {
return const EmptyType();
} else if (n == 1) {
return st.values.single;
} else {
final first = st.values.first;
if (first is Type) {
bool allMatch = true;
for (int i = 1; i < n; ++i) {
if (first != st.values[i]) {
allMatch = false;
break;
}
}
if (allMatch) {
return first;
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) 2019, 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.

// Regression test for https://github.com/dart-lang/sdk/issues/37455
// Verifies that TFA can infer type of a recursive call if it doesn't depend
// on the flow.

class A {
// Should be inferred as _GrowableList.
final List afield;

A(this.afield);

String toString() => afield.toString();
}

class B {
List _foo(Iterator<int> iter) {
List result = [];
while (iter.moveNext()) {
if (iter.current < 0) {
return result;
}
// Do a recursive call with the same arguments.
result.add(new A(_foo(iter)));
}
return result;
}
}

void main() {
var list = new B()._foo([1, 2, 3].iterator);
print(list);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
library #lib;
import self as self;
import "dart:core" as core;

class A extends core::Object {
[@vm.inferred-type.metadata=dart.core::_GrowableList<dynamic>] [@vm.procedure-attributes.metadata=hasDynamicUses:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false] final field core::List<dynamic> afield;
constructor •([@vm.inferred-type.metadata=dart.core::_GrowableList<dynamic>] core::List<dynamic> afield) → self::A
: self::A::afield = afield, super core::Object::•()
;
[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasThisUses:false,hasTearOffUses:false] method toString() → core::String
return [@vm.direct-call.metadata=dart.core::_GrowableList::toString] [@vm.inferred-type.metadata=!? (skip check)] [@vm.direct-call.metadata=#lib::A::afield] [@vm.inferred-type.metadata=dart.core::_GrowableList<dynamic>] this.{self::A::afield}.{core::Object::toString}();
}
class B extends core::Object {
synthetic constructor •() → self::B
: super core::Object::•()
;
[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasTearOffUses:false] method _foo([@vm.inferred-type.metadata=dart._internal::ListIterator<dart.core::int>] core::Iterator<core::int> iter) → core::List<dynamic> {
core::List<dynamic> result = <dynamic>[];
while ([@vm.direct-call.metadata=dart._internal::ListIterator::moveNext] [@vm.inferred-type.metadata=dart.core::bool (skip check)] iter.{core::Iterator::moveNext}()) {
if([@vm.inferred-type.metadata=dart.core::bool?] [@vm.direct-call.metadata=dart._internal::ListIterator::current] iter.{core::Iterator::current}.{core::num::<}(0)) {
return result;
}
[@vm.call-site-attributes.metadata=receiverType:dart.core::List<dynamic>] [@vm.direct-call.metadata=dart.core::_GrowableList::add] [@vm.inferred-type.metadata=!? (skip check)] result.{core::List::add}(new self::A::•([@vm.direct-call.metadata=#lib::B::_foo] [@vm.inferred-type.metadata=dart.core::_GrowableList<dynamic> (skip check)] this.{self::B::_foo}(iter)));
}
return result;
}
}
static method main() → void {
core::List<dynamic> list = [@vm.direct-call.metadata=#lib::B::_foo] [@vm.inferred-type.metadata=dart.core::_GrowableList<dynamic> (skip check)] new self::B::•().{self::B::_foo}([@vm.direct-call.metadata=dart.core::_GrowableList::iterator] [@vm.inferred-type.metadata=dart._internal::ListIterator<dart.core::int>]<core::int>[1, 2, 3].{core::Iterable::iterator});
core::print(list);
}

0 comments on commit 318a482

Please sign in to comment.