Add an optimization to the JavaScript AST to remove delegating/trampoline functions via code inlining.
http://groups.google.com/group/Google-Web-Toolkit-Contributors/t/bba1a8b27f98f77d

Patch by: bobv
Reviewed by: scottb



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1481 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 ae8e990..607bae4 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -49,12 +49,14 @@
 import com.google.gwt.dev.jjs.impl.ReplaceRebinds;
 import com.google.gwt.dev.jjs.impl.TypeMap;
 import com.google.gwt.dev.jjs.impl.TypeTightener;
+import com.google.gwt.dev.js.JsDelegationRemover;
 import com.google.gwt.dev.js.JsNormalizer;
 import com.google.gwt.dev.js.JsObfuscateNamer;
 import com.google.gwt.dev.js.JsPrettyNamer;
 import com.google.gwt.dev.js.JsSourceGenerationVisitor;
 import com.google.gwt.dev.js.JsStringInterner;
 import com.google.gwt.dev.js.JsSymbolResolver;
+import com.google.gwt.dev.js.JsUnusedFunctionRemover;
 import com.google.gwt.dev.js.JsVerboseNamer;
 import com.google.gwt.dev.js.ast.JsProgram;
 import com.google.gwt.dev.util.DefaultTextOutput;
@@ -394,7 +396,16 @@
       // (9) Resolve all unresolved JsNameRefs
       JsSymbolResolver.exec(jsProgram);
 
-      // (10) Obfuscate
+      // (10) Apply optimizations to JavaScript AST
+      do {
+        didChange = false;
+        // Remove delegating/trampoline functions
+        didChange = JsDelegationRemover.exec(jsProgram) || didChange;
+        // Remove unused functions, possible
+        didChange = JsUnusedFunctionRemover.exec(jsProgram) || didChange;
+      } while (didChange);
+
+      // (11) Obfuscate
       if (obfuscate) {
         JsStringInterner.exec(jsProgram);
         JsObfuscateNamer.exec(jsProgram);
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 83a5f0d..51258e9 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
@@ -556,6 +556,7 @@
         List<JsStatement> result = jsParser.parse(jsProgram.getScope(), sr, -1);
         JsExprStmt jsExprStmt = (JsExprStmt) result.get(0);
         JsFunction jsFunction = (JsFunction) jsExprStmt.getExpression();
+        jsFunction.setFromJava(true);
         ((JsniMethodBody) newMethod.getBody()).setFunc(jsFunction);
       } catch (IOException e) {
         throw new InternalCompilerException(
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
index c0b82fb..447af14 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -331,7 +331,7 @@
         jsFunction.setName(globalName);
       } else {
         // create a new peer JsFunction
-        jsFunction = new JsFunction(topScope, globalName);
+        jsFunction = new JsFunction(topScope, globalName, true);
         methodBodyMap.put(x.getBody(), jsFunction);
       }
       push(jsFunction.getScope());
diff --git a/dev/core/src/com/google/gwt/dev/js/JsDelegationRemover.java b/dev/core/src/com/google/gwt/dev/js/JsDelegationRemover.java
new file mode 100644
index 0000000..440445d
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/js/JsDelegationRemover.java
@@ -0,0 +1,594 @@
+/*
+ * 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.jjs.InternalCompilerException;
+import com.google.gwt.dev.js.ast.JsBinaryOperation;
+import com.google.gwt.dev.js.ast.JsBinaryOperator;
+import com.google.gwt.dev.js.ast.JsBlock;
+import com.google.gwt.dev.js.ast.JsBooleanLiteral;
+import com.google.gwt.dev.js.ast.JsCase;
+import com.google.gwt.dev.js.ast.JsConditional;
+import com.google.gwt.dev.js.ast.JsContext;
+import com.google.gwt.dev.js.ast.JsDecimalLiteral;
+import com.google.gwt.dev.js.ast.JsDefault;
+import com.google.gwt.dev.js.ast.JsExprStmt;
+import com.google.gwt.dev.js.ast.JsExpression;
+import com.google.gwt.dev.js.ast.JsFor;
+import com.google.gwt.dev.js.ast.JsForIn;
+import com.google.gwt.dev.js.ast.JsFunction;
+import com.google.gwt.dev.js.ast.JsIf;
+import com.google.gwt.dev.js.ast.JsIntegralLiteral;
+import com.google.gwt.dev.js.ast.JsInvocation;
+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.JsNullLiteral;
+import com.google.gwt.dev.js.ast.JsParameter;
+import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.js.ast.JsReturn;
+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.JsVisitor;
+import com.google.gwt.dev.js.ast.JsWhile;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Collapses delegating method calls.
+ */
+public class JsDelegationRemover {
+  /**
+   * This is used to clean up duplication invocations of a clinit. Whenever
+   * there is a possible branch in program flow, the remover will create a new
+   * instance of itself to handle the possible outcomes.
+   * 
+   * We don't look at combining the clinits that are called in all branch
+   * 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 {
+    /*
+     * 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.
+     */
+    private final Set<JsName> called;
+
+    public DuplicateClinitRemover() {
+      called = new HashSet<JsName>();
+    }
+
+    public DuplicateClinitRemover(Set<JsName> alreadyCalled) {
+      called = new HashSet<JsName>(alreadyCalled);
+    }
+
+    @Override
+    public boolean visit(JsBinaryOperation x, JsContext<JsExpression> ctx) {
+      if ((x.getOperator() == JsBinaryOperator.COMMA)
+          && (x.getArg1() instanceof JsInvocation)) {
+
+        JsName clinit = getClinitFromInvocation((JsInvocation) x.getArg1());
+        if ((clinit != null) && called.contains(clinit)) {
+
+          // Replace the binary operation with the RHS
+          ctx.replaceMe(x.getArg2());
+
+          // Manually call accept on the RHS to eliminate nested comma
+          // expressions.
+          accept(x.getArg2());
+
+          // Don't continue traversing the original binary operation
+          return false;
+        }
+      }
+
+      return true;
+    }
+
+    /**
+     * Most of the branching statements (as well as JsFunctions) will visit with
+     * a JsBlock, so we don't need to explicitly enumerate all JsStatement
+     * subtypes.
+     */
+    @Override
+    public boolean visit(JsBlock x, JsContext<JsStatement> ctx) {
+      (new DuplicateClinitRemover(called)).acceptWithInsertRemove(x
+          .getStatements());
+      return false;
+    }
+
+    @Override
+    public boolean visit(JsCase x, JsContext<JsSwitchMember> ctx) {
+      accept(x.getCaseExpr());
+      (new DuplicateClinitRemover(called)).acceptWithInsertRemove(x.getStmts());
+      return false;
+    }
+
+    @Override
+    public boolean visit(JsConditional x, JsContext<JsExpression> ctx) {
+      accept(x.getTestExpression());
+      (new DuplicateClinitRemover(called)).accept(x.getThenExpression());
+      (new DuplicateClinitRemover(called)).accept(x.getElseExpression());
+      return false;
+    }
+
+    @Override
+    public boolean visit(JsDefault x, JsContext<JsSwitchMember> ctx) {
+      (new DuplicateClinitRemover(called)).acceptWithInsertRemove(x.getStmts());
+      return false;
+    }
+
+    @Override
+    public boolean visit(JsFor x, JsContext<JsStatement> ctx) {
+      // The JsFor may have an expression xor a variable declaration.
+      if (x.getInitExpr() != null) {
+        accept(x.getInitExpr());
+      } else if (x.getInitVars() != null) {
+        accept(x.getInitVars());
+      }
+
+      // The condition is optional
+      if (x.getCondition() != null) {
+        accept(x.getCondition());
+      }
+
+      // We don't check the increment expression because even if it exists, it
+      // is not guaranteed to be called at all
+
+      // The body is not guaranteed to be a JsBlock
+      (new DuplicateClinitRemover(called)).accept(x.getBody());
+      return false;
+    }
+
+    @Override
+    public boolean visit(JsForIn x, JsContext<JsStatement> ctx) {
+      if (x.getIterExpr() != null) {
+        accept(x.getIterExpr());
+      }
+
+      accept(x.getObjExpr());
+
+      // The body is not guaranteed to be a JsBlock
+      (new DuplicateClinitRemover(called)).accept(x.getBody());
+      return false;
+    }
+
+    @Override
+    public boolean visit(JsIf x, JsContext<JsStatement> ctx) {
+      accept(x.getIfExpr());
+
+      (new DuplicateClinitRemover(called)).accept(x.getThenStmt());
+      if (x.getElseStmt() != null) {
+        (new DuplicateClinitRemover(called)).accept(x.getElseStmt());
+      }
+
+      return false;
+    }
+
+    /**
+     * Possibly record that we've seen a clinit in the current context.
+     */
+    @Override
+    public boolean visit(JsInvocation x, JsContext<JsExpression> ctx) {
+      JsName name = getClinitFromInvocation(x);
+      if (name != null) {
+        called.add(name);
+      }
+      return true;
+    }
+
+    @Override
+    public boolean visit(JsWhile x, JsContext<JsStatement> ctx) {
+      accept(x.getCondition());
+
+      // The body is not guaranteed to be a JsBlock
+      (new DuplicateClinitRemover(called)).accept(x.getBody());
+      return false;
+    }
+  }
+
+  /**
+   * 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.
+   */
+  private class IsDelegatingVisitor extends JsVisitor {
+    private final Set<JsName> parameterNames;
+    private final List<JsName> strictParameters;
+    private final List<JsName> flexibleParameters;
+    private boolean delegating = true;
+
+    public IsDelegatingVisitor(Set<JsName> parameterNames,
+        List<JsName> strictParameters, List<JsName> flexibleParameters) {
+      this.parameterNames = parameterNames;
+      this.strictParameters = strictParameters;
+      this.flexibleParameters = flexibleParameters;
+    }
+
+    @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) {
+        return;
+      }
+
+      JsExpression original = originalParameterExpressions.get(x.getName());
+
+      if (original != null) {
+        ctx.replaceMe(original);
+      }
+    }
+  }
+
+  /**
+   * This looks for all invocations in the program. Any invoked JsFunctions
+   * derived from Java functions are then examined for being possible delegation
+   * patterns.
+   */
+  private class RemoveDelegationVisitor extends JsModVisitor {
+    @Override
+    public void endVisit(JsInvocation x, JsContext<JsExpression> ctx) {
+      // Start by determining what is being invoked.
+      if (!(x.getQualifier() instanceof JsNameRef)) {
+        return;
+      }
+      JsNameRef ref = (JsNameRef) x.getQualifier();
+
+      /*
+       * We only want to look at invocations of things that we statically know
+       * to be functions. Otherwise, we can't know what statements the
+       * invocation would actually invoke. The static reference would be null
+       * when trying operate on references to external functions, or functions
+       * as arguments to another function.
+       */
+      if (!(ref.getName().getStaticRef() instanceof JsFunction)) {
+        return;
+      }
+
+      JsFunction f = (JsFunction) ref.getName().getStaticRef();
+      List<JsStatement> statements = f.getBody().getStatements();
+      JsStatement clinit;
+      JsStatement toHoist;
+
+      if (statements.size() == 1) {
+        // The simple case.
+        clinit = null;
+        toHoist = statements.get(0);
+
+      } else if (statements.size() == 2) {
+        /*
+         * In the case of DOM, or similarly-structured classes that use a static
+         * "impl" field, we need to account for the static initializer. What
+         * we'll do is to create a JS comma expression to encapsulate the
+         * invocation of the static initializer as well as the delegated
+         * function. As a subsequent optimization, we can go through all
+         * functions and look for repeated invocations of a clinit, removing the
+         * JsBinaryOperation and replacing it with its rhs.
+         */
+        clinit = statements.get(0);
+        toHoist = statements.get(1);
+        if (!isStaticInitializer(clinit)) {
+          return;
+        }
+
+      } else {
+        // The expression is too complicated for this optimization
+        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
+       * referenced by any other AST nodes. Consider the case of a common,
+       * delegating function. If the hoisted expressions were not distinct
+       * objects, it would not be possible to substitute different JsNameRefs at
+       * different call sites.
+       */
+      JsExpression replacement = hoistedExpression(toHoist);
+      if (replacement == null) {
+        return;
+      }
+
+      // Perform the name replacement
+      NameRefReplacerVisitor v = new NameRefReplacerVisitor(x, f);
+      replacement = v.accept(replacement);
+
+      // Assemble the (clinit(), hoisted) expression.
+      if (statements.size() == 2) {
+        replacement =
+            new JsBinaryOperation(JsBinaryOperator.COMMA, ((JsExprStmt) clinit)
+                .getExpression(), replacement);
+      }
+
+      ctx.replaceMe(replacement);
+    }
+  }
+
+  /**
+   * Static entry point used by JavaToJavaScriptCompiler.
+   */
+  public static boolean exec(JsProgram program) {
+    return new JsDelegationRemover(program).execImpl();
+  }
+
+  /**
+   * Given a JsInvocation, determine if it is invoking a class's static
+   * initializer. This just looks for an invocation of a function whose short
+   * identifier is "$clinit". Not fancy, but it works.
+   */
+  private static JsName getClinitFromInvocation(JsInvocation invocation) {
+    if (invocation.getQualifier() instanceof JsNameRef) {
+      JsNameRef nameRef = (JsNameRef) invocation.getQualifier();
+      JsName name = nameRef.getName();
+      if (name.getShortIdent().equals("$clinit")) {
+        return name;
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * 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
+   * at-will.
+   */
+  private static JsExpression hoistedExpression(JsStatement statement) {
+    JsExpression expression;
+    if (statement instanceof JsExprStmt) {
+      JsExprStmt exprStmt = (JsExprStmt) statement;
+      expression = exprStmt.getExpression();
+
+    } else if (statement instanceof JsReturn) {
+      JsReturn ret = (JsReturn) statement;
+      expression = ret.getExpr();
+
+    } else {
+      return null;
+    }
+
+    return JsHoister.hoist(expression);
+  }
+
+  /**
+   * Determines if a statement is an invocation of a static initializer.
+   */
+  private static boolean isStaticInitializer(JsStatement statement) {
+    if (!(statement instanceof JsExprStmt)) {
+      return false;
+    }
+
+    JsExprStmt exprStmt = (JsExprStmt) statement;
+    JsExpression expression = exprStmt.getExpression();
+
+    if (!(expression instanceof JsInvocation)) {
+      return false;
+    }
+
+    JsInvocation invocation = (JsInvocation) expression;
+
+    if (!(invocation.getQualifier() instanceof JsNameRef)) {
+      return false;
+    }
+
+    return getClinitFromInvocation(invocation) != null;
+  }
+
+  /**
+   * Indicates if an expression can be repeated multiple times in a delegation
+   * removal without side-effects.
+   */
+  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();
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsHoister.java b/dev/core/src/com/google/gwt/dev/js/JsHoister.java
new file mode 100644
index 0000000..27fede8
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/js/JsHoister.java
@@ -0,0 +1,262 @@
+/*
+ * 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.jjs.InternalCompilerException;
+import com.google.gwt.dev.js.ast.JsArrayAccess;
+import com.google.gwt.dev.js.ast.JsArrayLiteral;
+import com.google.gwt.dev.js.ast.JsBinaryOperation;
+import com.google.gwt.dev.js.ast.JsBooleanLiteral;
+import com.google.gwt.dev.js.ast.JsConditional;
+import com.google.gwt.dev.js.ast.JsContext;
+import com.google.gwt.dev.js.ast.JsDecimalLiteral;
+import com.google.gwt.dev.js.ast.JsExpression;
+import com.google.gwt.dev.js.ast.JsFunction;
+import com.google.gwt.dev.js.ast.JsIntegralLiteral;
+import com.google.gwt.dev.js.ast.JsInvocation;
+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.JsObjectLiteral;
+import com.google.gwt.dev.js.ast.JsPostfixOperation;
+import com.google.gwt.dev.js.ast.JsPrefixOperation;
+import com.google.gwt.dev.js.ast.JsPropertyInitializer;
+import com.google.gwt.dev.js.ast.JsRegExp;
+import com.google.gwt.dev.js.ast.JsStringLiteral;
+import com.google.gwt.dev.js.ast.JsThisRef;
+import com.google.gwt.dev.js.ast.JsVisitor;
+
+import java.util.List;
+import java.util.Stack;
+
+/**
+ * A utility class to clone JsExpression AST members for use by
+ * {@link JsDelegationRemover}. <b>Not all expressions are necessarily
+ * implemented</b>, only those that are safe to hoist into outer call sites.
+ */
+final class JsHoister {
+  /**
+   * Implements actual cloning logic. We rely on the JsExpressions to provide
+   * traversal logic. The {@link #stack} field is used to accumulate
+   * already-cloned JsExpression instances. One gotcha that falls out of this is
+   * that argument lists are on the stack in reverse order, so lists should be
+   * constructed via inserts, rather than appends.
+   */
+  private static class Cloner extends JsVisitor {
+    private final Stack<JsExpression> stack = new Stack<JsExpression>();
+    private boolean successful = true;
+
+    @Override
+    public void endVisit(JsArrayAccess x, JsContext<JsExpression> ctx) {
+      JsArrayAccess newExpression = new JsArrayAccess();
+      newExpression.setIndexExpr(stack.pop());
+      newExpression.setArrayExpr(stack.pop());
+      stack.push(newExpression);
+    }
+
+    @Override
+    public void endVisit(JsArrayLiteral x, JsContext<JsExpression> ctx) {
+      JsArrayLiteral toReturn = new JsArrayLiteral();
+      List<JsExpression> expressions = toReturn.getExpressions();
+      for (JsExpression e : x.getExpressions()) {
+        expressions.add(0, stack.pop());
+      }
+      stack.push(toReturn);
+    }
+
+    @Override
+    public void endVisit(JsBinaryOperation x, JsContext<JsExpression> ctx) {
+      JsBinaryOperation toReturn = new JsBinaryOperation(x.getOperator());
+      toReturn.setArg2(stack.pop());
+      toReturn.setArg1(stack.pop());
+      stack.push(toReturn);
+    }
+
+    @Override
+    public void endVisit(JsBooleanLiteral x, JsContext<JsExpression> ctx) {
+      stack.push(x);
+    }
+
+    @Override
+    public void endVisit(JsConditional x, JsContext<JsExpression> ctx) {
+      JsConditional toReturn = new JsConditional();
+      toReturn.setElseExpression(stack.pop());
+      toReturn.setThenExpression(stack.pop());
+      toReturn.setTestExpression(stack.pop());
+      stack.push(toReturn);
+    }
+
+    @Override
+    public void endVisit(JsDecimalLiteral x, JsContext<JsExpression> ctx) {
+      stack.push(x);
+    }
+
+    /**
+     * The only functions that would get be visited are those being used as
+     * first-class objects.
+     */
+    @Override
+    public void endVisit(JsFunction 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);
+    }
+
+    @Override
+    public void endVisit(JsIntegralLiteral x, JsContext<JsExpression> ctx) {
+      stack.push(x);
+    }
+
+    /**
+     * Cloning the invocation allows us to modify it without damaging other call
+     * sites.
+     */
+    @Override
+    public void endVisit(JsInvocation x, JsContext<JsExpression> ctx) {
+      JsInvocation toReturn = new JsInvocation();
+      List<JsExpression> params = toReturn.getArguments();
+      for (JsExpression e : x.getArguments()) {
+        params.add(0, stack.pop());
+      }
+      toReturn.setQualifier(stack.pop());
+      stack.push(toReturn);
+    }
+
+    /**
+     * Do a deep clone of a JsNameRef. Because JsNameRef chains are shared
+     * throughout the AST, you can't just go and change their qualifiers when
+     * re-writing an invocation.
+     */
+    @Override
+    public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
+      JsNameRef toReturn = new JsNameRef(x.getName());
+
+      if (x.getQualifier() != null) {
+        toReturn.setQualifier(stack.pop());
+      }
+      stack.push(toReturn);
+    }
+
+    @Override
+    public void endVisit(JsNew x, JsContext<JsExpression> ctx) {
+      JsNew toReturn = new JsNew();
+
+      List<JsExpression> arguments = toReturn.getArguments();
+      for (JsExpression a : x.getArguments()) {
+        arguments.add(0, stack.pop());
+      }
+      toReturn.setConstructorExpression(stack.pop());
+      stack.push(toReturn);
+    }
+
+    @Override
+    public void endVisit(JsNullLiteral x, JsContext<JsExpression> ctx) {
+      stack.push(x);
+    }
+
+    @Override
+    public void endVisit(JsObjectLiteral x, JsContext<JsExpression> ctx) {
+      JsObjectLiteral toReturn = new JsObjectLiteral();
+      List<JsPropertyInitializer> inits = toReturn.getPropertyInitializers();
+
+      for (JsPropertyInitializer init : x.getPropertyInitializers()) {
+        /*
+         * JsPropertyInitializers are the only non-JsExpression objects that we
+         * care about, so we just go ahead and create the objects in the loop,
+         * rather than expecting it to be on the stack and having to perform
+         * narrowing casts at all stack.pop() invocations.
+         */
+        JsPropertyInitializer newInit = new JsPropertyInitializer();
+        newInit.setValueExpr(stack.pop());
+        newInit.setLabelExpr(stack.pop());
+
+        inits.add(0, newInit);
+      }
+      stack.push(toReturn);
+    }
+
+    @Override
+    public void endVisit(JsPostfixOperation x, JsContext<JsExpression> ctx) {
+      JsPostfixOperation toReturn = new JsPostfixOperation(x.getOperator());
+      toReturn.setArg(stack.pop());
+      stack.push(toReturn);
+    }
+
+    @Override
+    public void endVisit(JsPrefixOperation x, JsContext<JsExpression> ctx) {
+      JsPrefixOperation toReturn = new JsPrefixOperation(x.getOperator());
+      toReturn.setArg(stack.pop());
+      stack.push(toReturn);
+    }
+
+    @Override
+    public void endVisit(JsRegExp x, JsContext<JsExpression> ctx) {
+      stack.push(x);
+    }
+
+    @Override
+    public void endVisit(JsStringLiteral x, JsContext<JsExpression> ctx) {
+      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);
+    }
+
+    public JsExpression getExpression() {
+      return (successful && checkStack()) ? stack.peek() : null;
+    }
+
+    private boolean checkStack() {
+      if (stack.size() > 1) {
+        throw new InternalCompilerException("Too many expressions on stack");
+      }
+
+      return stack.size() == 1;
+    }
+  }
+
+  /**
+   * Given a JsStatement, construct an expression to hoist into the outer
+   * caller. This does not perform any name replacement, nor does it verify the
+   * scope of referenced elements, but simply constructs a mutable copy of the
+   * expression that can be manipulated at-will.
+   * 
+   * @return A copy of the original expression, or <code>null</code> if the
+   *         expression cannot be hoisted.
+   */
+  public static JsExpression hoist(JsExpression expression) {
+    if (expression == null) {
+      return null;
+    }
+
+    Cloner c = new Cloner();
+    c.accept(expression);
+    return c.getExpression();
+  }
+
+  private JsHoister() {
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsUnusedFunctionRemover.java b/dev/core/src/com/google/gwt/dev/js/JsUnusedFunctionRemover.java
new file mode 100644
index 0000000..ef97683
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/js/JsUnusedFunctionRemover.java
@@ -0,0 +1,120 @@
+/*
+ * 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.jjs.InternalCompilerException;
+import com.google.gwt.dev.js.ast.JsContext;
+import com.google.gwt.dev.js.ast.JsExprStmt;
+import com.google.gwt.dev.js.ast.JsExpression;
+import com.google.gwt.dev.js.ast.JsFunction;
+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.JsProgram;
+import com.google.gwt.dev.js.ast.JsStatement;
+import com.google.gwt.dev.js.ast.JsVisitor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Removes JsFunctions that are never referenced in the program.
+ */
+public class JsUnusedFunctionRemover {
+
+  /**
+   * Finds all functions in the program.
+   */
+  private class JsFunctionVisitor extends JsVisitor {
+
+    @Override
+    public void endVisit(JsFunction x, JsContext<JsExpression> ctx) {
+      // Anonymous function, ignore it
+      if (x.getName() != null) {
+        toRemove.put(x.getName(), x);
+      }
+    }
+  }
+
+  /**
+   * Finds all function references in the program.
+   */
+  private class JsNameRefVisitor extends JsVisitor {
+
+    @Override
+    public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
+      toRemove.remove(x.getName());
+    }
+  }
+
+  private class RemovalVisitor extends JsModVisitor {
+
+    @Override
+    public void endVisit(JsExprStmt x, JsContext<JsStatement> ctx) {
+      if (!(x.getExpression() instanceof JsFunction)) {
+        return;
+      }
+
+      JsFunction f = (JsFunction) x.getExpression();
+
+      if (!f.isFromJava()) {
+        // This function didn't come from Java source, so we'll assume it's
+        // magic and ignore it.
+        return;
+      }
+
+      JsName name = f.getName();
+
+      if (toRemove.containsKey(name)) {
+        // Removing a static initializer indicates a problem in
+        // JsDelegationRemover.
+        if (name.getIdent().equals("$clinit")) {
+          throw new InternalCompilerException("Tried to remove clinit "
+              + name.getStaticRef().toSource());
+        }
+
+        // Remove the statement
+        ctx.removeMe();
+      }
+    }
+  }
+
+  public static boolean exec(JsProgram program) {
+    return (new JsUnusedFunctionRemover(program)).execImpl();
+  }
+
+  private final Map<JsName, JsFunction> toRemove =
+      new HashMap<JsName, JsFunction>();
+  private final JsProgram program;
+
+  public JsUnusedFunctionRemover(JsProgram program) {
+    this.program = program;
+  }
+
+  public boolean execImpl() {
+    // Find all functions
+    (new JsFunctionVisitor()).accept(program);
+
+    // Remove the functions that are referenced from the hit list
+    (new JsNameRefVisitor()).accept(program);
+
+    // Remove the unused functions from the JsProgram
+    RemovalVisitor removalVisitor = new RemovalVisitor();
+    removalVisitor.accept(program);
+
+    return removalVisitor.didChange();
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsCatchScope.java b/dev/core/src/com/google/gwt/dev/js/ast/JsCatchScope.java
index 51f4294..1791fc5 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsCatchScope.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsCatchScope.java
@@ -28,7 +28,7 @@
 
   public JsCatchScope(JsScope parent, String ident) {
     super(parent, "Catch scope");
-    this.name = new JsName(ident, ident);
+    this.name = new JsName(this, ident, ident);
   }
 
   @Override
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsFunction.java b/dev/core/src/com/google/gwt/dev/js/ast/JsFunction.java
index c2ce85c..7ae5889 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsFunction.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsFunction.java
@@ -27,20 +27,29 @@
   protected final List<JsParameter> params = new ArrayList<JsParameter>();
   protected final JsScope scope;
   private JsName name;
+  private boolean fromJava;
 
   /**
    * Creates an anonymous function.
    */
   public JsFunction(JsScope parent) {
-    this(parent, null);
+    this(parent, null, false);
   }
 
   /**
-   * Creates an named function.
+   * Creates a function that is not derived from Java source.
    */
   public JsFunction(JsScope parent, JsName name) {
+    this(parent, name, false);
+  }
+
+  /**
+   * Creates a named function, possibly derived from Java source.
+   */
+  public JsFunction(JsScope parent, JsName name, boolean fromJava) {
     assert (parent != null);
-    this.name = name;
+    this.fromJava = fromJava;
+    setName(name);
     String scopeName = (name == null) ? "<anonymous>" : name.getIdent();
     scopeName = "function " + scopeName;
     this.scope = new JsScope(parent, scopeName);
@@ -62,12 +71,25 @@
     return scope;
   }
 
+  public boolean isFromJava() {
+    return fromJava;
+  }
+
   public void setBody(JsBlock body) {
     this.body = body;
   }
 
+  public void setFromJava(boolean fromJava) {
+    this.fromJava = fromJava;
+  }
+
   public void setName(JsName name) {
     this.name = name;
+    if (name != null) {
+      if (isFromJava()) {
+        name.setStaticRef(this);
+      }
+    }
   }
 
   public void traverse(JsVisitor v, JsContext<JsExpression> ctx) {
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsName.java b/dev/core/src/com/google/gwt/dev/js/ast/JsName.java
index 91c0871..30e9497 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsName.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsName.java
@@ -20,19 +20,30 @@
  */
 public class JsName {
 
+  private final JsScope enclosing;
   private final String ident;
   private boolean isObfuscatable;
   private String shortIdent;
 
   /**
+   * A back-reference to the JsNode that the JsName refers to.
+   */
+  private JsNode staticRef;
+
+  /**
    * @param ident the unmangled ident to use for this name
    */
-  JsName(String ident, String shortIdent) {
+  JsName(JsScope enclosing, String ident, String shortIdent) {
+    this.enclosing = enclosing;
     this.ident = ident;
     this.shortIdent = shortIdent;
     this.isObfuscatable = true;
   }
 
+  public JsScope getEnclosing() {
+    return enclosing;
+  }
+
   public String getIdent() {
     return ident;
   }
@@ -41,6 +52,10 @@
     return shortIdent;
   }
 
+  public JsNode getStaticRef() {
+    return staticRef;
+  }
+
   public boolean isObfuscatable() {
     return isObfuscatable;
   }
@@ -57,6 +72,13 @@
     this.shortIdent = shortIdent;
   }
 
+  /**
+   * Should never be called except on immutable stuff.
+   */
+  public void setStaticRef(JsNode node) {
+    this.staticRef = node;
+  }
+
   @Override
   public String toString() {
     return ident;
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsNameRef.java b/dev/core/src/com/google/gwt/dev/js/ast/JsNameRef.java
index 7a4d701..7c4f0e1 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsNameRef.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsNameRef.java
@@ -18,7 +18,7 @@
 /**
  * Represents a JavaScript expression that references a name.
  */
-public final class JsNameRef extends JsExpression /*implements HasName*/ {
+public final class JsNameRef extends JsExpression implements HasName {
 
   private String ident;
   private JsName name;
@@ -36,6 +36,10 @@
     return (name == null) ? ident : name.getIdent();
   }
 
+  public JsName getName() {
+    return name;
+  }
+
   public JsExpression getQualifier() {
     return qualifier;
   }
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsParameter.java b/dev/core/src/com/google/gwt/dev/js/ast/JsParameter.java
index 8e293b6..af2e667 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsParameter.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsParameter.java
@@ -24,6 +24,7 @@
 
   public JsParameter(JsName name) {
     this.name = name;
+    name.setStaticRef(this);
   }
 
   public JsName getName() {
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsScope.java b/dev/core/src/com/google/gwt/dev/js/ast/JsScope.java
index 68d73fc..4d2a66a 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsScope.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsScope.java
@@ -198,7 +198,7 @@
    * Creates a new name in this scope.
    */
   protected JsName doCreateName(String ident, String shortIdent) {
-    JsName name = new JsName(ident, shortIdent);
+    JsName name = new JsName(this, ident, shortIdent);
     names.put(ident, name);
     return name;
   }