Do self-interceptor optimization on a per-use basis

The new code guards against updates when the interceptor is data and
not used as an interceptor.

Change-Id: Ibf6c0ca5f7efe859e82646aee6e571de82417bfe
Reviewed-on: https://dart-review.googlesource.com/68424
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Stephen Adams <sra@google.com>
diff --git a/pkg/compiler/lib/src/ssa/interceptor_simplifier.dart b/pkg/compiler/lib/src/ssa/interceptor_simplifier.dart
index a9be144..c32428c 100644
--- a/pkg/compiler/lib/src/ssa/interceptor_simplifier.dart
+++ b/pkg/compiler/lib/src/ssa/interceptor_simplifier.dart
@@ -82,8 +82,6 @@
     // the interceptor is already available in a local variable, but it is
     // possible that all uses can be rewritten to use different constants.
 
-    // TODO(sra): Also do self-interceptor rewrites on a per-use basis.
-
     HInstruction constant = tryComputeConstantInterceptor(
         invoke.inputs[1], interceptor.interceptedClasses);
     if (constant != null) {
@@ -92,16 +90,18 @@
     return false;
   }
 
-  bool canUseSelfForInterceptor(
-      HInstruction receiver, Set<ClassEntity> interceptedClasses) {
+  bool canUseSelfForInterceptor(HInstruction receiver,
+      {Set<ClassEntity> interceptedClasses}) {
     if (receiver.canBePrimitive(_abstractValueDomain)) {
       // Primitives always need interceptors.
       return false;
     }
-    if (receiver.canBeNull(_abstractValueDomain) &&
-        interceptedClasses.contains(_commonElements.jsNullClass)) {
-      // Need the JSNull interceptor.
-      return false;
+    if (receiver.canBeNull(_abstractValueDomain)) {
+      if (interceptedClasses == null ||
+          interceptedClasses.contains(_commonElements.jsNullClass)) {
+        // Need the JSNull interceptor.
+        return false;
+      }
     }
 
     // All intercepted classes extend `Interceptor`, so if the receiver can't be
@@ -111,13 +111,6 @@
 
   HInstruction tryComputeConstantInterceptor(
       HInstruction input, Set<ClassEntity> interceptedClasses) {
-    if (input == _graph.explicitReceiverParameter) {
-      // If `explicitReceiverParameter` is set it means the current method is an
-      // interceptor method, and `this` is the interceptor.  The caller just did
-      // `getInterceptor(foo).currentMethod(foo)` to enter the current method.
-      return _graph.thisInstruction;
-    }
-
     ClassEntity constantInterceptor = tryComputeConstantInterceptorFromType(
         input.instructionType, interceptedClasses);
 
@@ -191,8 +184,21 @@
     return result;
   }
 
+  static int useCount(HInstruction user, HInstruction used) =>
+      user.inputs.where((input) => input == used).length;
+
   bool visitInterceptor(HInterceptor node) {
-    if (node.isConstant()) return false;
+    if (node.receiver.nonCheck() == _graph.explicitReceiverParameter) {
+      // If `explicitReceiverParameter` is set it means the current method is an
+      // interceptor method, and `this` is the interceptor.  The caller just did
+      // `getInterceptor(foo).currentMethod(foo)` to enter the current method.
+      node.block.rewrite(node, _graph.thisInstruction);
+      return true;
+    }
+
+    rewriteSelfInterceptorUses(node);
+
+    if (node.usedBy.isEmpty) return true;
 
     // Specialize the interceptor with set of classes it intercepts, considering
     // all uses.  (The specialized interceptor has a shorter dispatch chain).
@@ -209,9 +215,6 @@
     // if `a.length` succeeds, which is indicated by the hashCode receiver being
     // a HTypeKnown instruction.
 
-    int useCount(HInstruction user, HInstruction used) =>
-        user.inputs.where((input) => input == used).length;
-
     Set<ClassEntity> interceptedClasses;
     HInstruction dominator = findDominator(node.usedBy);
     // If there is a call that dominates all other uses, we can use just the
@@ -282,18 +285,6 @@
 
     HInstruction receiver = node.receiver;
 
-    // TODO(sra): We should consider each use individually and then all uses
-    // together.  Each use might permit a different rewrite due to a refined
-    // receiver type.  Self-interceptor rewrites are always beneficial since the
-    // receiver is live at a invocation.  Constant-interceptor rewrites are only
-    // guaranteed to be beneficial if they can eliminate the need for the
-    // interceptor or reduce the uses to one that can be simplified with a
-    // one-shot interceptor or optimized is-check.
-
-    if (canUseSelfForInterceptor(receiver, interceptedClasses)) {
-      return rewriteToUseSelfAsInterceptor(node, receiver);
-    }
-
     // Try computing a constant interceptor.
     HInstruction constantInterceptor =
         tryComputeConstantInterceptor(receiver, interceptedClasses);
@@ -381,23 +372,62 @@
     return false;
   }
 
-  bool rewriteToUseSelfAsInterceptor(HInterceptor node, HInstruction receiver) {
-    for (HInstruction user in node.usedBy.toList()) {
-      if (user is HIs) {
-        user.changeUse(node, receiver);
-      } else {
-        // Use the potentially self-argument as new receiver. Note that the
-        // self-argument could potentially have a tighter type than the
-        // receiver which was the input to the interceptor.
-        assert(user.inputs[0] == node);
-        assert(receiver.nonCheck() == user.inputs[1].nonCheck());
-        user.changeUse(node, user.inputs[1]);
+  void rewriteSelfInterceptorUses(HInterceptor node) {
+    HInstruction receiver = node.receiver;
+
+    // At instructions that use the interceptor and its receiver, the receiver
+    // might be refined at the use site.
+
+    //     dynamic x = ...
+    //     if (x is Mumble) {
+    //       print(x.length);  // Self-interceptor here.
+    //     } else {
+    //       print(x.length);  //
+    //     }
+
+    finishInvoke(HInvoke invoke, Selector selector) {
+      HInstruction callReceiver = invoke.getDartReceiver(_closedWorld);
+      if (receiver.nonCheck() == callReceiver.nonCheck()) {
+        Set<ClassEntity> interceptedClasses = _interceptorData
+            .getInterceptedClassesOn(selector.name, _closedWorld);
+
+        if (canUseSelfForInterceptor(callReceiver,
+            interceptedClasses: interceptedClasses)) {
+          invoke.changeUse(node, callReceiver);
+        }
       }
     }
-    return false;
+
+    for (HInstruction user in node.usedBy.toList()) {
+      if (user is HIs) {
+        if (user.interceptor == node) {
+          HInstruction expression = user.expression;
+          if (canUseSelfForInterceptor(expression)) {
+            user.changeUse(node, expression);
+          }
+        }
+      } else if (user is HInvokeDynamic) {
+        if (user.isCallOnInterceptor(_closedWorld) &&
+            node == user.inputs[0] &&
+            useCount(user, node) == 1) {
+          finishInvoke(user, user.selector);
+        }
+      } else if (user is HInvokeSuper) {
+        if (user.isCallOnInterceptor(_closedWorld) &&
+            node == user.inputs[0] &&
+            useCount(user, node) == 1) {
+          finishInvoke(user, user.selector);
+        }
+      } else {
+        // TODO(sra): Are there other paired uses of the receiver and
+        // interceptor where we can make use of a strengthened receiver?
+      }
+    }
   }
 
   bool visitOneShotInterceptor(HOneShotInterceptor node) {
+    // 'Undo' the one-shot transformation if the receiver has a constant
+    // interceptor.
     HInstruction constant =
         tryComputeConstantInterceptor(node.inputs[1], node.interceptedClasses);