Allow static-dispatch style JS .call invocations to be inlined.

This is particularly relevant because super()/this() calls get translated as .call() ops, and this patch lets us inline them into the calling constructor.

http://gwt-code-reviews.appspot.com/201801/show
Review by: spoon


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7776 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/js/JsHoister.java b/dev/core/src/com/google/gwt/dev/js/JsHoister.java
index 20dae7e..9666276 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsHoister.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsHoister.java
@@ -221,16 +221,9 @@
       stack.push(x);
     }
 
-    /**
-     * A "this" reference can only effectively be hoisted if the call site is
-     * qualified by a JsNameRef, so we'll ignore this for now.
-     */
     @Override
     public void endVisit(JsThisRef x, JsContext<JsExpression> ctx) {
-      // Set a flag to indicate that we cannot continue, and push a null so
-      // we don't run out of elements on the stack.
-      successful = false;
-      stack.push(null);
+      stack.push(new JsThisRef(x.getSourceInfo()));
     }
 
     public JsExpression getExpression() {
diff --git a/dev/core/src/com/google/gwt/dev/js/JsInliner.java b/dev/core/src/com/google/gwt/dev/js/JsInliner.java
index 02373c6..2c15ef1 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsInliner.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsInliner.java
@@ -136,7 +136,7 @@
        */
       affectedBySideEffects = true;
     }
-    
+
     @Override
     public void endVisit(JsObjectLiteral x, JsContext<JsExpression> ctx) {
       affectedBySideEffects = true;
@@ -600,6 +600,9 @@
    * invocations occur.
    */
   private static class EvaluationOrderVisitor extends JsVisitor {
+    public static final JsName THIS_NAME = (new JsScope("fake scope") {
+    }).declareName("this");
+
     private boolean maintainsOrder = true;
     private final List<JsName> toEvaluate;
     private final List<JsName> unevaluated;
@@ -673,8 +676,19 @@
 
     @Override
     public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
-      JsName name = x.getName();
+      checkName(x.getName());
+    }
 
+    @Override
+    public void endVisit(JsThisRef x, JsContext<JsExpression> ctx) {
+      checkName(THIS_NAME);
+    }
+
+    public boolean maintainsOrder() {
+      return maintainsOrder && unevaluated.size() == 0;
+    }
+
+    private void checkName(JsName name) {
       if (!toEvaluate.contains(name)) {
         return;
       }
@@ -684,10 +698,6 @@
       }
     }
 
-    public boolean maintainsOrder() {
-      return maintainsOrder && unevaluated.size() == 0;
-    }
-
     /**
      * Determine if an expression contains a reference to a strict parameter.
      */
@@ -913,6 +923,7 @@
       }
 
       inlining.push(invokedFunction);
+      x = tryToUnravelExplicitCall(x);
       JsExpression op = process(x, callerFunction, invokedFunction);
 
       if (x != op) {
@@ -1030,6 +1041,7 @@
 
       List<JsExpression> hoisted = new ArrayList<JsExpression>(
           statements.size());
+      JsExpression thisExpr = ((JsNameRef) x.getQualifier()).getQualifier();
       List<JsName> localVariableNames = new ArrayList<JsName>();
       boolean sawReturnStatement = false;
 
@@ -1102,13 +1114,14 @@
       }
 
       // Confirm that the expression conforms to the desired heuristics
-      if (!isInlinable(program, callerFunction, invokedFunction,
+      if (!isInlinable(program, callerFunction, invokedFunction, thisExpr,
           x.getArguments(), op)) {
         return x;
       }
 
       // Perform the name replacement
-      NameRefReplacerVisitor v = new NameRefReplacerVisitor(x, invokedFunction);
+      NameRefReplacerVisitor v = new NameRefReplacerVisitor(thisExpr,
+          x.getArguments(), invokedFunction.getParameters());
       for (ListIterator<JsName> nameIterator = localVariableNames.listIterator(); nameIterator.hasNext();) {
         JsName name = nameIterator.next();
 
@@ -1180,21 +1193,12 @@
 
     @Override
     public void endVisit(JsInvocation x, JsContext<JsExpression> ctx) {
-      JsFunction function = isFunction(x.getQualifier());
-      if (function != null) {
-        Integer count = invocationCount.get(function);
-        if (count == null) {
-          assert (!removingCounts);
-          count = 1;
-        } else {
-          if (removingCounts) {
-            count -= 1;
-          } else {
-            count += 1;
-          }
-        }
-        invocationCount.put(function, count);
-      }
+      checkFunctionCall(x.getQualifier());
+    }
+
+    @Override
+    public void endVisit(JsNew x, JsContext<JsExpression> ctx) {
+      checkFunctionCall(x.getConstructorExpression());
     }
 
     public Integer invocationCount(JsFunction f) {
@@ -1210,6 +1214,24 @@
       accept(expr);
       removingCounts = false;
     }
+
+    private void checkFunctionCall(JsExpression qualifier) {
+      JsFunction function = isFunction(qualifier);
+      if (function != null) {
+        Integer count = invocationCount.get(function);
+        if (count == null) {
+          assert (!removingCounts);
+          count = 1;
+        } else {
+          if (removingCounts) {
+            count -= 1;
+          } else {
+            count += 1;
+          }
+        }
+        invocationCount.put(function, count);
+      }
+    }
   }
 
   /**
@@ -1228,15 +1250,13 @@
     final Map<JsName, JsExpression> paramsToArgsMap = new IdentityHashMap<JsName, JsExpression>();
 
     /**
-     * Constructor.
-     * 
-     * @param invocation The call site
-     * @param function The function that encloses the inlined statement
+     * A replacement expression for this references.
      */
-    public NameRefReplacerVisitor(JsInvocation invocation, JsFunction function) {
-      List<JsParameter> parameters = function.getParameters();
-      List<JsExpression> arguments = invocation.getArguments();
+    private JsExpression thisExpr;
 
+    public NameRefReplacerVisitor(JsExpression thisExpr,
+        List<JsExpression> arguments, List<JsParameter> parameters) {
+      this.thisExpr = thisExpr;
       if (parameters.size() != arguments.size()) {
         // This shouldn't happen if the cloned JsInvocation has been properly
         // configured
@@ -1270,6 +1290,12 @@
       }
     }
 
+    @Override
+    public void endVisit(JsThisRef x, JsContext<JsExpression> ctx) {
+      assert thisExpr != null;
+      ctx.replaceMe(thisExpr);
+    }
+
     /**
      * Set a replacement JsName for all references to a JsName.
      * 
@@ -1754,6 +1780,13 @@
     if (e instanceof JsNameRef) {
       JsNameRef ref = (JsNameRef) e;
 
+      // Unravel foo.call(...).
+      if (!ref.getName().isObfuscatable() && "call".equals(ref.getIdent())) {
+        if (ref.getQualifier() instanceof JsNameRef) {
+          ref = (JsNameRef) ref.getQualifier();
+        }
+      }
+
       JsNode staticRef = ref.getName().getStaticRef();
       if (staticRef instanceof JsFunction) {
         return (JsFunction) staticRef;
@@ -1767,7 +1800,8 @@
    * Determine if a statement can be inlined into a call site.
    */
   private static boolean isInlinable(JsProgram program, JsFunction caller,
-      JsFunction callee, List<JsExpression> arguments, JsNode<?> toInline) {
+      JsFunction callee, JsExpression thisExpr, List<JsExpression> arguments,
+      JsNode<?> toInline) {
 
     /*
      * This will happen with varargs-style JavaScript functions that rely on the
@@ -1810,18 +1844,30 @@
       return false;
     }
 
+    List<JsExpression> evalArgs;
+    if (thisExpr == null) {
+      evalArgs = arguments;
+    } else {
+      evalArgs = new ArrayList<JsExpression>(1 + arguments.size());
+      evalArgs.add(thisExpr);
+      evalArgs.addAll(arguments);
+    }
+
     /*
      * Determine if the evaluation of the invocation's arguments may create side
      * effects. This will determine how aggressively the parameters may be
      * reordered.
      */
-    if (isVolatile(program, arguments, caller)) {
+    if (isVolatile(program, evalArgs, caller)) {
       /*
        * Determine the order in which the parameters must be evaluated. This
        * will vary between call sites, based on whether or not the invocation's
        * arguments can be repeated without ill effect.
        */
       List<JsName> requiredOrder = new ArrayList<JsName>();
+      if (thisExpr != null && isVolatile(program, thisExpr, callee)) {
+        requiredOrder.add(EvaluationOrderVisitor.THIS_NAME);
+      }
       for (int i = 0; i < arguments.size(); i++) {
         JsExpression e = arguments.get(i);
         JsParameter p = callee.getParameters().get(i);
@@ -1889,6 +1935,34 @@
   }
 
   /**
+   * Transforms any <code>foo.call(this)</code> into <code>this.foo()</code> to
+   * be compatible with our inlining algorithm.
+   */
+  private static JsInvocation tryToUnravelExplicitCall(JsInvocation x) {
+    if (!(x.getQualifier() instanceof JsNameRef)) {
+      return x;
+    }
+    JsNameRef ref = (JsNameRef) x.getQualifier();
+    if (ref.getName().isObfuscatable() || !"call".equals(ref.getIdent())) {
+      return x;
+    }
+    List<JsExpression> oldArgs = x.getArguments();
+    if (oldArgs.size() < 1) {
+      return x;
+    }
+
+    JsNameRef oldTarget = (JsNameRef) ref.getQualifier();
+    JsNameRef newTarget = new JsNameRef(oldTarget.getSourceInfo(),
+        oldTarget.getName());
+    newTarget.setQualifier(oldArgs.get(0));
+    JsInvocation newCall = new JsInvocation(x.getSourceInfo());
+    newCall.setQualifier(newTarget);
+    // Don't have to clone because the returned invocation is transient.
+    newCall.getArguments().addAll(oldArgs.subList(1, oldArgs.size()));
+    return newCall;
+  }
+
+  /**
    * Utility class.
    */
   private JsInliner() {