Makes SDM+perFileCompile correctly handle switching browsers.

Previously the MinimalRebuildCache stored some cache information on a
per-permutation basis using permutation ID. But since SDM always
compiled just 1 permutation (even when switching browsers) the compile
process always ended up (improperly) using the same cache info.

Got rid of the nested MinimalRebuildCache.PermutationRebuildCache class.

Switched to SDM managing a map of BindingPropertySet <->
MinimalRebuildCache pairs.

Previously the "unit cache miss" process was being reused to mark "known
modified types" in the MinimalRebuildCache, but this was insufficient
when switching between browsers since it would notice just the types
changed since the last compile as opposed to the types changed since the
last compile specifically for this set of binding properties. This was
corrected by changing to have each MinimalRebuildCache instance
internally track it's own view of disk type modification times and
generated type content hashes.

As a side effect this changed modified type detection from being a side
effect of CompilationStateBuilder.doBuildFrom() to a side effect of
Precompile.precompile(), which created a need to move a lot of test
assertions from modified/stale types from CompilationStateBuilderTest to
CompilerTest. The assertions are much more detailed.

Added a noop subclass of MinimalRebuildCache to save time in regular
monolithic compiles.

Change-Id: I73001450d96749380e553cd70579ee9828e9620d
Review-Link: https://gwt-review.googlesource.com/#/c/8983/
diff --git a/dev/BUILD b/dev/BUILD
index 44b019f..64c0709 100644
--- a/dev/BUILD
+++ b/dev/BUILD
@@ -187,6 +187,7 @@
             "core/src/com/google/gwt/dev/GwtCreateMap.java",
             "core/src/com/google/gwt/dev/GwtVersion.java",
             "core/src/com/google/gwt/dev/MinimalRebuildCache.java",
+            "core/src/com/google/gwt/dev/NullRebuildCache.java",
             "core/src/com/google/gwt/dev/Permutation.java",
             "core/src/com/google/gwt/dev/PrecompileTaskOptions.java",
             "core/src/com/google/gwt/dev/PrecompileTaskOptionsImpl.java",
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java
index 93c91d4..07230f1 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java
@@ -27,6 +27,7 @@
 import com.google.gwt.dev.IncrementalBuilder;
 import com.google.gwt.dev.IncrementalBuilder.BuildResultStatus;
 import com.google.gwt.dev.MinimalRebuildCache;
+import com.google.gwt.dev.NullRebuildCache;
 import com.google.gwt.dev.cfg.BindingProperty;
 import com.google.gwt.dev.cfg.ConfigProps;
 import com.google.gwt.dev.cfg.ConfigurationProperty;
@@ -61,7 +62,8 @@
   private IncrementalBuilder incrementalBuilder;
   private String serverPrefix;
   private int compilesDone = 0;
-  private MinimalRebuildCache minimalRebuildCache = new MinimalRebuildCache();
+  private Map<Map<String, String>, MinimalRebuildCache> minimalRebuildCacheForProperties =
+      Maps.newHashMap();
 
   // after renaming
   private AtomicReference<String> moduleName = new AtomicReference<String>(null);
@@ -103,7 +105,7 @@
     ProgressTable dummy = new ProgressTable();
     Job job = new Job(originalModuleName, defaultProps, logger);
     job.onSubmitted(dummy);
-    Result result = compile(job, new MinimalRebuildCache());
+    Result result = compile(job);
     job.onFinished(result);
 
     assert result.isOk();
@@ -124,11 +126,7 @@
 
     Job.Result result;
     try {
-      if (options.shouldCompilePerFile()) {
-        result = compile(job, minimalRebuildCache);
-      } else {
-        result = compile(job, new MinimalRebuildCache());
-      }
+      result = compile(job);
     } catch (UnableToCompleteException e) {
       // No point in logging a stack trace for this exception
       job.getLogger().log(TreeLogger.Type.WARN, "recompile failed");
@@ -144,13 +142,13 @@
 
   /**
    * Calls the GWT compiler with the appropriate settings.
-   * Side-effect: the given cache will be used and saved in {@link #minimalRebuildCache}.
+   * Side-effect: a MinimalRebuildCache for the current binding properties will be found or created.
    *
    * @param job used for reporting progress. (Its result will not be set.)
    * @return a non-error Job.Result if successful.
    * @throws UnableToCompleteException for compile failures.
    */
-  private Job.Result compile(Job job, MinimalRebuildCache minimalRebuildCache)
+  private Job.Result compile(Job job)
       throws UnableToCompleteException {
 
     assert job.wasSubmitted();
@@ -186,7 +184,7 @@
 
         success = compileIncremental(compileLogger, compileDir);
       } else {
-        success = compileMonolithic(compileLogger, compileDir, job, minimalRebuildCache);
+        success = compileMonolithic(compileLogger, compileDir, job);
       }
     } finally {
       try {
@@ -202,9 +200,6 @@
       throw new UnableToCompleteException();
     }
 
-    // keep the minimal rebuild cache for the next compile
-    this.minimalRebuildCache = minimalRebuildCache;
-
     long elapsedTime = System.currentTimeMillis() - startTime;
     compileLogger.log(TreeLogger.Type.INFO,
         String.format("%.3fs total -- Compile completed", elapsedTime / 1000d));
@@ -297,8 +292,7 @@
     return buildResultStatus.isSuccess();
   }
 
-  private boolean compileMonolithic(TreeLogger compileLogger,
-      CompileDir compileDir, Job job, MinimalRebuildCache rebuildCache)
+  private boolean compileMonolithic(TreeLogger compileLogger, CompileDir compileDir, Job job)
       throws UnableToCompleteException {
 
     job.onCompilerProgress(
@@ -328,7 +322,9 @@
     CompilerOptions runOptions = new CompilerOptionsImpl(compileDir, newModuleName, options);
     compilerContext = compilerContextBuilder.options(runOptions).build();
 
-    boolean success = new Compiler(runOptions, rebuildCache).run(compileLogger, module);
+    // Looks up the matching rebuild cache using the final set of overridden binding properties.
+    MinimalRebuildCache minimalRebuildCache = getOrCreateMinimalRebuildCache(bindingProperties);
+    boolean success = new Compiler(runOptions, minimalRebuildCache).run(compileLogger, module);
     if (success) {
       publishedCompileDir = compileDir;
       lastBuildInput = input;
@@ -369,6 +365,21 @@
     }
   }
 
+  private MinimalRebuildCache getOrCreateMinimalRebuildCache(
+      Map<String, String> bindingProperties) {
+    if (!options.shouldCompilePerFile()) {
+      return new NullRebuildCache();
+    }
+
+    MinimalRebuildCache minimalRebuildCache =
+        minimalRebuildCacheForProperties.get(bindingProperties);
+    if (minimalRebuildCache == null) {
+      minimalRebuildCache = new MinimalRebuildCache();
+      minimalRebuildCacheForProperties.put(bindingProperties, minimalRebuildCache);
+    }
+    return minimalRebuildCache;
+  }
+
   /**
    * Loads the module and configures it for SuperDevMode. (Does not restrict permutations.)
    */
diff --git a/dev/core/src/com/google/gwt/dev/Compiler.java b/dev/core/src/com/google/gwt/dev/Compiler.java
index dd2759b..2141d6d 100644
--- a/dev/core/src/com/google/gwt/dev/Compiler.java
+++ b/dev/core/src/com/google/gwt/dev/Compiler.java
@@ -122,7 +122,8 @@
   private final CompilerOptionsImpl options;
 
   public Compiler(CompilerOptions compilerOptions) {
-    this(compilerOptions, new MinimalRebuildCache());
+    this(compilerOptions, compilerOptions.shouldCompilePerFile() ? new MinimalRebuildCache()
+        : new NullRebuildCache());
   }
 
   public Compiler(CompilerOptions compilerOptions, MinimalRebuildCache minimalRebuildCache) {
diff --git a/dev/core/src/com/google/gwt/dev/MinimalRebuildCache.java b/dev/core/src/com/google/gwt/dev/MinimalRebuildCache.java
index 4cdb863..2fae392 100644
--- a/dev/core/src/com/google/gwt/dev/MinimalRebuildCache.java
+++ b/dev/core/src/com/google/gwt/dev/MinimalRebuildCache.java
@@ -19,6 +19,8 @@
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.javac.CompilationUnit;
 import com.google.gwt.dev.javac.CompiledClass;
+import com.google.gwt.dev.javac.GeneratedUnit;
+import com.google.gwt.dev.javac.Shared;
 import com.google.gwt.dev.jjs.JsSourceMap;
 import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.jjs.ast.JTypeOracle;
@@ -35,106 +37,26 @@
 import com.google.gwt.thirdparty.guava.common.collect.Multimap;
 import com.google.gwt.thirdparty.guava.common.collect.Multimaps;
 import com.google.gwt.thirdparty.guava.common.collect.Sets;
-import com.google.gwt.thirdparty.guava.common.collect.Sets.SetView;
 
 import java.io.Serializable;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
 /**
  * MinimalRebuildCache contains compiler information that can be persisted between compiles to
  * decrease compilation time.
  * <p>
+ * Cached information is specific to a single permutation and it is the responsibility of the
+ * framework driving the Compiler to supply the right Cache instance for the right Module
+ * configuration and to make sure that the Compiler is only processing one permutation at a time.
+ * <p>
  * All type names referenced here are assumed to be binary names.
  */
 public class MinimalRebuildCache implements Serializable {
 
-  /**
-   * The permutation specific portion of persisted information.
-   */
-  public class PermutationRebuildCache {
-
-    // The implementation of a Type can vary between permutations because of permutation specific
-    // GWT.create() rewrites and JSO Devirtualization.
-    private final Map<String, String> jsByTypeName = Maps.newHashMap();
-    private final Multimap<String, String> referencedTypeNamesByTypeName = HashMultimap.create();
-    private final Map<String, JsSourceMap> sourceMapsByTypeName = Maps.newHashMap();
-    private final Map<String, StatementRanges> statementRangesByTypeName = Maps.newHashMap();
-    private final Multimap<String, String> typeNamesByReferencingTypeName = HashMultimap.create();
-
-    public void addTypeReference(String fromTypeName, String toTypeName) {
-      referencedTypeNamesByTypeName.put(fromTypeName, toTypeName);
-      typeNamesByReferencingTypeName.put(toTypeName, fromTypeName);
-    }
-
-    public void clearCachedTypeOutput(String typeName) {
-      jsByTypeName.remove(typeName);
-      statementRangesByTypeName.remove(typeName);
-      sourceMapsByTypeName.remove(typeName);
-    }
-
-    /**
-     * Computes and returns the names of the set of types that are transitively referencable
-     * starting from the set of root types.
-     */
-    public Set<String> computeReachableTypeNames() {
-      Set<String> openTypeNames = Sets.newHashSet(rootTypeNames);
-      Set<String> reachableTypeNames = Sets.newHashSet();
-
-      while (!openTypeNames.isEmpty()) {
-        Iterator<String> iterator = openTypeNames.iterator();
-        String toProcessTypeName = iterator.next();
-        iterator.remove();
-
-        reachableTypeNames.add(toProcessTypeName);
-
-        Collection<String> referencedTypes = referencedTypeNamesByTypeName.get(toProcessTypeName);
-        for (String referencedType : referencedTypes) {
-          if (reachableTypeNames.contains(referencedType)) {
-            continue;
-          }
-          openTypeNames.add(referencedType);
-        }
-      }
-      return reachableTypeNames;
-    }
-
-    public String getJs(String typeName) {
-      return jsByTypeName.get(typeName);
-    }
-
-    public JsSourceMap getSourceMap(String typeName) {
-      return sourceMapsByTypeName.get(typeName);
-    }
-
-    public StatementRanges getStatementRanges(String typeName) {
-      return statementRangesByTypeName.get(typeName);
-    }
-
-    public void removeReferencesFrom(String fromTypeName) {
-      Collection<String> toTypeNames = referencedTypeNamesByTypeName.get(fromTypeName);
-      for (String toTypeName : toTypeNames) {
-        typeNamesByReferencingTypeName.remove(toTypeName, fromTypeName);
-      }
-      referencedTypeNamesByTypeName.removeAll(fromTypeName);
-    }
-
-    public void setJsForType(TreeLogger logger, String typeName, String typeJs) {
-      logger.log(TreeLogger.SPAM, "caching JS for type " + typeName);
-      jsByTypeName.put(typeName, typeJs);
-    }
-
-    public void setSourceMapForType(String typeName, JsSourceMap sourceMap) {
-      sourceMapsByTypeName.put(typeName, sourceMap);
-    }
-
-    public void setStatementRangesForType(String typeName, StatementRanges statementRanges) {
-      statementRangesByTypeName.put(typeName, statementRanges);
-    }
-  }
-
   private static void appendSubTypes(Set<String> accumulatedTypeNames, Set<String> parentTypeNames,
       JTypeOracle typeOracle) {
     for (String parentTypeName : parentTypeNames) {
@@ -147,22 +69,82 @@
     }
   }
 
-  private final Set<String> allCompilationUnitNames = Sets.newHashSet();
+  /**
+   * Diffs lastModifiedByResourcePath from the previous compile against currentResources from the
+   * current compile. modifiedResourcePaths is wiped and recreated to be a list of just the modified
+   * or deleted resources, deletedResourcePaths is wiped and recreated to be a list of just the
+   * deleted resources and modifiedResourcePaths is updated in place with new lastModified dates.
+   */
+  private static void recordModifiedResources(Map<String, Long> currentModifiedByResourcePath,
+      Map<String, Long> lastModifiedByResourcePath, Set<String> modifiedResourcePaths,
+      Set<String> deletedResourcePaths) {
+    deletedResourcePaths.clear();
+    modifiedResourcePaths.clear();
+
+    Set<String> currentResourcePaths = Sets.newHashSet();
+    for (Entry<String, Long> entry : currentModifiedByResourcePath.entrySet()) {
+      String currentResourcePath = entry.getKey();
+      Long currentResourceModified = entry.getValue();
+
+      currentResourcePaths.add(currentResourcePath);
+      Long lastKnownModified =
+          lastModifiedByResourcePath.put(currentResourcePath, currentResourceModified);
+      if (!Objects.equal(lastKnownModified, currentResourceModified)) {
+        // Added or Modified resource.
+        modifiedResourcePaths.add(currentResourcePath);
+      }
+    }
+
+    // Removed resources.
+    {
+      // Figure out which resources were removed.
+      Set<String> removedResourcePaths = Sets.newHashSet(
+          Sets.difference(lastModifiedByResourcePath.keySet(), currentResourcePaths));
+      // Log them as "modified".
+      deletedResourcePaths.addAll(removedResourcePaths);
+      modifiedResourcePaths.addAll(removedResourcePaths);
+      // Remove any path to modified date entries for them.
+      for (String removedResourcePath : removedResourcePaths) {
+        lastModifiedByResourcePath.remove(removedResourcePath);
+      }
+    }
+  }
+
+  private static Set<String> resourcePathsToCompilationUnitNames(Set<String> resourcePaths) {
+    Set<String> compilationUnitNames = Sets.newHashSet();
+    for (String resourcePath : resourcePaths) {
+      compilationUnitNames.add(Shared.toTypeName(resourcePath));
+    }
+    return compilationUnitNames;
+  }
+
+  private static Map<String, Long> resourcesToModifiedByPath(Collection<Resource> resources) {
+    Map<String, Long> modifiedByPath = Maps.newHashMap();
+    for (Resource resource : resources) {
+      modifiedByPath.put(resource.getPath(), resource.getLastModified());
+    }
+    return modifiedByPath;
+  }
+
+  protected final ImmediateTypeRelations immediateTypeRelations = new ImmediateTypeRelations();
   private final Multimap<String, String> compilationUnitTypeNameByNestedTypeName =
       HashMultimap.create();
+  private final Map<String, String> contentHashByGeneratedTypeName = Maps.newHashMap();
   private final Set<String> deletedCompilationUnitNames = Sets.newHashSet();
+  private final Set<String> deletedDiskSourcePaths = Sets.newHashSet();
+  private final Set<String> deletedResourcePaths = Sets.newHashSet();
   private final Set<String> dualJsoImplInterfaceNames = Sets.newHashSet();
   private final ArtifactSet generatedArtifacts = new ArtifactSet();
-  private final ImmediateTypeRelations immediateTypeRelations = new ImmediateTypeRelations();
   private final IntTypeIdGenerator intTypeIdGenerator = new IntTypeIdGenerator();
+  private final Map<String, String> jsByTypeName = Maps.newHashMap();
   private final Set<String> jsoStatusChangedTypeNames = Sets.newHashSet();
   private final Set<String> jsoTypeNames = Sets.newHashSet();
+  private final Map<String, Long> lastModifiedByDiskSourcePath = Maps.newHashMap();
   private final Map<String, Long> lastModifiedByResourcePath = Maps.newHashMap();
   private final Set<String> modifiedCompilationUnitNames = Sets.newHashSet();
+  private final Set<String> modifiedDiskSourcePaths = Sets.newHashSet();
   private final Set<String> modifiedResourcePaths = Sets.newHashSet();
   private final Multimap<String, String> nestedTypeNamesByUnitTypeName = HashMultimap.create();
-  private final Map<Integer, PermutationRebuildCache> permutationRebuildCacheById =
-      Maps.newHashMap();
   private final PersistentPrettyNamerState persistentPrettyNamerState =
       new PersistentPrettyNamerState();
   private final Set<String> preambleTypeNames = Sets.newHashSet();
@@ -170,8 +152,13 @@
   private final Multimap<String, String> reboundTypeNamesByGeneratedTypeName =
       HashMultimap.create();
   private final Multimap<String, String> reboundTypeNamesByInputResource = HashMultimap.create();
+  private final Multimap<String, String> referencedTypeNamesByTypeName = HashMultimap.create();
   private final Set<String> rootTypeNames = Sets.newHashSet();
   private final Set<String> singleJsoImplInterfaceNames = Sets.newHashSet();
+  private final Map<String, JsSourceMap> sourceMapsByTypeName = Maps.newHashMap();
+  private final Set<String> staleTypeNames = Sets.newHashSet();
+  private final Map<String, StatementRanges> statementRangesByTypeName = Maps.newHashMap();
+  private final Multimap<String, String> typeNamesByReferencingTypeName = HashMultimap.create();
 
   /**
    * Accumulates generated artifacts so that they can be output on recompiles even if no generators
@@ -188,6 +175,11 @@
     this.modifiedCompilationUnitNames.addAll(modifiedCompilationUnitNames);
   }
 
+  public void addTypeReference(String fromTypeName, String toTypeName) {
+    referencedTypeNamesByTypeName.put(fromTypeName, toTypeName);
+    typeNamesByReferencingTypeName.put(toTypeName, fromTypeName);
+  }
+
   public void associateReboundTypeWithGeneratedType(String reboundTypeName,
       String generatedTypeName) {
     reboundTypeNamesByGeneratedTypeName.put(generatedTypeName, reboundTypeName);
@@ -205,9 +197,21 @@
   public void clearPerTypeJsCache() {
     rootTypeNames.clear();
     preambleTypeNames.clear();
-    modifiedCompilationUnitNames.clear();
-    modifiedCompilationUnitNames.addAll(allCompilationUnitNames);
-    permutationRebuildCacheById.clear();
+
+    deletedResourcePaths.clear();
+    modifiedResourcePaths.clear();
+    lastModifiedByResourcePath.clear();
+
+    deletedDiskSourcePaths.clear();
+    modifiedDiskSourcePaths.clear();
+    lastModifiedByDiskSourcePath.clear();
+    contentHashByGeneratedTypeName.clear();
+
+    jsByTypeName.clear();
+    referencedTypeNamesByTypeName.clear();
+    sourceMapsByTypeName.clear();
+    statementRangesByTypeName.clear();
+    typeNamesByReferencingTypeName.clear();
   }
 
   public void clearRebinderTypeAssociations(String rebinderTypeName) {
@@ -238,7 +242,9 @@
       return Sets.newHashSet();
     }
 
-    Set<String> staleTypeNames = Sets.newHashSet();
+    // Store calculate stale type names in a persisted field so that tests can inspect behavior.
+    staleTypeNames.clear();
+
     Set<String> modifiedTypeNames = computeModifiedTypeNames();
 
     // Accumulate the names of stale types resulting from some known type or resource modifications,
@@ -277,7 +283,7 @@
       clearCachedTypeOutput(staleTypeName);
     }
 
-    return staleTypeNames;
+    return Sets.newHashSet(staleTypeNames);
   }
 
   /**
@@ -327,9 +333,30 @@
     return modifiedTypeNames;
   }
 
-  @VisibleForTesting
-  public Set<String> getAllCompilationUnitNames() {
-    return allCompilationUnitNames;
+  /**
+   * Computes and returns the names of the set of types that are transitively referenceable starting
+   * from the set of root types.
+   */
+  public Set<String> computeReachableTypeNames() {
+    Set<String> openTypeNames = Sets.newHashSet(rootTypeNames);
+    Set<String> reachableTypeNames = Sets.newHashSet();
+
+    while (!openTypeNames.isEmpty()) {
+      Iterator<String> iterator = openTypeNames.iterator();
+      String toProcessTypeName = iterator.next();
+      iterator.remove();
+
+      reachableTypeNames.add(toProcessTypeName);
+
+      Collection<String> referencedTypes = referencedTypeNamesByTypeName.get(toProcessTypeName);
+      for (String referencedType : referencedTypes) {
+        if (reachableTypeNames.contains(referencedType)) {
+          continue;
+        }
+        openTypeNames.add(referencedType);
+      }
+    }
+    return reachableTypeNames;
   }
 
   public ArtifactSet getGeneratedArtifacts() {
@@ -344,20 +371,15 @@
     return intTypeIdGenerator;
   }
 
+  public String getJs(String typeName) {
+    return jsByTypeName.get(typeName);
+  }
+
   @VisibleForTesting
   public Set<String> getModifiedCompilationUnitNames() {
     return modifiedCompilationUnitNames;
   }
 
-  public PermutationRebuildCache getPermutationRebuildCache(int permutationId) {
-    if (!permutationRebuildCacheById.containsKey(permutationId)) {
-      PermutationRebuildCache permutationRebuildCache = new PermutationRebuildCache();
-      permutationRebuildCacheById.put(permutationId, permutationRebuildCache);
-      return permutationRebuildCache;
-    }
-    return permutationRebuildCacheById.get(permutationId);
-  }
-
   public PersistentPrettyNamerState getPersistentPrettyNamerState() {
     return persistentPrettyNamerState;
   }
@@ -366,16 +388,21 @@
     return preambleTypeNames;
   }
 
+  public JsSourceMap getSourceMap(String typeName) {
+    return sourceMapsByTypeName.get(typeName);
+  }
+
+  @VisibleForTesting
+  public Set<String> getStaleTypeNames() {
+    return staleTypeNames;
+  }
+
+  public StatementRanges getStatementRanges(String typeName) {
+    return statementRangesByTypeName.get(typeName);
+  }
+
   public boolean hasJs(String typeName) {
-    if (permutationRebuildCacheById.isEmpty()) {
-      return false;
-    }
-    for (PermutationRebuildCache permutationRebuildCache : permutationRebuildCacheById.values()) {
-      if (permutationRebuildCache.getJs(typeName) == null) {
-        return false;
-      }
-    }
-    return true;
+    return jsByTypeName.containsKey(typeName);
   }
 
   public boolean hasPreambleTypeNames() {
@@ -388,35 +415,56 @@
    * dates of build resources in the previous compile with those of the current compile.
    */
   public void recordBuildResources(ModuleDef module) {
-    // To start lastModifiedByResourcePath is data about the previous compile and by the end it
-    // contains data about the current compile.
+    Map<String, Long> currentModifiedByResourcePath =
+        resourcesToModifiedByPath(module.getBuildResourceOracle().getResources());
+    recordModifiedResources(currentModifiedByResourcePath, lastModifiedByResourcePath,
+        modifiedResourcePaths, deletedResourcePaths);
+  }
 
-    Set<Resource> currentResources = module.getBuildResourceOracle().getResources();
+  /**
+   * Records the paths and modification dates of source resources in the current compile and builds
+   * a list of known modified paths by comparing the paths and modification dates of source
+   * resources in the previous compile with those of the current compile.
+   */
+  @VisibleForTesting
+  public void recordDiskSourceResources(Map<String, Long> currentModifiedByDiskSourcePath) {
+    recordModifiedResources(currentModifiedByDiskSourcePath, lastModifiedByDiskSourcePath,
+        modifiedDiskSourcePaths, deletedDiskSourcePaths);
 
-    // Start with a from-scratch idea of which resources are modified.
-    modifiedResourcePaths.clear();
+    deletedCompilationUnitNames.clear();
+    deletedCompilationUnitNames.addAll(resourcePathsToCompilationUnitNames(deletedDiskSourcePaths));
+    modifiedCompilationUnitNames.clear();
+    modifiedCompilationUnitNames.addAll(
+        resourcePathsToCompilationUnitNames(modifiedDiskSourcePaths));
+  }
 
-    Set<String> currentResourcePaths = Sets.newHashSet();
-    for (Resource currentResource : currentResources) {
-      currentResourcePaths.add(currentResource.getPath());
-      Long lastKnownModified = lastModifiedByResourcePath.put(currentResource.getPath(),
-          currentResource.getLastModified());
-      if (!Objects.equal(lastKnownModified, currentResource.getLastModified())) {
+  /**
+   * Records the paths and modification dates of source resources in the current compile and builds
+   * a list of known modified paths by comparing the paths and modification dates of source
+   * resources in the previous compile with those of the current compile.
+   */
+  public void recordDiskSourceResources(ModuleDef module) {
+    Map<String, Long> currentModifiedByDiskSourcePath =
+        resourcesToModifiedByPath(module.getSourceResourceOracle().getResources());
+    recordDiskSourceResources(currentModifiedByDiskSourcePath);
+  }
+
+  /**
+   * Records the paths and content ids of generated source resources in the current compile and
+   * updates a list of known modified paths by comparing the paths and content ids of generated
+   * source resources in the previous compile with those of the current compile.
+   */
+  public void recordGeneratedUnits(Collection<GeneratedUnit> generatedUnits) {
+    // Not all Generators are run on each compile so it is not possible to compare previous and
+    // current generated units to detect deletions. As a result only modifications are tracked.
+
+    for (GeneratedUnit generatedUnit : generatedUnits) {
+      String currentStrongHash = generatedUnit.getStrongHash();
+      String lastKnownStrongHash =
+          contentHashByGeneratedTypeName.put(generatedUnit.getTypeName(), currentStrongHash);
+      if (!Objects.equal(lastKnownStrongHash, currentStrongHash)) {
         // Added or Modified resource.
-        modifiedResourcePaths.add(currentResource.getPath());
-      }
-    }
-
-    // Removed resources.
-    {
-      // Figure out which resources were removed.
-      SetView<String> removedResourcePaths =
-          Sets.difference(lastModifiedByResourcePath.keySet(), currentResourcePaths);
-      // Log them as "modified".
-      modifiedResourcePaths.addAll(removedResourcePaths);
-      // Remove any resource path to modified date entries for them.
-      for (String removedResourcePath : removedResourcePaths) {
-        lastModifiedByResourcePath.remove(removedResourcePath);
+        modifiedCompilationUnitNames.add(generatedUnit.getTypeName());
       }
     }
   }
@@ -445,16 +493,17 @@
     rebinderTypeNamesByReboundTypeName.put(reboundTypeName, rebinderType);
   }
 
-  public void setAllCompilationUnitNames(TreeLogger logger,
-      Set<String> newAllCompilationUnitNames) {
-    deletedCompilationUnitNames.clear();
-    deletedCompilationUnitNames.addAll(
-        Sets.difference(this.allCompilationUnitNames, newAllCompilationUnitNames));
-    logger.log(TreeLogger.DEBUG,
-        "caching list of known deleted compilation units " + deletedCompilationUnitNames);
+  public void removeReferencesFrom(String fromTypeName) {
+    Collection<String> toTypeNames = referencedTypeNamesByTypeName.get(fromTypeName);
+    for (String toTypeName : toTypeNames) {
+      typeNamesByReferencingTypeName.remove(toTypeName, fromTypeName);
+    }
+    referencedTypeNamesByTypeName.removeAll(fromTypeName);
+  }
 
-    this.allCompilationUnitNames.clear();
-    this.allCompilationUnitNames.addAll(newAllCompilationUnitNames);
+  public void setJsForType(TreeLogger logger, String typeName, String typeJs) {
+    logger.log(TreeLogger.SPAM, "caching JS for type " + typeName);
+    jsByTypeName.put(typeName, typeJs);
   }
 
   /**
@@ -463,12 +512,12 @@
    */
   public void setJsoTypeNames(Set<String> jsoTypeNames, Set<String> singleJsoImplInterfaceNames,
       Set<String> dualJsoImplInterfaceNames) {
-    jsoStatusChangedTypeNames.clear();
-
-    jsoStatusChangedTypeNames.addAll(Sets.symmetricDifference(this.jsoTypeNames, jsoTypeNames));
-    jsoStatusChangedTypeNames.addAll(
+    this.jsoStatusChangedTypeNames.clear();
+    this.jsoStatusChangedTypeNames.addAll(
+        Sets.symmetricDifference(this.jsoTypeNames, jsoTypeNames));
+    this.jsoStatusChangedTypeNames.addAll(
         Sets.symmetricDifference(this.singleJsoImplInterfaceNames, singleJsoImplInterfaceNames));
-    jsoStatusChangedTypeNames.addAll(
+    this.jsoStatusChangedTypeNames.addAll(
         Sets.symmetricDifference(this.dualJsoImplInterfaceNames, dualJsoImplInterfaceNames));
 
     this.jsoTypeNames.clear();
@@ -479,14 +528,6 @@
     this.dualJsoImplInterfaceNames.addAll(dualJsoImplInterfaceNames);
   }
 
-  public void setModifiedCompilationUnitNames(TreeLogger logger,
-      Set<String> modifiedCompilationUnitNames) {
-    logger.log(TreeLogger.DEBUG,
-        "caching list of known modified compilation units " + modifiedCompilationUnitNames);
-    this.modifiedCompilationUnitNames.clear();
-    this.modifiedCompilationUnitNames.addAll(modifiedCompilationUnitNames);
-  }
-
   public void setPreambleTypeNames(TreeLogger logger, Set<String> preambleTypeNames) {
     logger.log(TreeLogger.DEBUG, "caching list of known preamble types " + preambleTypeNames);
     this.preambleTypeNames.addAll(preambleTypeNames);
@@ -497,14 +538,20 @@
     this.rootTypeNames.addAll(rootTypeNames);
   }
 
+  public void setSourceMapForType(String typeName, JsSourceMap sourceMap) {
+    sourceMapsByTypeName.put(typeName, sourceMap);
+  }
+
+  public void setStatementRangesForType(String typeName, StatementRanges statementRanges) {
+    statementRangesByTypeName.put(typeName, statementRanges);
+  }
+
   private void appendReferencingTypes(Set<String> accumulatedTypeNames,
       Collection<String> referencedTypeNames) {
     for (String referencedTypeName : referencedTypeNames) {
-      for (PermutationRebuildCache permutationRebuildCache : permutationRebuildCacheById.values()) {
-        Collection<String> referencingTypeNames =
-            permutationRebuildCache.typeNamesByReferencingTypeName.get(referencedTypeName);
-        accumulatedTypeNames.addAll(referencingTypeNames);
-      }
+      Collection<String> referencingTypeNames =
+          typeNamesByReferencingTypeName.get(referencedTypeName);
+      accumulatedTypeNames.addAll(referencingTypeNames);
     }
   }
 
@@ -536,9 +583,9 @@
   }
 
   private void clearCachedTypeOutput(String staleTypeName) {
-    for (PermutationRebuildCache permutationRebuildCache : permutationRebuildCacheById.values()) {
-      permutationRebuildCache.clearCachedTypeOutput(staleTypeName);
-    }
+    jsByTypeName.remove(staleTypeName);
+    statementRangesByTypeName.remove(staleTypeName);
+    sourceMapsByTypeName.remove(staleTypeName);
   }
 
   /**
diff --git a/dev/core/src/com/google/gwt/dev/NullRebuildCache.java b/dev/core/src/com/google/gwt/dev/NullRebuildCache.java
new file mode 100644
index 0000000..c35f7b1e
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/NullRebuildCache.java
@@ -0,0 +1,205 @@
+/*
+ * 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.core.ext.TreeLogger;
+import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.core.ext.linker.StatementRanges;
+import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.javac.CompilationUnit;
+import com.google.gwt.dev.javac.GeneratedUnit;
+import com.google.gwt.dev.jjs.JsSourceMap;
+import com.google.gwt.dev.jjs.ast.JTypeOracle;
+import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.IntTypeIdGenerator;
+import com.google.gwt.dev.js.JsPersistentPrettyNamer.PersistentPrettyNamerState;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A MinimalRebuildCache that ignores all interaction.
+ */
+public class NullRebuildCache extends MinimalRebuildCache {
+
+  private static String failMessage =
+      "The RebuildCache should not be interacted with outside of per-file compiles.";
+
+  @Override
+  public void addGeneratedArtifacts(ArtifactSet generatedArtifacts) {
+  }
+
+  @Override
+  public void addModifiedCompilationUnitNames(TreeLogger logger,
+      Set<String> modifiedCompilationUnitNames) {
+  }
+
+  @Override
+  public void addTypeReference(String fromTypeName, String toTypeName) {
+  }
+
+  @Override
+  public void associateReboundTypeWithGeneratedType(String reboundTypeName,
+      String generatedTypeName) {
+  }
+
+  @Override
+  public void associateReboundTypeWithInputResource(String reboundTypeName,
+      String inputResourcePath) {
+  }
+
+  @Override
+  public void clearPerTypeJsCache() {
+  }
+
+  @Override
+  public void clearRebinderTypeAssociations(String rebinderTypeName) {
+  }
+
+  @Override
+  public void clearReboundTypeAssociations(String reboundTypeName) {
+  }
+
+  @Override
+  public Set<String> clearStaleTypeJsAndStatements(TreeLogger logger, JTypeOracle typeOracle) {
+    throw new UnsupportedOperationException(failMessage);
+  }
+
+  @Override
+  public Set<String> computeDeletedTypeNames() {
+    throw new UnsupportedOperationException(failMessage);
+  }
+
+  @Override
+  public Set<String> computeModifiedTypeNames() {
+    throw new UnsupportedOperationException(failMessage);
+  }
+
+  @Override
+  public Set<String> computeReachableTypeNames() {
+    throw new UnsupportedOperationException(failMessage);
+  }
+
+  @Override
+  public ArtifactSet getGeneratedArtifacts() {
+    throw new UnsupportedOperationException(failMessage);
+  }
+
+  @Override
+  public IntTypeIdGenerator getIntTypeIdGenerator() {
+    throw new UnsupportedOperationException(failMessage);
+  }
+
+  @Override
+  public String getJs(String typeName) {
+    throw new UnsupportedOperationException(failMessage);
+  }
+
+  @Override
+  public Set<String> getModifiedCompilationUnitNames() {
+    throw new UnsupportedOperationException(failMessage);
+  }
+
+  @Override
+  public PersistentPrettyNamerState getPersistentPrettyNamerState() {
+    throw new UnsupportedOperationException(failMessage);
+  }
+
+  @Override
+  public Set<String> getPreambleTypeNames() {
+    throw new UnsupportedOperationException(failMessage);
+  }
+
+  @Override
+  public JsSourceMap getSourceMap(String typeName) {
+    throw new UnsupportedOperationException(failMessage);
+  }
+
+  @Override
+  public Set<String> getStaleTypeNames() {
+    throw new UnsupportedOperationException(failMessage);
+  }
+
+  @Override
+  public StatementRanges getStatementRanges(String typeName) {
+    throw new UnsupportedOperationException(failMessage);
+  }
+
+  @Override
+  public boolean hasJs(String typeName) {
+    throw new UnsupportedOperationException(failMessage);
+  }
+
+  @Override
+  public boolean hasPreambleTypeNames() {
+    throw new UnsupportedOperationException(failMessage);
+  }
+
+  @Override
+  public void recordBuildResources(ModuleDef module) {
+  }
+
+  @Override
+  public void recordDiskSourceResources(Map<String, Long> currentModifiedByDiskSourcePath) {
+  }
+
+  @Override
+  public void recordDiskSourceResources(ModuleDef module) {
+  }
+
+  @Override
+  public void recordGeneratedUnits(Collection<GeneratedUnit> generatedUnits) {
+  }
+
+  @Override
+  public void recordNestedTypeName(String compilationUnitTypeName, String nestedTypeName) {
+  }
+
+  @Override
+  public void recordNestedTypeNamesPerType(CompilationUnit compilationUnit) {
+  }
+
+  @Override
+  public void recordRebinderTypeForReboundType(String reboundTypeName, String rebinderType) {
+  }
+
+  @Override
+  public void removeReferencesFrom(String fromTypeName) {
+  }
+
+  @Override
+  public void setJsForType(TreeLogger logger, String typeName, String typeJs) {
+  }
+
+  @Override
+  public void setJsoTypeNames(Set<String> jsoTypeNames, Set<String> singleJsoImplInterfaceNames,
+      Set<String> dualJsoImplInterfaceNames) {
+  }
+
+  @Override
+  public void setPreambleTypeNames(TreeLogger logger, Set<String> preambleTypeNames) {
+  }
+
+  @Override
+  public void setRootTypeNames(Collection<String> rootTypeNames) {
+  }
+
+  @Override
+  public void setSourceMapForType(String typeName, JsSourceMap sourceMap) {
+  }
+
+  @Override
+  public void setStatementRangesForType(String typeName, StatementRanges statementRanges) {
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/Precompile.java b/dev/core/src/com/google/gwt/dev/Precompile.java
index b5e9846..5d3d3ab 100644
--- a/dev/core/src/com/google/gwt/dev/Precompile.java
+++ b/dev/core/src/com/google/gwt/dev/Precompile.java
@@ -136,6 +136,12 @@
     PropertyPermutations allPermutations = new PropertyPermutations(
         compilerContext.getModule().getProperties(),
         compilerContext.getModule().getActiveLinkerNames());
+    if (compilerContext.getOptions().shouldCompilePerFile() && allPermutations.size() > 1) {
+      logger.log(TreeLogger.ERROR,
+          "Current binding properties are expanding to more than one permutation "
+          + "but per-file compilation requires that each compile operate on only one permutation.");
+      throw new UnableToCompleteException();
+    }
     return precompile(logger, compilerContext, 0, allPermutations);
   }
 
@@ -243,9 +249,8 @@
       ModuleDef module = compilerContext.getModule();
       PrecompileTaskOptions jjsOptions = compilerContext.getOptions();
       if (jjsOptions.shouldCompilePerFile()) {
+        compilerContext.getMinimalRebuildCache().recordDiskSourceResources(module);
         compilerContext.getMinimalRebuildCache().recordBuildResources(module);
-        // TODO(stalcup): record source resources here instead of in CompilationStateBuilder, for
-        // consistency.
       }
       CompilationState compilationState = module.getCompilationState(logger, compilerContext);
       if (jjsOptions.isStrict() && compilationState.hasErrors()) {
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java b/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java
index 9bd7dec..ccd08aa 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java
@@ -19,7 +19,6 @@
 import com.google.gwt.core.ext.TreeLogger.Type;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.CompilerContext;
-import com.google.gwt.dev.MinimalRebuildCache;
 import com.google.gwt.dev.javac.JdtCompiler.AdditionalTypeProviderDelegate;
 import com.google.gwt.dev.javac.JdtCompiler.UnitProcessor;
 import com.google.gwt.dev.javac.typemodel.LibraryTypeOracle;
@@ -539,8 +538,6 @@
     Map<CompilationUnitBuilder, CompilationUnit> cachedUnits = Maps.newIdentityHashMap();
 
     CompileMoreLater compileMoreLater = new CompileMoreLater(compilerContext, compilerDelegate);
-    Set<String> modifiedCompilationUnitNames = Sets.newHashSet();
-    Set<String> allCompilationUnitNames = Sets.newHashSet();
 
     // For each incoming Java source file...
     for (Resource resource : resources) {
@@ -549,13 +546,6 @@
 
       CompilationUnit cachedUnit = unitCache.find(resource.getPathPrefix() + resource.getPath());
 
-      String compilationUnitName = Shared.getTypeName(resource);
-      allCompilationUnitNames.add(compilationUnitName);
-      // Keep track of the names of units that are new or are known to have changed.
-      if (cachedUnit == null || cachedUnit.getLastModified() != resource.getLastModified()) {
-        // For the root type in a compilation unit the source and binary name are the same.
-        modifiedCompilationUnitNames.add(compilationUnitName);
-      }
       // Try to rescue cached units from previous sessions where a jar has been
       // recompiled.
       if (cachedUnit != null && cachedUnit.getLastModified() != resource.getLastModified()) {
@@ -577,9 +567,6 @@
       }
       builders.add(builder);
     }
-    MinimalRebuildCache minimalRebuildCache = compilerContext.getMinimalRebuildCache();
-    minimalRebuildCache.setModifiedCompilationUnitNames(logger, modifiedCompilationUnitNames);
-    minimalRebuildCache.setAllCompilationUnitNames(logger, allCompilationUnitNames);
     int cachedSourceCount = cachedUnits.size();
     int sourceCount = resources.size();
     if (logger.isLoggable(TreeLogger.TRACE)) {
@@ -628,10 +615,8 @@
 
     // Units we definitely want to build.
     List<CompilationUnitBuilder> builders = Lists.newArrayList();
-
     // Units we don't want to rebuild unless we have to.
     Map<CompilationUnitBuilder, CompilationUnit> cachedUnits = Maps.newIdentityHashMap();
-    Set<String> modifiedCompilationUnitNames = Sets.newHashSet();
 
     // For each incoming generated Java source file...
     for (GeneratedUnit generatedUnit : generatedUnits) {
@@ -641,10 +626,7 @@
       // Look for units previously compiled
       CompilationUnit cachedUnit = unitCache.find(builder.getContentId());
       // Keep track of the names of units that are new or are known to have changed.
-      if (cachedUnit == null) {
-        // For the root type in a compilation unit the source and binary name are the same.
-        modifiedCompilationUnitNames.add(generatedUnit.getTypeName());
-      } else if (cachedUnit != null) {
+      if (cachedUnit != null) {
         // Recompile generated units with errors so source can be dumped.
         if (!cachedUnit.isError()) {
           cachedUnits.put(builder, cachedUnit);
@@ -654,8 +636,9 @@
       }
       builders.add(builder);
     }
-    compilerContext.getMinimalRebuildCache().addModifiedCompilationUnitNames(logger,
-        modifiedCompilationUnitNames);
+    if (compilerContext.getOptions().shouldCompilePerFile()) {
+      compilerContext.getMinimalRebuildCache().recordGeneratedUnits(generatedUnits);
+    }
     compilationState.incrementGeneratedSourceCount(builders.size() + cachedUnits.size());
     compilationState.incrementCachedGeneratedSourceCount(cachedUnits.size());
     return compileMoreLater.compile(logger, compilerContext, builders,
diff --git a/dev/core/src/com/google/gwt/dev/jjs/AstConstructor.java b/dev/core/src/com/google/gwt/dev/jjs/AstConstructor.java
index b18ffb9..c4dc3e4 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/AstConstructor.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/AstConstructor.java
@@ -18,7 +18,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.CompilerContext;
-import com.google.gwt.dev.MinimalRebuildCache;
+import com.google.gwt.dev.NullRebuildCache;
 import com.google.gwt.dev.PrecompileTaskOptions;
 import com.google.gwt.dev.cfg.ConfigProps;
 import com.google.gwt.dev.javac.CompilationState;
@@ -51,7 +51,7 @@
     InternalCompilerException.preload();
 
     CompilerContext compilerContext = new CompilerContext.Builder().options(options)
-        .minimalRebuildCache(new MinimalRebuildCache()).build();
+        .minimalRebuildCache(new NullRebuildCache()).build();
 
     RebindPermutationOracle rpo = new RebindPermutationOracle() {
       @Override
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 156ab9f..252f183 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -35,7 +35,6 @@
 import com.google.gwt.core.linker.SoycReportLinker;
 import com.google.gwt.dev.CompilerContext;
 import com.google.gwt.dev.MinimalRebuildCache;
-import com.google.gwt.dev.MinimalRebuildCache.PermutationRebuildCache;
 import com.google.gwt.dev.Permutation;
 import com.google.gwt.dev.PrecompileTaskOptions;
 import com.google.gwt.dev.cfg.ConfigProps;
@@ -280,9 +279,7 @@
         if (options.shouldCompilePerFile()) {
           // Per file compilation needs the type reference graph to construct the set of reachable
           // types when linking.
-          PermutationRebuildCache permutationRebuildCache =
-              getMinimalRebuildCache().getPermutationRebuildCache(permutation.getId());
-          TypeReferencesRecorder.exec(jprogram, permutationRebuildCache);
+          TypeReferencesRecorder.exec(jprogram, getMinimalRebuildCache());
         }
         jprogram.typeOracle.recomputeAfterOptimizations(jprogram.getDeclaredTypes());
 
@@ -531,9 +528,7 @@
          */
         if (options.shouldCompilePerFile()) {
           transformer = new JsTypeLinker(logger, transformer, v.getClassRanges(),
-              v.getProgramClassRange(),
-              getMinimalRebuildCache().getPermutationRebuildCache(permutation.getId()),
-              jprogram.typeOracle);
+              v.getProgramClassRange(), getMinimalRebuildCache(), jprogram.typeOracle);
           transformer.exec();
         }
 
@@ -1041,9 +1036,10 @@
 
       // Free up memory.
       rpo.clear();
+      Set<String> deletedTypeNames = options.shouldCompilePerFile()
+          ? getMinimalRebuildCache().computeDeletedTypeNames() : Sets.<String> newHashSet();
       jprogram.typeOracle.computeBeforeAST(StandardTypes.createFrom(jprogram),
-          jprogram.getDeclaredTypes(), jprogram.getModuleDeclaredTypes(),
-          getMinimalRebuildCache().computeDeletedTypeNames());
+          jprogram.getDeclaredTypes(), jprogram.getModuleDeclaredTypes(), deletedTypeNames);
       return compilationState;
     }
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/MonolithicJavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/MonolithicJavaToJavaScriptCompiler.java
index 381c58c..1e8fcec 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/MonolithicJavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/MonolithicJavaToJavaScriptCompiler.java
@@ -47,6 +47,7 @@
 import com.google.gwt.dev.jjs.impl.RemoveSpecializations;
 import com.google.gwt.dev.jjs.impl.ReplaceGetClassOverrides;
 import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences;
+import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.IntTypeIdGenerator;
 import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.TypeOrder;
 import com.google.gwt.dev.jjs.impl.TypeCoercionNormalizer;
 import com.google.gwt.dev.jjs.impl.codesplitter.CodeSplitter;
@@ -120,8 +121,11 @@
         } else {
           TypeOrder typeIdOrder =
               options.shouldCompilePerFile() ? TypeOrder.ALPHABETICAL : TypeOrder.FREQUENCY;
+          IntTypeIdGenerator typeIdGenerator = options.shouldCompilePerFile()
+              ? compilerContext.getMinimalRebuildCache().getIntTypeIdGenerator()
+              : new IntTypeIdGenerator();
           return ResolveRuntimeTypeReferences.IntoIntLiterals.exec(jprogram, typeIdOrder,
-              compilerContext.getMinimalRebuildCache().getIntTypeIdGenerator());
+              typeIdGenerator);
         }
       } finally {
         event.end();
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 bd9bbf8..6a4d3d1 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
@@ -1828,14 +1828,16 @@
 
       Set<JDeclaredType> preambleTypes = generatePreamble(x, globalStmts);
 
-      // Record the names of preamble types so that it's possible to invalidate caches when the
-      // preamble types are known to have become stale.
-      if (!minimalRebuildCache.hasPreambleTypeNames()) {
-        Set<String> preambleTypeNames =  Sets.newHashSet();
-        for (JDeclaredType preambleType : preambleTypes) {
-          preambleTypeNames.add(preambleType.getName());
+      if (compilePerFile) {
+        // Record the names of preamble types so that it's possible to invalidate caches when the
+        // preamble types are known to have become stale.
+        if (!minimalRebuildCache.hasPreambleTypeNames()) {
+          Set<String> preambleTypeNames =  Sets.newHashSet();
+          for (JDeclaredType preambleType : preambleTypes) {
+            preambleTypeNames.add(preambleType.getName());
+          }
+          minimalRebuildCache.setPreambleTypeNames(logger, preambleTypeNames);
         }
-        minimalRebuildCache.setPreambleTypeNames(logger, preambleTypeNames);
       }
 
       // Sort normal types according to superclass relationship.
@@ -3333,6 +3335,8 @@
 
   private final boolean modularCompile;
 
+  private final boolean compilePerFile;
+
   /**
    * All of the fields in String and Array need special handling for interop.
    */
@@ -3387,6 +3391,7 @@
         compilerContext.getOptions().getOptimizationLevel() > OptionOptimize.OPTIMIZE_LEVEL_DRAFT;
     this.hasWholeWorldKnowledge = compilerContext.shouldCompileMonolithic()
         && !compilerContext.getOptions().shouldCompilePerFile();
+    this.compilePerFile = compilerContext.getOptions().shouldCompilePerFile();
     this.modularCompile = !compilerContext.shouldCompileMonolithic();
     this.symbolTable = symbolTable;
     this.typeIdsByType = typeIdsByType;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JsTypeLinker.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JsTypeLinker.java
index b8b02b5..a639adc 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JsTypeLinker.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JsTypeLinker.java
@@ -20,7 +20,7 @@
 import com.google.gwt.core.ext.linker.impl.NamedRange;
 import com.google.gwt.core.ext.linker.impl.StatementRangesBuilder;
 import com.google.gwt.core.ext.linker.impl.StatementRangesExtractor;
-import com.google.gwt.dev.MinimalRebuildCache.PermutationRebuildCache;
+import com.google.gwt.dev.MinimalRebuildCache;
 import com.google.gwt.dev.jjs.JsSourceMap;
 import com.google.gwt.dev.jjs.ast.JTypeOracle;
 import com.google.gwt.thirdparty.guava.common.collect.Lists;
@@ -46,7 +46,7 @@
   private final StringBuilder jsBuilder = new StringBuilder();
   private final Set<String> linkedTypeNames = Sets.newHashSet();
   private TreeLogger logger;
-  private final PermutationRebuildCache permutationRebuildCache;
+  private final MinimalRebuildCache minimalRebuildCache;
   private final JsSourceMapExtractor jsSourceMapExtractor;
   private final StatementRangesBuilder statementRangesBuilder = new StatementRangesBuilder();
   private final JsSourceMapBuilder jsSourceMapBuilder = new JsSourceMapBuilder();
@@ -56,7 +56,7 @@
 
   public JsTypeLinker(TreeLogger logger, JsAbstractTextTransformer textTransformer,
       List<NamedRange> typeRanges, NamedRange programTypeRange,
-      PermutationRebuildCache permutationRebuildCache, JTypeOracle typeOracle) {
+      MinimalRebuildCache minimalRebuildCache, JTypeOracle typeOracle) {
     super(textTransformer);
     this.logger = logger;
     this.statementRangesExtractor = new StatementRangesExtractor(statementRanges);
@@ -66,7 +66,7 @@
         programTypeRange.getStartLineNumber());
     this.footerRange = new NamedRange(FOOTER_NAME, programTypeRange.getEndPosition(), js.length(),
         programTypeRange.getEndLineNumber(), sourceInfoMap.getLines());
-    this.permutationRebuildCache = permutationRebuildCache;
+    this.minimalRebuildCache = minimalRebuildCache;
     this.typeOracle = typeOracle;
   }
 
@@ -84,31 +84,31 @@
 
   private List<String> computeReachableTypes() {
     List<String> reachableTypeNames =
-        Lists.newArrayList(permutationRebuildCache.computeReachableTypeNames());
+        Lists.newArrayList(minimalRebuildCache.computeReachableTypeNames());
     Collections.sort(reachableTypeNames);
     return reachableTypeNames;
   }
 
   private void extractOne(NamedRange typeRange) {
     String typeName = typeRange.getName();
-    permutationRebuildCache.setJsForType(logger, typeName,
+    minimalRebuildCache.setJsForType(logger, typeName,
         js.substring(typeRange.getStartPosition(), typeRange.getEndPosition()));
-    permutationRebuildCache.setStatementRangesForType(typeName,
+    minimalRebuildCache.setStatementRangesForType(typeName,
         statementRangesExtractor.extract(typeRange.getStartPosition(), typeRange.getEndPosition()));
-    permutationRebuildCache.setSourceMapForType(typeName, jsSourceMapExtractor.extract(
+    minimalRebuildCache.setSourceMapForType(typeName, jsSourceMapExtractor.extract(
         typeRange.getStartPosition(), typeRange.getEndPosition(), typeRange.getStartLineNumber(),
         typeRange.getEndLineNumber()));
   }
 
   private void linkAll(List<String> reachableTypeNames) {
     // Extract new JS.
-    if (permutationRebuildCache.getJs(HEADER_NAME) == null) {
+    if (minimalRebuildCache.getJs(HEADER_NAME) == null) {
       extractOne(headerRange);
     }
     for (NamedRange typeRange : typeRanges) {
       extractOne(typeRange);
     }
-    if (permutationRebuildCache.getJs(FOOTER_NAME) == null) {
+    if (minimalRebuildCache.getJs(FOOTER_NAME) == null) {
       extractOne(footerRange);
     }
 
@@ -136,7 +136,7 @@
     }
     linkedTypeNames.add(typeName);
 
-    String typeJs = permutationRebuildCache.getJs(typeName);
+    String typeJs = minimalRebuildCache.getJs(typeName);
     if (typeJs == null) {
       return;
     }
@@ -148,8 +148,8 @@
     }
 
     logger.log(TreeLogger.SPAM, "linking type " + typeName + " (" + typeJs.length() + " bytes)");
-    StatementRanges typeStatementRanges = permutationRebuildCache.getStatementRanges(typeName);
-    JsSourceMap typeSourceMap = permutationRebuildCache.getSourceMap(typeName);
+    StatementRanges typeStatementRanges = minimalRebuildCache.getStatementRanges(typeName);
+    JsSourceMap typeSourceMap = minimalRebuildCache.getSourceMap(typeName);
 
     jsBuilder.append(typeJs);
     statementRangesBuilder.append(typeStatementRanges);
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 9f7254e..80e3c49 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
@@ -13,7 +13,7 @@
  */
 package com.google.gwt.dev.jjs.impl;
 
-import com.google.gwt.dev.MinimalRebuildCache.PermutationRebuildCache;
+import com.google.gwt.dev.MinimalRebuildCache;
 import com.google.gwt.dev.jjs.ast.Context;
 import com.google.gwt.dev.jjs.ast.JArrayType;
 import com.google.gwt.dev.jjs.ast.JCastOperation;
@@ -41,15 +41,15 @@
  */
 public class TypeReferencesRecorder extends JVisitor {
 
-  public static void exec(JProgram program, PermutationRebuildCache permutationRebuildCache) {
-    new TypeReferencesRecorder(permutationRebuildCache).execImpl(program);
+  public static void exec(JProgram program, MinimalRebuildCache minimalRebuildCache) {
+    new TypeReferencesRecorder(minimalRebuildCache).execImpl(program);
   }
 
   private String fromTypeName;
-  private final PermutationRebuildCache permutationRebuildCache;
+  private final MinimalRebuildCache minimalRebuildCache;
 
-  public TypeReferencesRecorder(PermutationRebuildCache permutationRebuildCache) {
-    this.permutationRebuildCache = permutationRebuildCache;
+  public TypeReferencesRecorder(MinimalRebuildCache minimalRebuildCache) {
+    this.minimalRebuildCache = minimalRebuildCache;
   }
 
   @Override
@@ -135,7 +135,7 @@
   public boolean visit(JDeclaredType x, Context ctx) {
     fromTypeName = x.getName();
 
-    permutationRebuildCache.removeReferencesFrom(fromTypeName);
+    minimalRebuildCache.removeReferencesFrom(fromTypeName);
 
     // Gather superclass and implemented interface types.
     maybeRecordTypeRef(x.getSuperClass());
@@ -162,7 +162,7 @@
   }
 
   private void maybeRecordTypeRef(String fromTypeName, String toTypeName) {
-    permutationRebuildCache.addTypeReference(fromTypeName, toTypeName);
+    minimalRebuildCache.addTypeReference(fromTypeName, toTypeName);
   }
 
   private void maybeRecordTypeRefs(List<? extends JDeclaredType> toTypes) {
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 5483c47..e7ebb94 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
@@ -672,7 +672,7 @@
    * after each Generator) to eagerly process stale types (some of which don't exist yet) from
    * becoming a performance problem.
    */
-  private Set<String> staleTypeNames;
+  private Set<String> staleTypeNames = Sets.newHashSet();
 
   /**
    * A work queue of methods whose bodies we need to traverse. Prevents
@@ -707,10 +707,11 @@
     this.compiledClassesBySourceName = compilationState.getClassFileMapBySource();
     initializeNameBasedLocators();
     this.minimalRebuildCache = compilerContext.getMinimalRebuildCache();
-    this.staleTypeNames =
-        minimalRebuildCache.clearStaleTypeJsAndStatements(logger, program.typeOracle);
-
-    checkPreambleTypesStillFresh(logger);
+    if (compilePerFile) {
+      this.staleTypeNames =
+          minimalRebuildCache.clearStaleTypeJsAndStatements(logger, program.typeOracle);
+      checkPreambleTypesStillFresh(logger);
+    }
   }
 
   public void addRootTypes(Collection<String> sourceTypeNames) throws UnableToCompleteException {
@@ -967,7 +968,9 @@
     }
     // Staleness calculations need to be able to trace from CompilationUnit name to the names of
     // immediately nested types. So record those associations now.
-    compilerContext.getMinimalRebuildCache().recordNestedTypeNamesPerType(unit);
+    if (compilePerFile) {
+      compilerContext.getMinimalRebuildCache().recordNestedTypeNamesPerType(unit);
+    }
     // TODO(zundel): ask for a recompile if deserialization fails?
     List<JDeclaredType> types = unit.getTypes();
     assert containsAllTypes(unit, types);
diff --git a/dev/core/test/com/google/gwt/dev/CompilerTest.java b/dev/core/test/com/google/gwt/dev/CompilerTest.java
index ba3654f..3ed8650 100644
--- a/dev/core/test/com/google/gwt/dev/CompilerTest.java
+++ b/dev/core/test/com/google/gwt/dev/CompilerTest.java
@@ -368,6 +368,8 @@
           "<entry-point class='com.foo.TestEntryPoint'/>",
           "</module>");
 
+  private Set<String> emptySet = stringSet();
+
   public CompilerTest() {
     argProcessor = new Compiler.ArgProcessor(options);
   }
@@ -586,14 +588,14 @@
     MinimalRebuildCache relinkMinimalRebuildCache = new MinimalRebuildCache();
     File relinkApplicationDir = Files.createTempDir();
     compileToJs(compilerOptions, relinkApplicationDir, "com.foo.SimpleModule", originalResources,
-        relinkMinimalRebuildCache, output);
+        relinkMinimalRebuildCache, emptySet, output);
 
     // BarReferencesFoo Generator has now been run once.
     assertEquals(1, BarReferencesFooGenerator.runCount);
 
     // Recompile with no changes, which should not trigger any Generator runs.
     compileToJs(compilerOptions, relinkApplicationDir, "com.foo.SimpleModule",
-        Lists.<MockResource> newArrayList(), relinkMinimalRebuildCache, output);
+        Lists.<MockResource> newArrayList(), relinkMinimalRebuildCache, emptySet, output);
 
     // Since there were no changes BarReferencesFoo Generator was not run again.
     assertEquals(1, BarReferencesFooGenerator.runCount);
@@ -601,7 +603,8 @@
     // Recompile with a modified Foo class, which should invalidate Bar which was generated by a
     // GWT.create() call in the entry point.
     compileToJs(compilerOptions, relinkApplicationDir, "com.foo.SimpleModule",
-        Lists.<MockResource> newArrayList(fooResource), relinkMinimalRebuildCache, output);
+        Lists.<MockResource> newArrayList(fooResource), relinkMinimalRebuildCache,
+        stringSet("com.foo.TestEntryPoint", "com.foo.Foo", "com.foo.Bar"), output);
 
     // BarReferencesFoo Generator was run again.
     assertEquals(2, BarReferencesFooGenerator.runCount);
@@ -625,7 +628,7 @@
     MinimalRebuildCache relinkMinimalRebuildCache = new MinimalRebuildCache();
     File relinkApplicationDir = Files.createTempDir();
     compileToJs(compilerOptions, relinkApplicationDir, "com.foo.SimpleModule", originalResources,
-        relinkMinimalRebuildCache, output);
+        relinkMinimalRebuildCache, emptySet, output);
 
     // Generators have now been run once.
     assertEquals(1, CauseStringRebindGenerator.runCount);
@@ -634,7 +637,7 @@
 
     // Recompile with no changes, which should not trigger any Generator runs.
     compileToJs(compilerOptions, relinkApplicationDir, "com.foo.SimpleModule",
-        Lists.<MockResource> newArrayList(), relinkMinimalRebuildCache, output);
+        Lists.<MockResource> newArrayList(), relinkMinimalRebuildCache, emptySet, output);
 
     // Since there were no changes Generators were not run again.
     assertEquals(1, CauseStringRebindGenerator.runCount);
@@ -646,7 +649,8 @@
     // FooResourceGenerator.
     compileToJs(compilerOptions, relinkApplicationDir, "com.foo.SimpleModule",
         Lists.<MockResource> newArrayList(classNameToGenerateResource), relinkMinimalRebuildCache,
-        output);
+        stringSet("com.foo.TestEntryPoint", "com.foo.Baz", "com.foo.Bar",
+            "com.foo.HasCustomContent", "com.foo.FooReplacementOne"), output);
 
     // Generators were run again.
     assertEquals(2, CauseStringRebindGenerator.runCount);
@@ -671,7 +675,7 @@
     MinimalRebuildCache relinkMinimalRebuildCache = new MinimalRebuildCache();
     File relinkApplicationDir = Files.createTempDir();
     compileToJs(compilerOptions, relinkApplicationDir, "com.foo.SimpleModule", originalResources,
-        relinkMinimalRebuildCache, output);
+        relinkMinimalRebuildCache, emptySet, output);
 
     // Foo Generator has now been run once.
     assertEquals(1, FooResourceGenerator.runCount);
@@ -682,7 +686,8 @@
 
     // Recompile with just 1 file change, which should not trigger any Generator runs.
     compileToJs(compilerOptions, relinkApplicationDir, "com.foo.SimpleModule",
-        Lists.<MockResource> newArrayList(nonJsoFooResource), relinkMinimalRebuildCache, output);
+        Lists.<MockResource> newArrayList(nonJsoFooResource), relinkMinimalRebuildCache,
+        stringSet("com.foo.Foo"), output);
 
     // Foo Generator was not run again.
     assertEquals(1, FooResourceGenerator.runCount);
@@ -699,11 +704,11 @@
 
     String originalJs = compileToJs(relinkApplicationDir, "com.foo.SimpleModule", Lists
         .newArrayList(simpleModuleResource, simpleModelEntryPointResource, simpleModelResource),
-        relinkMinimalRebuildCache, output);
+        relinkMinimalRebuildCache, emptySet, output);
 
     // Compile again with absolutely no file changes and reusing the minimalRebuildCache.
     String relinkedJs = compileToJs(relinkApplicationDir, "com.foo.SimpleModule",
-        Lists.<MockResource> newArrayList(), relinkMinimalRebuildCache, output);
+        Lists.<MockResource> newArrayList(), relinkMinimalRebuildCache, emptySet, output);
 
     assertTrue(originalJs.equals(relinkedJs));
   }
@@ -715,41 +720,42 @@
 
     String originalJs = compileToJs(relinkApplicationDir, "com.foo.SimpleModule", Lists
         .newArrayList(simpleModuleResource, simpleModelEntryPointResource, simpleModelResource),
-        relinkMinimalRebuildCache, output);
+        relinkMinimalRebuildCache, emptySet, output);
 
     // Compile again with the same source but a new date stamp on SimpleModel and reusing the
     // minimalRebuildCache.
     String relinkedJs = compileToJs(relinkApplicationDir, "com.foo.SimpleModule",
-        Lists.<MockResource> newArrayList(simpleModelResource), relinkMinimalRebuildCache, output);
+        Lists.<MockResource> newArrayList(simpleModelResource), relinkMinimalRebuildCache,
+        stringSet("com.foo.TestEntryPoint", "com.foo.SimpleModel"), output);
 
     assertTrue(originalJs.equals(relinkedJs));
   }
 
   private void checkPerFileRecompile_regularClassMadeIntoJsoClass(JsOutputOption output)
-      throws UnableToCompleteException,
-      IOException, InterruptedException {
+      throws UnableToCompleteException, IOException, InterruptedException {
     CompilerOptions compilerOptions = new CompilerOptionsImpl();
     compilerOptions.setUseDetailedTypeIds(true);
 
-    checkRecompiledModifiedApp(compilerOptions, "com.foo.SimpleModule",
-        Lists.newArrayList(simpleModuleResource, jsoArrayTestEntryPointResource,
-            someClassReferringToJsoOneArrays, someClassReferringToJsoTwoArrays, jsoOne),
-        jsoTwo_before, jsoTwo_after, output);
+    checkRecompiledModifiedApp(compilerOptions, "com.foo.SimpleModule", Lists.newArrayList(
+        simpleModuleResource, jsoArrayTestEntryPointResource, someClassReferringToJsoOneArrays,
+        someClassReferringToJsoTwoArrays, jsoOne), jsoTwo_before, jsoTwo_after,
+        stringSet("com.foo.JsoTwo", "com.foo.SomeClassReferringToJsoTwoArrays"), output);
   }
 
   private void checkPerFileRecompile_functionSignatureChange(JsOutputOption output)
-      throws UnableToCompleteException,
-      IOException, InterruptedException {
+      throws UnableToCompleteException, IOException, InterruptedException {
     checkRecompiledModifiedApp("com.foo.SimpleModule",
         Lists.newArrayList(simpleModuleResource, simpleModelEntryPointResource),
-        simpleModelResource, modifiedFunctionSignatureSimpleModelResource, output);
+        simpleModelResource, modifiedFunctionSignatureSimpleModelResource,
+        stringSet("com.foo.TestEntryPoint", "com.foo.SimpleModel"), output);
   }
 
   private void checkPerFileRecompile_typeHierarchyChange(JsOutputOption output)
       throws UnableToCompleteException, IOException, InterruptedException {
     checkRecompiledModifiedApp("com.foo.SimpleModule", Lists.newArrayList(simpleModuleResource,
         modifiedSuperEntryPointResource, modelAResource, modelBResource, modelDResource),
-        modelCResource, modifiedSuperModelCResource, output);
+        modelCResource, modifiedSuperModelCResource,
+        stringSet("com.foo.TestEntryPoint", "com.foo.ModelC", "com.foo.ModelD"), output);
   }
 
   private void checkPerFileRecompile_singleJsoIntfDispatchChange(JsOutputOption output)
@@ -759,7 +765,8 @@
 
     checkRecompiledModifiedApp(compilerOptions, "com.foo.SimpleModule", Lists.newArrayList(
         simpleModuleResource, modifiedJsoIntfDispatchEntryPointResource, callerResource,
-        fooInterfaceResource), nonJsoFooResource, jsoFooResource, output);
+        fooInterfaceResource), nonJsoFooResource, jsoFooResource,
+        stringSet("com.foo.TestEntryPoint", "com.foo.Foo", "com.foo.Caller"), output);
   }
 
   private void checkPerFileRecompile_devirtualizeUnchangedJso(JsOutputOption output)
@@ -767,9 +774,10 @@
     CompilerOptions compilerOptions = new CompilerOptionsImpl();
     compilerOptions.setUseDetailedTypeIds(true);
 
-    checkRecompiledModifiedApp(compilerOptions, "com.foo.SimpleModule", Lists.newArrayList(
-        jsoTestModuleResource, simpleFactory, simpleIntf, simpleJso), jsoTestEntryPointResource,
-        jsoTestEntryPointResource, output);
+    checkRecompiledModifiedApp(compilerOptions, "com.foo.SimpleModule",
+        Lists.newArrayList(jsoTestModuleResource, simpleFactory, simpleIntf, simpleJso),
+        jsoTestEntryPointResource, jsoTestEntryPointResource, stringSet("com.foo.TestEntryPoint",
+            "com.google.gwt.lang.com_00046foo_00046SimpleModule__EntryMethodHolder"), output);
   }
 
   private void checkPerFileRecompile_multipleClassGenerator(JsOutputOption output)
@@ -777,9 +785,10 @@
     CompilerOptions compilerOptions = new CompilerOptionsImpl();
     compilerOptions.setUseDetailedTypeIds(true);
 
-    checkRecompiledModifiedApp(compilerOptions, "com.foo.SimpleModule", Lists.newArrayList(
-        multipleClassGeneratorModuleResource, generatorEntryPointResource), bazResource,
-        bazResource, output);
+    checkRecompiledModifiedApp(compilerOptions, "com.foo.SimpleModule",
+        Lists.newArrayList(multipleClassGeneratorModuleResource, generatorEntryPointResource),
+        bazResource, bazResource, stringSet("com.foo.Baz", "com.foo.TestEntryPoint", "com.foo.Bar"),
+        output);
   }
 
   private void checkPerFileRecompile_dualJsoIntfDispatchChange(JsOutputOption output)
@@ -790,7 +799,7 @@
     checkRecompiledModifiedApp(compilerOptions, "com.foo.SimpleModule", Lists.newArrayList(
         simpleModuleResource, modifiedJsoIntfDispatchEntryPointResource, callerResource,
         fooInterfaceResource, regularFooImplemetorResource), nonJsoFooResource, jsoFooResource,
-        output);
+        stringSet("com.foo.TestEntryPoint", "com.foo.Foo", "com.foo.Caller"), output);
   }
 
   private void checkPerFileRecompile_generatorInputResourceChange(JsOutputOption outputOption)
@@ -800,8 +809,9 @@
 
     checkRecompiledModifiedApp(compilerOptions, "com.foo.SimpleModule", Lists.newArrayList(
         resourceReadingGeneratorModuleResource, generatorEntryPointResource, fooInterfaceResource,
-        nonJsoFooResource), classNameToGenerateResource, modifiedClassNameToGenerateResource,
-        outputOption);
+        nonJsoFooResource), classNameToGenerateResource, modifiedClassNameToGenerateResource, Sets.<
+        String> newHashSet("com.foo.TestEntryPoint", "com.foo.FooReplacementOne",
+        "com.foo.HasCustomContent"), outputOption);
   }
 
   private void assertDeterministicBuild(String topLevelModule, int optimizationLevel)
@@ -833,8 +843,8 @@
 
       // It is only necessary to check that the filenames in the output directory are the same
       // because the names of the files for the JavaScript outputs are the hash of its contents.
-      assertEquals("First and second compile produced different outputs",
-          firstTimeOutput, secondTimeOutput);
+      assertEquals("First and second compile produced different outputs", firstTimeOutput,
+          secondTimeOutput);
     } finally {
       if (oldPersistentUnitCacheValue == null) {
         System.clearProperty(GWT_PERSISTENTUNITCACHE);
@@ -847,16 +857,17 @@
   }
 
   private void checkRecompiledModifiedApp(String moduleName, List<MockResource> sharedResources,
-      MockResource originalResource, MockResource modifiedResource, JsOutputOption output)
-      throws IOException, UnableToCompleteException, InterruptedException {
+      MockResource originalResource, MockResource modifiedResource,
+      Set<String> expectedStaleTypeNamesOnModify, JsOutputOption output) throws IOException,
+      UnableToCompleteException, InterruptedException {
     checkRecompiledModifiedApp(new CompilerOptionsImpl(), moduleName, sharedResources,
-        originalResource, modifiedResource, output);
+        originalResource, modifiedResource, expectedStaleTypeNamesOnModify, output);
   }
 
   private void checkRecompiledModifiedApp(CompilerOptions compilerOptions, String moduleName,
       List<MockResource> sharedResources, MockResource originalResource,
-      MockResource modifiedResource, JsOutputOption output) throws IOException,
-      UnableToCompleteException, InterruptedException {
+      MockResource modifiedResource, Set<String> expectedStaleTypeNamesOnModify,
+      JsOutputOption output) throws IOException, UnableToCompleteException, InterruptedException {
     List<MockResource> originalResources = Lists.newArrayList(sharedResources);
     originalResources.add(originalResource);
 
@@ -866,17 +877,17 @@
     // Compile the app with original files, modify a file and do a per-file recompile.
     MinimalRebuildCache relinkMinimalRebuildCache = new MinimalRebuildCache();
     File relinkApplicationDir = Files.createTempDir();
-    String originalAppFromScratchJs =
-        compileToJs(compilerOptions, relinkApplicationDir, moduleName, originalResources,
-            relinkMinimalRebuildCache, output);
+    String originalAppFromScratchJs = compileToJs(compilerOptions, relinkApplicationDir, moduleName,
+        originalResources, relinkMinimalRebuildCache, emptySet, output);
     String modifiedAppRelinkedJs = compileToJs(compilerOptions, relinkApplicationDir, moduleName,
-        Lists.<MockResource> newArrayList(modifiedResource), relinkMinimalRebuildCache, output);
+        Lists.<MockResource> newArrayList(modifiedResource), relinkMinimalRebuildCache,
+        expectedStaleTypeNamesOnModify, output);
 
     // Compile the app from scratch with the modified file.
     MinimalRebuildCache fromScratchMinimalRebuildCache = new MinimalRebuildCache();
     File fromScratchApplicationDir = Files.createTempDir();
     String modifiedAppFromScratchJs = compileToJs(compilerOptions, fromScratchApplicationDir,
-        moduleName, modifiedResources, fromScratchMinimalRebuildCache, output);
+        moduleName, modifiedResources, fromScratchMinimalRebuildCache, emptySet, output);
 
     // If a resource contents were changed between the original compile and the relink compile
     // check that the output JS has also changed. If all resources have the same content (their
@@ -892,16 +903,16 @@
 
   private String compileToJs(File applicationDir, String moduleName,
       List<MockResource> applicationResources, MinimalRebuildCache minimalRebuildCache,
-      JsOutputOption output)
-      throws IOException, UnableToCompleteException, InterruptedException {
-    return compileToJs(new CompilerOptionsImpl(), applicationDir, moduleName,  applicationResources,
-        minimalRebuildCache, output);
+      Set<String> expectedStaleTypeNames, JsOutputOption output) throws IOException,
+      UnableToCompleteException, InterruptedException {
+    return compileToJs(new CompilerOptionsImpl(), applicationDir, moduleName, applicationResources,
+        minimalRebuildCache, expectedStaleTypeNames, output);
   }
 
   private String compileToJs(CompilerOptions compilerOptions, File applicationDir,
       String moduleName, List<MockResource> applicationResources,
-      MinimalRebuildCache minimalRebuildCache,  JsOutputOption output)
-      throws IOException, UnableToCompleteException, InterruptedException {
+      MinimalRebuildCache minimalRebuildCache, Set<String> expectedStaleTypeNames,
+      JsOutputOption output) throws IOException, UnableToCompleteException, InterruptedException {
     // Make sure we're using a MemoryUnitCache.
     System.setProperty(GWT_PERSISTENTUNITCACHE, "false");
     // Wait 1 second so that any new file modification times are actually different.
@@ -957,9 +968,14 @@
       }
     }
     assertNotNull(outputJsFile);
+    assertEquals(expectedStaleTypeNames, minimalRebuildCache.getStaleTypeNames());
     return Files.toString(outputJsFile, Charsets.UTF_8);
   }
 
+  private Set<String> stringSet(String... strings) {
+    return Sets.newHashSet(strings);
+  }
+
   private void writeResourceTo(MockResource mockResource, File applicationDir) throws IOException {
     File resourceFile =
         new File(applicationDir.getAbsolutePath() + File.separator + mockResource.getPath());
diff --git a/dev/core/test/com/google/gwt/dev/MinimalRebuildCacheTest.java b/dev/core/test/com/google/gwt/dev/MinimalRebuildCacheTest.java
index d5ae46b..65a50fb 100644
--- a/dev/core/test/com/google/gwt/dev/MinimalRebuildCacheTest.java
+++ b/dev/core/test/com/google/gwt/dev/MinimalRebuildCacheTest.java
@@ -14,12 +14,14 @@
 package com.google.gwt.dev;
 
 import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.dev.MinimalRebuildCache.PermutationRebuildCache;
 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.Sets;
 
 import junit.framework.TestCase;
 
+import java.util.Map;
+
 /**
  * Tests for {@link MinimalRebuildCache}.
  */
@@ -29,8 +31,9 @@
 
   public void testComputeAndClearStale() {
     // These three compilation units exist.
-    minimalRebuildCache.setAllCompilationUnitNames(TreeLogger.NULL,
-        Sets.newHashSet("Foo", "Bar", "Baz"));
+    Map<String, Long> currentModifiedBySourcePath = new ImmutableMap.Builder<String, Long>().put(
+        "Foo.java", 0L).put("Bar.java", 0L).put("Baz.java", 0L).build();
+    minimalRebuildCache.recordDiskSourceResources(currentModifiedBySourcePath);
 
     // They each contain a type and nested type.
     minimalRebuildCache.recordNestedTypeName("Foo", "Foo");
@@ -41,40 +44,41 @@
     minimalRebuildCache.recordNestedTypeName("Baz", "Baz$Inner");
 
     // There's some JS for each type.
-    PermutationRebuildCache permutationRebuildCache =
-        minimalRebuildCache.getPermutationRebuildCache(0);
-    permutationRebuildCache.setJsForType(TreeLogger.NULL, "Foo", "Some Js for Foo");
-    permutationRebuildCache.setJsForType(TreeLogger.NULL, "Foo$Inner", "Some Js for Foo");
-    permutationRebuildCache.setJsForType(TreeLogger.NULL, "Bar", "Some Js for Bar");
-    permutationRebuildCache.setJsForType(TreeLogger.NULL, "Bar$Inner", "Some Js for Bar");
-    permutationRebuildCache.setJsForType(TreeLogger.NULL, "Baz", "Some Js for Baz");
-    permutationRebuildCache.setJsForType(TreeLogger.NULL, "Baz$Inner", "Some Js for Baz");
+    minimalRebuildCache.setJsForType(TreeLogger.NULL, "Foo", "Some Js for Foo");
+    minimalRebuildCache.setJsForType(TreeLogger.NULL, "Foo$Inner", "Some Js for Foo");
+    minimalRebuildCache.setJsForType(TreeLogger.NULL, "Bar", "Some Js for Bar");
+    minimalRebuildCache.setJsForType(TreeLogger.NULL, "Bar$Inner", "Some Js for Bar");
+    minimalRebuildCache.setJsForType(TreeLogger.NULL, "Baz", "Some Js for Baz");
+    minimalRebuildCache.setJsForType(TreeLogger.NULL, "Baz$Inner", "Some Js for Baz");
 
     // Record that Bar references Foo and Baz subclasses Foo.
-    permutationRebuildCache.addTypeReference("Bar", "Foo");
-    minimalRebuildCache.getImmediateTypeRelations()
-        .getImmediateSuperclassesByClass().put("Baz", "Foo");
+    minimalRebuildCache.addTypeReference("Bar", "Foo");
+    minimalRebuildCache.getImmediateTypeRelations().getImmediateSuperclassesByClass().put("Baz",
+        "Foo");
 
-    // In the next compile Foo is modified.
-    minimalRebuildCache.setModifiedCompilationUnitNames(TreeLogger.NULL, Sets.newHashSet("Foo"));
+    // In the next compile only Foo is modified.
+    Map<String, Long> laterModifiedBySourcePath = new ImmutableMap.Builder<String, Long>().put(
+        "Foo.java", 9999L).put("Bar.java", 0L).put("Baz.java", 0L).build();
+    minimalRebuildCache.recordDiskSourceResources(laterModifiedBySourcePath);
 
     // Request clearing of cache related to stale types.
     minimalRebuildCache.clearStaleTypeJsAndStatements(TreeLogger.NULL,
         new JTypeOracle(null, minimalRebuildCache, true));
 
     // Has the expected JS been cleared?
-    assertNull(permutationRebuildCache.getJs("Foo"));
-    assertNull(permutationRebuildCache.getJs("Foo$Inner"));
-    assertNull(permutationRebuildCache.getJs("Bar"));
-    assertNull(permutationRebuildCache.getJs("Baz"));
-    assertNotNull(permutationRebuildCache.getJs("Bar$Inner"));
-    assertNotNull(permutationRebuildCache.getJs("Baz$Inner"));
+    assertNull(minimalRebuildCache.getJs("Foo"));
+    assertNull(minimalRebuildCache.getJs("Foo$Inner"));
+    assertNull(minimalRebuildCache.getJs("Bar"));
+    assertNull(minimalRebuildCache.getJs("Baz"));
+    assertNotNull(minimalRebuildCache.getJs("Bar$Inner"));
+    assertNotNull(minimalRebuildCache.getJs("Baz$Inner"));
   }
 
   public void testComputeDeletedTypes() {
     // These three compilation units exist.
-    minimalRebuildCache.setAllCompilationUnitNames(TreeLogger.NULL,
-        Sets.newHashSet("Foo", "Bar", "Baz"));
+    Map<String, Long> currentModifiedBySourcePath = new ImmutableMap.Builder<String, Long>().put(
+        "Foo.java", 0L).put("Bar.java", 0L).put("Baz.java", 0L).build();
+    minimalRebuildCache.recordDiskSourceResources(currentModifiedBySourcePath);
 
     // They each contain a type and nested type.
     minimalRebuildCache.recordNestedTypeName("Foo", "Foo");
@@ -85,7 +89,9 @@
     minimalRebuildCache.recordNestedTypeName("Baz", "Baz$Inner");
 
     // In the next compile it turns out there are fewer compilation units, Baz is gone.
-    minimalRebuildCache.setAllCompilationUnitNames(TreeLogger.NULL, Sets.newHashSet("Foo", "Bar"));
+    Map<String, Long> laterModifiedBySourcePath =
+        new ImmutableMap.Builder<String, Long>().put("Foo.java", 0L).put("Bar.java", 0L).build();
+    minimalRebuildCache.recordDiskSourceResources(laterModifiedBySourcePath);
 
     // Is the correct deleted type set calculated?
     assertEquals(Sets.newHashSet("Baz", "Baz$Inner"),
diff --git a/dev/core/test/com/google/gwt/dev/javac/CompilationStateTest.java b/dev/core/test/com/google/gwt/dev/javac/CompilationStateTest.java
index a7465f0..481c8c3 100644
--- a/dev/core/test/com/google/gwt/dev/javac/CompilationStateTest.java
+++ b/dev/core/test/com/google/gwt/dev/javac/CompilationStateTest.java
@@ -27,7 +27,6 @@
 import com.google.gwt.dev.js.ast.JsRootScope;
 import com.google.gwt.dev.js.ast.JsStatement;
 import com.google.gwt.dev.js.ast.JsVisitor;
-import com.google.gwt.dev.resource.Resource;
 import com.google.gwt.dev.util.DefaultTextOutput;
 import com.google.gwt.dev.util.TextOutput;
 import com.google.gwt.dev.util.Util;
@@ -86,19 +85,28 @@
     return text.toString();
   }
 
-  public void testAddGeneratedCompilationUnit() {
+  public void testAddGeneratedCompilationUnit_compilePerFile() {
+    checkAddGeneratedCompilationUnit(true);
+  }
+
+  public void testAddGeneratedCompilationUnit_regular() {
+    checkAddGeneratedCompilationUnit(false);
+  }
+
+  private void checkAddGeneratedCompilationUnit(boolean compilePerFile) {
+    compilerContext.getOptions().setCompilePerFile(compilePerFile);
+
     MinimalRebuildCache minimalRebuildCache = compilerContext.getMinimalRebuildCache();
 
     // Compile and ensure that not-yet-generated class Foo is not seen.
     validateCompilationState();
-    assertEquals(minimalRebuildCache.getAllCompilationUnitNames(),
-        getNamesOf(oracle.getResources()));
     assertFalse(minimalRebuildCache.getModifiedCompilationUnitNames().contains("test.Foo"));
 
     // Add a generated unit and ensure it shows up as a new modified unit.
     addGeneratedUnits(JavaResourceBase.FOO);
     validateCompilationState(Shared.getTypeName(JavaResourceBase.FOO));
-    assertTrue(minimalRebuildCache.getModifiedCompilationUnitNames().contains("test.Foo"));
+    assertEquals(compilePerFile,
+        minimalRebuildCache.getModifiedCompilationUnitNames().contains("test.Foo"));
 
     rebuildCompilationState();
     validateCompilationState();
@@ -429,7 +437,6 @@
    * @param updatedSet CompilationUnits that are generated the next time.
    * @param reusedTypes Main type of the units that can be reused between the
    *          initialSet and updatedSet.
-   * @param numInvalidated Number of types invalidated from graveyardUnits.
    */
   private void testCachingOverMultipleRefreshes(MockJavaResource[] initialSet,
       MockJavaResource[] updatedSet, Set<String> reusedTypes) {
@@ -444,8 +451,6 @@
     assertEquals(oracle.getResources().size() + initialSet.length,
         units1.size());
     assertUnitsChecked(units1.values());
-    assertTrue(compilerContext.getMinimalRebuildCache().getModifiedCompilationUnitNames()
-        .containsAll(getNamesOf(Arrays.asList(initialSet))));
 
     // Add 'updatedSet' generatedUnits on the second cycle.
     rebuildCompilationState();
@@ -487,14 +492,6 @@
     }
   }
 
-  private Set<String> getNamesOf(Iterable<? extends Resource> initialSet) {
-    Set<String> compilationUnitTypeNames = new HashSet<String>();
-    for (Resource mockResource : initialSet) {
-      compilationUnitTypeNames.add(Shared.getTypeName(mockResource));
-    }
-    return compilationUnitTypeNames;
-  }
-
   private void validateSerializedTestUnit(MockJavaResource resource,
       CompilationUnit unit) throws Exception {
     assertNotNull(unit);
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 dec40bf..286e1b4 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,7 +19,6 @@
 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.MinimalRebuildCache.PermutationRebuildCache;
 import com.google.gwt.dev.jjs.JsSourceMap;
 import com.google.gwt.dev.jjs.SourceInfo;
 import com.google.gwt.dev.jjs.SourceOrigin;
@@ -74,8 +73,6 @@
     String originalJs = sb.toString();
 
     MinimalRebuildCache minimalRebuildCache = new MinimalRebuildCache();
-    PermutationRebuildCache permutationRebuildCache =
-        minimalRebuildCache.getPermutationRebuildCache(1);
 
     // Create type inheritance.
     Map<String, String> superClassesByClass =
@@ -90,16 +87,16 @@
     minimalRebuildCache.setRootTypeNames(Lists.newArrayList("com.some.app.EntryPoint"));
 
     // Record type references.
-    permutationRebuildCache.addTypeReference("com.some.app.EntryPoint",
+    minimalRebuildCache.addTypeReference("com.some.app.EntryPoint",
         "com.some.app.SomeController");
-    permutationRebuildCache.addTypeReference("com.some.app.SomeController",
+    minimalRebuildCache.addTypeReference("com.some.app.SomeController",
         "com.some.app.SomeBModel");
-    permutationRebuildCache.addTypeReference("com.some.app.SomeController",
+    minimalRebuildCache.addTypeReference("com.some.app.SomeController",
         "com.some.app.SomeAModel");
 
     JsTypeLinker jsTypeLinker = new JsTypeLinker(TreeLogger.NULL,
         new JsNoopTransformer(originalJs, srb.build(), smb.build()), classRanges, programRange,
-        permutationRebuildCache, new JTypeOracle(null, minimalRebuildCache, true));
+        minimalRebuildCache, new JTypeOracle(null, minimalRebuildCache, true));
 
     // Run the JS Type Linker.
     jsTypeLinker.exec();
@@ -119,7 +116,7 @@
     superClassesByClass.put("com.some.app.SomeAModel", "com.some.app.SomeBModel");
     jsTypeLinker = new JsTypeLinker(TreeLogger.NULL,
         new JsNoopTransformer(originalJs, srb.build(), smb.build()), classRanges, programRange,
-        permutationRebuildCache, new JTypeOracle(null, minimalRebuildCache, true));
+        minimalRebuildCache, new JTypeOracle(null, minimalRebuildCache, true));
     jsTypeLinker.exec();
     assertEquals("<preamble>\n<java.lang.Object />\n<java.lang.Class />\n</preamble>\n"
         + "<com.some.app.EntryPoint>\n" + "<com.some.app.SomeModelB>\n"
@@ -133,12 +130,12 @@
 
     // Stop referring to SomeModelA from the Controller and verify that SomeModelA is not in the
     // output.
-    permutationRebuildCache.removeReferencesFrom("com.some.app.SomeController");
-    permutationRebuildCache.addTypeReference("com.some.app.SomeController",
+    minimalRebuildCache.removeReferencesFrom("com.some.app.SomeController");
+    minimalRebuildCache.addTypeReference("com.some.app.SomeController",
         "com.some.app.SomeBModel");
     jsTypeLinker = new JsTypeLinker(TreeLogger.NULL,
         new JsNoopTransformer(originalJs, srb.build(), smb.build()), classRanges, programRange,
-        permutationRebuildCache, new JTypeOracle(null, minimalRebuildCache, true));
+        minimalRebuildCache, new JTypeOracle(null, minimalRebuildCache, true));
     jsTypeLinker.exec();
     assertEquals("<preamble>\n<java.lang.Object />\n<java.lang.Class />\n</preamble>\n"
         + "<com.some.app.EntryPoint>\n" + "<com.some.app.SomeModelB>\n"
diff --git a/user/test/com/google/gwt/core/ext/SingleScriptLinkerTest.gwt.xml b/user/test/com/google/gwt/core/ext/SingleScriptLinkerTest.gwt.xml
index 1397e45..fb9c519 100644
--- a/user/test/com/google/gwt/core/ext/SingleScriptLinkerTest.gwt.xml
+++ b/user/test/com/google/gwt/core/ext/SingleScriptLinkerTest.gwt.xml
@@ -16,7 +16,7 @@
 	<inherits name='com.google.gwt.core.ext.LinkerTest' />
 	<add-linker name='sso' />
 
-	<define-property name='sso.test' values='one,two,three'/>
+	<define-property name='sso.test' values='two'/>
 	<property-provider name='sso.test'><![CDATA[
 		return "two";
 	]]></property-provider>
diff --git a/user/test/com/google/gwt/dev/jjs/CodeSplitterCollapsedPropertiesTest.gwt.xml b/user/test/com/google/gwt/dev/jjs/CodeSplitterCollapsedPropertiesTest.gwt.xml
index 4c7dc73..bb900cc 100644
--- a/user/test/com/google/gwt/dev/jjs/CodeSplitterCollapsedPropertiesTest.gwt.xml
+++ b/user/test/com/google/gwt/dev/jjs/CodeSplitterCollapsedPropertiesTest.gwt.xml
@@ -15,7 +15,7 @@
   <inherits name="com.google.gwt.useragent.UserAgent" />
   <source path="test" />
 
-  <define-property name="collapsedProperty" values="one,two"/>
+  <define-property name="collapsedProperty" values="one"/>
   <property-provider name="collapsedProperty"><![CDATA[
     return 'one';
   ]]></property-provider>