Improve the safety and correctness of inlining performed on the JavaScript AST.

Patch by: bobv
Review by: scottb
Suggested by: mmastrac


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1520 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/js/JsDelegationRemover.java b/dev/core/src/com/google/gwt/dev/js/JsDelegationRemover.java
index 440445d..9f88d85 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsDelegationRemover.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsDelegationRemover.java
@@ -36,23 +36,33 @@
 import com.google.gwt.dev.js.ast.JsModVisitor;
 import com.google.gwt.dev.js.ast.JsName;
 import com.google.gwt.dev.js.ast.JsNameRef;
+import com.google.gwt.dev.js.ast.JsNew;
 import com.google.gwt.dev.js.ast.JsNullLiteral;
 import com.google.gwt.dev.js.ast.JsParameter;
+import com.google.gwt.dev.js.ast.JsPostfixOperation;
+import com.google.gwt.dev.js.ast.JsPrefixOperation;
 import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.js.ast.JsRegExp;
 import com.google.gwt.dev.js.ast.JsReturn;
+import com.google.gwt.dev.js.ast.JsScope;
 import com.google.gwt.dev.js.ast.JsStatement;
 import com.google.gwt.dev.js.ast.JsStringLiteral;
 import com.google.gwt.dev.js.ast.JsSwitchMember;
 import com.google.gwt.dev.js.ast.JsThisRef;
+import com.google.gwt.dev.js.ast.JsThrow;
+import com.google.gwt.dev.js.ast.JsUnaryOperation;
 import com.google.gwt.dev.js.ast.JsVisitor;
 import com.google.gwt.dev.js.ast.JsWhile;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.Stack;
 
 /**
  * Collapses delegating method calls.
@@ -67,12 +77,12 @@
    * choices. This will not produce the most efficient elimination of clinit
    * calls, but it handles the general case and is simple to verify.
    */
-  private class DuplicateClinitRemover extends JsModVisitor {
+  private static class DuplicateClinitRemover extends JsModVisitor {
     /*
      * TODO: Most of the special casing below can be removed if complex
      * statements always use blocks, rather than plain statements.
      */
-    
+
     /**
      * Retains the names of the clinit functions that we know have been called.
      */
@@ -212,131 +222,145 @@
   }
 
   /**
-   * Examines a statement to determine if it matches the delegator pattern. In
-   * order to be considered a delegator, all JsNameReferences must either be
-   * references to global/root objects or to the parameters of the enclosing
-   * function. References to strict parameters must occur exactly once and in
-   * the order in which the parameters are defined. A flexible parameter (one
-   * that may be evaluated multiple times without effect) may be evaluated any
-   * number of times and in any order.
+   * Determines if a list of names is guaranteed to be evaluated in a particular
+   * order.
    */
-  private class IsDelegatingVisitor extends JsVisitor {
-    private final Set<JsName> parameterNames;
-    private final List<JsName> strictParameters;
-    private final List<JsName> flexibleParameters;
-    private boolean delegating = true;
+  private static class EvaluationOrderVisitor extends JsVisitor {
+    private boolean maintainsOrder = true;
+    private final List<JsName> toEvaluate;
+    private final List<JsName> unevaluated;
 
-    public IsDelegatingVisitor(Set<JsName> parameterNames,
-        List<JsName> strictParameters, List<JsName> flexibleParameters) {
-      this.parameterNames = parameterNames;
-      this.strictParameters = strictParameters;
-      this.flexibleParameters = flexibleParameters;
+    public EvaluationOrderVisitor(List<JsName> toEvaluate) {
+      this.toEvaluate = toEvaluate;
+      this.unevaluated = new ArrayList<JsName>(toEvaluate);
+    }
+
+    @Override
+    public void endVisit(JsBinaryOperation x, JsContext<JsExpression> ctx) {
+      JsBinaryOperator op = x.getOperator();
+
+      // We don't care about the left-hand expression, because it is guaranteed
+      // to be evaluated.
+      boolean rightStrict = refersToRequiredName(x.getArg2());
+      boolean conditionalEvaluation =
+          JsBinaryOperator.AND.equals(op) || JsBinaryOperator.OR.equals(op);
+
+      if (rightStrict && conditionalEvaluation) {
+        maintainsOrder = false;
+      }
+    }
+
+    /**
+     * If the condition would cause conditional evaluation of strict parameters,
+     * don't allow inlining.
+     */
+    @Override
+    public void endVisit(JsConditional x, JsContext<JsExpression> ctx) {
+      boolean thenStrict = refersToRequiredName(x.getThenExpression());
+      boolean elseStrict = refersToRequiredName(x.getElseExpression());
+
+      if (thenStrict || elseStrict) {
+        maintainsOrder = false;
+      }
+    }
+
+    /**
+     * The statement declares a function closure. This makes actual evaluation
+     * order of the parameters difficult or impossible to determine, so we'll
+     * just ignore them.
+     */
+    @Override
+    public void endVisit(JsFunction x, JsContext<JsExpression> ctx) {
+      maintainsOrder = false;
+    }
+
+    /**
+     * The innermost invocation we see must consume all presently unevaluated
+     * parameters to ensure that an exception does not prevent their evaluation.
+     * 
+     * In the case of a nested invocation, such as
+     * <code>F(r1, r2, G(r3, r4), f1);</code> the evaluation order is
+     * guaranteed to be maintained, provided that no required parameters occur
+     * after the nested invocation.
+     */
+    @Override
+    public void endVisit(JsInvocation x, JsContext<JsExpression> ctx) {
+      if (unevaluated.size() > 0) {
+        maintainsOrder = false;
+      }
     }
 
     @Override
     public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
-      // Short circuit
-      if (!delegating) {
-        return;
-      }
-
-      // Don't examine JsNameRefs that are intermediate elements in a chain
-      if (x.getQualifier() != null) {
-        return;
-      }
-
       JsName name = x.getName();
 
-      if (parameterNames.contains(name)) {
-        // The name is a reference to a parameter defined in the function
-        if (flexibleParameters.contains(name)) {
-          // It doesn't matter how many times an optional parameter is used
-
-        } else if (strictParameters.indexOf(name) != 0) {
-          // We saw a required parameter being used out of declaration order
-          delegating = false;
-
-        } else {
-          // Record that the required parameter was used.
-          if (strictParameters.remove(0) != name) {
-            throw new InternalCompilerException(
-                "Unexpected name removed from strict parameter list.");
-          }
-        }
-      } else {
-        // See if the name refers to a global/static name.
-        boolean isStatic =
-            name.getEnclosing() == program.getScope()
-                || name.getEnclosing() == program.getRootScope();
-        delegating &= isStatic;
-      }
-    }
-
-    public boolean isDelegating() {
-      return delegating && (strictParameters.size() == 0);
-    }
-  }
-
-  /**
-   * Given a call site, replace with an equivalent expression, assuming that the
-   * callee is a delegation-style function.
-   */
-  private class NameRefReplacerVisitor extends JsModVisitor {
-    /**
-     * Set up a map of parameter names back to the expressions that will be
-     * passed in from the outer call site.
-     */
-    final Map<JsName, JsExpression> originalParameterExpressions =
-        new HashMap<JsName, JsExpression>();
-
-    /**
-     * Constructor.
-     * 
-     * @param invocation The delegating JsInvocation (the inner delegation)
-     * @param function The function that encloses the outer delegation
-     */
-    public NameRefReplacerVisitor(JsInvocation invocation, JsFunction function) {
-      List<JsParameter> parameters = function.getParameters();
-      List<JsExpression> arguments = invocation.getArguments();
-
-      if (parameters.size() != arguments.size()) {
-        // This shouldn't happen if the cloned JsInvocation has been properly
-        // configured
-        throw new InternalCompilerException(
-            "Mismatch on parameters and arguments");
-      }
-
-      for (int i = 0; i < parameters.size(); i++) {
-        JsParameter p = parameters.get(i);
-        JsExpression e = arguments.get(i);
-        originalParameterExpressions.put(p.getName(), e);
-      }
-    }
-
-    /**
-     * Replace JsNameRefs that refer to parameters with the expression passed
-     * into the function invocation.
-     */
-    @Override
-    public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
-      if (x.getQualifier() != null) {
+      if (!toEvaluate.contains(name)) {
         return;
       }
 
-      JsExpression original = originalParameterExpressions.get(x.getName());
-
-      if (original != null) {
-        ctx.replaceMe(original);
+      if (unevaluated.size() == 0 || !unevaluated.remove(0).equals(name)) {
+        maintainsOrder = false;
       }
     }
+
+    public boolean maintainsOrder() {
+      return maintainsOrder && unevaluated.size() == 0;
+    }
+
+    /**
+     * Determine if an expression contains a reference to a strict parameter.
+     */
+    private boolean refersToRequiredName(JsExpression e) {
+      RefersToNameVisitor v = new RefersToNameVisitor(toEvaluate);
+      v.accept(e);
+      return v.refersToName();
+    }
   }
 
   /**
-   * This looks for all invocations in the program. Any invoked JsFunctions
-   * derived from Java functions are then examined for being possible delegation
-   * patterns.
+   * Collect all of the idents used in an AST node. The collector can be
+   * configured to collect idents from qualified xor unqualified JsNameRefs.
    */
-  private class RemoveDelegationVisitor extends JsModVisitor {
+  private static class IdentCollector extends JsVisitor {
+    private final Set<String> idents = new HashSet<String>();
+    private final boolean collectQualified;
+
+    public IdentCollector(boolean collectQualified) {
+      this.collectQualified = collectQualified;
+    }
+
+    @Override
+    public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
+      boolean hasQualifier = x.getQualifier() != null;
+
+      if ((collectQualified && !hasQualifier)
+          || (!collectQualified && hasQualifier)) {
+        return;
+      }
+
+      assert x.getIdent() != null;
+      idents.add(x.getIdent());
+    }
+
+    public Set<String> getIdents() {
+      return idents;
+    }
+  }
+
+  /**
+   * This class looks for function invocations that can be inlined and perform
+   * the replacement.
+   */
+  private static class InliningVisitor extends JsModVisitor {
+    final Stack<JsFunction> functionStack = new Stack<JsFunction>();
+
+    @Override
+    public void endVisit(JsFunction x, JsContext<JsExpression> ctx) {
+      if (!functionStack.pop().equals(x)) {
+        throw new InternalCompilerException("Unexpected function popped");
+      }
+    }
+
     @Override
     public void endVisit(JsInvocation x, JsContext<JsExpression> ctx) {
       // Start by determining what is being invoked.
@@ -387,11 +411,6 @@
         return;
       }
 
-      // Confirm that the statement conforms to the desired pattern
-      if (!isDelegatingStatement(x, f, toHoist)) {
-        return;
-      }
-
       /*
        * Create a replacement expression to use in place of the invocation. It
        * is important that the replacement is newly-minted and therefore not
@@ -405,6 +424,12 @@
         return;
       }
 
+      // Confirm that the statement conforms to the desired pattern
+      if (!isInlinableStatement(functionStack.peek(), x.getArguments(), f,
+          toHoist)) {
+        return;
+      }
+
       // Perform the name replacement
       NameRefReplacerVisitor v = new NameRefReplacerVisitor(x, f);
       replacement = v.accept(replacement);
@@ -416,15 +441,314 @@
                 .getExpression(), replacement);
       }
 
+      // Replace the original invocation with the inlined statement
       ctx.replaceMe(replacement);
     }
+
+    @Override
+    public boolean visit(JsFunction x, JsContext<JsExpression> ctx) {
+      functionStack.push(x);
+      return true;
+    }
   }
 
   /**
+   * Replace references to JsNames with the inlined JsExpression.
+   */
+  private static class NameRefReplacerVisitor extends JsModVisitor {
+    /**
+     * Set up a map of parameter names back to the expressions that will be
+     * passed in from the outer call site.
+     */
+    final Map<JsName, JsExpression> paramsToArgsMap =
+        new HashMap<JsName, JsExpression>();
+
+    /**
+     * Constructor.
+     * 
+     * @param invocation The call site
+     * @param function The function that encloses the inlined statement
+     */
+    public NameRefReplacerVisitor(JsInvocation invocation, JsFunction function) {
+      List<JsParameter> parameters = function.getParameters();
+      List<JsExpression> arguments = invocation.getArguments();
+
+      if (parameters.size() != arguments.size()) {
+        // This shouldn't happen if the cloned JsInvocation has been properly
+        // configured
+        throw new InternalCompilerException(
+            "Mismatch on parameters and arguments");
+      }
+
+      for (int i = 0; i < parameters.size(); i++) {
+        JsParameter p = parameters.get(i);
+        JsExpression e = arguments.get(i);
+        paramsToArgsMap.put(p.getName(), e);
+      }
+    }
+
+    /**
+     * Replace JsNameRefs that refer to parameters with the expression passed
+     * into the function invocation.
+     */
+    @Override
+    public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
+      if (x.getQualifier() != null) {
+        return;
+      }
+
+      /*
+       * TODO if we ever allow mutable JsExpression types to be considered
+       * always flexible, then it would be necessary to clone the expression.
+       */
+      JsExpression original = paramsToArgsMap.get(x.getName());
+
+      if (original != null) {
+        ctx.replaceMe(original);
+      }
+    }
+  }
+
+  /**
+   * Detects uses of parameters that would produce incorrect results if inlined.
+   * Generally speaking, we disallow the use of parameters as lvalues.
+   */
+  private static class ParameterUsageVisitor extends JsVisitor {
+    private final Set<JsName> parameterNames;
+    private boolean lvalue = false;
+
+    public ParameterUsageVisitor(Set<JsName> parameterNames) {
+      this.parameterNames = parameterNames;
+    }
+
+    /**
+     * Disallow inlining if the left-hand side of an assignment is a parameter.
+     */
+    @Override
+    public void endVisit(JsBinaryOperation x, JsContext<JsExpression> ctx) {
+      JsBinaryOperator op = x.getOperator();
+
+      // Don't allow assignments to the left-hand side.
+      if (op.isAssignment() && isParameter(x.getArg1())) {
+        lvalue = true;
+      }
+    }
+
+    /**
+     * Delegates to {@link #checkUnaryOperation(JsUnaryOperation)}.
+     */
+    @Override
+    public void endVisit(JsPostfixOperation x, JsContext<JsExpression> ctx) {
+      checkUnaryOperation(x);
+    }
+
+    /**
+     * Delegates to {@link #checkUnaryOperation(JsUnaryOperation)}.
+     */
+    @Override
+    public void endVisit(JsPrefixOperation x, JsContext<JsExpression> ctx) {
+      checkUnaryOperation(x);
+    }
+
+    public boolean parameterAsLValue() {
+      return lvalue;
+    }
+
+    /**
+     * Disallow modification of parameters via unary operations.
+     */
+    private void checkUnaryOperation(JsUnaryOperation x) {
+      if (x.getOperator().isModifying() && isParameter(x.getArg())) {
+        lvalue = true;
+      }
+    }
+
+    /**
+     * Determine if a JsExpression is a JsNameRef that refers to a parameter.
+     */
+    private boolean isParameter(JsExpression e) {
+      if (!(e instanceof JsNameRef)) {
+        return false;
+      }
+
+      JsNameRef ref = (JsNameRef) e;
+      if (ref.getQualifier() != null) {
+        return false;
+      }
+
+      JsName name = ref.getName();
+      return parameterNames.contains(name);
+    }
+  }
+
+  /**
+   * Given a collection of JsNames, determine if an AST node refers to any of
+   * those names.
+   */
+  private static class RefersToNameVisitor extends JsVisitor {
+    private final Collection<JsName> names;
+    private boolean refersToName;
+    private boolean refersToUnbound;
+
+    public RefersToNameVisitor(Collection<JsName> names) {
+      this.names = names;
+    }
+
+    @Override
+    public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
+      JsName name = x.getName();
+
+      if (name == null) {
+        refersToUnbound = true;
+      } else {
+        refersToName = refersToName || names.contains(name);
+      }
+    }
+
+    public boolean refersToName() {
+      return refersToName;
+    }
+
+    public boolean refersToUnbound() {
+      return refersToUnbound;
+    }
+  }
+
+  /**
+   * Examine a node to determine if it might produce side effects.
+   */
+  private static class SideEffectsVisitor extends JsVisitor {
+    private boolean hasSideEffects;
+
+    @Override
+    public void endVisit(JsBinaryOperation x, JsContext<JsExpression> ctx) {
+      hasSideEffects |= (x.getOperator().isAssignment());
+    }
+
+    @Override
+    public void endVisit(JsInvocation x, JsContext<JsExpression> ctx) {
+      /*
+       * We don't actually need to drill-down into other functions to see if
+       * they do or do not have side-effects. The simple, side-effect free
+       * function invocations will naturally be inlined in subsequent
+       * iterations.
+       */
+      hasSideEffects = true;
+    }
+
+    @Override
+    public void endVisit(JsNew x, JsContext<JsExpression> ctx) {
+      /*
+       * The typical use of the new keyword in JavaScript generated by GWT is to
+       * create a prototypical object, and then pass it into a Java-derived
+       * constructor. Given that the majority of the uses of new would not
+       * benefit from inlining, it's not worth the extra complexity of worrying
+       * about yet another set of special cases.
+       */
+      hasSideEffects = true;
+    }
+
+    @Override
+    public void endVisit(JsPostfixOperation x, JsContext<JsExpression> ctx) {
+      hasSideEffects |= x.getOperator().isModifying();
+    }
+
+    @Override
+    public void endVisit(JsPrefixOperation x, JsContext<JsExpression> ctx) {
+      hasSideEffects |= x.getOperator().isModifying();
+    }
+
+    @Override
+    public void endVisit(JsThrow x, JsContext<JsStatement> ctx) {
+      hasSideEffects = true;
+    }
+
+    public boolean hasSideEffects() {
+      return hasSideEffects;
+    }
+  }
+
+  /**
+   * This ensures that changing the scope of an expression from its enclosing
+   * function into the scope of the call site will not cause unqualified
+   * identifiers to resolve to different values.
+   */
+  private static class StableNameChecker extends JsVisitor {
+    private final JsScope callerScope;
+    private final JsScope calleeScope;
+    private final Collection<JsName> parameterNames;
+    private boolean stable = true;
+
+    public StableNameChecker(JsScope callerScope, JsScope calleeScope,
+        Collection<JsName> parameterNames) {
+      this.callerScope = callerScope;
+      this.calleeScope = calleeScope;
+      this.parameterNames = parameterNames;
+    }
+
+    @Override
+    public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
+      // We can ignore qualified reference, since their scope is always that
+      // of the qualifier.
+      if (x.getQualifier() != null) {
+        return;
+      }
+
+      // Attempt to resolve the ident in both scopes
+      JsName callerName = callerScope.findExistingName(x.getIdent());
+      JsName calleeName = calleeScope.findExistingName(x.getIdent());
+
+      if (callerName == null && calleeName == null) {
+        // They both reference out-of-module names
+
+      } else if (parameterNames.contains(calleeName)) {
+        // A reference to a parameter, which will be replaced by an argument
+
+      } else if (callerName != null && callerName.equals(calleeName)) {
+        // The names are known to us and are the same
+
+      } else {
+        stable = false;
+      }
+    }
+
+    public boolean isStable() {
+      return stable;
+    }
+  }
+
+  /**
+   * A List of expression types that are known to never be affected by
+   * side-effects. Used by {@link #alwaysFlexible(JsExpression)}.
+   */
+  private static final List<Class<?>> ALWAYS_FLEXIBLE =
+      Arrays.asList(new Class<?>[] {JsBooleanLiteral.class,
+          JsDecimalLiteral.class, JsIntegralLiteral.class, JsNullLiteral.class,
+          JsRegExp.class, JsStringLiteral.class, JsThisRef.class});
+
+  /**
    * Static entry point used by JavaToJavaScriptCompiler.
    */
   public static boolean exec(JsProgram program) {
-    return new JsDelegationRemover(program).execImpl();
+    InliningVisitor v = new InliningVisitor();
+    v.accept(program);
+
+    DuplicateClinitRemover r = new DuplicateClinitRemover();
+    r.accept(program);
+
+    return v.didChange() || r.didChange();
+  }
+
+  /**
+   * Indicates if an expression can be repeated multiple times in a delegation
+   * removal without side-effects.
+   */
+  private static boolean alwaysFlexible(JsExpression e) {
+    if (e instanceof JsNameRef) {
+      return false;
+    } else {
+      return ALWAYS_FLEXIBLE.contains(e.getClass());
+    }
   }
 
   /**
@@ -445,6 +769,44 @@
   }
 
   /**
+   * Check to see if the to-be-inlined statement shares any idents with the
+   * call-side arguments. Two passes are made: the first one looks for qualified
+   * names; the second pass looks for unqualified names, but ignores identifiers
+   * that refer to function parameters.
+   */
+  private static boolean hasCommonIdents(List<JsExpression> arguments,
+      JsStatement toInline, Collection<String> parameterIdents) {
+
+    // This is a fire-twice loop
+    boolean checkQualified = false;
+    do {
+      checkQualified = !checkQualified;
+
+      // Collect the idents used in the arguments and the statement
+      IdentCollector argCollector = new IdentCollector(checkQualified);
+      argCollector.acceptList(arguments);
+      IdentCollector statementCollector = new IdentCollector(checkQualified);
+      statementCollector.accept(toInline);
+
+      Set<String> idents = argCollector.getIdents();
+
+      // Unqualified idents may be references to parameters, thus ignored
+      if (!checkQualified) {
+        idents.removeAll(parameterIdents);
+      }
+
+      // Perform the set difference
+      idents.retainAll(statementCollector.getIdents());
+
+      if (idents.size() > 0) {
+        return true;
+      }
+    } while (checkQualified);
+
+    return false;
+  }
+
+  /**
    * Given a delegated JsStatement, construct an expression to hoist into the
    * outer caller. This does not perform any name replacement, but simply
    * constructs a mutable copy of the expression that can be manipulated
@@ -468,6 +830,104 @@
   }
 
   /**
+   * Determine if a statement can be inlined into a call site.
+   */
+  private static boolean isInlinableStatement(JsFunction caller,
+      List<JsExpression> arguments, JsFunction callee, JsStatement toInline) {
+    /*
+     * This will happen with varargs-style JavaScript functions that rely on the
+     * "arguments" array. The reference to arguments would be detected in
+     * BoundedScopeVisitor, but the code below assumes the same number of
+     * parameters and arguments.
+     */
+    if (arguments.size() != callee.getParameters().size()) {
+      return false;
+    }
+
+    // Build up a list of all parameter names
+    Set<JsName> parameterNames = new HashSet<JsName>();
+    Set<String> parameterIdents = new HashSet<String>();
+    for (JsParameter param : callee.getParameters()) {
+      parameterNames.add(param.getName());
+      parameterIdents.add(param.getName().getIdent());
+    }
+
+    /*
+     * Make sure that inlining won't change the final name of non-parameter
+     * idents due to the change of scope. The most likely cause would be the use
+     * of an unqualified variable reference in a JSNI block that happened to
+     * conflict with a Java-derived identifier.
+     */
+    StableNameChecker detector =
+        new StableNameChecker(caller.getScope(), callee.getScope(),
+            parameterNames);
+    detector.accept(toInline);
+    if (!detector.isStable()) {
+      return false;
+    }
+
+    /*
+     * Ensure that the names referred to by the argument list and the statement
+     * are disjoint. This prevents inlining of the following:
+     * 
+     * static int i; public void add(int a) { i += a; }; add(i++);
+     * 
+     */
+    if (hasCommonIdents(arguments, toInline, parameterIdents)) {
+      return false;
+    }
+
+    /*
+     * Determine if the evaluation of the invocation's arguments may create side
+     * effects. This will determine how aggressively the parameters may be
+     * reordered.
+     */
+    SideEffectsVisitor sideEffects = new SideEffectsVisitor();
+    sideEffects.acceptList(arguments);
+    boolean maintainOrder = sideEffects.hasSideEffects();
+
+    if (maintainOrder) {
+      /*
+       * 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>();
+      for (int i = 0; i < arguments.size(); i++) {
+        JsExpression e = arguments.get(i);
+        JsParameter p = callee.getParameters().get(i);
+
+        if (!alwaysFlexible(e)) {
+          requiredOrder.add(p.getName());
+        }
+      }
+
+      /*
+       * Verify that the non-reorderable arguments are evaluated in the right
+       * order.
+       */
+      if (requiredOrder.size() > 0) {
+        EvaluationOrderVisitor orderVisitor =
+            new EvaluationOrderVisitor(requiredOrder);
+        orderVisitor.accept(toInline);
+        if (!orderVisitor.maintainsOrder()) {
+          return false;
+        }
+      }
+    }
+
+    // Check that parameters aren't used in such a way as to prohibit inlining
+    ParameterUsageVisitor v = new ParameterUsageVisitor(parameterNames);
+    v.accept(toInline);
+    if (v.parameterAsLValue()) {
+      return false;
+    }
+
+    // Hooray!
+    return true;
+  }
+
+  /**
    * Determines if a statement is an invocation of a static initializer.
    */
   private static boolean isStaticInitializer(JsStatement statement) {
@@ -492,103 +952,8 @@
   }
 
   /**
-   * Indicates if an expression can be repeated multiple times in a delegation
-   * removal without side-effects.
+   * Utility class.
    */
-  private static boolean requiresSingleEvaluation(JsExpression e) {
-    if (e instanceof JsNameRef) {
-      JsNameRef ref = (JsNameRef) e;
-      if (ref.getQualifier() != null) {
-        return requiresSingleEvaluation(ref.getQualifier());
-      } else {
-        return false;
-      }
-    } else if (e instanceof JsBooleanLiteral) {
-      return false;
-    } else if (e instanceof JsDecimalLiteral) {
-      return false;
-    } else if (e instanceof JsIntegralLiteral) {
-      return false;
-    } else if (e instanceof JsNullLiteral) {
-      return false;
-    } else if (e instanceof JsStringLiteral) {
-      return false;
-    } else if (e instanceof JsThisRef) {
-      return false;
-    } else {
-      return true;
-    }
-  }
-
-  /**
-   * The intended victim for the optimizer.
-   */
-  private final JsProgram program;
-
-  /**
-   * Constructor.
-   */
-  private JsDelegationRemover(JsProgram program) {
-    this.program = program;
-  }
-
-  /**
-   * Instance execution method.
-   */
-  private boolean execImpl() {
-    RemoveDelegationVisitor v = new RemoveDelegationVisitor();
-    v.accept(program);
-
-    DuplicateClinitRemover r = new DuplicateClinitRemover();
-    r.accept(program);
-
-    return v.didChange() || r.didChange();
-  }
-
-  /**
-   * Determine if a statement matches the delegator pattern.
-   */
-  private boolean isDelegatingStatement(JsInvocation invocation,
-      JsFunction enclosing, JsStatement statement) {
-
-    // Build up a map of parameter names
-    Set<JsName> parameterNames = new HashSet<JsName>();
-
-    for (JsParameter param : enclosing.getParameters()) {
-      parameterNames.add(param.getName());
-    }
-
-    if (invocation.getArguments().size() != enclosing.getParameters().size()) {
-      /*
-       * This will happen with varargs-style JavaScript functions that rely on
-       * the "arguments" array. The reference to arguments would be detected in
-       * IsDelegatingVisitor, but the bucketing code below assumes the same
-       * number of parameters and arguments.
-       */
-      return false;
-    }
-
-    /*
-     * Determine which function parameters can safely be duplicated or
-     * re-ordered in the delegation removal. This will vary between call sites,
-     * based on whether or not the invocation's arguments can be repeated
-     * without ill effect.
-     */
-    List<JsName> strictParameters = new ArrayList<JsName>();
-    List<JsName> flexibleParameters = new ArrayList<JsName>();
-    for (int i = 0; i < invocation.getArguments().size(); i++) {
-      JsExpression e = invocation.getArguments().get(i);
-      if (requiresSingleEvaluation(e)) {
-        strictParameters.add(enclosing.getParameters().get(i).getName());
-      } else {
-        flexibleParameters.add(enclosing.getParameters().get(i).getName());
-      }
-    }
-
-    IsDelegatingVisitor v =
-        new IsDelegatingVisitor(parameterNames, strictParameters,
-            flexibleParameters);
-    v.accept(statement);
-    return v.isDelegating();
+  private JsDelegationRemover() {
   }
 }