Revision: 7915
Author: sp...@google.com
Date: Tue Apr 13 10:38:07 2010
Log: Rolling back in r7776, which improves JsInliner, along with r7913 and r7914, which fix
bugs in it.  The merge commands were:

svn merge --ignore-ancestry -r 7775:7776 https://google-web-toolkit.googlecode.com/svn/trunk svn merge --ignore-ancestry -r 7912:7914 https://google-web-toolkit.googlecode.com/svn/trunk


http://code.google.com/p/google-web-toolkit/source/detail?r=7915

Modified:
/branches/snapshot-2010.03.29-r7809/dev/core/src/com/google/gwt/dev/js/JsHoister.java /branches/snapshot-2010.03.29-r7809/dev/core/src/com/google/gwt/dev/js/JsInliner.java /branches/snapshot-2010.03.29-r7809/dev/core/test/com/google/gwt/dev/js/JsInlinerTest.java

=======================================
--- /branches/snapshot-2010.03.29-r7809/dev/core/src/com/google/gwt/dev/js/JsHoister.java Mon Apr 5 14:44:40 2010 +++ /branches/snapshot-2010.03.29-r7809/dev/core/src/com/google/gwt/dev/js/JsHoister.java Tue Apr 13 10:38:07 2010
@@ -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() {
=======================================
--- /branches/snapshot-2010.03.29-r7809/dev/core/src/com/google/gwt/dev/js/JsInliner.java Mon Apr 5 14:44:40 2010 +++ /branches/snapshot-2010.03.29-r7809/dev/core/src/com/google/gwt/dev/js/JsInliner.java Tue Apr 13 10:38:07 2010
@@ -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;
@@ -670,11 +673,39 @@
         maintainsOrder = false;
       }
     }
-
+
     @Override
     public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
-      JsName name = x.getName();
-
+      checkName(x.getName());
+    }
+
+    @Override
+    public void endVisit(JsNew x, JsContext<JsExpression> ctx) {
+      /*
+       * Unless all arguments have already been evaluated, assume
+       * that invoking the new expression might interfere with
+       * the evaluation of the argument.
+       *
+       * It would be possible to allow this if the invoked function
+       * either does nothing or does nothing that affects the
+       * remaining arguments.  However, currently there is no
+       * analysis of the invoked function.
+       */
+      if (unevaluated.size() > 0) {
+        maintainsOrder = false;
+      }
+    }
+
+    @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;
       }
@@ -683,10 +714,6 @@
         maintainsOrder = false;
       }
     }
-
-    public boolean maintainsOrder() {
-      return maintainsOrder && unevaluated.size() == 0;
-    }

     /**
* Determine if an expression contains a reference to a strict parameter.
@@ -913,6 +940,7 @@
       }

       inlining.push(invokedFunction);
+      x = tryToUnravelExplicitCall(x);
       JsExpression op = process(x, callerFunction, invokedFunction);

       if (x != op) {
@@ -1030,6 +1058,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 +1131,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 +1210,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 +1231,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 +1267,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
@@ -1269,6 +1306,12 @@
         ctx.replaceMe(replacement);
       }
     }
+
+    @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.
@@ -1325,25 +1368,36 @@

   /**
* Detects uses of parameters that would produce incorrect results if inlined.
-   * Generally speaking, we disallow the use of parameters as lvalues.
+   * Generally speaking, we disallow the use of parameters as lvalues. Also
+ * detects trying to inline a method which references 'this' where the call
+   * site has no qualifier.
    */
   private static class ParameterUsageVisitor extends JsVisitor {
-    private boolean lvalue = false;
+    private final boolean hasThisExpr;
     private final Set<JsName> parameterNames;
-
-    public ParameterUsageVisitor(Set<JsName> parameterNames) {
+    private boolean violation = false;
+
+ public ParameterUsageVisitor(boolean hasThisExpr, Set<JsName> parameterNames) {
+      this.hasThisExpr = hasThisExpr;
       this.parameterNames = parameterNames;
     }

     @Override
     public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
       if (ctx.isLvalue() && isParameter(x)) {
-        lvalue = true;
+        violation = true;
       }
     }

-    public boolean parameterAsLValue() {
-      return lvalue;
+    @Override
+    public void endVisit(JsThisRef x, JsContext<JsExpression> ctx) {
+      if (!hasThisExpr) {
+        violation = true;
+      }
+    }
+
+    public boolean hasViolation() {
+      return violation;
     }

     /**
@@ -1753,6 +1807,13 @@
   private static JsFunction isFunction(JsExpression e) {
     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) {
@@ -1767,7 +1828,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
@@ -1809,19 +1871,31 @@
     if (hasCommonIdents(arguments, toInline, parameterIdents)) {
       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);
@@ -1848,9 +1922,10 @@
     }

// Check that parameters aren't used in such a way as to prohibit inlining
-    ParameterUsageVisitor v = new ParameterUsageVisitor(parameterNames);
+    ParameterUsageVisitor v = new ParameterUsageVisitor(thisExpr != null,
+        parameterNames);
     v.accept(toInline);
-    if (v.parameterAsLValue()) {
+    if (v.hasViolation()) {
       return false;
     }

@@ -1887,6 +1962,34 @@
     return hasSideEffects(list)
         || affectedBySideEffects(program, list, context);
   }
+
+  /**
+ * 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.
=======================================
--- /branches/snapshot-2010.03.29-r7809/dev/core/test/com/google/gwt/dev/js/JsInlinerTest.java Wed Oct 28 09:10:53 2009 +++ /branches/snapshot-2010.03.29-r7809/dev/core/test/com/google/gwt/dev/js/JsInlinerTest.java Tue Apr 13 10:38:07 2010
@@ -79,6 +79,29 @@
         + "function c1() { return ex2 ? a1() :c1(); } c1()";
     compare(expected, input);
   }
+
+  /**
+   * Test that a new expression breaks argument ordering.
+   */
+  public void testOrderingNew() throws Exception {
+    StringBuffer code = new StringBuffer();
+    // A static variable x
+    code.append("var x;");
+
+    // foo() uses x
+    code.append("function foo() { alert('x = ' + x); }");
+
+    // callee does "new foo" before evaluating its argument
+    code.append("function callee(arg) { new foo(); return arg; }");
+
+    // caller invokes callee with a multi that initializes x
+    code.append("function caller() { callee((x=1,2)); }");
+
+    // bootstrap the program
+    code.append("caller();");
+
+    compare(code.toString(), code.toString());
+  }

   public void testSelfRecursion() throws Exception {
     String input = "function a1() { return blah && b1() }"
@@ -94,7 +117,6 @@
input = optimize(input, JsSymbolResolver.class, FixStaticRefsVisitor.class,
         JsInliner.class, JsUnusedFunctionRemover.class);
     expected = optimize(expected);
-    System.err.println("Input vs ");
     assertEquals(expected, input);
   }
 }

--
http://groups.google.com/group/Google-Web-Toolkit-Contributors

To unsubscribe, reply using "remove me" as the subject.

Reply via email to