Compiler now prunes unused parameters!

Patch by: mmastrac
Review by: me


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1494 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java b/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
index 24f3df6..30f90c9 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
@@ -31,9 +31,12 @@
 import com.google.gwt.dev.jjs.ast.js.JsniMethodBody;
 import com.google.gwt.dev.js.JsParser;
 import com.google.gwt.dev.js.JsParserException;
+import com.google.gwt.dev.js.JsAbstractSymbolResolver;
 import com.google.gwt.dev.js.JsParserException.SourceDetail;
 import com.google.gwt.dev.js.ast.JsExprStmt;
 import com.google.gwt.dev.js.ast.JsFunction;
+import com.google.gwt.dev.js.ast.JsName;
+import com.google.gwt.dev.js.ast.JsNameRef;
 import com.google.gwt.dev.js.ast.JsProgram;
 import com.google.gwt.dev.js.ast.JsStatement;
 
@@ -582,6 +585,11 @@
         JsFunction jsFunction = (JsFunction) jsExprStmt.getExpression();
         jsFunction.setFromJava(true);
         ((JsniMethodBody) newMethod.getBody()).setFunc(jsFunction);
+
+        // Ensure that we've resolved the parameter and local references within
+        // the JSNI method for later pruning.
+        JsParameterResolver localResolver = new JsParameterResolver(jsFunction);
+        localResolver.accept(jsFunction);
       } catch (IOException e) {
         throw new InternalCompilerException(
             "Internal error parsing JSNI in method '" + newMethod
@@ -756,6 +764,31 @@
     }
   }
 
+  /**
+   * Resolves the scope of JS identifiers solely within the scope of a method.
+   */
+  private static class JsParameterResolver extends JsAbstractSymbolResolver {
+    private final JsFunction jsFunction;
+
+    public JsParameterResolver(JsFunction jsFunction) {
+      this.jsFunction = jsFunction;
+    }
+
+    @Override
+    public void resolve(JsNameRef x) {
+      // Only resolve unqualified names
+      if (x.getQualifier() == null) {
+        JsName name = getScope().findExistingName(x.getIdent());
+
+        // Ensure that we're resolving a name from the function's parameters
+        if (name != null
+            && jsFunction.getParameters().contains(name.getStaticRef())) {
+          x.resolve(name);
+        }
+      }
+    }
+  }
+
   public static TypeDeclaration[] exec(TypeMap typeMap,
       CompilationUnitDeclaration[] unitDecls, JsProgram jsProgram) {
     createPeersForTypes(unitDecls, typeMap);
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 5832632..26c71f6 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
@@ -46,23 +46,33 @@
 import com.google.gwt.dev.jjs.ast.JVariable;
 import com.google.gwt.dev.jjs.ast.JVariableRef;
 import com.google.gwt.dev.jjs.ast.JVisitor;
+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.JsContext;
+import com.google.gwt.dev.js.ast.JsExpression;
+import com.google.gwt.dev.js.ast.JsFunction;
+import com.google.gwt.dev.js.ast.JsName;
+import com.google.gwt.dev.js.ast.JsNameRef;
+import com.google.gwt.dev.js.ast.JsVisitor;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
- * Remove globally unreferenced classes, interfaces, methods, and fields from
- * the AST. This algorithm is based on having known "entry points" into the
- * application which serve as the root(s) from which reachability is determined
- * and everything else is rescued. Pruner determines reachability at a global
- * level based on method calls and new operations; it does not perform any local
- * code flow analysis. But, a local code flow optimization pass that can
- * eliminate method calls would allow Pruner to prune additional nodes.
+ * Remove globally unreferenced classes, interfaces, methods, parameters, and
+ * fields from the AST. This algorithm is based on having known "entry points"
+ * into the application which serve as the root(s) from which reachability is
+ * determined and everything else is rescued. Pruner determines reachability at
+ * a global level based on method calls and new operations; it does not perform
+ * any local code flow analysis. But, a local code flow optimization pass that
+ * can eliminate method calls would allow Pruner to prune additional nodes.
  * 
  * Note: references to pruned types may still exist in the tree after this pass
  * runs, however, it should only be in contexts that do not rely on any code
@@ -70,13 +80,13 @@
  * a pruned type, or to try to cast to a pruned type. These will cause natural
  * failures at run time; or later optimizations might be able to hard-code
  * failures at compile time.
+ * 
+ * Note: this class is limited to pruning parameters of static methods only.
  */
 public class Pruner {
 
   /**
-   * Remove assignments to pruned fields and locals.
-   * 
-   * TODO(later): prune params too
+   * Remove assignments to pruned fields, locals and params.
    */
   private class CleanupRefsVisitor extends JModVisitor {
 
@@ -88,7 +98,8 @@
         if (lhs.hasSideEffects()) {
           return;
         }
-        if (lhs instanceof JFieldRef || lhs instanceof JLocalRef) {
+        if (lhs instanceof JFieldRef || lhs instanceof JLocalRef
+            || lhs instanceof JParameterRef) {
           JVariable var = ((JVariableRef) lhs).getTarget();
           if (!referencedNonTypes.contains(var)) {
             // Just replace with my RHS
@@ -97,6 +108,58 @@
         }
       }
     }
+
+    @Override
+    public void endVisit(JMethodCall x, Context ctx) {
+      JMethod method = x.getTarget();
+
+      // Did we prune the parameters of the method we're calling?
+      if (removedMethodParamsMap.containsKey(method)) {
+        // This must be a static method
+        assert method.isStatic();
+        assert x.getInstance() == null;
+
+        JMethodCall newCall = new JMethodCall(program, x.getSourceInfo(), null,
+            method);
+
+        JMultiExpression currentMulti = null;
+
+        ArrayList<JParameter> removedParams = removedMethodParamsMap.get(method);
+
+        for (int i = 0; i < x.getArgs().size(); i++) {
+          JParameter param = removedParams.get(i);
+          JExpression arg = x.getArgs().get(i);
+
+          if (referencedNonTypes.contains(param)) {
+            // We rescued this parameter
+
+            // Do we need to add the multi we've been building?
+            if (currentMulti == null) {
+              newCall.getArgs().add(arg);
+            } else {
+              currentMulti.exprs.add(arg);
+              newCall.getArgs().add(currentMulti);
+              currentMulti = null;
+            }
+          } else if (arg.hasSideEffects()) {
+            // We didn't rescue this parameter and it has side-effects. Add it
+            // to a new multi
+            if (currentMulti == null) {
+              currentMulti = new JMultiExpression(program, x.getSourceInfo());
+            }
+            currentMulti.exprs.add(arg);
+          }
+        }
+
+        // We have orphaned parameters - add them on the end, wrapped in the
+        // multi (JS ignores extra parameters)
+        if (currentMulti != null) {
+          newCall.getArgs().add(currentMulti);
+        }
+
+        ctx.replaceMe(newCall);
+      }
+    }
   }
 
   /**
@@ -173,6 +236,51 @@
     }
 
     @Override
+    public boolean visit(JMethod x, Context ctx) {
+      if (x.isStatic()) {
+        /*
+         * Don't prune parameters on unreferenced methods. The methods might not
+         * be reachable through the current method traversal routines, but might
+         * be used or checked elsewhere.
+         * 
+         * Basically, if we never actually checked if the method parameters were
+         * used or not, don't prune them. Doing so would leave a number of
+         * dangling JParameterRefs that blow up in later optimizations.
+         */
+        if (!referencedNonTypes.contains(x)) {
+          return true;
+        }
+
+        JsFunction func = x.isNative()
+            ? ((JsniMethodBody) x.getBody()).getFunc() : null;
+
+        ArrayList<JParameter> removedParams = new ArrayList<JParameter>(
+            x.params);
+
+        for (int i = 0; i < x.params.size(); i++) {
+          JParameter param = x.params.get(i);
+
+          if (!referencedNonTypes.contains(param)) {
+            x.params.remove(i);
+
+            didChange = true;
+
+            removedMethodParamsMap.put(x, removedParams);
+
+            // Remove the associated JSNI parameter
+            if (func != null) {
+              func.getParameters().remove(i);
+            }
+
+            i--;
+          }
+        }
+      }
+
+      return true;
+    }
+
+    @Override
     public boolean visit(JMethodBody x, Context ctx) {
       for (Iterator<JLocal> it = x.locals.iterator(); it.hasNext();) {
         JLocal local = it.next();
@@ -287,6 +395,9 @@
         } else if (lhs instanceof JLocalRef) {
           // locals are ok to skip
           doSkip = true;
+        } else if (lhs instanceof JParameterRef) {
+          // parameters are ok to skip
+          doSkip = true;
         } else if (lhs instanceof JFieldRef) {
           JFieldRef fieldRef = (JFieldRef) lhs;
           /*
@@ -303,13 +414,6 @@
           } else if (instance instanceof JThisRef) {
             // a this ref cannot be null
             doSkip = true;
-          } else if (instance instanceof JParameterRef) {
-            JParameter param = ((JParameterRef) instance).getParameter();
-            JMethod enclosingMethod = param.getEnclosingMethod();
-            if (program.isStaticImpl(enclosingMethod)
-                && enclosingMethod.params.get(0) == param) {
-              doSkip = true;
-            }
           }
         }
 
@@ -404,6 +508,33 @@
     }
 
     @Override
+    public boolean visit(final JMethod x, Context ctx) {
+      if (x.isNative()) {
+        // Manually rescue native parameter references
+        final JsniMethodBody body = (JsniMethodBody) x.getBody();
+        final JsFunction func = body.getFunc();
+
+        new JsVisitor() {
+          @Override
+          public void endVisit(JsNameRef nameRef, JsContext<JsExpression> ctx) {
+            JsName ident = nameRef.getName();
+
+            if (ident != null) {
+              // If we're referencing a parameter, rescue the associated
+              // JParameter
+              int index = func.getParameters().indexOf(ident.getStaticRef());
+              if (index != -1) {
+                rescue(x.params.get(index));
+              }
+            }
+          }
+        }.accept(func);
+      }
+
+      return true;
+    }
+
+    @Override
     public boolean visit(JMethodCall call, Context ctx) {
       JMethod target = call.getTarget();
       // JLS 12.4.1: references to static methods rescue the enclosing class
@@ -444,6 +575,13 @@
     }
 
     @Override
+    public boolean visit(JParameterRef x, Context ctx) {
+      // rescue the parameter for future pruning purposes
+      rescue(x.getParameter());
+      return true;
+    }
+
+    @Override
     public boolean visit(JsniFieldRef x, Context ctx) {
       /*
        * SPECIAL: this could be an assignment that passes a value from
@@ -467,6 +605,18 @@
       for (int i = 0, c = params.size(); i < c; ++i) {
         JParameter param = params.get(i);
         maybeRescueJavaScriptObjectPassingIntoJava(param.getType());
+
+        /*
+         * Because we're not currently tracking methods through JSNI, we need to
+         * assume that it's not safe to prune parameters of a method referenced
+         * as such.
+         * 
+         * A better solution would be to perform basic escape analysis to ensure
+         * that the function reference never escapes, or at minimum, ensure that
+         * the method is immediately called after retrieving the method
+         * reference.
+         */
+        rescue(param);
       }
       // JsniMethodRef rescues as JMethodCall
       return visit((JMethodCall) x, ctx);
@@ -640,10 +790,11 @@
     return new Pruner(program, noSpecialTypes).execImpl();
   }
 
-  private final boolean saveCodeGenTypes;
   private final JProgram program;
   private final Set<JNode> referencedNonTypes = new HashSet<JNode>();
   private final Set<JReferenceType> referencedTypes = new HashSet<JReferenceType>();
+  private final Map<JMethod, ArrayList<JParameter>> removedMethodParamsMap = new HashMap<JMethod, ArrayList<JParameter>>();
+  private final boolean saveCodeGenTypes;
   private JMethod stringValueOfChar = null;
 
   private Pruner(JProgram program, boolean saveCodeGenTypes) {
@@ -681,6 +832,7 @@
 
       referencedTypes.clear();
       referencedNonTypes.clear();
+      removedMethodParamsMap.clear();
       madeChanges = true;
     }
     return madeChanges;
diff --git a/dev/core/src/com/google/gwt/dev/js/JsAbstractSymbolResolver.java b/dev/core/src/com/google/gwt/dev/js/JsAbstractSymbolResolver.java
new file mode 100644
index 0000000..b28cfcc
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/js/JsAbstractSymbolResolver.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2007 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.js;
+
+import com.google.gwt.dev.js.ast.JsCatch;
+import com.google.gwt.dev.js.ast.JsContext;
+import com.google.gwt.dev.js.ast.JsExpression;
+import com.google.gwt.dev.js.ast.JsFunction;
+import com.google.gwt.dev.js.ast.JsNameRef;
+import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.js.ast.JsScope;
+import com.google.gwt.dev.js.ast.JsVisitor;
+
+import java.util.Stack;
+
+/**
+ * Base class for any recursive resolver classes.
+ */
+public abstract class JsAbstractSymbolResolver extends JsVisitor {
+
+  private final Stack<JsScope> scopeStack = new Stack<JsScope>();
+
+  @Override
+  public void endVisit(JsCatch x, JsContext<JsCatch> ctx) {
+    popScope();
+  }
+
+  @Override
+  public void endVisit(JsFunction x, JsContext<JsExpression> ctx) {
+    popScope();
+  }
+
+  @Override
+  public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
+    if (x.isResolved()) {
+      return;
+    }
+
+    resolve(x);
+  }
+
+  @Override
+  public void endVisit(JsProgram x, JsContext<JsProgram> ctx) {
+    popScope();
+  }
+
+  @Override
+  public boolean visit(JsCatch x, JsContext<JsCatch> ctx) {
+    pushScope(x.getScope());
+    return true;
+  }
+
+  @Override
+  public boolean visit(JsFunction x, JsContext<JsExpression> ctx) {
+    pushScope(x.getScope());
+    return true;
+  }
+
+  @Override
+  public boolean visit(JsProgram x, JsContext<JsProgram> ctx) {
+    pushScope(x.getScope());
+    return true;
+  }
+
+  protected JsScope getScope() {
+    return scopeStack.peek();
+  }
+
+  protected abstract void resolve(JsNameRef x);
+  
+  private void popScope() {
+    scopeStack.pop();
+  }
+
+  private void pushScope(JsScope scope) {
+    scopeStack.push(scope);
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsSymbolResolver.java b/dev/core/src/com/google/gwt/dev/js/JsSymbolResolver.java
index aad946f..1f0c050 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsSymbolResolver.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsSymbolResolver.java
@@ -15,17 +15,9 @@
  */
 package com.google.gwt.dev.js;
 
-import com.google.gwt.dev.js.ast.JsCatch;
-import com.google.gwt.dev.js.ast.JsContext;
-import com.google.gwt.dev.js.ast.JsExpression;
-import com.google.gwt.dev.js.ast.JsFunction;
 import com.google.gwt.dev.js.ast.JsName;
 import com.google.gwt.dev.js.ast.JsNameRef;
 import com.google.gwt.dev.js.ast.JsProgram;
-import com.google.gwt.dev.js.ast.JsScope;
-import com.google.gwt.dev.js.ast.JsVisitor;
-
-import java.util.Stack;
 
 /**
  * Resolves any unresolved JsNameRefs.
@@ -35,26 +27,10 @@
   /**
    * Resolves any unresolved JsNameRefs.
    */
-  private class JsResolveSymbolsVisitor extends JsVisitor {
-
-    private final Stack<JsScope> scopeStack = new Stack<JsScope>();
+  private class JsResolveSymbolsVisitor extends JsAbstractSymbolResolver {
 
     @Override
-    public void endVisit(JsCatch x, JsContext<JsCatch> ctx) {
-      popScope();
-    }
-
-    @Override
-    public void endVisit(JsFunction x, JsContext<JsExpression> ctx) {
-      popScope();
-    }
-
-    @Override
-    public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
-      if (x.isResolved()) {
-        return;
-      }
-
+    protected void resolve(JsNameRef x) {
       JsName name;
       String ident = x.getIdent();
       if (x.getQualifier() == null) {
@@ -74,41 +50,6 @@
       }
       x.resolve(name);
     }
-
-    @Override
-    public void endVisit(JsProgram x, JsContext<JsProgram> ctx) {
-      popScope();
-    }
-
-    @Override
-    public boolean visit(JsCatch x, JsContext<JsCatch> ctx) {
-      pushScope(x.getScope());
-      return true;
-    }
-
-    @Override
-    public boolean visit(JsFunction x, JsContext<JsExpression> ctx) {
-      pushScope(x.getScope());
-      return true;
-    }
-
-    @Override
-    public boolean visit(JsProgram x, JsContext<JsProgram> ctx) {
-      pushScope(x.getScope());
-      return true;
-    }
-
-    private JsScope getScope() {
-      return scopeStack.peek();
-    }
-
-    private void popScope() {
-      scopeStack.pop();
-    }
-
-    private void pushScope(JsScope scope) {
-      scopeStack.push(scope);
-    }
   }
 
   public static void exec(JsProgram program) {