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'));
+  }));
+}