Avoid using call-through stub on calls to fields.
This usually removes the demand for the call-through stub and sometimes enables optimizations on the field load, e.g.:
WhereIterator: {
"^": "Iterator;_iterator,_f",
moveNext$0: function() {
for (var t1 = this._iterator; t1.moveNext$0();)
if (this._f$1(t1.get$current()) === true)
return true;
return false;
},
_f$1: function(arg0) {
return this._f.call$1(arg0);
}
...
-->
WhereIterator: {
"^": "Iterator;_iterator,_f",
moveNext$0: function() {
var t1, t2;
for (t1 = this._iterator, t2 = this._f; t1.moveNext$0();)
if (t2.call$1(t1.get$current()) === true)
return true;
return false;
},
...
BUG= https://github.com/dart-lang/sdk/issues/22197
R=sigmund@google.com
Review URL: https://codereview.chromium.org/2180363002 .
diff --git a/pkg/compiler/lib/src/ssa/optimize.dart b/pkg/compiler/lib/src/ssa/optimize.dart
index e1eee00c..d89f9de 100644
--- a/pkg/compiler/lib/src/ssa/optimize.dart
+++ b/pkg/compiler/lib/src/ssa/optimize.dart
@@ -450,7 +450,33 @@
node.element = element;
}
}
+ return node;
}
+
+ // Replace method calls through fields with a closure call on the value of
+ // the field. This usually removes the demand for the call-through stub and
+ // makes the field load available to further optimization, e.g. LICM.
+
+ if (element != null &&
+ element.isField &&
+ element.name == node.selector.name) {
+ if (!backend.isNative(element) && !node.isCallOnInterceptor(compiler)) {
+ HInstruction receiver = node.getDartReceiver(compiler);
+ TypeMask type =
+ TypeMaskFactory.inferredTypeForElement(element, compiler);
+ HInstruction load = new HFieldGet(element, receiver, type);
+ node.block.addBefore(node, load);
+ Selector callSelector = new Selector.callClosureFrom(node.selector);
+ List<HInstruction> inputs = <HInstruction>[load]
+ ..addAll(node.inputs.skip(node.isInterceptedCall ? 2 : 1));
+ HInstruction closureCall =
+ new HInvokeClosure(callSelector, inputs, node.instructionType)
+ ..sourceInformation = node.sourceInformation;
+ node.block.addAfter(load, closureCall);
+ return closureCall;
+ }
+ }
+
return node;
}
diff --git a/tests/compiler/dart2js/elide_callthrough_stub_test.dart b/tests/compiler/dart2js/elide_callthrough_stub_test.dart
new file mode 100644
index 0000000..bdb9667
--- /dev/null
+++ b/tests/compiler/dart2js/elide_callthrough_stub_test.dart
@@ -0,0 +1,60 @@
+// Copyright (c) 2016, 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.
+
+// Check that calls through fields elide the call-through stub. This
+// optimization is done by the simplifier, so inlining does not need to be
+// enabled.
+
+import 'package:async_helper/async_helper.dart';
+import 'package:expect/expect.dart';
+import 'compiler_helper.dart';
+
+const String TEST1 = r'''
+class W {
+ final Function _fun;
+ W(this._fun);
+ foo(zzz) => _fun(zzz); // this._fun$1(zzz) --> this._fun.call$1(zzz)
+}
+add1(x) => x + 1;
+main() {
+ var w = new W(add1);
+ var x = w.foo(42);
+}
+''';
+
+const String TEST2 = r'''
+class W {
+ final Function __fun;
+ Function get _fun => __fun;
+ W(this.__fun);
+ foo(zzz) => _fun(zzz); // this._fun$1(zzz) stays same.
+}
+add1(x) => x + 1;
+main() {
+ var w = new W(add1);
+ var x = w.foo(42);
+}
+''';
+
+main() {
+ asyncTest(() => compileAll(TEST1).then((generated) {
+ // Direct call through field.
+ Expect.isTrue(generated.contains(r'this._fun.call$1(zzz)'));
+ // No stub.
+ Expect.isFalse(generated.contains(r'_fun$1:'));
+ // No call to stub.
+ Expect.isFalse(generated.contains(r'_fun$1('));
+ }));
+
+ asyncTest(() => compileAll(TEST2).then((generated) {
+ // No call through field.
+ Expect.isFalse(generated.contains(r'this._fun.call$1(zzz)'));
+ // Call through stub.
+ Expect.isTrue(generated.contains(r'this._fun$1(zzz)'));
+ // Stub is generated.
+ Expect.isTrue(generated.contains(r'_fun$1:'));
+ // Call through getter (inside stub).
+ Expect.isTrue(generated.contains(r'get$_fun().call$1'));
+ }));
+}