Make call sites and method agree in number of parameters after pruning.

When pruning the last few parameters of a method, the pruner would
allow for extra arguments in calls, relying on JS semantics for
calls in which calls might pass more parameters than are declared in
the method declaration (which will on most cases be ignored).

Having a mismatch in the number of arguments implies those calls won't
be inlined (by either fo the inliners).

Change-Id: Ie302158e35ebef3ded0f037eccd1c3c4f6f63ee4
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
index 0512029..fa11ba6 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
@@ -18,6 +18,7 @@
 import com.google.gwt.dev.jjs.InternalCompilerException;
 import com.google.gwt.dev.jjs.SourceInfo;
 import com.google.gwt.dev.jjs.SourceOrigin;
+import com.google.gwt.dev.jjs.ast.js.JsniMethodBody;
 import com.google.gwt.dev.util.StringInterner;
 import com.google.gwt.dev.util.collect.Lists;
 
@@ -499,6 +500,9 @@
    */
   public void removeParam(int index) {
     params = Lists.remove(params, index);
+    if (isNative()) {
+      ((JsniMethodBody) getBody()).getFunc().getParameters().remove(index);
+    }
   }
 
   /**
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethodCall.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethodCall.java
index 30c5c03..1c44461 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethodCall.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethodCall.java
@@ -145,6 +145,13 @@
   }
 
   /**
+   * Creates a new method call to the same method using the same instance but without parameters.
+   */
+  public JMethodCall cloneWithoutParameters() {
+    return new JMethodCall(this, instance);
+  }
+
+  /**
    * Returns the call arguments.
    */
   public List<JExpression> getArgs() {
@@ -197,13 +204,6 @@
   }
 
   /**
-   * Removes the argument at the specified index.
-   */
-  public void removeArg(int index) {
-    args = Lists.remove(args, index);
-  }
-
-  /**
    * Resolve an external reference during AST stitching.
    */
   public void resolve(JMethod newMethod) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JNewInstance.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JNewInstance.java
index 02caf0d..8288f2e 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JNewInstance.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JNewInstance.java
@@ -36,6 +36,11 @@
     setStaticDispatchOnly();
   }
 
+  @Override
+  public JNewInstance cloneWithoutParameters() {
+    return new JNewInstance(this);
+  }
+
   public JClassType getClassType() {
     return getTarget().getEnclosingType();
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
index 35a20a2..4755cf4 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
@@ -195,9 +195,12 @@
    */
   public static JExpressionStatement createAssignmentStmt(SourceInfo info, JExpression lhs,
       JExpression rhs) {
-    JBinaryOperation assign =
-        new JBinaryOperation(info, lhs.getType(), JBinaryOperator.ASG, lhs, rhs);
-    return assign.makeStatement();
+    return createAssignment(info, lhs, rhs).makeStatement();
+  }
+
+  public static JBinaryOperation createAssignment(SourceInfo info, JExpression lhs,
+      JExpression rhs) {
+    return new JBinaryOperation(info, lhs.getType(), JBinaryOperator.ASG, lhs, rhs);
   }
 
   public static JLocal createLocal(SourceInfo info, String name, JType type, boolean isFinal,
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/js/JMultiExpression.java b/dev/core/src/com/google/gwt/dev/jjs/ast/js/JMultiExpression.java
index 4a8d6a5..1509c47 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/js/JMultiExpression.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/js/JMultiExpression.java
@@ -66,6 +66,13 @@
   }
 
   /**
+   * Adds {@code expressions} to the multi expression at position {@code index}.
+   */
+  public void addExpressions(int index, JExpression...expressions) {
+    this.expressions.addAll(index, Arrays.asList(expressions));
+  }
+
+  /**
    * Adds a list of expressions to the multi expression at position {@code index}.
    */
   public void addExpressions(int index, List<JExpression> expressions) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java
index 947c914..e0dd5bc 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java
@@ -61,6 +61,10 @@
    */
   private class BreakupAssignOpsVisitor extends JModVisitorWithTemporaryVariableCreation {
 
+    public BreakupAssignOpsVisitor(OptimizerContext optimizerCtx) {
+      super(optimizerCtx);
+    }
+
     /**
      * Replaces side effects in lvalue.
      */
@@ -270,11 +274,11 @@
   private final CloneExpressionVisitor cloner = new CloneExpressionVisitor();
 
   public void accept(JNode node) {
-    BreakupAssignOpsVisitor breaker = new BreakupAssignOpsVisitor();
+    BreakupAssignOpsVisitor breaker =
+        new BreakupAssignOpsVisitor(OptimizerContext.NULL_OPTIMIZATION_CONTEXT);
     breaker.accept(node);
   }
 
-
   // Name to assign to temporaries. All temporaries are created with the same name, which is
   // not a problem as they are referred to by reference.
   // {@link GenerateJavaScriptAst.FixNameClashesVisitor} will resolve into unique names when
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
index 078f750..25aff76 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
@@ -71,6 +71,7 @@
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
 import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
 import com.google.gwt.thirdparty.guava.common.collect.Lists;
 import com.google.gwt.thirdparty.guava.common.collect.Sets;
 
@@ -79,7 +80,6 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
-import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -460,16 +460,6 @@
         ignoringExpressionOutput.remove(instance);
       }
 
-      int paramCount = target.getParams().size();
-      for (int i = paramCount; i < x.getArgs().size(); ++i) {
-        JExpression arg = x.getArgs().get(i);
-        ignoringExpressionOutput.remove(arg);
-        if (!arg.hasSideEffects()) {
-          x.removeArg(i--);
-          madeChanges();
-        }
-      }
-
       // Normal optimizations.
       JDeclaredType targetType = target.getEnclosingType();
       if (targetType == program.getTypeJavaLangString() ||
@@ -761,9 +751,6 @@
       if (target.isStatic() && x.getInstance() != null) {
         ignoringExpressionOutput.add(x.getInstance());
       }
-      List<JExpression> args = x.getArgs();
-      List<JExpression> ignoredArgs = args.subList(target.getParams().size(), args.size());
-      ignoringExpressionOutput.addAll(ignoredArgs);
       return true;
     }
 
@@ -2003,20 +1990,22 @@
 
   private final JProgram program;
 
-  private final Map<JType, Class<?>> typeClassMap = new IdentityHashMap<JType, Class<?>>();
+  private final Map<JType, Class<?>> typeClassMap;
 
   public DeadCodeElimination(JProgram program) {
     this.program = program;
-    typeClassMap.put(program.getTypeJavaLangObject(), Object.class);
-    typeClassMap.put(program.getTypeJavaLangString(), String.class);
-    typeClassMap.put(program.getTypePrimitiveBoolean(), boolean.class);
-    typeClassMap.put(program.getTypePrimitiveByte(), byte.class);
-    typeClassMap.put(program.getTypePrimitiveChar(), char.class);
-    typeClassMap.put(program.getTypePrimitiveDouble(), double.class);
-    typeClassMap.put(program.getTypePrimitiveFloat(), float.class);
-    typeClassMap.put(program.getTypePrimitiveInt(), int.class);
-    typeClassMap.put(program.getTypePrimitiveLong(), long.class);
-    typeClassMap.put(program.getTypePrimitiveShort(), short.class);
+    typeClassMap = new ImmutableMap.Builder()
+        .put(program.getTypeJavaLangObject(), Object.class)
+        .put(program.getTypeJavaLangString(), String.class)
+        .put(program.getTypePrimitiveBoolean(), boolean.class)
+        .put(program.getTypePrimitiveByte(), byte.class)
+        .put(program.getTypePrimitiveChar(), char.class)
+        .put(program.getTypePrimitiveDouble(), double.class)
+        .put(program.getTypePrimitiveFloat(), float.class)
+        .put(program.getTypePrimitiveInt(), int.class)
+        .put(program.getTypePrimitiveLong(), long.class)
+        .put(program.getTypePrimitiveShort(), short.class)
+        .build();
   }
 
   private OptimizerStats execImpl(Iterable<? extends JNode> nodes, OptimizerContext optimizerCtx) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java
index 8e72803..d622401 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java
@@ -166,13 +166,8 @@
       JMethodCall createCall(SourceInfo info, JProgram program, JType type,
           JLiteral superclassLiteral) {
 
-        // Class.createForClass(packageName, typeName, runtimeTypeReference, superclassliteral)
-        JMethodCall call = createBaseCall(info, program, type, "Class.createForInterface");
-
-        call.addArg(new JRuntimeTypeReference(info, program.getTypeJavaLangObject(),
-            (JReferenceType) type));
-        call.addArg(superclassLiteral);
-        return call;
+        // Class.createForInterface(packageName, typeName)
+        return createBaseCall(info, program, type, "Class.createForInterface");
       }
     };
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JModVisitorWithTemporaryVariableCreation.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JModVisitorWithTemporaryVariableCreation.java
index e4ea7f4..c9487aa 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JModVisitorWithTemporaryVariableCreation.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JModVisitorWithTemporaryVariableCreation.java
@@ -21,7 +21,6 @@
 import com.google.gwt.dev.jjs.ast.JLocal;
 import com.google.gwt.dev.jjs.ast.JLocalRef;
 import com.google.gwt.dev.jjs.ast.JMethodBody;
-import com.google.gwt.dev.jjs.ast.JModVisitor;
 import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.jjs.ast.JStatement;
 import com.google.gwt.dev.jjs.ast.JType;
@@ -33,19 +32,16 @@
  * A JModVisitor capable of creating temporary local variables and placing their declarations in an
  * appropriate preceding place.
  */
-public abstract class JModVisitorWithTemporaryVariableCreation extends JModVisitor {
+public abstract class JModVisitorWithTemporaryVariableCreation extends JChangeTrackingVisitor {
 
   /**
    * Stack to keep track of where to insert the new variable declaration.
    * The top of the stack is the statement where declarations will be inserted.
    */
   private final Deque<Context> currentDeclarationInsertionPoint = Queues.newArrayDeque();
-  private JMethodBody currentMethodBody = null;
 
-  @Override
-  public void endVisit(JMethodBody body, Context ctx) {
-    assert currentMethodBody == body;
-    currentMethodBody = null;
+  public JModVisitorWithTemporaryVariableCreation(OptimizerContext optimizerCtx) {
+    super(optimizerCtx);
   }
 
   @Override
@@ -58,13 +54,6 @@
   }
 
   @Override
-  public boolean visit(JMethodBody body, Context ctx) {
-    assert currentMethodBody == null;
-    currentMethodBody = body;
-    return true;
-  }
-
-  @Override
   public final boolean visit(JStatement x, Context ctx) {
     if (ctx.canInsert()) {
       currentDeclarationInsertionPoint.push(ctx);
@@ -73,7 +62,8 @@
   }
 
   protected JLocal createTempLocal(SourceInfo info, JType type) {
-    assert currentMethodBody != null;
+    assert !getCurrentMethod().isNative();
+    JMethodBody currentMethodBody = (JMethodBody) getCurrentMethod().getBody();
     String temporaryLocalName = newTemporaryLocalName(info, type, currentMethodBody);
     JLocal local = JProgram.createLocal(info, temporaryLocalName, type, false, currentMethodBody);
     JDeclarationStatement declarationStatement =
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java b/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java
index 20645e1..1031899 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java
@@ -32,6 +32,8 @@
 import com.google.gwt.dev.jjs.ast.JField;
 import com.google.gwt.dev.jjs.ast.JFieldRef;
 import com.google.gwt.dev.jjs.ast.JInterfaceType;
+import com.google.gwt.dev.jjs.ast.JLocal;
+import com.google.gwt.dev.jjs.ast.JLocalRef;
 import com.google.gwt.dev.jjs.ast.JMethod;
 import com.google.gwt.dev.jjs.ast.JMethodBody;
 import com.google.gwt.dev.jjs.ast.JMethodCall;
@@ -48,21 +50,19 @@
 import com.google.gwt.dev.jjs.ast.JVariableRef;
 import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
 import com.google.gwt.dev.jjs.ast.js.JsniFieldRef;
-import com.google.gwt.dev.jjs.ast.js.JsniMethodBody;
 import com.google.gwt.dev.jjs.ast.js.JsniMethodRef;
-import com.google.gwt.dev.js.ast.JsFunction;
 import com.google.gwt.dev.util.collect.Stack;
 import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
 import com.google.gwt.thirdparty.guava.common.base.Predicate;
 import com.google.gwt.thirdparty.guava.common.base.Predicates;
+import com.google.gwt.thirdparty.guava.common.collect.ArrayListMultimap;
+import com.google.gwt.thirdparty.guava.common.collect.Iterables;
+import com.google.gwt.thirdparty.guava.common.collect.ListMultimap;
 
-import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 /**
@@ -90,9 +90,9 @@
    * references to pruned variables and methods by references to the null field
    * and null method, and drop assignments to pruned variables.
    */
-  private class CleanupRefsVisitor extends JChangeTrackingVisitor {
+  private class CleanupRefsVisitor extends JModVisitorWithTemporaryVariableCreation {
     private final Stack<JExpression> lValues = new Stack<JExpression>();
-    private final Map<JMethod, ArrayList<JParameter>> methodToOriginalParamsMap;
+    private final ListMultimap<JMethod, JParameter> priorParametersByMethod;
     private final Set<? extends JNode> referencedNonTypes;
     {
       // Initialize a sentinel value to avoid having to check for empty stack.
@@ -100,10 +100,11 @@
     }
 
     public CleanupRefsVisitor(Set<? extends JNode> referencedNodes,
-        Map<JMethod, ArrayList<JParameter>> methodToOriginalParamsMap, OptimizerContext optimizerCtx) {
+        ListMultimap<JMethod, JParameter> priorParametersByMethod,
+        OptimizerContext optimizerCtx) {
       super(optimizerCtx);
       this.referencedNonTypes = referencedNodes;
-      this.methodToOriginalParamsMap = methodToOriginalParamsMap;
+      this.priorParametersByMethod = priorParametersByMethod;
     }
 
     @Override
@@ -129,6 +130,7 @@
 
     @Override
     public void endVisit(JDeclarationStatement x, Context ctx) {
+      super.endVisit(x, ctx);
       lValues.pop();
       // The variable may have been pruned.
       if (isVariablePruned(x.getVariableRef().getTarget())) {
@@ -175,11 +177,7 @@
         return;
       }
 
-      // Did we prune the parameters of the method we're calling?
-      if (methodToOriginalParamsMap.containsKey(method)) {
-        JMethodCall newCall = new JMethodCall(x, x.getInstance());
-        replaceForPrunedParameters(x, newCall, ctx);
-      }
+      maybeReplaceForPrunedParameters(x, ctx);
     }
 
     @Override
@@ -203,11 +201,7 @@
 
     @Override
     public void endVisit(JNewInstance x, Context ctx) {
-      // Did we prune the parameters of the method we're calling?
-      if (methodToOriginalParamsMap.containsKey(x.getTarget())) {
-        JMethodCall newCall = new JNewInstance(x);
-        replaceForPrunedParameters(x, newCall, ctx);
-      }
+      maybeReplaceForPrunedParameters(x, ctx);
     }
 
     @Override
@@ -251,6 +245,7 @@
 
     @Override
     public boolean visit(JDeclarationStatement x, Context ctx) {
+      super.visit(x, ctx);
       lValues.push(x.getVariableRef());
       return true;
     }
@@ -296,41 +291,77 @@
       }
     }
 
-    private void replaceForPrunedParameters(JMethodCall x, JMethodCall newCall, Context ctx) {
+    // Arguments for pruned parameters will be pushed right into a multiexpression that will be
+    // evaluated with the next arg, e.g. m(arg1, (prunnedArg2, prunnedArg3, arg4)).
+    private void maybeReplaceForPrunedParameters(JMethodCall x, Context ctx) {
+      if (!priorParametersByMethod.containsKey(x.getTarget())) {
+        // No parameter was pruned.
+        return;
+      }
+
+      JMethodCall replacementCall = x.cloneWithoutParameters();
+
       assert !x.getTarget().canBePolymorphic();
-      List<JParameter> originalParams = methodToOriginalParamsMap.get(x.getTarget());
-      JMultiExpression currentMulti = null;
-      for (int i = 0, c = x.getArgs().size(); i < c; ++i) {
-        JExpression arg = x.getArgs().get(i);
-        JParameter param = null;
-        if (i < originalParams.size()) {
-          param = originalParams.get(i);
-        }
+      List<JParameter> originalParams = priorParametersByMethod.get(x.getTarget());
 
-        if (param != null && referencedNonTypes.contains(param)) {
-          // If there is an existing multi, terminate it.
-          if (currentMulti != null) {
-            currentMulti.addExpressions(arg);
-            newCall.addArg(currentMulti);
-            currentMulti = null;
-          } else {
-            newCall.addArg(arg);
-          }
+      // The method and the call agree in the number of parameters.
+      assert originalParams.size() == x.getArgs().size();
+
+      // Traverse the call arguments left to right.
+      SourceInfo sourceInfo = x.getSourceInfo();
+      JMultiExpression unevaluatedArgumentsForPrunedParameters =
+          new JMultiExpression(sourceInfo);
+      List<JExpression> args = x.getArgs();
+      for (int currentArgumentIndex = 0; currentArgumentIndex < args.size();
+          ++currentArgumentIndex) {
+        JExpression arg = args.get(currentArgumentIndex);
+
+        // If the parameter was not pruned .
+        if (referencedNonTypes.contains(originalParams.get(currentArgumentIndex))) {
+          // Add the current argument to the list of unevaluated arguments and pass the multi
+          // expression to the call.
+          unevaluatedArgumentsForPrunedParameters.addExpressions(arg);
+          replacementCall.addArg(unevaluatedArgumentsForPrunedParameters);
+          // Reset the accumulating multi expression.
+          unevaluatedArgumentsForPrunedParameters =  new JMultiExpression(sourceInfo);
         } else if (arg.hasSideEffects()) {
-          // The argument is only needed for side effects, add it to a multi.
-          if (currentMulti == null) {
-            currentMulti = new JMultiExpression(x.getSourceInfo());
-          }
-          currentMulti.addExpressions(arg);
+          // If the argument was pruned and has sideffects accumulate it; otherwise discard.
+          unevaluatedArgumentsForPrunedParameters.addExpressions(arg);
         }
       }
 
-      // Add any orphaned parameters on the end. Extra params are OK.
-      if (currentMulti != null) {
-        newCall.addArg(currentMulti);
+      if (unevaluatedArgumentsForPrunedParameters.isEmpty()) {
+        // We are done, all (side effectful) parameters have been evaluated.
+        ctx.replaceMe(replacementCall);
+        return;
       }
 
-      ctx.replaceMe(newCall);
+      // If the last few parameters where pruned, we need to evaluate the (side effectful) arguments
+      // for those parameters.
+      if (replacementCall.getArgs().isEmpty()) {
+        // All parameters have been pruned, replace by (prunedArg1, ..., prunedArgn, m()).
+        unevaluatedArgumentsForPrunedParameters.addExpressions(replacementCall);
+        ctx.replaceMe(unevaluatedArgumentsForPrunedParameters);
+        return;
+      }
+      // Some parameters have been pruned from the end, replace by
+      // m(arg1,..., (lastArg = lastUnprunedArg, remainingArgs, lastArg))
+      JExpression lastArg = Iterables.getLast(replacementCall.getArgs());
+      JLocal tempVar =
+          createTempLocal(sourceInfo, Iterables.getLast(
+              Iterables.filter(originalParams, Predicates.in(referencedNonTypes))).getType());
+      unevaluatedArgumentsForPrunedParameters.addExpressions(0, JProgram.createAssignment(
+          lastArg.getSourceInfo(), new JLocalRef(sourceInfo, tempVar), lastArg));
+      unevaluatedArgumentsForPrunedParameters.addExpressions(new JLocalRef(sourceInfo, tempVar));
+      replacementCall.setArg(replacementCall.getArgs().size() - 1, unevaluatedArgumentsForPrunedParameters);
+      ctx.replaceMe(replacementCall);
+    }
+
+    @Override
+    protected String newTemporaryLocalName(SourceInfo info, JType type, JMethodBody methodBody) {
+      // The name can be reused a later pass will make sure each instance of JLocal in a method
+      // has a different name.
+      return "lastArg";
     }
   }
 
@@ -339,8 +370,8 @@
    * unreferenced methods and fields from their containing classes.
    */
   private class PruneVisitor extends JChangeTrackingVisitor {
-    private final Map<JMethod, ArrayList<JParameter>> methodToOriginalParamsMap =
-        new HashMap<JMethod, ArrayList<JParameter>>();
+    private final ListMultimap<JMethod, JParameter> priorParametersByMethod =
+        ArrayListMultimap.create();
     private final Set<? extends JNode> referencedNonTypes;
     private final Set<? extends JReferenceType> referencedTypes;
 
@@ -351,8 +382,8 @@
       this.referencedNonTypes = referencedNodes;
     }
 
-    public Map<JMethod, ArrayList<JParameter>> getMethodToOriginalParamsMap() {
-      return methodToOriginalParamsMap;
+    public ListMultimap<JMethod, JParameter> getPriorParametersByMethod() {
+      return priorParametersByMethod;
     }
 
     @Override
@@ -418,21 +449,14 @@
           return true;
         }
 
-        JsFunction func = x.isNative() ? ((JsniMethodBody) x.getBody()).getFunc() : null;
-
-        ArrayList<JParameter> originalParams = new ArrayList<JParameter>(x.getParams());
+        priorParametersByMethod.putAll(x, x.getParams());
 
         for (int i = 0; i < x.getParams().size(); ++i) {
           JParameter param = x.getParams().get(i);
           if (!referencedNonTypes.contains(param)) {
             x.removeParam(i);
             madeChanges();
-            // Remove the associated JSNI parameter
-            if (func != null) {
-              func.getParameters().remove(i);
-            }
             --i;
-            methodToOriginalParamsMap.put(x, originalParams);
           }
         }
       }
@@ -659,7 +683,7 @@
     }
     CleanupRefsVisitor cleaner =
         new CleanupRefsVisitor(livenessAnalyzer.getLiveFieldsAndMethods(), pruner
-            .getMethodToOriginalParamsMap(), optimizerCtx);
+            .getPriorParametersByMethod(), optimizerCtx);
     cleaner.accept(program.getDeclaredTypes());
 
     JavaAstVerifier.assertProgramIsConsistent(program);
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/JModVisitorWithTemporaryVariableCreationTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/JModVisitorWithTemporaryVariableCreationTest.java
index 15cc407..1dbf024 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/JModVisitorWithTemporaryVariableCreationTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/JModVisitorWithTemporaryVariableCreationTest.java
@@ -40,6 +40,10 @@
      */
     private JExpression dontBother;
 
+    public AlwaysReplacer() {
+      super(OptimizerContext.NULL_OPTIMIZATION_CONTEXT);
+    }
+
     @Override
     public boolean visit(JExpressionStatement x, Context ctx) {
       dontBother = x.getExpr();
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/PrunerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/PrunerTest.java
index 4d1916e..6e71b96 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/PrunerTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/PrunerTest.java
@@ -53,6 +53,9 @@
         "  public void method2() { field2 = usedConstant; }",
         "  UsedClass(UninstantiatedClass c) { }",
         "  UsedClass(UninstantiatedClass c1, UninstantiatedClass c2) { }",
+        "  UsedClass(UninstantiatedClass c1, int i, UninstantiatedClass c2) { field2 = i; }",
+        "  UsedClass(UninstantiatedClass c1, int i, UninstantiatedClass c2, int j) " +
+            "{ field2 = i + j; }",
         "}");
     addSnippetClassDecl(
         "static native void usedNativeMethod(UninstantiatedClass c, UsedClass c2)",
@@ -107,6 +110,8 @@
         "methodWithUninstantiatedParam(null);",
         "new UsedClass(null);",
         "new UsedClass(returnUninstantiatedClass(), returnUninstantiatedClass());",
+        "new UsedClass(returnUninstantiatedClass(), 3, returnUninstantiatedClass());",
+        "new UsedClass(returnUninstantiatedClass(), 3, returnUninstantiatedClass(), 4);",
         "UninstantiatedClass localUninstantiated = null;",
         "JsProtoImpl jsp = new JsProtoImpl();"
         )).intoString(
@@ -118,7 +123,12 @@
             "null.nullMethod();",
             "EntryPoint.methodWithUninstantiatedParam();",
             "new EntryPoint$UsedClass();",
-            "new EntryPoint$UsedClass((EntryPoint.returnUninstantiatedClass(), EntryPoint.returnUninstantiatedClass()));",
+            "EntryPoint.returnUninstantiatedClass();",
+            "EntryPoint.returnUninstantiatedClass();",
+            "new EntryPoint$UsedClass();",
+            "int lastArg;",
+            "new EntryPoint$UsedClass((lastArg = (EntryPoint.returnUninstantiatedClass(), 3), EntryPoint.returnUninstantiatedClass(), lastArg));",
+            "new EntryPoint$UsedClass((EntryPoint.returnUninstantiatedClass(), 3), (EntryPoint.returnUninstantiatedClass(), 4));",
             "new EntryPoint$JsProtoImpl();"
             );