SameParameterValue optimization.

This optimization detects cases when the same value literal is used
as parameter value across the whole program.

Patch by: mike.aizatsky
Review by: spoon



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6681 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index 58ee760..b3a760d 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -85,6 +85,7 @@
 import com.google.gwt.dev.jjs.impl.ReplaceRebinds;
 import com.google.gwt.dev.jjs.impl.ReplaceRunAsyncs;
 import com.google.gwt.dev.jjs.impl.ResolveRebinds;
+import com.google.gwt.dev.jjs.impl.SameParameterValueOptimizer;
 import com.google.gwt.dev.jjs.impl.SourceGenerationVisitor;
 import com.google.gwt.dev.jjs.impl.TypeMap;
 import com.google.gwt.dev.jjs.impl.TypeTightener;
@@ -637,7 +638,11 @@
     if (isAggressivelyOptimize) {
       // inlining
       didChange = MethodInliner.exec(jprogram) || didChange;
+
+      // remove same parameters value
+      didChange = SameParameterValueOptimizer.exec(jprogram) || didChange;
     }
+    
     // prove that any types that have been culled from the main tree are
     // unreferenced due to type tightening?
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/SameParameterValueOptimizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/SameParameterValueOptimizer.java
new file mode 100644
index 0000000..5df9651
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/SameParameterValueOptimizer.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2008 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.jjs.impl;
+
+import com.google.gwt.dev.jjs.ast.Context;
+import com.google.gwt.dev.jjs.ast.JBinaryOperation;
+import com.google.gwt.dev.jjs.ast.JExpression;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JMethodCall;
+import com.google.gwt.dev.jjs.ast.JModVisitor;
+import com.google.gwt.dev.jjs.ast.JNode;
+import com.google.gwt.dev.jjs.ast.JParameter;
+import com.google.gwt.dev.jjs.ast.JParameterRef;
+import com.google.gwt.dev.jjs.ast.JPostfixOperation;
+import com.google.gwt.dev.jjs.ast.JPrefixOperation;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.ast.JValueLiteral;
+import com.google.gwt.dev.jjs.ast.JVisitor;
+
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Detects when same literal is passed as parameter value, and uses literal
+ * instead of parameter. The unused parameter will be removed by other analyses.
+ */
+public class SameParameterValueOptimizer {
+  /**
+   * Fill parameterValues map.
+   */
+  private class AnalysisVisitor extends JVisitor {
+    @Override
+    public void endVisit(JBinaryOperation x, Context ctx) {
+      if (x.isAssignment() && x.getLhs() instanceof JParameterRef) {
+        parameterValues.put(((JParameterRef) x.getLhs()).getParameter(), null);
+      }
+    }
+
+    @Override
+    public void endVisit(JMethodCall x, Context ctx) {
+      JMethod method = x.getTarget();
+      if (x.canBePolymorphic() || method.isNative()) {
+        return;
+      }
+
+      JMethod staticImpl = program.staticImplFor(method);
+      if (staticImpl != null && 
+          staticImpl.getEnclosingType().getMethods().contains(staticImpl)) {
+        // instance method is still alive.
+        return;
+      }
+
+      List<JExpression> args = x.getArgs();
+      List<JParameter> params = method.getParams();
+
+      for (int i = 0; i < args.size() && i < params.size(); i++) {
+        JParameter param = params.get(i);
+        JExpression arg = args.get(i);
+
+        if (!(arg instanceof JValueLiteral)) {
+          parameterValues.put(param, null);
+          continue;
+        }
+
+        if (!parameterValues.containsKey(param)) {
+          parameterValues.put(param, (JValueLiteral) arg);
+          continue;
+        }
+
+        JValueLiteral commonParamValue = parameterValues.get(param);
+        if (commonParamValue == null) {
+          continue;
+        }
+
+        if (!equalLiterals(commonParamValue, (JValueLiteral) arg)) {
+          parameterValues.put(param, null);
+        }
+      }
+    }
+
+    @Override
+    public void endVisit(JPostfixOperation x, Context ctx) {
+      if (x.getArg() instanceof JParameterRef) {
+        parameterValues.put(((JParameterRef) x.getArg()).getParameter(), null);
+      }
+    }
+
+    @Override
+    public void endVisit(JPrefixOperation x, Context ctx) {
+      if (x.getArg() instanceof JParameterRef) {
+        parameterValues.put(((JParameterRef) x.getArg()).getParameter(), null);
+      }
+    }
+    
+    private boolean equalLiterals(JValueLiteral l1, JValueLiteral l2) {
+      Object v1 = l1.getValueObj();
+      Object v2 = l2.getValueObj();
+      
+      if (v1 == v2) {
+        return true;
+      }
+      
+      if (v1 == null || v2 == null) {
+        return false;
+      }
+      
+      return v1.equals(v2);
+    }
+  }
+
+  /**
+   * Substitute all parameter references with expression.
+   */
+  private class SubstituteParameterVisitor extends JModVisitor {
+    private CloneExpressionVisitor cloner;
+    private final JExpression expression;
+    private final JParameter parameter;
+
+    public SubstituteParameterVisitor(JParameter parameter,
+        JExpression expression) {
+      this.parameter = parameter;
+      this.expression = expression;
+      cloner = new CloneExpressionVisitor(program);
+    }
+
+    @Override
+    public void endVisit(JParameterRef x, Context ctx) {
+      if (x.getParameter() == parameter) {
+        ctx.replaceMe(cloner.cloneExpression(expression));
+      }
+    }
+  }
+
+  public static boolean exec(JProgram program) {
+    return new SameParameterValueOptimizer(program).execImpl(program);
+  }
+
+  /**
+   * Parameter values.
+   * 
+   * If doesn't contain a parameter, then its value is unknown.
+   * If contains parameter, and value is null - the parameter's value is not the 
+   * same across all calls.
+   * If value is not null - the parameter's value is the same across all calls.
+   */
+  private Map<JParameter, JValueLiteral> parameterValues = 
+    new IdentityHashMap<JParameter, JValueLiteral>();
+
+  private final JProgram program;
+
+  private SameParameterValueOptimizer(JProgram program) {
+    this.program = program;
+  }
+
+  private boolean execImpl(JNode node) {
+    AnalysisVisitor analysisVisitor = new AnalysisVisitor();
+    analysisVisitor.accept(node);
+
+    boolean madeChanges = false;
+    for (JParameter parameter : parameterValues.keySet()) {
+      JValueLiteral valueLiteral = parameterValues.get(parameter);
+      if (valueLiteral != null) {
+        SubstituteParameterVisitor substituteParameterVisitor = 
+          new SubstituteParameterVisitor(parameter, valueLiteral);
+        substituteParameterVisitor.accept(parameter.getEnclosingMethod());
+        madeChanges |= substituteParameterVisitor.didChange();
+      }
+    }
+
+    return madeChanges;
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/OptimizerTestBase.java b/dev/core/test/com/google/gwt/dev/jjs/impl/OptimizerTestBase.java
index 50cf016..b87d821 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/OptimizerTestBase.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/OptimizerTestBase.java
@@ -64,15 +64,18 @@
   }
 
   public static JMethod findMainMethod(JProgram program) {
+    return findMethod(program, "onModuleLoad");
+  }
+
+  public static JMethod findMethod(JProgram program, String methodName) {
     JDeclaredType mainType = program.getFromTypeMap("test.EntryPoint");
-    JMethod mainMethod = null;
     for (JMethod method : mainType.getMethods()) {
-      if (method.getName().equals("onModuleLoad")) {
-        mainMethod = method;
-        break;
+      if (method.getName().equals(methodName)) {
+        return method;
       }
     }
-    return mainMethod;
+    
+    return null;
   }
 
   /**
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/SameParameterValueOptimizerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/SameParameterValueOptimizerTest.java
new file mode 100644
index 0000000..3e69927
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/SameParameterValueOptimizerTest.java
@@ -0,0 +1,64 @@
+// Copyright 2009 Google Inc. All Rights Reserved.
+
+package com.google.gwt.dev.jjs.impl;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JProgram;
+
+/**
+ * Test for SameParameterValueOptimizer.
+ */
+public class SameParameterValueOptimizerTest extends OptimizerTestBase {
+  public void testSameParameter() throws Exception {
+    assertOptimize("foo", "static void foo(int i) { int j = i; }",
+        "foo(1); foo(1);").into(
+        "public static void foo(int i){",
+        "  int j = 1;",
+        "}");
+  }
+
+  public void testDifferentParameter() throws Exception {
+    assertOptimize("foo", "static void foo(int i) { int j = i; }",
+        "foo(1); foo(2);").into(
+        "public static void foo(int i){",
+        "  int j = i;",
+        "}");
+  }
+
+  public void testNonConstParameter() throws Exception {
+    assertOptimize("foo", "static int foo(int i) { return i; }",
+        "foo(foo(1));").into(
+        "public static int foo(int i){",
+        "  return i;",
+        "}");
+  }
+
+  private OptimizationResult assertOptimize(String methodName,  
+      String methodDecl, String codeSnippet) throws UnableToCompleteException {
+    addSnippetClassDecl(methodDecl);
+    JProgram program = compileSnippet("void", codeSnippet);
+    SameParameterValueOptimizer.exec(program);
+    JMethod method = findMethod(program, methodName);
+    return new OptimizationResult(method);
+  }
+  
+  private static class OptimizationResult {
+    private final JMethod method;
+
+    public OptimizationResult(JMethod method) {
+      this.method = method;
+    }
+
+    public void into(String...expectedStrings) {
+      StringBuffer expected = new StringBuffer();
+      for (String s : expectedStrings) {
+        if (expected.length() != 0) {
+          expected.append("\n");
+        }
+        expected.append(s);
+      }
+      assertEquals(expected.toString(), method.toSource());
+    }
+  }
+}