Removes UpRefVisitor from ControlFlowAnalyzer, which scans the whole
program to find extra methods that need rescuing due to virtual
method calls.  Instead, such methods are now checked at the
time a new method or type becomes instantiable.

Also, adds PerfLogger calls to GWTCompiler, to make it easy to time
an entire compile.

Review by: scottb


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@4015 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/GWTCompiler.java b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
index 9e90603..fd4786a 100644
--- a/dev/core/src/com/google/gwt/dev/GWTCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
@@ -20,6 +20,7 @@
 import com.google.gwt.dev.CompilePerms.CompilePermsOptionsImpl;
 import com.google.gwt.dev.CompileTaskRunner.CompileTask;
 import com.google.gwt.dev.Precompile.PrecompileOptionsImpl;
+import com.google.gwt.dev.util.PerfLogger;
 import com.google.gwt.dev.util.arg.ArgHandlerExtraDir;
 
 import java.io.File;
@@ -100,6 +101,7 @@
     if (options.isValidateOnly()) {
       return new Precompile(options).run(logger);
     } else {
+      PerfLogger.start("compile");
       logger = logger.branch(TreeLogger.INFO, "Compiling module "
           + options.getModuleName());
       if (new Precompile(options).run(logger)) {
@@ -112,11 +114,13 @@
         if (new CompilePerms(permsOptions).run(logger)) {
           if (new Link(options).run(logger)) {
             logger.log(TreeLogger.INFO, "Compilation succeeded");
+            PerfLogger.end();
             return true;
           }
         }
       }
       logger.log(TreeLogger.ERROR, "Compilation failed");
+      PerfLogger.end();
       return false;
     }
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java
index d0e5e1f..42afd16 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java
@@ -267,7 +267,6 @@
 
     initiallyLive = new ControlFlowAnalyzer(jprogram);
     traverseEntry(initiallyLive, 0);
-    initiallyLive.finishTraversal();
 
     methodsInJavaScript = fragmentExtractor.findAllMethodsInJavaScript();
   }
@@ -310,7 +309,6 @@
       // Traverse leftoversFragmentHasLoaded, because it should not
       // go into any of the exclusive fragments.
       cfa.traverseFromLeftoversFragmentHasLoaded();
-      cfa.finishTraversal();
       allButOnes.add(cfa);
     }
 
@@ -326,7 +324,6 @@
       traverseEntry(everything, entry);
     }
     everything.traverseFromLeftoversFragmentHasLoaded();
-    everything.finishTraversal();
     return everything;
   }
 
@@ -368,7 +365,6 @@
     for (int base = 1; base < numEntries; base++) {
       ControlFlowAnalyzer baseCfa = new ControlFlowAnalyzer(initiallyLive);
       traverseEntry(baseCfa, base);
-      baseCfa.finishTraversal();
       LivenessPredicate baseLive = new CfaLivenessPredicate(baseCfa);
 
       // secondary base
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
index 22ff39d..51d0721 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
@@ -57,15 +57,15 @@
 import com.google.gwt.dev.js.ast.JsVisitor;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
  * This class finds out what code in a program is live based on starting
- * execution at a specified location. Note that the client must call
- * {@link #finishTraversal()} after the other traversal methods have been
- * called, or the results will be incomplete.
+ * execution at a specified location.
  */
 public class ControlFlowAnalyzer {
 
@@ -182,7 +182,7 @@
       // Rescue my super type
       rescue(type.extnds, true, isInstantiated);
 
-      // Rescue my clinit (it won't ever be explicitly referenced
+      // Rescue my clinit (it won't ever be explicitly referenced)
       rescue(type.methods.get(0));
 
       // JLS 12.4.1: don't rescue my super interfaces just because I'm rescued.
@@ -192,6 +192,8 @@
         rescue(intfType, false, isInstantiated);
       }
 
+      rescueMethodsIfInstantiable(type);
+
       return false;
     }
 
@@ -256,6 +258,8 @@
         accept(it);
       }
 
+      rescueMethodsIfInstantiable(type);
+
       return false;
     }
 
@@ -305,7 +309,18 @@
 
     @Override
     public boolean visit(JMethodCall call, Context ctx) {
-      rescue(call.getTarget());
+      JMethod method = call.getTarget();
+      if (method.isStatic()
+          || program.isJavaScriptObject(method.getEnclosingType())
+          || instantiatedTypes.contains(method.getEnclosingType())) {
+        rescue(method);
+      } else {
+        // It's a virtual method whose class is not instantiable
+        if (!liveFieldsAndMethods.contains(method)) {
+          methodsLiveExceptForInstantiability.add(method);
+        }
+      }
+
       return true;
     }
 
@@ -448,7 +463,10 @@
       if (method != null) {
         if (!liveFieldsAndMethods.contains(method)) {
           liveFieldsAndMethods.add(method);
+          methodsLiveExceptForInstantiability.remove(method);
+
           accept(method);
+
           if (method.isNative()) {
             /*
              * SPECIAL: returning from this method passes a value from
@@ -456,6 +474,9 @@
              */
             maybeRescueJavaScriptObjectPassingIntoJava(method.getType());
           }
+
+          rescueOverridingMethods(method);
+
           return true;
         }
       }
@@ -528,57 +549,72 @@
         rescue(stringValueOfChar);
       }
     }
-  }
 
-  /**
-   * Traverse methods that are reachable via virtual method calls. Specifically,
-   * traverse methods whose classes are instantiable and which override a method
-   * that is live.
-   */
-  private class UpRefVisitor extends JVisitor {
-
-    private boolean didRescue = false;
-
-    public boolean didRescue() {
-      return didRescue;
-    }
-
-    @Override
-    public boolean visit(JClassType x, Context ctx) {
-      return instantiatedTypes.contains(x);
-    }
-
-    @Override
-    public boolean visit(JMethod x, Context ctx) {
-      if (liveFieldsAndMethods.contains(x)) {
-        return false;
-      }
-
-      for (JMethod override : program.typeOracle.getAllOverrides(x)) {
-        if (liveFieldsAndMethods.contains(override)) {
-          rescuer.rescue(x);
-          didRescue = true;
-          return false;
+    /**
+     * If the type is instantiable, rescue any of its virtual methods that a
+     * previously seen method call could call.
+     */
+    private void rescueMethodsIfInstantiable(JReferenceType type) {
+      if (instantiatedTypes.contains(type)) {
+        for (JMethod method : type.methods) {
+          if (!method.isStatic()) {
+            if (methodsLiveExceptForInstantiability.contains(method)) {
+              rescue(method);
+              continue;
+            }
+          }
         }
       }
-      return false;
     }
 
-    @Override
-    public boolean visit(JProgram x, Context ctx) {
-      didRescue = false;
-      return true;
+    /**
+     * Assume that <code>method</code> is live. Rescue any overriding methods
+     * that might be called if <code>method</code> is called through virtual
+     * dispatch.
+     */
+    private void rescueOverridingMethods(JMethod method) {
+      if (!method.isStatic()) {
+
+        List<JMethod> overriders = methodsThatOverrideMe.get(method);
+        if (overriders != null) {
+          for (JMethod overrider : overriders) {
+            if (liveFieldsAndMethods.contains(overrider)) {
+              // The override is already alive, do nothing.
+            } else if (instantiatedTypes.contains(overrider.getEnclosingType())) {
+              // The enclosing class is alive, make my override reachable.
+              rescue(overrider);
+            } else {
+              // The enclosing class is not yet alive, put override in limbo.
+              methodsLiveExceptForInstantiability.add(overrider);
+            }
+          }
+        }
+      }
     }
   }
 
   private Set<JReferenceType> instantiatedTypes = new HashSet<JReferenceType>();
   private Set<JNode> liveFieldsAndMethods = new HashSet<JNode>();
   private Set<String> liveStrings = new HashSet<String>();
+
+  /**
+   * Schrodinger's methods... aka "limbo". :) These are instance methods that
+   * seem to be reachable, only their enclosing type is uninstantiable. We place
+   * these methods into purgatory until/unless the enclosing type is found to be
+   * instantiable.
+   */
+  private Set<JMethod> methodsLiveExceptForInstantiability = new HashSet<JMethod>();
+
+  /**
+   * A precomputed map of all instance methods onto a set of methods that
+   * override each key method.
+   */
+  private Map<JMethod, List<JMethod>> methodsThatOverrideMe;
+
   private final JProgram program;
   private Set<JReferenceType> referencedTypes = new HashSet<JReferenceType>();
   private final RescueVisitor rescuer = new RescueVisitor();
   private JMethod stringValueOfChar = null;
-  private final UpRefVisitor upRefer = new UpRefVisitor();
 
   public ControlFlowAnalyzer(ControlFlowAnalyzer cfa) {
     program = cfa.program;
@@ -587,21 +623,14 @@
     referencedTypes = new HashSet<JReferenceType>(cfa.referencedTypes);
     stringValueOfChar = cfa.stringValueOfChar;
     liveStrings = new HashSet<String>(cfa.liveStrings);
+    methodsLiveExceptForInstantiability = new HashSet<JMethod>(
+        cfa.methodsLiveExceptForInstantiability);
+    methodsThatOverrideMe = cfa.methodsThatOverrideMe;
   }
 
   public ControlFlowAnalyzer(JProgram program) {
     this.program = program;
-  }
-
-  /**
-   * Finish any remaining traversal that is needed. This must be called after
-   * calling any of the other traversal methods in order to get accurate
-   * results. It can also be called eagerly.
-   */
-  public void finishTraversal() {
-    do {
-      upRefer.accept(program);
-    } while (upRefer.didRescue());
+    buildMethodsOverriding();
   }
 
   /**
@@ -653,9 +682,7 @@
     class ReplaceStringLiterals extends JModVisitor {
       @Override
       public void endVisit(JStringLiteral stringLiteral, Context ctx) {
-        ctx.replaceMe(program.getLiteralString(
-            stringLiteral.getSourceInfo().makeChild(ControlFlowAnalyzer.class,
-                "remove string literals"), ""));
+        ctx.replaceMe(program.getLiteralNull());
       }
     }
 
@@ -683,4 +710,20 @@
   public void traverseFromReferenceTo(JReferenceType type) {
     rescuer.rescue(type, true, false);
   }
+
+  private void buildMethodsOverriding() {
+    methodsThatOverrideMe = new HashMap<JMethod, List<JMethod>>();
+    for (JReferenceType type : program.getDeclaredTypes()) {
+      for (JMethod method : type.methods) {
+        for (JMethod overridden : program.typeOracle.getAllOverrides(method)) {
+          List<JMethod> overs = methodsThatOverrideMe.get(overridden);
+          if (overs == null) {
+            overs = new ArrayList<JMethod>();
+            methodsThatOverrideMe.put(overridden, overs);
+          }
+          overs.add(method);
+        }
+      }
+    }
+  }
 }
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 231e624..a948c56 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
@@ -463,7 +463,6 @@
         livenessAnalyzer.traverseFrom(method);
       }
       livenessAnalyzer.traverseFromLeftoversFragmentHasLoaded();
-      livenessAnalyzer.finishTraversal();
 
       program.typeOracle.setInstantiatedTypes(livenessAnalyzer.getInstantiatedTypes());