Adds control-flow based incremental linker pruning.

Previously incremental linking was accomplished by creating and 
maintaining an index of type<->type references, using these references 
to construct a transitive graph of referenceable types and outputting 
the full JS for each such type.

Though simple and fast it also had larger total output than necessary
since some types were unnecessarly kept (for example types were kept
that were only referenced in functions that could never be called).

This patch switches to incremental linking based on control flow. This 
is accomplished by maintaining caller->callee, instantiating 
method->instantiated type, overridden method->overriding
method and other control flow information indexes, using these indexes
to construct a complete graph of reachable methods, and then throwing 
out any types that do not supply at least one of the reachable method.

Change-Id: Ied32634bf535f74c0e934f3940226c05f4f6d3b1
Review-Link: https://gwt-review.googlesource.com/#/c/10680/
diff --git a/dev/BUILD b/dev/BUILD
index f0ec39a..3c325e4 100644
--- a/dev/BUILD
+++ b/dev/BUILD
@@ -183,6 +183,7 @@
             "core/src/com/google/gwt/dev/PrecompileTaskOptions.java",
             "core/src/com/google/gwt/dev/PrecompileTaskOptionsImpl.java",
             "core/src/com/google/gwt/dev/PrecompilationResult.java",
+            "core/src/com/google/gwt/dev/StringAnalyzableTypeEnvironment.java",
             "core/src/com/google/gwt/dev/cfg/**/*.java",
             "core/src/com/google/gwt/dev/javac/**/*.java",
             "core/src/com/google/gwt/dev/jdt/**/*.java",
diff --git a/dev/build.xml b/dev/build.xml
index c9c9a08..948831b 100755
--- a/dev/build.xml
+++ b/dev/build.xml
@@ -64,6 +64,7 @@
         <fileset dir="${gwt.tools.lib}">
           <include name="apache/tapestry-util-text-4.0.2.jar"/>
           <include name="apache/ant-1.6.5.jar"/>
+          <include name="colt/colt-1.2.jar"/>
           <include name="eclipse/jdt-3.10.0.jar"/>
           <include name="eclipse/jdtCompilerAdapter-3.10.0.jar"/>
           <include name="objectweb/asm-5.0.3/lib/asm-all-5.0.3.jar"/>
@@ -114,6 +115,7 @@
           <zipfileset src="${gwt.tools.lib}/objectweb/asm-5.0.3/lib/asm-all-5.0.3.jar"/>
           <zipfileset src="${gwt.tools.lib}/apache/tapestry-util-text-4.0.2.jar"/>
           <zipfileset src="${gwt.tools.lib}/apache/ant-1.6.5.jar"/>
+          <zipfileset src="${gwt.tools.lib}/colt/colt-1.2.jar"/>
           <zipfileset
               src="${gwt.tools.lib}/eclipse/jdt-3.10.0.jar"/>
           <zipfileset
@@ -197,6 +199,7 @@
       <include name="org/eclipse/jdt/**"/>
       <classpath>
         <pathelement location="${gwt.tools.lib}/apache/ant-1.6.5.jar"/>
+        <pathelement location="${gwt.tools.lib}/colt/colt-1.2.jar"/>
         <pathelement location="${gwt.tools.lib}/objectweb/asm-5.0.3/lib/asm-all-5.0.3.jar"/>
         <pathelement
             location="${gwt.tools.lib}/apache/commons/commons-collections-3.2.1.jar"/>
diff --git a/dev/core/src/com/google/gwt/dev/MinimalRebuildCache.java b/dev/core/src/com/google/gwt/dev/MinimalRebuildCache.java
index 8770d7d..bd1c9f2 100644
--- a/dev/core/src/com/google/gwt/dev/MinimalRebuildCache.java
+++ b/dev/core/src/com/google/gwt/dev/MinimalRebuildCache.java
@@ -25,6 +25,7 @@
 import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.jjs.ast.JTypeOracle;
 import com.google.gwt.dev.jjs.ast.JTypeOracle.ImmediateTypeRelations;
+import com.google.gwt.dev.jjs.impl.RapidTypeAnalyzer;
 import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.IntTypeMapper;
 import com.google.gwt.dev.js.JsIncrementalNamer.JsIncrementalNamerState;
 import com.google.gwt.dev.resource.Resource;
@@ -40,9 +41,11 @@
 import com.google.gwt.thirdparty.guava.common.collect.Multimaps;
 import com.google.gwt.thirdparty.guava.common.collect.Sets;
 
+import cern.colt.list.IntArrayList;
+
 import java.io.Serializable;
 import java.util.Collection;
-import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
@@ -164,6 +167,7 @@
       HashMultimap.create();
   private final IntTypeMapper intTypeMapper = new IntTypeMapper();
   private final Map<String, String> jsByTypeName = Maps.newHashMap();
+  private final JsIncrementalNamerState jsIncrementalNamerState = new JsIncrementalNamerState();
   private final Set<String> jsoStatusChangedTypeNames = Sets.newHashSet();
   private final Set<String> jsoTypeNames = Sets.newHashSet();
   private Integer lastLinkedJsBytes;
@@ -174,8 +178,6 @@
   private final Set<String> modifiedDiskSourcePaths = Sets.newHashSet();
   private final Set<String> modifiedResourcePaths = Sets.newHashSet();
   private final Multimap<String, String> nestedTypeNamesByUnitTypeName = HashMultimap.create();
-  private final JsIncrementalNamerState jsIncrementalNamerState =
-      new JsIncrementalNamerState();
   private final Set<String> preambleTypeNames = Sets.newHashSet();
   private transient ImmutableSet<String> processedStaleTypeNames = ImmutableSet.<String> of();
   private final Multimap<String, String> rebinderTypeNamesByReboundTypeName = HashMultimap.create();
@@ -189,6 +191,8 @@
   private final Map<String, JsSourceMap> sourceMapsByTypeName = Maps.newHashMap();
   private final Set<String> staleTypeNames = Sets.newHashSet();
   private final Map<String, StatementRanges> statementRangesByTypeName = Maps.newHashMap();
+  private StringAnalyzableTypeEnvironment typeEnvironment =
+      new StringAnalyzableTypeEnvironment(this);
   private final Multimap<String, String> typeNamesByReferencingTypeName = HashMultimap.create();
 
   /**
@@ -362,31 +366,77 @@
   }
 
   /**
-   * Computes and returns the names of the set of types that are transitively referenceable starting
-   * from the set of root types.
+   * Computes and returns the names of the set of types that are referenceable within the method
+   * level control flow starting from the entry methods, immortal codegen types and exported
+   * JsInterop methods.
    * <p>
    * Should only be called once per compile, so that the "lastReachableTypeNames" list accurately
    * reflects the reachable types of the immediately previous compile.
    */
   public Set<String> computeReachableTypeNames() {
-    Set<String> openTypeNames = Sets.newHashSet(rootTypeNames);
-    Set<String> reachableTypeNames = Sets.newHashSet();
+    RapidTypeAnalyzer rapidTypeAnalyzer = new RapidTypeAnalyzer(typeEnvironment);
 
-    while (!openTypeNames.isEmpty()) {
-      Iterator<String> iterator = openTypeNames.iterator();
-      String toProcessTypeName = iterator.next();
-      iterator.remove();
+    // Artificially reach and traverse immortal codegen types since references to these may have
+    // been synthesized in JS generation. These will not be pruned.
+    for (String immortalCodegenTypeName : JProgram.IMMORTAL_CODEGEN_TYPES_SET) {
+      int immortalCodegenTypeId = typeEnvironment.getTypeIdByName(immortalCodegenTypeName);
+      rapidTypeAnalyzer.markTypeIdReachable(immortalCodegenTypeId);
+      // Includes the clinit method.
+      rapidTypeAnalyzer.markMemberMethodIdsReachable(immortalCodegenTypeId);
+    }
 
-      reachableTypeNames.add(toProcessTypeName);
+    // Artificially reach @JsExport and @JsType methods. Since the enclosing types are
+    // not being marked instantiated or reachable there's still a chance that they will be pruned
+    // if no instantiations or static references are found.
+    IntArrayList enclosingTypeIds = typeEnvironment.getEnclosingTypeIdsOfExportedMethods();
+    for (int i = 0; i < enclosingTypeIds.size(); i++) {
+      int enclosingTypeId = enclosingTypeIds.get(i);
+      IntArrayList exportedMethodIds =
+          typeEnvironment.getExportedMemberMethodIdsIn(enclosingTypeId);
+      if (exportedMethodIds == null) {
+        continue;
+      }
 
-      Collection<String> referencedTypes = referencedTypeNamesByTypeName.get(toProcessTypeName);
-      for (String referencedType : referencedTypes) {
-        if (reachableTypeNames.contains(referencedType)) {
-          continue;
-        }
-        openTypeNames.add(referencedType);
+      for (int j = 0; j < exportedMethodIds.size(); j++) {
+        int exportedMethodId = exportedMethodIds.get(j);
+        rapidTypeAnalyzer.markMethodIdReachable(exportedMethodId, false);
       }
     }
+
+    // Artificially reach types with @JsExport'ed or @JsType'ed static fields or methods (including
+    // Constructors). These types will not be pruned.
+    IntArrayList typeIdsWithExportedStaticReferences =
+        typeEnvironment.getTypeIdsWithExportedStaticReferences();
+    for (int i = 0; i < typeIdsWithExportedStaticReferences.size(); i++) {
+      int typeId = typeIdsWithExportedStaticReferences.get(i);
+      rapidTypeAnalyzer.markTypeIdReachable(typeId);
+      String typeName = typeEnvironment.getTypeNameById(typeId);
+      int typeClinitMethodId = typeEnvironment.getMethodIdByName(typeName + "::$clinit()");
+      rapidTypeAnalyzer.markMethodIdReachable(typeClinitMethodId, false);
+    }
+
+    // Reach and traverse entry method types. This is just the EntryMethodHolder
+    // class and its init() function. It will not be pruned. This is the conceptual "root" of the
+    // application execution.
+    IntArrayList entryMethodIds = typeEnvironment.getEntryMethodIds();
+    for (int i = 0; i < entryMethodIds.size(); i++) {
+      int entryMethodId = entryMethodIds.get(i);
+      int typeId = typeEnvironment.getEnclosingTypeId(entryMethodId);
+      rapidTypeAnalyzer.markTypeIdReachable(typeId);
+      // Includes the clinit method.
+      rapidTypeAnalyzer.markMemberMethodIdsReachable(typeId);
+    }
+
+    // Perform rapid type analysis.
+    IntArrayList reachableTypeIds = rapidTypeAnalyzer.computeReachableTypeIds();
+
+    // Translate ids back to strings and keep the results around for the next compile.
+    Set<String> reachableTypeNames = Sets.newHashSet();
+    for (int i = 0; i < reachableTypeIds.size(); i++) {
+      int reachableTypeId = reachableTypeIds.get(i);
+      reachableTypeNames.add(typeEnvironment.getTypeNameById(reachableTypeId));
+    }
+
     copyCollection(reachableTypeNames, lastReachableTypeNames);
     return reachableTypeNames;
   }
@@ -405,6 +455,7 @@
     this.intTypeMapper.copyFrom(that.intTypeMapper);
     this.jsIncrementalNamerState.copyFrom(that.jsIncrementalNamerState);
     this.immediateTypeRelations.copyFrom(that.immediateTypeRelations);
+    this.typeEnvironment.copyFrom(that.typeEnvironment);
 
     copyMap(that.compilationUnitTypeNameByNestedTypeName,
         this.compilationUnitTypeNameByNestedTypeName);
@@ -443,50 +494,6 @@
     copyCollection(that.staleTypeNames, this.staleTypeNames);
   }
 
-  @VisibleForTesting
-  boolean hasSameContent(MinimalRebuildCache that) {
-    // Ignoring processedStaleTypeNames since it is transient.
-    return
-        this.immediateTypeRelations.hasSameContent(that.immediateTypeRelations) && Objects.equal(
-            this.compilationUnitTypeNameByNestedTypeName,
-            that.compilationUnitTypeNameByNestedTypeName)
-        && Objects.equal(this.contentHashByGeneratedTypeName, that.contentHashByGeneratedTypeName)
-        && Objects.equal(this.deletedCompilationUnitNames, that.deletedCompilationUnitNames)
-        && Objects.equal(this.deletedDiskSourcePaths, that.deletedDiskSourcePaths)
-        && Objects.equal(this.deletedResourcePaths, that.deletedResourcePaths)
-        && Objects.equal(this.dualJsoImplInterfaceNames, that.dualJsoImplInterfaceNames)
-        && Objects.equal(this.generatedArtifacts, that.generatedArtifacts) && Objects.equal(
-            this.generatedCompilationUnitNamesByReboundTypeNames,
-            that.generatedCompilationUnitNamesByReboundTypeNames)
-        && this.intTypeMapper.hasSameContent(that.intTypeMapper)
-        && Objects.equal(this.jsByTypeName, that.jsByTypeName)
-        && Objects.equal(this.jsoStatusChangedTypeNames, that.jsoStatusChangedTypeNames)
-        && Objects.equal(this.jsoTypeNames, that.jsoTypeNames)
-        && Objects.equal(this.lastLinkedJsBytes, that.lastLinkedJsBytes)
-        && Objects.equal(this.lastModifiedByDiskSourcePath, that.lastModifiedByDiskSourcePath)
-        && Objects.equal(this.lastModifiedByResourcePath, that.lastModifiedByResourcePath)
-        && Objects.equal(this.lastReachableTypeNames, that.lastReachableTypeNames)
-        && Objects.equal(this.modifiedCompilationUnitNames, that.modifiedCompilationUnitNames)
-        && Objects.equal(this.modifiedDiskSourcePaths, that.modifiedDiskSourcePaths)
-        && Objects.equal(this.modifiedResourcePaths, that.modifiedResourcePaths)
-        && Objects.equal(this.nestedTypeNamesByUnitTypeName, that.nestedTypeNamesByUnitTypeName)
-        && this.jsIncrementalNamerState.hasSameContent(that.jsIncrementalNamerState)
-        && Objects.equal(this.preambleTypeNames, that.preambleTypeNames) && Objects.equal(
-            this.rebinderTypeNamesByReboundTypeName, that.rebinderTypeNamesByReboundTypeName)
-        && Objects.equal(this.reboundTypeNamesByGeneratedCompilationUnitNames,
-            that.reboundTypeNamesByGeneratedCompilationUnitNames) && Objects.equal(
-            this.reboundTypeNamesByInputResource, that.reboundTypeNamesByInputResource)
-        && Objects.equal(this.referencedTypeNamesByTypeName, that.referencedTypeNamesByTypeName)
-        && Objects.equal(this.rootTypeNames, that.rootTypeNames)
-        && Objects.equal(this.singleJsoImplInterfaceNames, that.singleJsoImplInterfaceNames)
-        && Objects.equal(this.sourceCompilationUnitNames, that.sourceCompilationUnitNames)
-        && Objects.equal(this.sourceMapsByTypeName, that.sourceMapsByTypeName)
-        && Objects.equal(this.staleTypeNames, that.staleTypeNames)
-        && Objects.equal(this.statementRangesByTypeName, that.statementRangesByTypeName)
-        && Objects.equal(this.typeNamesByReferencingTypeName,
-            that.typeNamesByReferencingTypeName);
-  }
-
   /**
    * Return the set of provided typeNames with unreachable types filtered out.
    */
@@ -545,6 +552,10 @@
     return statementRangesByTypeName.get(typeName);
   }
 
+  public StringAnalyzableTypeEnvironment getTypeEnvironment() {
+    return typeEnvironment;
+  }
+
   public IntTypeMapper getTypeMapper() {
     return intTypeMapper;
   }
@@ -669,6 +680,10 @@
     referencedTypeNamesByTypeName.removeAll(fromTypeName);
   }
 
+  public void setEntryMethodNames(List<String> entryMethodNames) {
+    typeEnvironment.setEntryMethodNames(entryMethodNames);
+  }
+
   public void setJsForType(TreeLogger logger, String typeName, String typeJs) {
     logger.log(TreeLogger.SPAM, "caching JS for type " + typeName);
     jsByTypeName.put(typeName, typeJs);
@@ -729,6 +744,48 @@
     statementRangesByTypeName.put(typeName, statementRanges);
   }
 
+  @VisibleForTesting
+  boolean hasSameContent(MinimalRebuildCache that) {
+    // Ignoring processedStaleTypeNames since it is transient.
+    return this.immediateTypeRelations.hasSameContent(that.immediateTypeRelations) && Objects.equal(
+        this.compilationUnitTypeNameByNestedTypeName, that.compilationUnitTypeNameByNestedTypeName)
+        && Objects.equal(this.contentHashByGeneratedTypeName, that.contentHashByGeneratedTypeName)
+        && Objects.equal(this.deletedCompilationUnitNames, that.deletedCompilationUnitNames)
+        && Objects.equal(this.deletedDiskSourcePaths, that.deletedDiskSourcePaths)
+        && Objects.equal(this.deletedResourcePaths, that.deletedResourcePaths)
+        && Objects.equal(this.dualJsoImplInterfaceNames, that.dualJsoImplInterfaceNames)
+        && Objects.equal(this.generatedArtifacts, that.generatedArtifacts) && Objects.equal(
+            this.generatedCompilationUnitNamesByReboundTypeNames,
+            that.generatedCompilationUnitNamesByReboundTypeNames)
+        && this.intTypeMapper.hasSameContent(that.intTypeMapper)
+        && Objects.equal(this.jsByTypeName, that.jsByTypeName)
+        && Objects.equal(this.jsoStatusChangedTypeNames, that.jsoStatusChangedTypeNames)
+        && Objects.equal(this.jsoTypeNames, that.jsoTypeNames)
+        && Objects.equal(this.lastLinkedJsBytes, that.lastLinkedJsBytes)
+        && Objects.equal(this.lastModifiedByDiskSourcePath, that.lastModifiedByDiskSourcePath)
+        && Objects.equal(this.lastModifiedByResourcePath, that.lastModifiedByResourcePath)
+        && Objects.equal(this.lastReachableTypeNames, that.lastReachableTypeNames)
+        && Objects.equal(this.modifiedCompilationUnitNames, that.modifiedCompilationUnitNames)
+        && Objects.equal(this.modifiedDiskSourcePaths, that.modifiedDiskSourcePaths)
+        && Objects.equal(this.modifiedResourcePaths, that.modifiedResourcePaths)
+        && Objects.equal(this.nestedTypeNamesByUnitTypeName, that.nestedTypeNamesByUnitTypeName)
+        && this.jsIncrementalNamerState.hasSameContent(that.jsIncrementalNamerState)
+        && Objects.equal(this.preambleTypeNames, that.preambleTypeNames) && Objects.equal(
+            this.rebinderTypeNamesByReboundTypeName, that.rebinderTypeNamesByReboundTypeName)
+        && Objects.equal(this.reboundTypeNamesByGeneratedCompilationUnitNames,
+            that.reboundTypeNamesByGeneratedCompilationUnitNames)
+        && Objects.equal(this.reboundTypeNamesByInputResource, that.reboundTypeNamesByInputResource)
+        && Objects.equal(this.referencedTypeNamesByTypeName, that.referencedTypeNamesByTypeName)
+        && Objects.equal(this.rootTypeNames, that.rootTypeNames)
+        && Objects.equal(this.singleJsoImplInterfaceNames, that.singleJsoImplInterfaceNames)
+        && Objects.equal(this.sourceCompilationUnitNames, that.sourceCompilationUnitNames)
+        && Objects.equal(this.sourceMapsByTypeName, that.sourceMapsByTypeName)
+        && Objects.equal(this.staleTypeNames, that.staleTypeNames)
+        && Objects.equal(this.statementRangesByTypeName, that.statementRangesByTypeName)
+        && this.typeEnvironment.hasSameContent(that.typeEnvironment)
+        && Objects.equal(this.typeNamesByReferencingTypeName, that.typeNamesByReferencingTypeName);
+  }
+
   private void appendReferencingTypes(Set<String> accumulatedTypeNames,
       Collection<String> referencedTypeNames) {
     for (String referencedTypeName : referencedTypeNames) {
diff --git a/dev/core/src/com/google/gwt/dev/StringAnalyzableTypeEnvironment.java b/dev/core/src/com/google/gwt/dev/StringAnalyzableTypeEnvironment.java
new file mode 100644
index 0000000..9723497
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/StringAnalyzableTypeEnvironment.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2014 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;
+
+import com.google.gwt.dev.jjs.impl.RapidTypeAnalyzer.AnalyzableTypeEnvironment;
+import com.google.gwt.dev.util.collect.IntHashMultimap;
+import com.google.gwt.dev.util.collect.IntMultimap;
+import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
+import com.google.gwt.thirdparty.guava.common.base.Objects;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
+import com.google.gwt.thirdparty.guava.common.collect.Maps;
+
+import cern.colt.list.IntArrayList;
+import cern.colt.map.OpenIntIntHashMap;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An AnalyzableTypeEnvironment that is built up from inserted method and type name strings.
+ */
+public class StringAnalyzableTypeEnvironment implements AnalyzableTypeEnvironment, Serializable {
+
+  @SuppressWarnings({"rawtypes", "unchecked"})
+  private static void copyCollection(Collection fromCollection, Collection toCollection) {
+    toCollection.clear();
+    toCollection.addAll(fromCollection);
+  }
+
+  private static void copyCollection(IntArrayList fromCollection, IntArrayList toCollection) {
+    toCollection.clear();
+    toCollection.addAllOf(fromCollection);
+  }
+
+  @SuppressWarnings({"rawtypes", "unchecked"})
+  private static void copyMap(Map fromMap, Map toMap) {
+    toMap.clear();
+    toMap.putAll(fromMap);
+  }
+
+  private static void copyMap(OpenIntIntHashMap fromMap, OpenIntIntHashMap toMap) {
+    IntArrayList keys = fromMap.keys();
+    for (int i = 0; i < keys.size(); i++) {
+      int key = keys.get(i);
+      int value = fromMap.get(key);
+      toMap.put(key, value);
+    }
+  }
+
+  private static void copyMultimap(IntMultimap fromMap, IntMultimap toMap) {
+    toMap.clear();
+    toMap.putAll(fromMap);
+  }
+
+  private final IntMultimap calleeMethodIdsByCallerMethodId = new IntMultimap();
+  private final OpenIntIntHashMap enclosingTypeIdByMethodId = new OpenIntIntHashMap();
+  private final IntArrayList entryMethodIds = new IntArrayList();
+  private final IntMultimap exportedMethodIdsByTypeId = new IntMultimap();
+  private final IntMultimap instantiatedTypeIdsByMethodId = new IntMultimap();
+  private final IntHashMultimap memberMethodIdsByTypeId = new IntHashMultimap();
+  private final Map<String, Integer> methodIdsByName = Maps.newHashMap();
+  private final List<String> methodNamesById = Lists.newArrayList();
+  private MinimalRebuildCache minimalRebuildCache;
+  private final IntMultimap overidingMethodIdsByOverriddenMethodId = new IntMultimap();
+  private final IntHashMultimap overriddenMethodIdsByOverridingMethodId = new IntHashMultimap();
+  private final IntHashMultimap staticallyReferencedTypeIdsByMethodId = new IntHashMultimap();
+  private final Map<String, Integer> typeIdsByName = Maps.newHashMap();
+  private final OpenIntIntHashMap typeIdsWithExportedStaticReferences = new OpenIntIntHashMap();
+  private final List<String> typeNamesById = Lists.newArrayList();
+
+  StringAnalyzableTypeEnvironment(MinimalRebuildCache minimalRebuildCache) {
+    this.minimalRebuildCache = minimalRebuildCache;
+  }
+
+  @Override
+  public IntArrayList getMemberMethodIdsIn(int enclosingTypeId) {
+    return memberMethodIdsByTypeId.get(enclosingTypeId);
+  }
+
+  @Override
+  public IntArrayList getMethodIdsCalledBy(int callerMethodId) {
+    return calleeMethodIdsByCallerMethodId.get(callerMethodId);
+  }
+
+  @Override
+  public IntArrayList getOverriddenMethodIds(int overridingMethodId) {
+    return overriddenMethodIdsByOverridingMethodId.get(overridingMethodId);
+  }
+
+  @Override
+  public IntArrayList getOverridingMethodIds(int overriddenMethodId) {
+    return overidingMethodIdsByOverriddenMethodId.get(overriddenMethodId);
+  }
+
+  @Override
+  public IntArrayList getStaticallyReferencedTypeIdsIn(int reachableMethodId) {
+    return staticallyReferencedTypeIdsByMethodId.get(reachableMethodId);
+  }
+
+  @Override
+  public int getSuperTypeId(int typeId) {
+    String typeName = getTypeNameById(typeId);
+    String superTypeName = minimalRebuildCache.getImmediateTypeRelations()
+        .getImmediateSuperclassesByClass().get(typeName);
+    if (superTypeName != null) {
+      return getTypeIdByName(superTypeName);
+    }
+    return -1;
+  }
+
+  @Override
+  public IntArrayList getTypeIdsInstantiatedIn(int inMethodId) {
+    return instantiatedTypeIdsByMethodId.get(inMethodId);
+  }
+
+  public void recordExportedMethodInType(String methodName, String typeName) {
+    int typeId = getTypeIdByName(typeName);
+    int methodId = getMethodIdByName(methodName);
+    exportedMethodIdsByTypeId.put(typeId, methodId);
+  }
+
+  public void recordExportedStaticReferenceInType(String typeName) {
+    int typeId = getTypeIdByName(typeName);
+    typeIdsWithExportedStaticReferences.put(typeId, typeId);
+  }
+
+  public void recordMethodCallsMethod(String callerMethodName, String calleeMethodName) {
+    calleeMethodIdsByCallerMethodId.put(getMethodIdByName(callerMethodName),
+        getMethodIdByName(calleeMethodName));
+  }
+
+  public void recordMethodInstantiatesType(String methodName, String instantiatedTypeName) {
+    instantiatedTypeIdsByMethodId.put(getMethodIdByName(methodName),
+        getTypeIdByName(instantiatedTypeName));
+  }
+
+  public void recordMethodOverridesMethod(String overriderMethodName, String overriddenMethodName) {
+    int overriderMethodId = getMethodIdByName(overriderMethodName);
+    int overriddenMethodId = getMethodIdByName(overriddenMethodName);
+    overriddenMethodIdsByOverridingMethodId.put(overriderMethodId, overriddenMethodId);
+    overidingMethodIdsByOverriddenMethodId.put(overriddenMethodId, overriderMethodId);
+  }
+
+  public void recordStaticReferenceInMethod(String typeName, String methodName) {
+    staticallyReferencedTypeIdsByMethodId.put(getMethodIdByName(methodName),
+        getTypeIdByName(typeName));
+  }
+
+  public void recordTypeEnclosesMethod(String enclosingTypeName, String nestedMethodName) {
+    int enclosingTypeId = getTypeIdByName(enclosingTypeName);
+    int nestedMethodId = getMethodIdByName(nestedMethodName);
+    memberMethodIdsByTypeId.put(enclosingTypeId, nestedMethodId);
+    enclosingTypeIdByMethodId.put(nestedMethodId, enclosingTypeId);
+  }
+
+  /**
+   * Remove control flow index entries that are created by the processing of the given type.
+   */
+  public void removeControlFlowIndexesFor(String typeName) {
+    int typeId = getTypeIdByName(typeName);
+    exportedMethodIdsByTypeId.remove(typeId);
+    typeIdsWithExportedStaticReferences.removeKey(typeId);
+
+    IntArrayList memberMethodIds = memberMethodIdsByTypeId.get(typeId);
+    if (memberMethodIds == null) {
+      return;
+    }
+    memberMethodIdsByTypeId.remove(typeId);
+    for (int i = 0; i < memberMethodIds.size(); i++) {
+      int memberMethodId = memberMethodIds.get(i);
+      enclosingTypeIdByMethodId.removeKey(memberMethodId);
+      calleeMethodIdsByCallerMethodId.remove(memberMethodId);
+      instantiatedTypeIdsByMethodId.remove(memberMethodId);
+
+      IntArrayList overriddenMethodIds =
+          overriddenMethodIdsByOverridingMethodId.remove(memberMethodId);
+      if (overriddenMethodIds != null) {
+        for (int j = 0; j < overriddenMethodIds.size(); j++) {
+          int overriddenMethodId = overriddenMethodIds.get(j);
+          overidingMethodIdsByOverriddenMethodId.remove(memberMethodId, overriddenMethodId);
+        }
+      }
+      staticallyReferencedTypeIdsByMethodId.remove(memberMethodId);
+    }
+  }
+
+  public void setEntryMethodNames(List<String> entryMethodNames) {
+    this.entryMethodIds.clear();
+    for (String entryMethodName : entryMethodNames) {
+      this.entryMethodIds.add(getMethodIdByName(entryMethodName));
+    }
+  }
+
+  void copyFrom(StringAnalyzableTypeEnvironment that) {
+    copyMap(that.typeIdsWithExportedStaticReferences, this.typeIdsWithExportedStaticReferences);
+    copyMap(that.enclosingTypeIdByMethodId, this.enclosingTypeIdByMethodId);
+    copyMap(that.methodIdsByName, this.methodIdsByName);
+    copyMap(that.typeIdsByName, this.typeIdsByName);
+
+    copyMultimap(that.memberMethodIdsByTypeId, this.memberMethodIdsByTypeId);
+    copyMultimap(that.calleeMethodIdsByCallerMethodId, this.calleeMethodIdsByCallerMethodId);
+    copyMultimap(that.exportedMethodIdsByTypeId, this.exportedMethodIdsByTypeId);
+    copyMultimap(that.instantiatedTypeIdsByMethodId, this.instantiatedTypeIdsByMethodId);
+    copyMultimap(that.overidingMethodIdsByOverriddenMethodId,
+        this.overidingMethodIdsByOverriddenMethodId);
+    copyMultimap(that.overriddenMethodIdsByOverridingMethodId,
+        this.overriddenMethodIdsByOverridingMethodId);
+    copyMultimap(that.staticallyReferencedTypeIdsByMethodId,
+        this.staticallyReferencedTypeIdsByMethodId);
+
+    copyCollection(that.entryMethodIds, this.entryMethodIds);
+    copyCollection(that.methodNamesById, this.methodNamesById);
+    copyCollection(that.typeNamesById, this.typeNamesById);
+  }
+
+  int getEnclosingTypeId(int memberMethodId) {
+    return enclosingTypeIdByMethodId.get(memberMethodId);
+  }
+
+  IntArrayList getEnclosingTypeIdsOfExportedMethods() {
+    return exportedMethodIdsByTypeId.keys();
+  }
+
+  IntArrayList getEntryMethodIds() {
+    return entryMethodIds;
+  }
+
+  IntArrayList getExportedMemberMethodIdsIn(int enclosingTypeId) {
+    return exportedMethodIdsByTypeId.get(enclosingTypeId);
+  }
+
+  int getMethodIdByName(String methodName) {
+    if (methodIdsByName.containsKey(methodName)) {
+      return methodIdsByName.get(methodName);
+    }
+    int methodId = methodNamesById.size();
+    methodIdsByName.put(methodName, methodId);
+    methodNamesById.add(methodName);
+    return methodId;
+  }
+
+  int getTypeIdByName(String typeName) {
+    if (typeIdsByName.containsKey(typeName)) {
+      return typeIdsByName.get(typeName);
+    }
+    int typeId = typeNamesById.size();
+    typeIdsByName.put(typeName, typeId);
+    typeNamesById.add(typeName);
+    return typeId;
+  }
+
+  IntArrayList getTypeIdsWithExportedStaticReferences() {
+    return typeIdsWithExportedStaticReferences.keys();
+  }
+
+  String getTypeNameById(int typeId) {
+    return typeNamesById.get(typeId);
+  }
+
+  @VisibleForTesting
+  boolean hasSameContent(StringAnalyzableTypeEnvironment that) {
+    return Objects.equal(this.calleeMethodIdsByCallerMethodId, that.calleeMethodIdsByCallerMethodId)
+        && Objects.equal(this.memberMethodIdsByTypeId, that.memberMethodIdsByTypeId)
+        && Objects.equal(this.entryMethodIds, that.entryMethodIds)
+        && Objects.equal(this.instantiatedTypeIdsByMethodId, that.instantiatedTypeIdsByMethodId)
+        && Objects.equal(this.overidingMethodIdsByOverriddenMethodId,
+            that.overidingMethodIdsByOverriddenMethodId) && Objects.equal(
+            this.overriddenMethodIdsByOverridingMethodId,
+            that.overriddenMethodIdsByOverridingMethodId) && Objects.equal(
+            this.staticallyReferencedTypeIdsByMethodId, that.staticallyReferencedTypeIdsByMethodId)
+        && Objects.equal(this.enclosingTypeIdByMethodId, that.enclosingTypeIdByMethodId) && Objects
+            .equal(this.exportedMethodIdsByTypeId, that.exportedMethodIdsByTypeId) && Objects.equal(
+            this.typeIdsWithExportedStaticReferences, that.typeIdsWithExportedStaticReferences);
+  }
+}
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 e89bf9e..6588b36 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -64,6 +64,7 @@
 import com.google.gwt.dev.jjs.impl.AssertionRemover;
 import com.google.gwt.dev.jjs.impl.AstDumper;
 import com.google.gwt.dev.jjs.impl.CompileTimeConstantsReplacer;
+import com.google.gwt.dev.jjs.impl.ControlFlowRecorder;
 import com.google.gwt.dev.jjs.impl.DeadCodeElimination;
 import com.google.gwt.dev.jjs.impl.EnumOrdinalizer;
 import com.google.gwt.dev.jjs.impl.Finalizer;
@@ -275,7 +276,7 @@
         // AST has happened (to record for example reference to types declaring compile-time
         // constants) and 2) after all normalizations to collect synthetic references (e.g. to
         // record references to runtime classes like LongLib).
-        maybeRecordTypeReferences(false);
+        maybeRecordReferencesAndControlFlow(false);
 
         // Replace compile time constants by their values.
         // TODO(rluble): eventually move to normizeSemantics.
@@ -293,7 +294,7 @@
         postNormalizationOptimizeJava();
 
         // Now that the AST has stopped mutating update with the final references.
-        maybeRecordTypeReferences(true);
+        maybeRecordReferencesAndControlFlow(true);
 
         jprogram.typeOracle.recomputeAfterOptimizations(jprogram.getDeclaredTypes());
 
@@ -375,11 +376,13 @@
       }
     }
 
-    private void maybeRecordTypeReferences(boolean onlyUpdate) {
+    private void maybeRecordReferencesAndControlFlow(boolean onlyUpdate) {
       if (options.isIncrementalCompileEnabled()) {
         // Per file compilation needs the type reference graph to construct the set of reachable
         // types when linking.
-        TypeReferencesRecorder.exec(jprogram, getMinimalRebuildCache(),onlyUpdate);
+        TypeReferencesRecorder.exec(jprogram, getMinimalRebuildCache(), onlyUpdate);
+        ControlFlowRecorder.exec(jprogram, getMinimalRebuildCache().getTypeEnvironment(),
+            onlyUpdate);
       }
     }
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowRecorder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowRecorder.java
new file mode 100644
index 0000000..dde4a23
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowRecorder.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2014 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.StringAnalyzableTypeEnvironment;
+import com.google.gwt.dev.jjs.ast.Context;
+import com.google.gwt.dev.jjs.ast.JClassLiteral;
+import com.google.gwt.dev.jjs.ast.JDeclaredType;
+import com.google.gwt.dev.jjs.ast.JField;
+import com.google.gwt.dev.jjs.ast.JFieldRef;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JMethodCall;
+import com.google.gwt.dev.jjs.ast.JNewInstance;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.ast.JType;
+import com.google.gwt.dev.jjs.ast.JVisitor;
+import com.google.gwt.dev.jjs.ast.js.JsniFieldRef;
+import com.google.gwt.dev.jjs.ast.js.JsniMethodRef;
+import com.google.gwt.thirdparty.guava.common.collect.Sets;
+
+import java.util.Set;
+
+/**
+ * Records control flow information.
+ * <p>
+ * Collects caller->callee, instantiating method->instantiated type, overridden method->overriding
+ * method, exported methods and other control flow information in TypeEnvironment indexes to support
+ * control flow based link time pruning.
+ */
+public class ControlFlowRecorder extends JVisitor {
+
+  public static void exec(JProgram program,
+      StringAnalyzableTypeEnvironment stringAnalyzableTypeEnvironment, boolean onlyUpdate) {
+    new ControlFlowRecorder(stringAnalyzableTypeEnvironment, onlyUpdate, program).execImpl();
+  }
+
+  private static String computeName(JMethod method) {
+    return method.getJsniSignature(true, false);
+  }
+
+  private final Set<String> bannedMethodNames = Sets.newHashSet();
+  private String currentMethodName;
+  private final boolean onlyUpdate;
+  private final JProgram program;
+  private final StringAnalyzableTypeEnvironment stringAnalyzableTypeEnvironment;
+
+  public ControlFlowRecorder(StringAnalyzableTypeEnvironment stringAnalyzableTypeEnvironment,
+      boolean onlyUpdate, JProgram program) {
+    this.stringAnalyzableTypeEnvironment = stringAnalyzableTypeEnvironment;
+    this.onlyUpdate = onlyUpdate;
+    this.program = program;
+
+    bannedMethodNames.add(computeName(program.getTypeClassLiteralHolder().getClinitMethod()));
+  }
+
+  @Override
+  public void endVisit(JClassLiteral x, Context ctx) {
+    JType type = x.getRefType();
+    if (type instanceof JDeclaredType) {
+      String typeName = type.getName();
+      stringAnalyzableTypeEnvironment.recordStaticReferenceInMethod(typeName, currentMethodName);
+      maybeRecordClinitCall(typeName);
+    }
+  }
+
+  @Override
+  public void endVisit(JFieldRef x, Context ctx) {
+    processJFieldRef(x);
+  }
+
+  @Override
+  public void endVisit(JsniFieldRef x, Context ctx) {
+    processJFieldRef(x);
+  }
+
+  @Override
+  public void endVisit(JsniMethodRef x, Context ctx) {
+    processMethodCall(x);
+  }
+
+  @Override
+  public boolean visit(JDeclaredType x, Context ctx) {
+    if (!onlyUpdate) {
+      stringAnalyzableTypeEnvironment.removeControlFlowIndexesFor(x.getName());
+    }
+
+    return true;
+  }
+
+  @Override
+  public boolean visit(JField x, Context ctx) {
+    String typeName = x.getEnclosingType().getName();
+
+    if (program.typeOracle.isExportedField(x) && x.isStatic()) {
+      stringAnalyzableTypeEnvironment.recordExportedStaticReferenceInType(typeName);
+    }
+
+    return true;
+  }
+
+  @Override
+  public boolean visit(JMethod x, Context ctx) {
+    String typeName = x.getEnclosingType().getName();
+    currentMethodName = computeName(x);
+
+    if (bannedMethodNames.contains(currentMethodName)) {
+      return false;
+    }
+
+    stringAnalyzableTypeEnvironment.recordTypeEnclosesMethod(typeName, currentMethodName);
+
+    for (JMethod overriddenMethod : program.typeOracle.getOverriddenMethodsOf(x)) {
+      String overriddenMethodName = computeName(overriddenMethod);
+      stringAnalyzableTypeEnvironment.recordMethodOverridesMethod(currentMethodName,
+          overriddenMethodName);
+    }
+
+    if (program.typeOracle.isExportedMethod(x) || program.typeOracle.isJsTypeMethod(x)) {
+      if (x.isStatic() || x.isConstructor()) {
+        stringAnalyzableTypeEnvironment.recordExportedStaticReferenceInType(typeName);
+      }
+      stringAnalyzableTypeEnvironment.recordExportedMethodInType(currentMethodName, typeName);
+    }
+
+    return true;
+  }
+
+  @Override
+  public boolean visit(JMethodCall x, Context ctx) {
+    processMethodCall(x);
+    return true;
+  }
+
+  @Override
+  public boolean visit(JNewInstance x, Context ctx) {
+    String typeName = x.getTarget().getEnclosingType().getName();
+    stringAnalyzableTypeEnvironment.recordMethodInstantiatesType(currentMethodName, typeName);
+    maybeRecordClinitCall(typeName);
+    // Apply JMethodCall handling as well.
+    return super.visit(x, ctx);
+  }
+
+  private void execImpl() {
+    accept(program);
+  }
+
+  private void maybeRecordClinitCall(String typeName) {
+    String typeClinitMethod = typeName + "::$clinit()";
+    if (!typeClinitMethod.equals(currentMethodName)) {
+      stringAnalyzableTypeEnvironment.recordMethodCallsMethod(currentMethodName, typeClinitMethod);
+    }
+  }
+
+  private void processJFieldRef(JFieldRef x) {
+    if (x.getTarget() instanceof JField) {
+      JField field = (JField) x.getTarget();
+      if (field.isStatic()) {
+        String typeName = field.getEnclosingType().getName();
+        stringAnalyzableTypeEnvironment.recordStaticReferenceInMethod(typeName, currentMethodName);
+        maybeRecordClinitCall(typeName);
+      }
+    }
+  }
+
+  private void processMethodCall(JMethodCall x) {
+    JMethod targetMethod = x.getTarget();
+    String calleeMethodName = computeName(targetMethod);
+    stringAnalyzableTypeEnvironment.recordMethodCallsMethod(currentMethodName, calleeMethodName);
+
+    if (targetMethod.isStatic()) {
+      String typeName = targetMethod.getEnclosingType().getName();
+      stringAnalyzableTypeEnvironment.recordStaticReferenceInMethod(typeName, currentMethodName);
+      maybeRecordClinitCall(typeName);
+    }
+
+    // Instantiations in JSNI don't use JNewInstance and must be recognized by method calls on
+    // Constructor functions.
+    if (targetMethod.isConstructor()) {
+      String typeName = targetMethod.getEnclosingType().getName();
+      stringAnalyzableTypeEnvironment.recordMethodInstantiatesType(currentMethodName, typeName);
+      maybeRecordClinitCall(typeName);
+    }
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
index 3cd916e..4d02bad 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
@@ -2466,8 +2466,7 @@
             continue;
           }
           if (m.getAccess() == AccessModifier.PUBLIC
-            && (m.isStatic() ||
-              (m instanceof JConstructor))) {
+              && (m.isStatic() || (m instanceof JConstructor))) {
             m.setExportName("");
           }
         }
@@ -2475,7 +2474,7 @@
           if (f.getExportName() != null) {
             continue;
           }
-          if (f.isStatic()) {
+          if (f.isStatic() || type instanceof JInterfaceType) {
             f.setExportName("");
           }
         }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/RapidTypeAnalyzer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/RapidTypeAnalyzer.java
new file mode 100644
index 0000000..18ed6d8
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/RapidTypeAnalyzer.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright 2014 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.util.collect.IntMultimap;
+import com.google.gwt.dev.util.collect.IntStack;
+
+import cern.colt.list.IntArrayList;
+import cern.colt.map.OpenIntIntHashMap;
+
+import java.util.BitSet;
+
+/**
+ * Uses method and type control flow indexes to compute a reachable types set.
+ * <p>
+ * Achieves high performance by operating strictly on methods and types which have been mapped to
+ * unique int ids but this performance comes at the cost of forcing callers to provide such a
+ * compliant AnalyzableTypeEnvironment.
+ */
+public class RapidTypeAnalyzer {
+
+  /**
+   * An interface for a type environment sufficient for rapid type analysis.
+   */
+  public interface AnalyzableTypeEnvironment {
+
+    /**
+     * Returns a list of the ids of member methods in the given type and null if there are none.
+     */
+    IntArrayList getMemberMethodIdsIn(int enclosingTypeId);
+
+    /**
+     * Returns a list of the ids of methods called by the given method and null if there are none.
+     */
+    IntArrayList getMethodIdsCalledBy(int callerMethodId);
+
+    /**
+     * Returns a list of the ids of methods overriding the given method and null if there are none.
+     */
+    IntArrayList getOverriddenMethodIds(int overridingMethodId);
+
+    /**
+     * Returns a list of the ids of methods overridden by the given method and null if there are
+     * none.
+     */
+    IntArrayList getOverridingMethodIds(int overriddenMethodId);
+
+    /**
+     * Returns a list of the ids of types that are statically referenced within the given method and
+     * null if there are none.
+     */
+    IntArrayList getStaticallyReferencedTypeIdsIn(int methodId);
+
+    /**
+     * Returns the id of the super type of the given type and -1 if there is no super type.
+     */
+    int getSuperTypeId(int typeId);
+
+    /**
+     * Returns a list of the ids of types instantiated within the given method and null if there are
+     * none.
+     */
+    IntArrayList getTypeIdsInstantiatedIn(int methodId);
+  }
+
+  /**
+   * Contains control flow indexes upon which this analysis is based.
+   */
+  private AnalyzableTypeEnvironment analyzableTypeEnvironment;
+
+  /**
+   * The set of ids of types which have so far been found to be instantiated. They are tracked so
+   * that so processing the consequences of the instantiation of a type can be limited to just once
+   * per type.
+   */
+  private BitSet instantiatedTypeIds = new BitSet();
+
+  /**
+   * A mapping from known-to-exist method ids to the ids of methods that both override them and are
+   * members in types that are known to be instantiated. If at some point a known-to-exist method
+   * becomes reachable then the previously characterized associated overriding methods immediately
+   * become reachable as well.
+   */
+  private IntMultimap knownOverridingMethodIdsByOverriddenMethodId = new IntMultimap();
+
+  /**
+   * The ids of all methods that override some already known reachable method. If at some point a
+   * type becomes instantiated than any of its methods that are already in this set suddenly become
+   * reachable.
+   */
+  private BitSet overidingMethodIdsOfReachableMethods = new BitSet();
+
+  /**
+   * Ids of methods that have been reached and processed and should not be reprocessed.
+   * <p>
+   * AKA the closed set.
+   */
+  private BitSet reachableMethodIds = new BitSet();
+
+  /**
+   * The answer calculated by this reachability analyzer, aka the accumulation of ids of types that
+   * have been calculated to be reachable.
+   */
+  private OpenIntIntHashMap reachableTypeIds = new OpenIntIntHashMap();
+
+  /**
+   * Ids of methods that are known to be reachable but for which the consequences of that
+   * reachability has not been processed. As exploration progresses newly seen methods are enqueued
+   * here for later processing.
+   * <p>
+   * AKA the open set.
+   */
+  private IntStack unprocessedReachableMethodIds = new IntStack();
+
+  public RapidTypeAnalyzer(AnalyzableTypeEnvironment analyzableTypeEnvironment) {
+    this.analyzableTypeEnvironment = analyzableTypeEnvironment;
+  }
+
+  /**
+   * Follow control flow to find and return the set of ids of reachable types.
+   */
+  public IntArrayList computeReachableTypeIds() {
+    while (!unprocessedReachableMethodIds.isEmpty()) {
+      int reachableMethodId = unprocessedReachableMethodIds.pop();
+
+      addReachableTypeIds(
+          analyzableTypeEnvironment.getStaticallyReferencedTypeIdsIn(reachableMethodId));
+      markTypeIdsInstantiated(
+          analyzableTypeEnvironment.getTypeIdsInstantiatedIn(reachableMethodId));
+      markMethodIdsReachable(analyzableTypeEnvironment.getMethodIdsCalledBy(reachableMethodId),
+          true);
+    }
+    return reachableTypeIds.keys();
+  }
+
+  /**
+   * Enqueues the methods within a given type (and some related overriding method ids) as reachable
+   * and not yet processed.
+   */
+  public void markMemberMethodIdsReachable(int typeId) {
+    IntArrayList memberMethodIds = analyzableTypeEnvironment.getMemberMethodIdsIn(typeId);
+    if (memberMethodIds == null) {
+      return;
+    }
+    markMethodIdsReachable(memberMethodIds, true);
+  }
+
+  /**
+   * Enqueues a given method id (and some related overriding method ids) as reachable and not yet
+   * processed.
+   */
+  public void markMethodIdReachable(int methodId, boolean cascade) {
+    // If the given method has already been marked reachable.
+    if (reachableMethodIds.get(methodId)) {
+      // Ignore it.
+      return;
+    }
+
+    overidingMethodIdsOfReachableMethods.set(methodId);
+    unprocessedReachableMethodIds.push(methodId);
+    reachableMethodIds.set(methodId);
+    recordOverridingMethodIdsOfReachableMethod(methodId);
+
+    if (cascade) {
+      // The current method is being marked reachable so any methods that override it should be
+      // marked reachable as well.
+      IntArrayList values = knownOverridingMethodIdsByOverriddenMethodId.get(methodId);
+      if (values != null) {
+        for (int i = 0; i < values.size(); i++) {
+          int overridingMethodId = values.get(i);
+          // Don't cascade in this nested invocation since at this point any overriding methods are
+          // already marked reachable.
+          markMethodIdReachable(overridingMethodId, false);
+        }
+      }
+    }
+  }
+
+  /**
+   * Records that a type is reachable.
+   */
+  public void markTypeIdReachable(int typeId) {
+    reachableTypeIds.put(typeId, typeId);
+  }
+
+  private void addReachableTypeIds(IntArrayList typeIds) {
+    if (typeIds == null) {
+      return;
+    }
+    for (int i = 0; i < typeIds.size(); i++) {
+      int typeId = typeIds.get(i);
+      markTypeIdReachable(typeId);
+    }
+  }
+
+  private void markMethodIdsReachable(IntArrayList methodIds, boolean cascade) {
+    if (methodIds == null) {
+      return;
+    }
+    for (int i = 0; i < methodIds.size(); i++) {
+      markMethodIdReachable(methodIds.get(i), cascade);
+    }
+  }
+
+  private void markTypeIdInstantiated(int typeId) {
+    if (!instantiatedTypeIds.get(typeId)) {
+      instantiatedTypeIds.set(typeId);
+      markTypeIdReachable(typeId);
+      IntArrayList memberMethodIds = analyzableTypeEnvironment.getMemberMethodIdsIn(typeId);
+
+      if (memberMethodIds != null) {
+        for (int i = 0; i < memberMethodIds.size(); i++) {
+          int memberMethodId = memberMethodIds.get(i);
+          IntArrayList overriddenMethodIds =
+              analyzableTypeEnvironment.getOverriddenMethodIds(memberMethodId);
+          if (overriddenMethodIds != null) {
+            for (int j = 0; j < overriddenMethodIds.size(); j++) {
+              int overriddenMethodId = overriddenMethodIds.get(j);
+              knownOverridingMethodIdsByOverriddenMethodId.put(overriddenMethodId, memberMethodId);
+            }
+          }
+        }
+
+        for (int i = 0; i < memberMethodIds.size(); i++) {
+          int memberMethodId = memberMethodIds.get(i);
+          if (overidingMethodIdsOfReachableMethods.get(memberMethodId)) {
+            markMethodIdReachable(memberMethodId, true);
+          }
+        }
+      }
+
+      int superTypeId = analyzableTypeEnvironment.getSuperTypeId(typeId);
+      if (superTypeId != -1) {
+        markTypeIdInstantiated(superTypeId);
+      }
+    }
+  }
+
+  private void markTypeIdsInstantiated(IntArrayList typeIds) {
+    if (typeIds == null) {
+      return;
+    }
+    for (int i = 0; i < typeIds.size(); i++) {
+      int typeId = typeIds.get(i);
+      markTypeIdInstantiated(typeId);
+    }
+  }
+
+  /**
+   * Records the ids of methods that override the provided known reachable method, so that they can
+   * themselves be efficiently marked reachable at some later time if their enclosing type is
+   * instantiated.
+   */
+  private void recordOverridingMethodIdsOfReachableMethod(int methodId) {
+    IntArrayList overridingMethodIds = analyzableTypeEnvironment.getOverridingMethodIds(methodId);
+    if (overridingMethodIds != null) {
+      for (int i = 0; i < overridingMethodIds.size(); i++) {
+        int overridingMethodId = overridingMethodIds.get(i);
+        overidingMethodIdsOfReachableMethods.set(overridingMethodId);
+      }
+    }
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeReferencesRecorder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeReferencesRecorder.java
index 398c30e..6af0dc4 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeReferencesRecorder.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeReferencesRecorder.java
@@ -37,7 +37,7 @@
 import java.util.List;
 
 /**
- * Collects and Type->Type references.
+ * Records Type->Type references.
  */
 public class TypeReferencesRecorder extends JVisitor {
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java b/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
index 6295000..179caf8 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
@@ -773,8 +773,10 @@
    */
   public void exec() throws UnableToCompleteException {
     // Trace execution from entry points and resolve references.
+    List<String> entryMethodNames = new ArrayList<String>();
     for (JMethod entryMethod : program.getEntryMethods()) {
       flowInto(entryMethod);
+      entryMethodNames.add(entryMethod.getJsniSignature(true, false));
     }
 
     // Ensure that root types are loaded and possibly (depending on mode) traversed.
@@ -792,6 +794,7 @@
       }
     }
     minimalRebuildCache.setRootTypeNames(rootTypeBinaryNames);
+    minimalRebuildCache.setEntryMethodNames(entryMethodNames);
 
     // Some fields and methods in codegen types might only become referenced as the result of
     // visitor execution after unification. Since we don't want those fields are methods to be
diff --git a/dev/core/src/com/google/gwt/dev/util/collect/IntHashMultimap.java b/dev/core/src/com/google/gwt/dev/util/collect/IntHashMultimap.java
new file mode 100644
index 0000000..ffe488a
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/collect/IntHashMultimap.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014 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.util.collect;
+
+import cern.colt.list.IntArrayList;
+
+/**
+ * An int multimap that cannot hold duplicate key-value pairs.
+ * <p>
+ * Because only int primitives are used performance and memory usage can surpass Object set
+ * multimaps.
+ */
+public class IntHashMultimap extends IntMultimap {
+
+  @Override
+  public void put(int key, int value) {
+    Object objectValues = map.get(key);
+    if (objectValues != null) {
+      IntArrayList listValues = (IntArrayList) objectValues;
+      // Don't add duplicate values.
+      if (!listValues.contains(value)) {
+        listValues.add(value);
+      }
+    } else {
+      IntArrayList listValues = new IntArrayList();
+      listValues.add(value);
+      map.put(key, listValues);
+    }
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/collect/IntMultimap.java b/dev/core/src/com/google/gwt/dev/util/collect/IntMultimap.java
new file mode 100644
index 0000000..094c45a
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/collect/IntMultimap.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2014 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.util.collect;
+
+import cern.colt.list.IntArrayList;
+import cern.colt.map.OpenIntObjectHashMap;
+
+import java.io.Serializable;
+
+/**
+ * A collection that maps individual int keys to multiple int values.
+ * <p>
+ * Because only int primitives are used performance and memory usage can surpass Object multimaps.
+ */
+public class IntMultimap implements Serializable {
+
+  protected OpenIntObjectHashMap map = new OpenIntObjectHashMap();
+
+  public void clear() {
+    map.clear();
+  }
+
+  @Override
+  public boolean equals(Object object) {
+    if (object instanceof IntMultimap) {
+      IntMultimap that = (IntMultimap) object;
+
+      if (map.size() != that.map.size()) {
+        return false;
+      }
+
+      // Colt's OpenIntObjectHashMap.equals() is broken, so check the contents manually.
+      IntArrayList keys = map.keys();
+      for (int i = 0; i < keys.size(); i++) {
+        int key = keys.get(i);
+        if (!get(key).equals(that.get(key))) {
+          return false;
+        }
+      }
+
+      return true;
+    }
+    return false;
+  }
+
+  public IntArrayList get(int key) {
+    return (IntArrayList) map.get(key);
+  }
+
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+
+    IntArrayList keys = map.keys();
+    for (int i = 0; i < keys.size(); i++) {
+      int key = keys.get(i);
+      result = prime * result + key;
+
+      IntArrayList values = get(key);
+      if (values == null) {
+        continue;
+      }
+      for (int j = 0; j < values.size(); j++) {
+        int value = values.get(j);
+        result = prime * result + value;
+      }
+    }
+
+    return result;
+  }
+
+  public IntArrayList keys() {
+    return map.keys();
+  }
+
+  public void put(int key, int value) {
+    Object objectValues = map.get(key);
+    if (objectValues != null) {
+      IntArrayList listValues = (IntArrayList) objectValues;
+      listValues.add(value);
+    } else {
+      IntArrayList listValues = new IntArrayList();
+      listValues.add(value);
+      map.put(key, listValues);
+    }
+  }
+
+  public void putAll(IntMultimap thatMap) {
+    IntArrayList keys = thatMap.map.keys();
+    for (int key : keys.elements()) {
+      IntArrayList values = (IntArrayList) thatMap.map.get(key);
+      map.put(key, values.copy());
+    }
+  }
+
+  public IntArrayList remove(int key) {
+    IntArrayList values = get(key);
+    map.removeKey(key);
+    return values;
+  }
+
+  public void remove(int key, int value) {
+    IntArrayList values = get(key);
+    if (values != null) {
+      int valueIndex = values.indexOf(value);
+      if (valueIndex != -1) {
+        values.remove(valueIndex);
+      }
+    }
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/collect/IntStack.java b/dev/core/src/com/google/gwt/dev/util/collect/IntStack.java
new file mode 100644
index 0000000..9437497
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/collect/IntStack.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2014 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.util.collect;
+
+import cern.colt.list.IntArrayList;
+
+import java.io.Serializable;
+
+/**
+ * An int stack.
+ * <p>
+ * Because only int primitives are used performance and memory usage can surpass Object stacks.
+ */
+public class IntStack implements Serializable {
+
+  private IntArrayList values = new IntArrayList();
+
+  public boolean isEmpty() {
+    return values.isEmpty();
+  }
+
+  public int pop() {
+    int lastIndex = values.size() - 1;
+    int value = values.get(lastIndex);
+    values.remove(lastIndex);
+    return value;
+  }
+
+  public void push(int value) {
+    values.add(value);
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/CompilerTest.java b/dev/core/test/com/google/gwt/dev/CompilerTest.java
index dafa666..8cd2251 100644
--- a/dev/core/test/com/google/gwt/dev/CompilerTest.java
+++ b/dev/core/test/com/google/gwt/dev/CompilerTest.java
@@ -71,7 +71,7 @@
   private MockJavaResource referencesParentResource =
       JavaResourceBase.createMockJavaResource("com.foo.ReferencesParent",
           "package com.foo;",
-          "public abstract class ReferencesParent {",
+          "public class ReferencesParent {",
           "  void run() {",
           "    PackagePrivateParent packagePrivateParent = null;",
           "    packagePrivateParent.someFunction();",
@@ -85,8 +85,8 @@
           "public class TestEntryPoint implements EntryPoint {",
           "  @Override",
           "  public void onModuleLoad() {",
-          "    ReferencesParent referencesParent = null;",
-          "    PackagePrivateChild packagePrivateChild = null;",
+          "    ReferencesParent referencesParent = new ReferencesParent();",
+          "    PackagePrivateChild packagePrivateChild = new PackagePrivateChild();",
           "  }",
           "}");
 
@@ -1221,7 +1221,7 @@
     checkRecompiledModifiedApp("com.foo.SuperFromStaleInnerModule",
         Lists.newArrayList(superFromStaleInnerModuleResource,
             superFromStaleInnerEntryPointResource), interfaceOneResource, interfaceOneResource,
-        stringSet("com.foo.SuperFromStaleInnerEntryPoint$B$1", "com.foo.InterfaceOne"), output);
+        stringSet("com.foo.SuperFromStaleInnerEntryPoint$B$1"), output);
   }
 
   private void checkIncrementalRecompile_deterministicUiBinder(JsOutputOption output)
@@ -1230,7 +1230,7 @@
 
     checkRecompiledModifiedApp(compilerOptions, "com.foo.UiBinderTestModule", Lists.newArrayList(
         uiBinderTestModuleResource, uiBinderTestEntryPointResource, myWidgetUiXml), myWidget,
-        myWidget, stringSet("com.foo.MyWidget", "com.foo.MyWidget$Binder", "com.foo.TestEntryPoint",
+        myWidget, stringSet("com.foo.MyWidget", "com.foo.TestEntryPoint",
             "com.foo.MyWidget_BinderImpl", "com.foo.MyWidget_BinderImpl$Widgets"), output);
   }
 
@@ -1238,16 +1238,14 @@
       throws UnableToCompleteException, IOException, InterruptedException {
     // Switches from a white styled widget to a grey styled widget in the CSS in the style tag
     // nested in the .ui.xml template file.
+    String binderImpl = "com.foo.MyWidget_BinderImpl";
     checkRecompiledModifiedApp("com.foo.UiBinderTestModule",
         Lists.newArrayList(uiBinderTestModuleResource, uiBinderTestEntryPointResource, myWidget),
-        myWidgetWithWhiteStyleUiXml, myWidgetWithGreyStyleUiXml,
-        stringSet("com.foo.MyWidget",
-            "com.foo.MyWidget_BinderImpl$Template",
-            "com.foo.MyWidget_BinderImpl_GenBundle_default_InlineClientBundleGenerator",
-            "com.foo.MyWidget_BinderImpl_GenBundle_default_InlineClientBundleGenerator$1",
-            "com.foo.MyWidget_BinderImpl_TemplateImpl",
-            "com.foo.MyWidget_BinderImpl_GenBundle_default_InlineClientBundleGenerator$styleInitializer",
-            "com.foo.MyWidget_BinderImpl", "com.foo.MyWidget_BinderImpl$Widgets"), output);
+        myWidgetWithWhiteStyleUiXml, myWidgetWithGreyStyleUiXml, stringSet("com.foo.MyWidget",
+            binderImpl, binderImpl + "_GenBundle_default_InlineClientBundleGenerator",
+            binderImpl + "_GenBundle_default_InlineClientBundleGenerator$1",
+            binderImpl + "_GenBundle_default_InlineClientBundleGenerator$styleInitializer",
+            binderImpl + "_TemplateImpl", binderImpl + "$Widgets"), output);
   }
 
   private void checkIncrementalRecompile_packagePrivateOverride(JsOutputOption output)
diff --git a/dev/core/test/com/google/gwt/dev/MinimalRebuildCacheManagerTest.java b/dev/core/test/com/google/gwt/dev/MinimalRebuildCacheManagerTest.java
index 84ab280..3d1cf8f 100644
--- a/dev/core/test/com/google/gwt/dev/MinimalRebuildCacheManagerTest.java
+++ b/dev/core/test/com/google/gwt/dev/MinimalRebuildCacheManagerTest.java
@@ -75,6 +75,14 @@
         "Foo.java", 9999L).put("Bar.java", 0L).put("Baz.java", 0L).build();
     startingCache.recordDiskSourceResources(laterModifiedBySourcePath);
     startingCache.setRootTypeNames(Sets.newHashSet("Foo", "Bar", "Baz"));
+    StringAnalyzableTypeEnvironment typeEnvironment = startingCache.getTypeEnvironment();
+    typeEnvironment.recordTypeEnclosesMethod("Foo", "Foo::$clinit()");
+    typeEnvironment.recordTypeEnclosesMethod("Bar", "Bar::$clinit()");
+    typeEnvironment.recordTypeEnclosesMethod("Baz", "Baz::$clinit()");
+    typeEnvironment.recordMethodInstantiatesType("Foo::start()", "Bar");
+    typeEnvironment.recordMethodCallsMethod("Foo::start()", "Bar::run()");
+    typeEnvironment.recordMethodInstantiatesType("Bar::start()", "Baz");
+    typeEnvironment.recordMethodCallsMethod("Bar::run()", "Baz::run()");
     startingCache.computeReachableTypeNames();
     startingCache.computeAndClearStaleTypesCache(TreeLogger.NULL,
         new JTypeOracle(null, startingCache, true));
diff --git a/dev/core/test/com/google/gwt/dev/MinimalRebuildCacheTest.java b/dev/core/test/com/google/gwt/dev/MinimalRebuildCacheTest.java
index 6e78fac..91f88fd 100644
--- a/dev/core/test/com/google/gwt/dev/MinimalRebuildCacheTest.java
+++ b/dev/core/test/com/google/gwt/dev/MinimalRebuildCacheTest.java
@@ -16,6 +16,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.dev.jjs.ast.JTypeOracle;
 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;
 
 import junit.framework.TestCase;
@@ -36,12 +37,19 @@
     minimalRebuildCache.recordDiskSourceResources(currentModifiedBySourcePath);
 
     // They each contain a type and nested type.
+    StringAnalyzableTypeEnvironment typeEnvironment = minimalRebuildCache.getTypeEnvironment();
     minimalRebuildCache.recordNestedTypeName("Foo", "Foo");
+    typeEnvironment.recordTypeEnclosesMethod("Foo", "Foo::$clinit()");
     minimalRebuildCache.recordNestedTypeName("Foo", "Foo$Inner");
+    typeEnvironment.recordTypeEnclosesMethod("Foo$Inner", "Foo$Inner::$clinit()");
     minimalRebuildCache.recordNestedTypeName("Bar", "Bar");
+    typeEnvironment.recordTypeEnclosesMethod("Bar", "Bar::$clinit()");
     minimalRebuildCache.recordNestedTypeName("Bar", "Bar$Inner");
+    typeEnvironment.recordTypeEnclosesMethod("Bar$Inner", "Bar$Inner::$clinit()");
     minimalRebuildCache.recordNestedTypeName("Baz", "Baz");
+    typeEnvironment.recordTypeEnclosesMethod("Baz", "Baz::$clinit()");
     minimalRebuildCache.recordNestedTypeName("Baz", "Baz$Inner");
+    typeEnvironment.recordTypeEnclosesMethod("Baz$Inner", "Baz$Inner::$clinit()");
 
     // There's some JS for each type.
     minimalRebuildCache.setJsForType(TreeLogger.NULL, "Foo", "Some Js for Foo");
@@ -53,13 +61,23 @@
 
     // Record that Bar references Foo and Baz subclasses Foo.
     minimalRebuildCache.addTypeReference("Bar", "Foo");
+    typeEnvironment.recordMethodCallsMethod("Bar::start()", "Foo::run()");
+    typeEnvironment.recordStaticReferenceInMethod("Foo", "Bar::start()");
+    typeEnvironment.recordTypeEnclosesMethod("Bar", "Bar::start()");
+    typeEnvironment.recordTypeEnclosesMethod("Foo", "Foo::run()");
     minimalRebuildCache.getImmediateTypeRelations().getImmediateSuperclassesByClass().put("Baz",
         "Foo");
+    typeEnvironment.recordMethodCallsMethod("Foo::run()", "Baz::run()");
+    typeEnvironment.recordStaticReferenceInMethod("Baz", "Foo::run()");
+    typeEnvironment.recordTypeEnclosesMethod("Baz", "Baz::run()");
 
     // Record that these types reference their inner classes.
     minimalRebuildCache.addTypeReference("Foo", "Foo$Inner");
     minimalRebuildCache.addTypeReference("Bar", "Bar$Inner");
     minimalRebuildCache.addTypeReference("Baz", "Baz$Inner");
+    typeEnvironment.recordStaticReferenceInMethod("Bar$Inner", "Bar::start()");
+    typeEnvironment.recordStaticReferenceInMethod("Foo$Inner", "Foo::run()");
+    typeEnvironment.recordStaticReferenceInMethod("Baz$Inner", "Baz::run()");
 
     // In the next compile only Foo is modified.
     Map<String, Long> laterModifiedBySourcePath = new ImmutableMap.Builder<String, Long>().put(
@@ -68,6 +86,7 @@
 
     // Ensure the types are known to be reachable.
     minimalRebuildCache.setRootTypeNames(Sets.newHashSet("Foo", "Bar", "Baz"));
+    minimalRebuildCache.setEntryMethodNames(Lists.newArrayList("Bar::start()"));
     minimalRebuildCache.computeReachableTypeNames();
 
     // Request clearing of cache related to stale types.
diff --git a/dev/core/test/com/google/gwt/dev/UnstableNestedAnonymousGenerator.java b/dev/core/test/com/google/gwt/dev/UnstableNestedAnonymousGenerator.java
index 2c4fc33..199fc6b 100644
--- a/dev/core/test/com/google/gwt/dev/UnstableNestedAnonymousGenerator.java
+++ b/dev/core/test/com/google/gwt/dev/UnstableNestedAnonymousGenerator.java
@@ -50,6 +50,7 @@
       OutputVersion outputVersion = outputVersionOrder.removeFirst();
 
       pw.println("package com.foo;");
+      pw.println("import java.lang.Runnable;");
       pw.println("public class NestedAnonymousClasses {");
       if (outputVersion == OutputVersion.A) {
         insertClassDefinitionOne(pw);
@@ -58,11 +59,12 @@
         insertClassDefinitionTwo(pw);
         insertClassDefinitionOne(pw);
       }
+      pw.println("  public NestedAnonymousClasses() {run();}");
       pw.println("  void run() {");
-      pw.println("    new Object() {");
-      pw.println("      void run() {");
-      pw.println("        new Object() {");
-      pw.println("          void run() {");
+      pw.println("    new Runnable() {");
+      pw.println("      public void run() {");
+      pw.println("        new Runnable() {");
+      pw.println("          public void run() {");
       if (outputVersion == OutputVersion.A) {
         pw.println("ClassOne classOne = new ClassOne();");
         pw.println("ClassTwo classTwo = new ClassTwo();");
@@ -71,9 +73,9 @@
         pw.println("ClassOne classOne = new ClassOne();");
       }
       pw.println("          }");
-      pw.println("        };");
+      pw.println("        }.run();");
       pw.println("      }");
-      pw.println("    };");
+      pw.println("    }.run();");
       pw.println("  }");
       pw.println("}");
       pw.flush();
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/JsTypeLinkerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/JsTypeLinkerTest.java
index 813dd26..ac9ed92 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/JsTypeLinkerTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/JsTypeLinkerTest.java
@@ -19,6 +19,7 @@
 import com.google.gwt.core.ext.linker.impl.StatementRangesBuilder;
 import com.google.gwt.core.ext.soyc.Range;
 import com.google.gwt.dev.MinimalRebuildCache;
+import com.google.gwt.dev.StringAnalyzableTypeEnvironment;
 import com.google.gwt.dev.jjs.JsSourceMap;
 import com.google.gwt.dev.jjs.SourceOrigin;
 import com.google.gwt.dev.jjs.ast.JTypeOracle;
@@ -73,22 +74,54 @@
     // Create type inheritance.
     Map<String, String> superClassesByClass =
         minimalRebuildCache.getImmediateTypeRelations().getImmediateSuperclassesByClass();
+    StringAnalyzableTypeEnvironment typeEnvironment = minimalRebuildCache.getTypeEnvironment();
+    typeEnvironment.recordTypeEnclosesMethod("java.lang.Object", "java.lang.Object::$clinit()");
     superClassesByClass.put("java.lang.Class", "java.lang.Object");
+    typeEnvironment.recordTypeEnclosesMethod("java.lang.Class", "java.lang.Class::$clinit()");
     superClassesByClass.put("com.some.app.SomeAModel", "java.lang.Object");
+    typeEnvironment.recordTypeEnclosesMethod("com.some.app.SomeAModel",
+        "com.some.app.SomeAModel::$clinit()");
     superClassesByClass.put("com.some.app.SomeBModel", "java.lang.Object");
+    typeEnvironment.recordTypeEnclosesMethod("com.some.app.SomeBModel",
+        "com.some.app.SomeBModel::$clinit()");
     superClassesByClass.put("com.some.app.SomeController", "java.lang.Object");
+    typeEnvironment.recordTypeEnclosesMethod("com.some.app.SomeController",
+        "com.some.app.SomeController::$clinit()");
     superClassesByClass.put("com.some.app.EntryPoint", "java.lang.Object");
+    typeEnvironment.recordTypeEnclosesMethod("com.some.app.EntryPoint",
+        "com.some.app.EntryPoint::$clinit()");
 
     // Record root types.
     minimalRebuildCache.setRootTypeNames(Lists.newArrayList("com.some.app.EntryPoint"));
+    minimalRebuildCache.setEntryMethodNames(
+        Lists.newArrayList("com.some.app.EntryPoint::onModuleLoad()"));
+    typeEnvironment.recordTypeEnclosesMethod("com.some.app.EntryPoint",
+        "com.some.app.EntryPoint::onModuleLoad()");
 
     // Record type references.
-    minimalRebuildCache.addTypeReference("com.some.app.EntryPoint",
+    minimalRebuildCache.addTypeReference("com.some.app.EntryPoint", "com.some.app.SomeController");
+    typeEnvironment.recordMethodInstantiatesType("com.some.app.EntryPoint::onModuleLoad()",
         "com.some.app.SomeController");
-    minimalRebuildCache.addTypeReference("com.some.app.SomeController",
+    typeEnvironment.recordMethodCallsMethod("com.some.app.EntryPoint::onModuleLoad()",
+        "com.some.app.SomeController::createData()");
+    typeEnvironment.recordTypeEnclosesMethod("com.some.app.SomeController",
+        "com.some.app.SomeController::createData()");
+
+    minimalRebuildCache.addTypeReference("com.some.app.SomeController", "com.some.app.SomeBModel");
+    typeEnvironment.recordMethodInstantiatesType("com.some.app.SomeController::createData()",
         "com.some.app.SomeBModel");
-    minimalRebuildCache.addTypeReference("com.some.app.SomeController",
+    typeEnvironment.recordMethodCallsMethod("com.some.app.SomeController::createData()",
+        "com.some.app.SomeBModel::SomeBModel()");
+    typeEnvironment.recordTypeEnclosesMethod("com.some.app.SomeBModel",
+        "com.some.app.SomeBModel::SomeBModel()");
+
+    minimalRebuildCache.addTypeReference("com.some.app.SomeController", "com.some.app.SomeAModel");
+    typeEnvironment.recordMethodInstantiatesType("com.some.app.SomeController::createData()",
         "com.some.app.SomeAModel");
+    typeEnvironment.recordMethodCallsMethod("com.some.app.SomeController::createData()",
+        "com.some.app.SomeAModel::SomeAModel()");
+    typeEnvironment.recordTypeEnclosesMethod("com.some.app.SomeAModel",
+        "com.some.app.SomeAModel::SomeAModel()");
 
     JsTypeLinker jsTypeLinker = new JsTypeLinker(TreeLogger.NULL,
         new JsNoopTransformer(originalJs, srb.build(), smb.build()), classRanges, programRange,
@@ -127,8 +160,18 @@
     // Stop referring to SomeModelA from the Controller and verify that SomeModelA is not in the
     // output.
     minimalRebuildCache.removeReferencesFrom("com.some.app.SomeController");
-    minimalRebuildCache.addTypeReference("com.some.app.SomeController",
+    minimalRebuildCache.addTypeReference("com.some.app.SomeController", "com.some.app.SomeBModel");
+
+    typeEnvironment.removeControlFlowIndexesFor("com.some.app.SomeController");
+    typeEnvironment.recordTypeEnclosesMethod("com.some.app.SomeController",
+        "com.some.app.SomeController::createData()");
+    typeEnvironment.recordTypeEnclosesMethod("com.some.app.SomeController",
+        "com.some.app.SomeController::$clinit()");
+    typeEnvironment.recordMethodInstantiatesType("com.some.app.SomeController::createData()",
         "com.some.app.SomeBModel");
+    typeEnvironment.recordMethodCallsMethod("com.some.app.SomeController::createData()",
+        "com.some.app.SomeBModel::SomeBModel()");
+
     jsTypeLinker = new JsTypeLinker(TreeLogger.NULL,
         new JsNoopTransformer(originalJs, srb.build(), smb.build()), classRanges, programRange,
         minimalRebuildCache, new JTypeOracle(null, minimalRebuildCache, true));
diff --git a/eclipse/settings/code-style/gwt-checkstyle-tests.xml b/eclipse/settings/code-style/gwt-checkstyle-tests.xml
index 99e9b97..ae1f9b7 100644
--- a/eclipse/settings/code-style/gwt-checkstyle-tests.xml
+++ b/eclipse/settings/code-style/gwt-checkstyle-tests.xml
@@ -102,7 +102,7 @@
         </module>
         <module name="ImportOrder">
             <property name="severity" value="error"/>
-            <property name="groups" value="com.google, com,  junit, net,org, java,javax"/>
+            <property name="groups" value="com.google, cern, com, junit, net, org, java, javax"/>
             <property name="ordered" value="true"/>
             <property name="separated" value="true"/>
             <property name="option" value="top"/>
diff --git a/eclipse/settings/code-style/gwt-checkstyle.xml b/eclipse/settings/code-style/gwt-checkstyle.xml
index a71fe5a..671974a 100644
--- a/eclipse/settings/code-style/gwt-checkstyle.xml
+++ b/eclipse/settings/code-style/gwt-checkstyle.xml
@@ -94,7 +94,7 @@
         </module>
         <module name="ImportOrder">
             <property name="severity" value="error"/>
-            <property name="groups" value="com.google, com,  junit, net,org, java,javax"/>
+            <property name="groups" value="com.google, cern, com, junit, net, org, java, javax"/>
             <property name="ordered" value="true"/>
             <property name="separated" value="true"/>
             <property name="option" value="top"/>
diff --git a/servlet/build.xml b/servlet/build.xml
index 1bdefc0..843887a 100644
--- a/servlet/build.xml
+++ b/servlet/build.xml
@@ -24,6 +24,7 @@
     <mkdir dir="${gwt.build.lib}" />
     <gwt.jar>
       <!-- Rebased dependencies go in gwt-servlet too -->
+      <zipfileset src="${gwt.tools.lib}/colt/colt-1.2.jar" />
       <zipfileset src="${gwt.tools.lib}/guava/guava-16.0.1/guava-16.0.1-rebased.jar" />
       <zipfileset src="${gwt.tools.lib}/jscomp/20131118.json.rebased/sourcemap-rebased.jar" />
       <zipfileset src="${gwt.tools.lib}/json/android-sdk-19.1/json-android-rebased.jar" />