Refactor GWT compiler driver.

This patch removes the remnants of Library compiler from
the compiler drivers and does some other minor cleanups.

Change-Id: I9d8a830ccaeb60d46b6026375f8be38f4af5b1f4
diff --git a/dev/core/src/com/google/gwt/dev/CompileOnePerm.java b/dev/core/src/com/google/gwt/dev/CompileOnePerm.java
index a31149d..f7230de 100644
--- a/dev/core/src/com/google/gwt/dev/CompileOnePerm.java
+++ b/dev/core/src/com/google/gwt/dev/CompileOnePerm.java
@@ -20,6 +20,7 @@
 import com.google.gwt.dev.CompileTaskRunner.CompileTask;
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.ModuleDefLoader;
+import com.google.gwt.dev.jjs.JavaToJavaScriptCompiler;
 import com.google.gwt.dev.jjs.PermutationResult;
 import com.google.gwt.dev.util.PerfCounter;
 import com.google.gwt.dev.util.arg.ArgHandlerPerm;
@@ -149,7 +150,8 @@
     assert subPerms.length == 1;
 
     PermutationResult permResult =
-        precompilation.getUnifiedAst().compilePermutation(logger, compilerContext, subPerms[0]);
+        JavaToJavaScriptCompiler.compilePermutation(precompilation.getUnifiedAst(), logger,
+            compilerContext, subPerms[0]);
     Link.linkOnePermutationToJar(logger, module, compilerContext.getPublicResourceOracle(),
         precompilation.getGeneratedArtifacts(), permResult, makePermFilename(
             compilerWorkDir, permId), precompilationOptions);
diff --git a/dev/core/src/com/google/gwt/dev/CompilePerms.java b/dev/core/src/com/google/gwt/dev/CompilePerms.java
index fad0dc0..964ac20 100644
--- a/dev/core/src/com/google/gwt/dev/CompilePerms.java
+++ b/dev/core/src/com/google/gwt/dev/CompilePerms.java
@@ -21,6 +21,7 @@
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.ModuleDefLoader;
 import com.google.gwt.dev.cfg.PropertyPermutations;
+import com.google.gwt.dev.jjs.JavaToJavaScriptCompiler;
 import com.google.gwt.dev.jjs.PermutationResult;
 import com.google.gwt.dev.jjs.UnifiedAst;
 import com.google.gwt.dev.util.FileBackedObject;
@@ -194,7 +195,8 @@
    */
   public static PermutationResult compile(TreeLogger logger, CompilerContext compilerContext,
       Permutation permutation, UnifiedAst unifiedAst) throws UnableToCompleteException {
-    return unifiedAst.compilePermutation(logger, compilerContext, permutation);
+    return JavaToJavaScriptCompiler.compilePermutation(unifiedAst, logger, compilerContext,
+        permutation);
   }
 
   /**
diff --git a/dev/core/src/com/google/gwt/dev/Precompile.java b/dev/core/src/com/google/gwt/dev/Precompile.java
index d5cafde..ce835f5 100644
--- a/dev/core/src/com/google/gwt/dev/Precompile.java
+++ b/dev/core/src/com/google/gwt/dev/Precompile.java
@@ -24,14 +24,12 @@
 import com.google.gwt.core.ext.linker.impl.StandardLinkerContext;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.dev.CompileTaskRunner.CompileTask;
-import com.google.gwt.dev.cfg.ConfigurationProperty;
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.ModuleDefLoader;
 import com.google.gwt.dev.cfg.PropertyPermutations;
 import com.google.gwt.dev.javac.CompilationState;
 import com.google.gwt.dev.javac.CompilationUnit;
-import com.google.gwt.dev.jjs.AbstractCompiler;
-import com.google.gwt.dev.jjs.JavaScriptCompiler;
+import com.google.gwt.dev.jjs.JavaToJavaScriptCompiler;
 import com.google.gwt.dev.jjs.UnifiedAst;
 import com.google.gwt.dev.shell.CheckForUpdates;
 import com.google.gwt.dev.shell.CheckForUpdates.UpdateResult;
@@ -180,7 +178,7 @@
       compilationState = null;
       // Never optimize on a validation run.
       jjsOptions.setOptimizePrecompile(false);
-      getCompiler(module).precompile(
+      JavaToJavaScriptCompiler.precompile(
           logger, compilerContext, rpo, declEntryPts, additionalRootTypes, true, null);
       return true;
     } catch (UnableToCompleteException e) {
@@ -202,28 +200,6 @@
     return collapsedPermutations;
   }
 
-  static AbstractCompiler getCompiler(ModuleDef module) {
-    ConfigurationProperty compilerClassProp =
-        module.getProperties().findConfigProp("x.compiler.class");
-    String compilerClassName = compilerClassProp != null ? compilerClassProp.getValue() : null;
-    if (compilerClassName == null || compilerClassName.length() == 0) {
-      return new JavaScriptCompiler();
-    }
-    Throwable caught;
-    try {
-      Class<?> compilerClass = Class.forName(compilerClassName);
-      return (AbstractCompiler) compilerClass.newInstance();
-    } catch (ClassNotFoundException e) {
-      caught = e;
-    } catch (InstantiationException e) {
-      caught = e;
-    } catch (IllegalAccessException e) {
-      caught = e;
-    }
-    throw new RuntimeException("Unable to instantiate compiler class '" + compilerClassName + "'",
-        caught);
-  }
-
   static Precompilation precompile(TreeLogger logger, CompilerContext compilerContext,
       int permutationBase, PropertyPermutations allPermutations) {
     return precompile(logger, compilerContext, permutationBase,
@@ -277,7 +253,7 @@
           jjsOptions.isCompilerMetricsEnabled()
               ? new PrecompilationMetricsArtifact(permutationBase) : null;
       UnifiedAst unifiedAst =
-          getCompiler(module).precompile(logger, compilerContext, rpo, declEntryPts, null,
+          JavaToJavaScriptCompiler.precompile(logger, compilerContext, rpo, declEntryPts, null,
               rpo.getPermutationCount() == 1, precompilationMetrics);
 
       if (jjsOptions.isCompilerMetricsEnabled()) {
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ConfigProps.java b/dev/core/src/com/google/gwt/dev/cfg/ConfigProps.java
index cc52757..7015ed4 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ConfigProps.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ConfigProps.java
@@ -116,6 +116,18 @@
   }
 
   /**
+   * Returns a single-valued property as a string if possible.
+   * If not set or not single-valued, returns the default value.
+   */
+  public String getString(String key, String defaultValue) {
+    List<String> values = getStrings(key);
+    if (values.size() != 1 || values.get(0) == null) {
+      return defaultValue;
+    }
+    return values.get(0);
+  }
+
+  /**
    * Returns all the values of a multi-valued configuration property, or an empty list
    * if not found.
    *
diff --git a/dev/core/src/com/google/gwt/dev/jjs/AbstractCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/AbstractCompiler.java
deleted file mode 100644
index b94d585..0000000
--- a/dev/core/src/com/google/gwt/dev/jjs/AbstractCompiler.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2009 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;
-
-import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.core.ext.linker.PrecompilationMetricsArtifact;
-import com.google.gwt.dev.CompilerContext;
-import com.google.gwt.dev.jdt.RebindPermutationOracle;
-
-/**
- * A Compiler used to compile a GWT project into artifacts.
- */
-public interface AbstractCompiler {
-  /**
-   * Performs a precompilation, returning an object that can then be used to
-   * compile individual permutations..
-   *
-   * @param logger the logger to use
-   * @param compilerContext shared read only compiler state
-   * @param rpo the RebindPermutationOracle
-   * @param declEntryPts the set of entry classes declared in a GWT module;
-   *          these will be automatically rebound
-   * @param additionalRootTypes additional classes that should serve as code
-   *          roots; will not be rebound; may be <code>null</code>
-   * @param singlePermutation if true, do not pre-optimize the resulting AST or
-   *          allow serialization of the result
-   * @param precompilationMetrics if not null, the precompile with gather some
-   *          diagnostics for the compile.
-   * @return the unified AST used to drive permutation compiles
-   * @throws UnableToCompleteException if an error other than
-   *           {@link OutOfMemoryError} occurs
-   */
-  UnifiedAst precompile(TreeLogger logger, CompilerContext compilerContext,
-      RebindPermutationOracle rpo, String[] declEntryPts, String[] additionalRootTypes,
-      boolean singlePermutation, PrecompilationMetricsArtifact precompilationMetrics)
-      throws UnableToCompleteException;
-}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaScriptCompiler.java
deleted file mode 100644
index aa647ec..0000000
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaScriptCompiler.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2009 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;
-
-import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.core.ext.linker.PrecompilationMetricsArtifact;
-import com.google.gwt.dev.CompilerContext;
-import com.google.gwt.dev.jdt.RebindPermutationOracle;
-
-/**
- * Uses the default compiler {@link JavaToJavaScriptCompiler}.
- */
-public class JavaScriptCompiler implements AbstractCompiler {
-
-  @Override
-  public UnifiedAst precompile(TreeLogger logger, CompilerContext compilerContext,
-      RebindPermutationOracle rpo, String[] declEntryPts, String[] additionalRootTypes,
-      boolean singlePermutation, PrecompilationMetricsArtifact precompilationMetrics)
-      throws UnableToCompleteException {
-    JavaToJavaScriptCompiler javaToJavaScriptCompiler =
-        new MonolithicJavaToJavaScriptCompiler(logger, compilerContext);
-    return javaToJavaScriptCompiler.precompile(
-        rpo, declEntryPts, additionalRootTypes, singlePermutation, precompilationMetrics);
-  }
-}
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 6c6337a..5956fce 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -29,6 +29,7 @@
 import com.google.gwt.core.ext.soyc.SourceMapRecorder;
 import com.google.gwt.core.ext.soyc.coderef.DependencyGraphRecorder;
 import com.google.gwt.core.ext.soyc.coderef.EntityRecorder;
+import com.google.gwt.core.ext.soyc.impl.DependencyRecorder;
 import com.google.gwt.core.ext.soyc.impl.SizeMapRecorder;
 import com.google.gwt.core.ext.soyc.impl.SplitPointRecorder;
 import com.google.gwt.core.ext.soyc.impl.StoryRecorder;
@@ -59,42 +60,67 @@
 import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.jjs.ast.JTypeOracle.StandardTypes;
 import com.google.gwt.dev.jjs.ast.JVisitor;
+import com.google.gwt.dev.jjs.impl.ArrayNormalizer;
 import com.google.gwt.dev.jjs.impl.AssertionNormalizer;
 import com.google.gwt.dev.jjs.impl.AssertionRemover;
 import com.google.gwt.dev.jjs.impl.AstDumper;
+import com.google.gwt.dev.jjs.impl.CatchBlockNormalizer;
 import com.google.gwt.dev.jjs.impl.CompileTimeConstantsReplacer;
+import com.google.gwt.dev.jjs.impl.ComputeCastabilityInformation;
+import com.google.gwt.dev.jjs.impl.ComputeExhaustiveCastabilityInformation;
+import com.google.gwt.dev.jjs.impl.ComputeInstantiatedJsoInterfaces;
+import com.google.gwt.dev.jjs.impl.ControlFlowAnalyzer;
 import com.google.gwt.dev.jjs.impl.ControlFlowRecorder;
 import com.google.gwt.dev.jjs.impl.DeadCodeElimination;
+import com.google.gwt.dev.jjs.impl.Devirtualizer;
+import com.google.gwt.dev.jjs.impl.EnumNameObfuscator;
 import com.google.gwt.dev.jjs.impl.EnumOrdinalizer;
+import com.google.gwt.dev.jjs.impl.EqualityNormalizer;
 import com.google.gwt.dev.jjs.impl.Finalizer;
 import com.google.gwt.dev.jjs.impl.FixAssignmentsToUnboxOrCast;
 import com.google.gwt.dev.jjs.impl.FullOptimizerContext;
 import com.google.gwt.dev.jjs.impl.GenerateJavaScriptAST;
+import com.google.gwt.dev.jjs.impl.HandleCrossFragmentReferences;
+import com.google.gwt.dev.jjs.impl.ImplementCastsAndTypeChecks;
 import com.google.gwt.dev.jjs.impl.ImplementClassLiteralsAsFields;
 import com.google.gwt.dev.jjs.impl.JavaAstVerifier;
 import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap;
+import com.google.gwt.dev.jjs.impl.JjsUtils;
 import com.google.gwt.dev.jjs.impl.JsAbstractTextTransformer;
 import com.google.gwt.dev.jjs.impl.JsFunctionClusterer;
 import com.google.gwt.dev.jjs.impl.JsInteropRestrictionChecker;
 import com.google.gwt.dev.jjs.impl.JsNoopTransformer;
 import com.google.gwt.dev.jjs.impl.JsTypeLinker;
 import com.google.gwt.dev.jjs.impl.JsniRestrictionChecker;
+import com.google.gwt.dev.jjs.impl.LongCastNormalizer;
+import com.google.gwt.dev.jjs.impl.LongEmulationNormalizer;
 import com.google.gwt.dev.jjs.impl.MakeCallsStatic;
 import com.google.gwt.dev.jjs.impl.MethodCallSpecializer;
 import com.google.gwt.dev.jjs.impl.MethodCallTightener;
 import com.google.gwt.dev.jjs.impl.MethodInliner;
 import com.google.gwt.dev.jjs.impl.OptimizerContext;
 import com.google.gwt.dev.jjs.impl.OptimizerStats;
+import com.google.gwt.dev.jjs.impl.PostOptimizationCompoundAssignmentNormalizer;
 import com.google.gwt.dev.jjs.impl.Pruner;
 import com.google.gwt.dev.jjs.impl.RecordRebinds;
+import com.google.gwt.dev.jjs.impl.RemoveEmptySuperCalls;
+import com.google.gwt.dev.jjs.impl.RemoveSpecializations;
 import com.google.gwt.dev.jjs.impl.ReplaceDefenderMethodReferences;
+import com.google.gwt.dev.jjs.impl.ReplaceGetClassOverrides;
 import com.google.gwt.dev.jjs.impl.ResolveRebinds;
+import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences;
+import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.ClosureUniqueIdTypeMapper;
+import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.IntTypeMapper;
+import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.StringTypeMapper;
 import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.TypeMapper;
+import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.TypeOrder;
 import com.google.gwt.dev.jjs.impl.SameParameterValueOptimizer;
 import com.google.gwt.dev.jjs.impl.SourceInfoCorrelator;
+import com.google.gwt.dev.jjs.impl.TypeCoercionNormalizer;
 import com.google.gwt.dev.jjs.impl.TypeReferencesRecorder;
 import com.google.gwt.dev.jjs.impl.TypeTightener;
 import com.google.gwt.dev.jjs.impl.UnifyAst;
+import com.google.gwt.dev.jjs.impl.codesplitter.CodeSplitter;
 import com.google.gwt.dev.jjs.impl.codesplitter.CodeSplitters;
 import com.google.gwt.dev.jjs.impl.codesplitter.MultipleDependencyGraphRecorder;
 import com.google.gwt.dev.jjs.impl.codesplitter.ReplaceRunAsyncs;
@@ -106,6 +132,7 @@
 import com.google.gwt.dev.js.EvalFunctionsAtTopScope;
 import com.google.gwt.dev.js.FreshNameGenerator;
 import com.google.gwt.dev.js.JsBreakUpLargeVarStatements;
+import com.google.gwt.dev.js.JsDuplicateCaseFolder;
 import com.google.gwt.dev.js.JsDuplicateFunctionRemover;
 import com.google.gwt.dev.js.JsIncrementalNamer;
 import com.google.gwt.dev.js.JsInliner;
@@ -121,6 +148,7 @@
 import com.google.gwt.dev.js.JsStaticEval;
 import com.google.gwt.dev.js.JsSymbolResolver;
 import com.google.gwt.dev.js.JsUnusedFunctionRemover;
+import com.google.gwt.dev.js.JsVerboseNamer;
 import com.google.gwt.dev.js.SizeBreakdown;
 import com.google.gwt.dev.js.ast.JsContext;
 import com.google.gwt.dev.js.ast.JsForIn;
@@ -147,7 +175,6 @@
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
 import com.google.gwt.soyc.SoycDashboard;
 import com.google.gwt.soyc.io.ArtifactsOutputDirectory;
-import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
 import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
 import com.google.gwt.thirdparty.guava.common.collect.Iterables;
 import com.google.gwt.thirdparty.guava.common.collect.Lists;
@@ -160,6 +187,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.lang.management.ManagementFactory;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -170,7 +198,6 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
-import java.util.TreeSet;
 import java.util.zip.GZIPInputStream;
 
 import javax.xml.parsers.ParserConfigurationException;
@@ -183,1063 +210,1332 @@
  * which is called once per permutation. This allow build systems the option of distributing and
  * parallelizing some of the work.
  */
-public abstract class JavaToJavaScriptCompiler {
+public final class JavaToJavaScriptCompiler {
 
   /**
-   * Compile a permutation.
+   * Ending optimization passes when the rate of change has reached this value results in gaining
+   * nearly all of the impact while avoiding the long tail of costly but low-impact passes.
    */
-  protected abstract class PermutationCompiler {
+  private static final float EFFICIENT_CHANGE_RATE = 0.01f;
+  /**
+   * Continuing to apply optimizations till the rate of change reaches this value causes the AST to
+   * reach a fixed point.
+   */
+  private static final int FIXED_POINT_CHANGE_RATE = 0;
+  /**
+   * Limits the number of optimization passes against the possible danger of an AST that does not
+   * converge.
+   */
+  private static final int MAX_PASSES = 100;
 
-    protected Permutation permutation;
+  static {
+    // Preload the internal compiler exception just in case we run out of memory?.
+    InternalCompilerException.preload();
+  }
 
-    public PermutationCompiler(Permutation permutation) {
-      this.permutation = permutation;
-    }
+  private final CompilerContext compilerContext;
+  private final TreeLogger logger;
+  private final ModuleDef module;
+  private final PrecompileTaskOptions options;
+  private JsProgram jsProgram;
+  private JProgram jprogram;
 
-    /**
-     * Takes as input an unresolved Java AST (a Java AST wherein all rebind result classes are
-     * available and have not yet been pruned down to the set applicable for a particular
-     * permutation) that was previously constructed by the Precompiler and from that constructs
-     * output Js source code and related information. This Js source and related information is
-     * packaged into a Permutation instance and then returned.
-     *
-     * Permutation compilation is INTENDED to progress as a series of stages:
-     *
-     * <pre>
-     * 1. initialize local state
-     * 2. transform unresolved Java AST to resolved Java AST
-     * 3. normalize the resolved Java AST
-     * 4. optimize the resolved Java AST
-     * 5. construct the Js AST
-     * 6. normalize the Js AST
-     * 7. optimize the Js AST
-     * 8. generate Js source
-     * 9. construct and return a value
-     * </pre>
-     *
-     * There are some other types of work here (mostly metrics and data gathering) which do not
-     * serve the goal of output program construction. This work should really be moved into
-     * subclasses or some sort of callback or plugin system so as not to visually pollute the real
-     * compile logic.<br />
-     *
-     * Significant amounts of visitors implementing the intended above stages are triggered here but
-     * in the wrong order. They have been noted for future cleanup.
+  public JavaToJavaScriptCompiler(TreeLogger logger, CompilerContext compilerContext) {
+    this.logger = logger;
+    this.compilerContext = compilerContext;
+    this.module = compilerContext.getModule();
+    this.options = compilerContext.getOptions();
+  }
+
+  public static UnifiedAst precompile(TreeLogger logger, CompilerContext compilerContext,
+      RebindPermutationOracle rpo, String[] declEntryPts, String[] additionalRootTypes,
+      boolean singlePermutation, PrecompilationMetricsArtifact precompilationMetrics)
+      throws UnableToCompleteException {
+    return new JavaToJavaScriptCompiler(logger, compilerContext).precompile(
+        rpo, declEntryPts, additionalRootTypes, singlePermutation, precompilationMetrics);
+  }
+
+  /**
+   * Compiles a particular permutation.
+   *
+   * @param logger the logger to use
+   * @param compilerContext shared read only compiler state
+   * @param permutation the permutation to compile
+   * @return the permutation result
+   * @throws UnableToCompleteException if an error other than {@link OutOfMemoryError} occurs
+   */
+  public static PermutationResult compilePermutation(UnifiedAst unifiedAst,
+      TreeLogger logger, CompilerContext compilerContext, Permutation permutation)
+      throws UnableToCompleteException {
+    JavaToJavaScriptCompiler javaToJavaScriptCompiler =
+        new JavaToJavaScriptCompiler(logger, compilerContext);
+    return javaToJavaScriptCompiler.compilePermutation(permutation, unifiedAst);
+  }
+
+  /**
+   * Takes as input an unresolved Java AST (a Java AST wherein all rebind result classes are
+   * available and have not yet been pruned down to the set applicable for a particular permutation)
+   * that was previously constructed by the Precompiler and from that constructs output Js source
+   * code and related information. This Js source and related information is packaged into a
+   * Permutation instance and then returned.
+   *
+   * Permutation compilation is INTENDED to progress as a series of stages:
+   *
+   * <pre>
+   * 1. initialize local state
+   * 2. transform unresolved Java AST to resolved Java AST
+   * 3. normalize the resolved Java AST
+   * 4. optimize the resolved Java AST
+   * 5. construct the Js AST
+   * 6. normalize the Js AST
+   * 7. optimize the Js AST
+   * 8. generate Js source
+   * 9. construct and return a value
+   * </pre>
+   *
+   * There are some other types of work here (mostly metrics and data gathering) which do not serve
+   * the goal of output program construction. This work should really be moved into subclasses or
+   * some sort of callback or plugin system so as not to visually pollute the real compile logic.<br
+   * />
+   *
+   * Significant amounts of visitors implementing the intended above stages are triggered here but
+   * in the wrong order. They have been noted for future cleanup.
+   */
+  private PermutationResult compilePermutation(Permutation permutation, UnifiedAst unifiedAst)
+      throws UnableToCompleteException {
+    Event jjsCompilePermutationEvent = SpeedTracerLogger.start(
+        CompilerEventType.JJS_COMPILE_PERMUTATION, "name", permutation.getProps().prettyPrint()
+    );
+    /*
+     * Do not introduce any new pass here unless it is logically a part of one of the 9 defined
+     * stages and is physically located in that stage.
      */
-    public PermutationResult compilePermutation(UnifiedAst unifiedAst)
-        throws UnableToCompleteException {
-      Event jjsCompilePermutationEvent = SpeedTracerLogger.start(
-          CompilerEventType.JJS_COMPILE_PERMUTATION, "name", permutation.getProps().prettyPrint()
-      );
-      /*
-       * Do not introduce any new pass here unless it is logically a part of one of the 9 defined
-       * stages and is physically located in that stage.
-       */
 
-      long permStartMs = System.currentTimeMillis();
-      try {
-        Event javaEvent = SpeedTracerLogger.start(CompilerEventType.PERMUTATION_JAVA);
+    long permStartMs = System.currentTimeMillis();
+    try {
+      Event javaEvent = SpeedTracerLogger.start(CompilerEventType.PERMUTATION_JAVA);
 
-        // (1) Initialize local state.
-        long startTimeMs = System.currentTimeMillis();
-        PermProps props = permutation.getProps();
-        int permutationId = permutation.getId();
-        AST ast = unifiedAst.getFreshAst();
-        jprogram = ast.getJProgram();
-        jsProgram = ast.getJsProgram();
-        Map<StandardSymbolData, JsName> symbolTable =
-            new TreeMap<StandardSymbolData, JsName>(new SymbolData.ClassIdentComparator());
+      // (1) Initialize local state.
+      long startTimeMs = System.currentTimeMillis();
+      PermProps props = permutation.getProps();
+      int permutationId = permutation.getId();
+      AST ast = unifiedAst.getFreshAst();
+      jprogram = ast.getJProgram();
+      jsProgram = ast.getJsProgram();
+      Map<StandardSymbolData, JsName> symbolTable =
+          new TreeMap<StandardSymbolData, JsName>(new SymbolData.ClassIdentComparator());
 
-        // TODO(stalcup): hide metrics gathering in a callback or subclass
-        logger.log(TreeLogger.INFO, "Compiling permutation " + permutationId + "...");
+      // TODO(stalcup): hide metrics gathering in a callback or subclass
+      logger.log(TreeLogger.INFO, "Compiling permutation " + permutationId + "...");
 
-        // (2) Transform unresolved Java AST to resolved Java AST
-        ResolveRebinds.exec(jprogram, permutation.getGwtCreateAnswers());
+      // (2) Transform unresolved Java AST to resolved Java AST
+      ResolveRebinds.exec(jprogram, permutation.getGwtCreateAnswers());
 
-        // TODO(stalcup): hide metrics gathering in a callback or subclass
-        // This has to happen before optimizations because functions might
-        // be optimized out; we want those marked as "not executed", not "not
-        // instrumentable".
-        Multimap<String, Integer> instrumentableLines = null;
-        if (System.getProperty("gwt.coverage") != null) {
-          instrumentableLines = BaselineCoverageGatherer.exec(jprogram);
-        }
-
-        // TypeOracle needs this to make decisions in several optimization passes
-        jprogram.typeOracle.setJsInteropMode(compilerContext.getOptions().getJsInteropMode());
-
-        // Record initial set of type->type references.
-        // type->type references need to be collected in two phases, 1) before any process to the
-        // 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).
-        maybeRecordReferencesAndControlFlow(false);
-
-        // Replace compile time constants by their values.
-        // TODO(rluble): eventually move to normizeSemantics.
-        CompileTimeConstantsReplacer.exec(jprogram);
-
-        // TODO(stalcup): move to after normalize.
-        // (3) Optimize the resolved Java AST
-        optimizeJava();
-
-        // TODO(stalcup): move to before optimize.
-        // (4) Normalize the resolved Java AST
-        TypeMapper<?> typeMapper = normalizeSemantics();
-
-        // TODO(stalcup): this stage shouldn't exist, move into optimize.
-        postNormalizationOptimizeJava();
-
-        // Now that the AST has stopped mutating update with the final references.
-        maybeRecordReferencesAndControlFlow(true);
-
-        jprogram.typeOracle.recomputeAfterOptimizations(jprogram.getDeclaredTypes());
-
-        javaEvent.end();
-
-        Event javaScriptEvent = SpeedTracerLogger.start(CompilerEventType.PERMUTATION_JAVASCRIPT);
-
-        // (5) Construct the Js AST
-        Pair<? extends JavaToJavaScriptMap, Set<JsNode>> jjsMapAndInlineableFunctions =
-            GenerateJavaScriptAST.exec(logger, jprogram, jsProgram,
-                compilerContext, typeMapper, symbolTable, props);
-        JavaToJavaScriptMap jjsmap = jjsMapAndInlineableFunctions.getLeft();
-
-        // TODO(stalcup): hide metrics gathering in a callback or subclass
-        if (System.getProperty("gwt.coverage") != null) {
-          CoverageInstrumentor.exec(jsProgram, instrumentableLines);
-        }
-
-        // (6) Normalize the Js AST
-        JsNormalizer.exec(jsProgram);
-
-        // TODO(stalcup): move to AST construction
-        JsSymbolResolver.exec(jsProgram);
-        if (options.getNamespace() == JsNamespaceOption.PACKAGE) {
-          JsNamespaceChooser.exec(jsProgram, jjsmap);
-        }
-
-        // TODO(stalcup): move to normalization
-        EvalFunctionsAtTopScope.exec(jsProgram, jjsmap);
-
-        // (7) Optimize the JS AST.
-        final Set<JsNode> inlinableJsFunctions = jjsMapAndInlineableFunctions.getRight();
-        optimizeJs(inlinableJsFunctions);
-
-        // TODO(stalcup): move to normalization
-        // Must run before code splitter and namer.
-        JsStackEmulator.exec(jprogram, jsProgram, props, jjsmap);
-
-        // TODO(stalcup): move to normalization
-        Pair<SyntheticArtifact, MultipleDependencyGraphRecorder> dependenciesAndRecorder =
-            splitJsIntoFragments(props, permutationId, jjsmap);
-
-        // TODO(stalcup): move to optimize.
-        Map<JsName, JsLiteral> internedLiteralByVariableName = renameJsSymbols(props, jjsmap);
-
-        // TODO(stalcup): move to normalization
-        JsBreakUpLargeVarStatements.exec(jsProgram, props.getConfigProps());
-
-        // (8) Generate Js source
-        List<JsSourceMap> sourceInfoMaps = new ArrayList<JsSourceMap>();
-        boolean isSourceMapsEnabled = props.isTrueInAnyPermutation("compiler.useSourceMaps");
-        String[] jsFragments = new String[jsProgram.getFragmentCount()];
-        StatementRanges[] ranges = new StatementRanges[jsFragments.length];
-        SizeBreakdown[] sizeBreakdowns = options.isJsonSoycEnabled() || options.isSoycEnabled()
-            || options.isCompilerMetricsEnabled() ? new SizeBreakdown[jsFragments.length] : null;
-        generateJavaScriptCode(jjsmap, jsFragments, ranges, sizeBreakdowns, sourceInfoMaps,
-            isSourceMapsEnabled || options.isJsonSoycEnabled());
-
-        javaScriptEvent.end();
-
-        // (9) Construct and return a value
-        PermutationResult permutationResult =
-            new PermutationResultImpl(jsFragments, permutation, makeSymbolMap(symbolTable), ranges);
-
-        // TODO(stalcup): hide metrics gathering in a callback or subclass
-        addSyntheticArtifacts(unifiedAst, permutation, startTimeMs, permutationId, jjsmap,
-            dependenciesAndRecorder, internedLiteralByVariableName, isSourceMapsEnabled, jsFragments,
-            sizeBreakdowns, sourceInfoMaps, permutationResult);
-        return permutationResult;
-      } catch (Throwable e) {
-        throw CompilationProblemReporter.logAndTranslateException(logger, e);
-      } finally {
-        jjsCompilePermutationEvent.end();
-        logTrackingStats();
-        if (logger.isLoggable(TreeLogger.TRACE)) {
-          logger.log(TreeLogger.TRACE,
-              "Permutation took " + (System.currentTimeMillis() - permStartMs) + " ms");
-        }
+      // TODO(stalcup): hide metrics gathering in a callback or subclass
+      // This has to happen before optimizations because functions might
+      // be optimized out; we want those marked as "not executed", not "not
+      // instrumentable".
+      Multimap<String, Integer> instrumentableLines = null;
+      if (System.getProperty("gwt.coverage") != null) {
+        instrumentableLines = BaselineCoverageGatherer.exec(jprogram);
       }
-    }
 
-    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);
-        ControlFlowRecorder.exec(jprogram, getMinimalRebuildCache().getTypeEnvironment(),
-            onlyUpdate);
+      // TypeOracle needs this to make decisions in several optimization passes
+      jprogram.typeOracle.setJsInteropMode(compilerContext.getOptions().getJsInteropMode());
+
+      // Record initial set of type->type references.
+      // type->type references need to be collected in two phases, 1) before any process to the
+      // 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).
+      maybeRecordReferencesAndControlFlow(false);
+
+      // Replace compile time constants by their values.
+      // TODO(rluble): eventually move to normizeSemantics.
+      CompileTimeConstantsReplacer.exec(jprogram);
+
+      // TODO(stalcup): move to after normalize.
+      // (3) Optimize the resolved Java AST
+      optimizeJava();
+
+      // TODO(stalcup): move to before optimize.
+      // (4) Normalize the resolved Java AST
+      TypeMapper<?> typeMapper = normalizeSemantics();
+
+      // TODO(stalcup): this stage shouldn't exist, move into optimize.
+      postNormalizationOptimizeJava();
+
+      // Now that the AST has stopped mutating update with the final references.
+      maybeRecordReferencesAndControlFlow(true);
+
+      jprogram.typeOracle.recomputeAfterOptimizations(jprogram.getDeclaredTypes());
+
+      javaEvent.end();
+
+      Event javaScriptEvent = SpeedTracerLogger.start(CompilerEventType.PERMUTATION_JAVASCRIPT);
+
+      // (5) Construct the Js AST
+      Pair<? extends JavaToJavaScriptMap, Set<JsNode>> jjsMapAndInlineableFunctions =
+          GenerateJavaScriptAST.exec(logger, jprogram, jsProgram,
+              compilerContext, typeMapper, symbolTable, props);
+      JavaToJavaScriptMap jjsmap = jjsMapAndInlineableFunctions.getLeft();
+
+      // TODO(stalcup): hide metrics gathering in a callback or subclass
+      if (System.getProperty("gwt.coverage") != null) {
+        CoverageInstrumentor.exec(jsProgram, instrumentableLines);
       }
-    }
 
-    protected abstract  void optimizeJs(Set<JsNode> inlinableJsFunctions)
-        throws InterruptedException;
+      // (6) Normalize the Js AST
+      JsNormalizer.exec(jsProgram);
 
-    protected abstract void optimizeJava() throws InterruptedException;
-
-    protected abstract void postNormalizationOptimizeJava();
-
-    protected abstract Map<JsName, JsLiteral> runDetailedNamer(ConfigProps config)
-        throws IllegalNameException;
-
-    protected abstract Pair<SyntheticArtifact, MultipleDependencyGraphRecorder> splitJsIntoFragments(
-        PermProps props, int permutationId, JavaToJavaScriptMap jjsmap);
-
-    private CompilationMetricsArtifact addCompilerMetricsArtifact(UnifiedAst unifiedAst,
-        Permutation permutation, long startTimeMs, SizeBreakdown[] sizeBreakdowns,
-        PermutationResult permutationResult) {
-      CompilationMetricsArtifact compilationMetrics = null;
-      // TODO: enable this when ClosureCompiler is enabled
-      if (options.isCompilerMetricsEnabled()) {
-        if (options.isClosureCompilerEnabled()) {
-          logger.log(TreeLogger.WARN, "Incompatible options: -XenableClosureCompiler and "
-              + "-XcompilerMetric; ignoring -XcompilerMetric.");
-        } else {
-          compilationMetrics = new CompilationMetricsArtifact(permutation.getId());
-          compilationMetrics.setCompileElapsedMilliseconds(
-              System.currentTimeMillis() - startTimeMs);
-          compilationMetrics.setElapsedMilliseconds(
-              System.currentTimeMillis() - ManagementFactory.getRuntimeMXBean().getStartTime());
-          compilationMetrics.setJsSize(sizeBreakdowns);
-          compilationMetrics.setPermutationDescription(permutation.getProps().prettyPrint());
-          permutationResult.addArtifacts(Lists.newArrayList(
-              unifiedAst.getModuleMetrics(), unifiedAst.getPrecompilationMetrics(),
-              compilationMetrics));
-        }
+      // TODO(stalcup): move to AST construction
+      JsSymbolResolver.exec(jsProgram);
+      if (options.getNamespace() == JsNamespaceOption.PACKAGE) {
+        JsNamespaceChooser.exec(jsProgram, jjsmap);
       }
-      return compilationMetrics;
-    }
 
-    private void addSourceMapArtifacts(int permutationId, JavaToJavaScriptMap jjsmap,
-        Pair<SyntheticArtifact, MultipleDependencyGraphRecorder> dependenciesAndRecorder,
-        boolean isSourceMapsEnabled, SizeBreakdown[] sizeBreakdowns,
-        List<JsSourceMap> sourceInfoMaps, PermutationResult permutationResult) {
-      if (options.isJsonSoycEnabled()) {
-        // TODO: enable this when ClosureCompiler is enabled
-        if (options.isClosureCompilerEnabled()) {
-          logger.log(TreeLogger.WARN, "Incompatible options: -XenableClosureCompiler and "
-              + "-XjsonSoyc; ignoring -XjsonSoyc.");
-        } else {
-          // Is a super set of SourceMapRecorder.makeSourceMapArtifacts().
-          permutationResult.addArtifacts(EntityRecorder.makeSoycArtifacts(
-              permutationId, sourceInfoMaps, options.getSourceMapFilePrefix(),
-              jjsmap, sizeBreakdowns,
-              ((DependencyGraphRecorder) dependenciesAndRecorder.getRight()), jprogram));
-        }
-      } else if (isSourceMapsEnabled) {
-        // TODO: enable this when ClosureCompiler is enabled
-        if (options.isClosureCompilerEnabled()) {
-          logger.log(TreeLogger.WARN, "Incompatible options: -XenableClosureCompiler and "
-              + "compiler.useSourceMaps=true; ignoring compiler.useSourceMaps=true.");
-        } else {
-          logger.log(TreeLogger.INFO, "Source Maps Enabled");
-          permutationResult.addArtifacts(SourceMapRecorder.exec(permutationId, sourceInfoMaps,
-              options.getSourceMapFilePrefix()));
-        }
-      }
-    }
+      // TODO(stalcup): move to normalization
+      EvalFunctionsAtTopScope.exec(jsProgram, jjsmap);
 
-    /**
-     * Adds generated artifacts from previous compiles when doing per-file compiles.
-     * <p>
-     * All generators are run on first compile but only some very small subset are rerun on
-     * recompiles. Care must be taken to ensure that all generated artifacts (such as png/html/css
-     * files) are still registered for output even when no generators are run in the current
-     * compile.
-     */
-    private void maybeAddGeneratedArtifacts(PermutationResult permutationResult) {
-      if (options.isIncrementalCompileEnabled()) {
-        permutationResult.addArtifacts(
-            compilerContext.getMinimalRebuildCache().getGeneratedArtifacts());
-      }
-    }
+      // (7) Optimize the JS AST.
+      final Set<JsNode> inlinableJsFunctions = jjsMapAndInlineableFunctions.getRight();
+      optimizeJs(inlinableJsFunctions);
 
-    private void addSoycArtifacts(UnifiedAst unifiedAst, int permutationId,
-        JavaToJavaScriptMap jjsmap,
-        Pair<SyntheticArtifact, MultipleDependencyGraphRecorder> dependenciesAndRecorder,
-        Map<JsName, JsLiteral> internedLiteralByVariableName, String[] js,
-        SizeBreakdown[] sizeBreakdowns,
-        List<JsSourceMap> sourceInfoMaps, PermutationResult permutationResult,
-        CompilationMetricsArtifact compilationMetrics)
-        throws IOException, UnableToCompleteException {
-      // TODO: enable this when ClosureCompiler is enabled
-      if (options.isClosureCompilerEnabled()) {
-        if (options.isSoycEnabled()) {
-          logger.log(TreeLogger.WARN, "Incompatible options: -XenableClosureCompiler and "
-              + "-compileReport; ignoring -compileReport.");
-        }
-      } else {
-        permutationResult.addArtifacts(makeSoycArtifacts(permutationId, js, sizeBreakdowns,
-            options.isSoycExtra() ? sourceInfoMaps : null, dependenciesAndRecorder.getLeft(),
-            jjsmap, internedLiteralByVariableName, unifiedAst.getModuleMetrics(),
-            unifiedAst.getPrecompilationMetrics(), compilationMetrics,
-            options.isSoycHtmlDisabled()));
-      }
-    }
+      // TODO(stalcup): move to normalization
+      // Must run before code splitter and namer.
+      JsStackEmulator.exec(jprogram, jsProgram, props, jjsmap);
 
-    private void addSyntheticArtifacts(UnifiedAst unifiedAst, Permutation permutation,
-        long startTimeMs, int permutationId, JavaToJavaScriptMap jjsmap,
-        Pair<SyntheticArtifact, MultipleDependencyGraphRecorder> dependenciesAndRecorder,
-        Map<JsName, JsLiteral> internedLiteralByVariableName, boolean isSourceMapsEnabled,
-        String[] jsFragments, SizeBreakdown[] sizeBreakdowns,
-        List<JsSourceMap> sourceInfoMaps, PermutationResult permutationResult)
-        throws IOException, UnableToCompleteException {
+      // TODO(stalcup): move to normalization
+      Pair<SyntheticArtifact, MultipleDependencyGraphRecorder> dependenciesAndRecorder =
+          splitJsIntoFragments(props, permutationId, jjsmap);
 
-      assert internedLiteralByVariableName != null;
+      // TODO(stalcup): move to optimize.
+      Map<JsName, JsLiteral> internedLiteralByVariableName = renameJsSymbols(props, jjsmap);
 
-      Event event = SpeedTracerLogger.start(CompilerEventType.PERMUTATION_ARTIFACTS);
+      // TODO(stalcup): move to normalization
+      JsBreakUpLargeVarStatements.exec(jsProgram, props.getConfigProps());
 
-      CompilationMetricsArtifact compilationMetrics = addCompilerMetricsArtifact(
-          unifiedAst, permutation, startTimeMs, sizeBreakdowns, permutationResult);
-      addSoycArtifacts(unifiedAst, permutationId, jjsmap, dependenciesAndRecorder,
-          internedLiteralByVariableName, jsFragments, sizeBreakdowns, sourceInfoMaps,
-          permutationResult, compilationMetrics);
-      addSourceMapArtifacts(permutationId, jjsmap, dependenciesAndRecorder, isSourceMapsEnabled,
+      // (8) Generate Js source
+      List<JsSourceMap> sourceInfoMaps = new ArrayList<JsSourceMap>();
+      boolean isSourceMapsEnabled = props.isTrueInAnyPermutation("compiler.useSourceMaps");
+      String[] jsFragments = new String[jsProgram.getFragmentCount()];
+      StatementRanges[] ranges = new StatementRanges[jsFragments.length];
+      SizeBreakdown[] sizeBreakdowns = options.isJsonSoycEnabled() || options.isSoycEnabled()
+          || options.isCompilerMetricsEnabled() ? new SizeBreakdown[jsFragments.length] : null;
+      generateJavaScriptCode(jjsmap, jsFragments, ranges, sizeBreakdowns, sourceInfoMaps,
+          isSourceMapsEnabled || options.isJsonSoycEnabled());
+
+      javaScriptEvent.end();
+
+      // (9) Construct and return a value
+      PermutationResult permutationResult =
+          new PermutationResultImpl(jsFragments, permutation, makeSymbolMap(symbolTable), ranges);
+
+      // TODO(stalcup): hide metrics gathering in a callback or subclass
+      addSyntheticArtifacts(unifiedAst, permutation, startTimeMs, permutationId, jjsmap,
+          dependenciesAndRecorder, internedLiteralByVariableName, isSourceMapsEnabled, jsFragments,
           sizeBreakdowns, sourceInfoMaps, permutationResult);
-      maybeAddGeneratedArtifacts(permutationResult);
-
-      event.end();
-    }
-
-    /**
-     * Generate Js code from the given Js ASTs. Also produces information about that transformation.
-     */
-    private void generateJavaScriptCode(JavaToJavaScriptMap jjsMap, String[] jsFragments,
-        StatementRanges[] ranges, SizeBreakdown[] sizeBreakdowns,
-        List<JsSourceMap> sourceInfoMaps, boolean sourceMapsEnabled) {
-
-      Event generateJavascriptEvent =
-          SpeedTracerLogger.start(CompilerEventType.GENERATE_JAVASCRIPT);
-
-      boolean useClosureCompiler = options.isClosureCompilerEnabled();
-      if (useClosureCompiler) {
-        ClosureJsRunner runner = new ClosureJsRunner();
-        runner.compile(jprogram, jsProgram, jsFragments, options.getOutput());
-        generateJavascriptEvent.end();
-        return;
-      }
-
-      for (int i = 0; i < jsFragments.length; i++) {
-        DefaultTextOutput out = new DefaultTextOutput(options.getOutput().shouldMinimize());
-        JsReportGenerationVisitor v = new JsReportGenerationVisitor(out, jjsMap,
-            options.isJsonSoycEnabled());
-        v.accept(jsProgram.getFragmentBlock(i));
-
-        StatementRanges statementRanges = v.getStatementRanges();
-        String code = out.toString();
-        JsSourceMap infoMap = (sourceInfoMaps != null) ? v.getSourceInfoMap() : null;
-
-        JsAbstractTextTransformer transformer =
-            new JsNoopTransformer(code, statementRanges, infoMap);
-
-        /**
-         * Cut generated JS up on class boundaries and re-link the source (possibly making use of
-         * source from previous compiles, thus making it possible to perform partial recompiles).
-         */
-        if (options.isIncrementalCompileEnabled()) {
-          transformer = new JsTypeLinker(logger, transformer, v.getClassRanges(),
-              v.getProgramClassRange(), getMinimalRebuildCache(), jprogram.typeOracle);
-          transformer.exec();
-        }
-
-        /**
-         * Reorder function decls to improve compression ratios. Also restructures the top level
-         * blocks into sub-blocks if they exceed 32767 statements.
-         */
-        Event functionClusterEvent = SpeedTracerLogger.start(CompilerEventType.FUNCTION_CLUSTER);
-        // TODO(cromwellian) move to the Js AST optimization, re-enable sourcemaps + clustering
-        if (!sourceMapsEnabled && !options.isClosureCompilerFormatEnabled()
-            && options.shouldClusterSimilarFunctions()
-            && options.getNamespace() == JsNamespaceOption.NONE
-            && options.getOutput() == JsOutputOption.OBFUSCATED) {
-          transformer = new JsFunctionClusterer(transformer);
-          transformer.exec();
-        }
-        functionClusterEvent.end();
-
-        jsFragments[i] = transformer.getJs();
-        ranges[i] = transformer.getStatementRanges();
-        if (sizeBreakdowns != null) {
-          sizeBreakdowns[i] = v.getSizeBreakdown();
-        }
-        if (sourceInfoMaps != null) {
-          sourceInfoMaps.add(transformer.getSourceInfoMap());
-        }
-      }
-
-      generateJavascriptEvent.end();
-    }
-
-    private Collection<? extends Artifact<?>> makeSoycArtifacts(int permutationId, String[] js,
-        SizeBreakdown[] sizeBreakdowns, List<JsSourceMap> sourceInfoMaps,
-        SyntheticArtifact dependencies, JavaToJavaScriptMap jjsmap,
-        Map<JsName, JsLiteral> internedLiteralByVariableName,
-        ModuleMetricsArtifact moduleMetricsArtifact,
-        PrecompilationMetricsArtifact precompilationMetricsArtifact,
-        CompilationMetricsArtifact compilationMetrics, boolean htmlReportsDisabled)
-        throws IOException, UnableToCompleteException {
-      Memory.maybeDumpMemory("makeSoycArtifactsStart");
-      List<SyntheticArtifact> soycArtifacts = new ArrayList<SyntheticArtifact>();
-
-      ByteArrayOutputStream baos = new ByteArrayOutputStream();
-
-      Event soycEvent = SpeedTracerLogger.start(CompilerEventType.MAKE_SOYC_ARTIFACTS);
-
-      Event recordSplitPoints = SpeedTracerLogger.start(
-          CompilerEventType.MAKE_SOYC_ARTIFACTS, "phase", "recordSplitPoints");
-      SplitPointRecorder.recordSplitPoints(jprogram, baos, logger);
-      SyntheticArtifact splitPoints = new SyntheticArtifact(
-          SoycReportLinker.class, "splitPoints" + permutationId + ".xml.gz", baos.toByteArray());
-      soycArtifacts.add(splitPoints);
-      recordSplitPoints.end();
-
-      SyntheticArtifact sizeMaps = null;
-      if (sizeBreakdowns != null) {
-        Event recordSizeMap = SpeedTracerLogger.start(
-            CompilerEventType.MAKE_SOYC_ARTIFACTS, "phase", "recordSizeMap");
-        baos.reset();
-        SizeMapRecorder.recordMap(logger, baos, sizeBreakdowns, jjsmap,
-            internedLiteralByVariableName);
-        sizeMaps = new SyntheticArtifact(
-            SoycReportLinker.class, "stories" + permutationId + ".xml.gz", baos.toByteArray());
-        soycArtifacts.add(sizeMaps);
-        recordSizeMap.end();
-      }
-
-      if (sourceInfoMaps != null) {
-        Event recordStories = SpeedTracerLogger.start(
-            CompilerEventType.MAKE_SOYC_ARTIFACTS, "phase", "recordStories");
-        baos.reset();
-        StoryRecorder.recordStories(logger, baos, sourceInfoMaps, js);
-        soycArtifacts.add(new SyntheticArtifact(
-            SoycReportLinker.class, "detailedStories" + permutationId + ".xml.gz",
-            baos.toByteArray()));
-        recordStories.end();
-      }
-
-      if (dependencies != null) {
-        soycArtifacts.add(dependencies);
-      }
-
-      // Set all of the main SOYC artifacts private.
-      for (SyntheticArtifact soycArtifact : soycArtifacts) {
-        soycArtifact.setVisibility(Visibility.Private);
-      }
-
-      if (!htmlReportsDisabled && sizeBreakdowns != null) {
-        Event generateCompileReport = SpeedTracerLogger.start(
-            CompilerEventType.MAKE_SOYC_ARTIFACTS, "phase", "generateCompileReport");
-        ArtifactsOutputDirectory outDir = new ArtifactsOutputDirectory();
-        SoycDashboard dashboard = new SoycDashboard(outDir);
-        dashboard.startNewPermutation(Integer.toString(permutationId));
-        try {
-          dashboard.readSplitPoints(openWithGunzip(splitPoints));
-          if (sizeMaps != null) {
-            dashboard.readSizeMaps(openWithGunzip(sizeMaps));
-          }
-          if (dependencies != null) {
-            dashboard.readDependencies(openWithGunzip(dependencies));
-          }
-          Memory.maybeDumpMemory("soycReadDependenciesEnd");
-        } catch (ParserConfigurationException e) {
-          throw new InternalCompilerException(
-              "Error reading compile report information that was just generated", e);
-        } catch (SAXException e) {
-          throw new InternalCompilerException(
-              "Error reading compile report information that was just generated", e);
-        }
-        dashboard.generateForOnePermutation();
-        if (moduleMetricsArtifact != null && precompilationMetricsArtifact != null
-            && compilationMetrics != null) {
-          dashboard.generateCompilerMetricsForOnePermutation(
-              moduleMetricsArtifact, precompilationMetricsArtifact, compilationMetrics);
-        }
-        soycArtifacts.addAll(outDir.getArtifacts());
-        generateCompileReport.end();
-      }
-
-      soycEvent.end();
-
-      return soycArtifacts;
-    }
-
-    private SymbolData[] makeSymbolMap(Map<StandardSymbolData, JsName> symbolTable) {
-      // Keep tracks of a list of referenced name. If it is not used, don't
-      // add it to symbol map.
-      final Set<String> nameUsed = new HashSet<String>();
-      final Map<JsName, Integer> nameToFragment = new HashMap<JsName, Integer>();
-
-      for (int i = 0; i < jsProgram.getFragmentCount(); i++) {
-        final Integer fragId = i;
-        new JsVisitor() {
-            @Override
-          public void endVisit(JsForIn x, JsContext ctx) {
-            if (x.getIterVarName() != null) {
-              nameUsed.add(x.getIterVarName().getIdent());
-            }
-          }
-
-            @Override
-          public void endVisit(JsFunction x, JsContext ctx) {
-            if (x.getName() != null) {
-              nameToFragment.put(x.getName(), fragId);
-              nameUsed.add(x.getName().getIdent());
-            }
-          }
-
-            @Override
-          public void endVisit(JsLabel x, JsContext ctx) {
-            nameUsed.add(x.getName().getIdent());
-          }
-
-            @Override
-          public void endVisit(JsNameOf x, JsContext ctx) {
-            if (x.getName() != null) {
-              nameUsed.add(x.getName().getIdent());
-            }
-          }
-
-            @Override
-          public void endVisit(JsNameRef x, JsContext ctx) {
-            // Obviously this isn't even that accurate. Some of them are
-            // variable names, some of the are property. At least this
-            // this give us a safe approximation. Ideally we need
-            // the code removal passes to remove stuff in the scope objects.
-            if (x.isResolved()) {
-              nameUsed.add(x.getName().getIdent());
-            }
-          }
-
-            @Override
-          public void endVisit(JsParameter x, JsContext ctx) {
-            nameUsed.add(x.getName().getIdent());
-          }
-
-            @Override
-
-          public void endVisit(JsVars.JsVar x, JsContext ctx) {
-            nameUsed.add(x.getName().getIdent());
-          }
-
-        }.accept(jsProgram.getFragmentBlock(i));
-      }
-
-      // TODO(acleung): This is a temp fix. Once we know this is safe. We
-      // new to rewrite it to avoid extra ArrayList creations.
-      // Or we should just consider serializing it as an ArrayList if
-      // it is that much trouble to determine the true size.
-      List<SymbolData> result = new ArrayList<SymbolData>();
-
-      for (Map.Entry<StandardSymbolData, JsName> entry : symbolTable.entrySet()) {
-        StandardSymbolData symbolData = entry.getKey();
-        symbolData.setSymbolName(entry.getValue().getShortIdent());
-        Integer fragNum = nameToFragment.get(entry.getValue());
-        if (fragNum != null) {
-          symbolData.setFragmentNumber(fragNum);
-        }
-        if (nameUsed.contains(entry.getValue().getIdent()) || entry.getKey().isClass()) {
-          result.add(symbolData);
-        }
-      }
-
-      return result.toArray(new SymbolData[result.size()]);
-    }
-
-    /**
-     * Transform patterns that can't be represented in JS (such as multiple catch blocks) into
-     * equivalent but compatible patterns and take JVM semantics (such as numeric casts) that are
-     * not explicit in the AST and make them explicit.<br />
-     *
-     * These passes can not be reordering because of subtle interdependencies.
-     */
-    protected abstract TypeMapper<?> normalizeSemantics();
-
-    /**
-     * Open an emitted artifact and gunzip its contents.
-     */
-    private InputStream openWithGunzip(EmittedArtifact artifact)
-        throws IOException, UnableToCompleteException {
-      return new BufferedInputStream(new GZIPInputStream(artifact.getContents(TreeLogger.NULL)));
-    }
-
-    protected void optimizeJsLoop(Collection<JsNode> toInline) throws InterruptedException {
-      int optimizationLevel = options.getOptimizationLevel();
-      List<OptimizerStats> allOptimizerStats = new ArrayList<OptimizerStats>();
-      int counter = 0;
-      while (true) {
-        counter++;
-        if (Thread.interrupted()) {
-          throw new InterruptedException();
-        }
-        Event optimizeJsEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE_JS);
-
-        OptimizerStats stats = new OptimizerStats("Pass " + counter);
-
-        // Remove unused functions if possible.
-        stats.add(JsStaticEval.exec(jsProgram));
-        // Inline Js function invocations
-        stats.add(JsInliner.exec(jsProgram, toInline));
-        // Remove unused functions if possible.
-        stats.add(JsUnusedFunctionRemover.exec(jsProgram));
-
-        // Save the stats to print out after optimizers finish.
-        allOptimizerStats.add(stats);
-
-        optimizeJsEvent.end();
-        if ((optimizationLevel < OptionOptimize.OPTIMIZE_LEVEL_MAX && counter > optimizationLevel)
-            || !stats.didChange()) {
-          break;
-        }
-      }
-
-      if (optimizationLevel > OptionOptimize.OPTIMIZE_LEVEL_DRAFT) {
-        DuplicateExecuteOnceRemover.exec(jsProgram);
+      return permutationResult;
+    } catch (Throwable e) {
+      throw CompilationProblemReporter.logAndTranslateException(logger, e);
+    } finally {
+      jjsCompilePermutationEvent.end();
+      if (logger.isLoggable(TreeLogger.TRACE)) {
+        logger.log(TreeLogger.TRACE,
+            "Permutation took " + (System.currentTimeMillis() - permStartMs) + " ms");
       }
     }
+  }
 
-    private Map<JsName, JsLiteral> renameJsSymbols(PermProps props, JavaToJavaScriptMap jjsmap)
-        throws UnableToCompleteException {
-      Map<JsName, JsLiteral> internedLiteralByVariableName;
-      try {
-        switch (options.getOutput()) {
-          case OBFUSCATED:
-            internedLiteralByVariableName = runObfuscateNamer(props);
-            break;
-          case PRETTY:
-            internedLiteralByVariableName = runPrettyNamer(props.getConfigProps(), jjsmap);
-            break;
-          case DETAILED:
-            internedLiteralByVariableName = runDetailedNamer(props.getConfigProps());
-            break;
-          default:
-            throw new InternalCompilerException("Unknown output mode");
-        }
-      } catch (IllegalNameException e) {
-        logger.log(TreeLogger.ERROR, e.getMessage(), e);
-        throw new UnableToCompleteException();
-      }
-      return internedLiteralByVariableName == null ?
-          ImmutableMap.<JsName, JsLiteral>of() : internedLiteralByVariableName;
-    }
-
-    private Map<JsName, JsLiteral> runObfuscateNamer(PermProps props) throws IllegalNameException {
-      Map<JsName, JsLiteral> internedLiteralByVariableName =
-          JsLiteralInterner.exec(jprogram, jsProgram, (byte) (JsLiteralInterner.INTERN_ALL
-              & (byte) (jprogram.typeOracle.isJsInteropEnabled()
-              ? ~JsLiteralInterner.INTERN_STRINGS : ~0)));
-      FreshNameGenerator freshNameGenerator = JsObfuscateNamer.exec(jsProgram,
-          props.getConfigProps());
-      if (options.shouldRemoveDuplicateFunctions()
-          && JsStackEmulator.getStackMode(props) == JsStackEmulator.StackMode.STRIP) {
-        JsDuplicateFunctionRemover.exec(jsProgram, freshNameGenerator);
-      }
-      return internedLiteralByVariableName;
-    }
-
-    private Map<JsName, JsLiteral> runPrettyNamer(ConfigProps config, JavaToJavaScriptMap jjsmap)
-        throws IllegalNameException {
-      if (compilerContext.getOptions().isIncrementalCompileEnabled()) {
-        JsIncrementalNamer.exec(jsProgram, config,
-            compilerContext.getMinimalRebuildCache().getPersistentPrettyNamerState(), jjsmap);
-        return null;
-      }
-
-      // We don't intern strings in pretty mode to improve readability
-      Map<JsName, JsLiteral> internedLiteralByVariableName = JsLiteralInterner.exec(
-          jprogram, jsProgram,
-          (byte) (JsLiteralInterner.INTERN_ALL & ~JsLiteralInterner.INTERN_STRINGS));
-
-      JsPrettyNamer.exec(jsProgram, config);
-      return internedLiteralByVariableName;
+  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);
+      ControlFlowRecorder.exec(jprogram, getMinimalRebuildCache().getTypeEnvironment(),
+          onlyUpdate);
     }
   }
 
   /**
-   * Performs precompilation.
+   * Transform patterns that can't be represented in JS (such as multiple catch blocks) into
+   * equivalent but compatible patterns and take JVM semantics (such as numeric casts) that are not
+   * explicit in the AST and make them explicit.<br />
+   *
+   * These passes can not be reordering because of subtle interdependencies.
    */
-  protected abstract class Precompiler {
+  protected TypeMapper<?> normalizeSemantics() {
+    Event event = SpeedTracerLogger.start(CompilerEventType.JAVA_NORMALIZERS);
+    try {
+      Devirtualizer.exec(jprogram);
+      CatchBlockNormalizer.exec(jprogram);
+      PostOptimizationCompoundAssignmentNormalizer.exec(jprogram);
+      LongCastNormalizer.exec(jprogram);
+      LongEmulationNormalizer.exec(jprogram);
+      TypeCoercionNormalizer.exec(jprogram);
 
-    protected RebindPermutationOracle rpo;
-    protected String[] entryPointTypeNames;
-
-    public Precompiler(RebindPermutationOracle rpo, String[] entryPointTypeNames) {
-      this.rpo = rpo;
-      this.entryPointTypeNames = entryPointTypeNames;
-    }
-
-    protected abstract void beforeUnifyAst(Set<String> allRootTypes)
-        throws UnableToCompleteException;
-
-    protected abstract void checkEntryPoints(String[] additionalRootTypes);
-
-    protected abstract void createJProgram(CompilerContext compilerContext);
-
-    /**
-     * Takes as input a CompilationState and transforms that into a unified by not yet resolved Java
-     * AST (a Java AST wherein cross-class references have been connected and all rebind result
-     * classes are available and have not yet been pruned down to the set applicable for a
-     * particular permutation). This AST is packaged into a UnifiedAst instance and then returned.
-     *
-     * Precompilation is INTENDED to progress as a series of stages:
-     *
-     * <pre>
-     * 1. initialize local state
-     * 2. assert preconditions
-     * 3. construct and unify the unresolved Java AST
-     * 4. normalize the unresolved Java AST  // arguably should be removed
-     * 5. optimize the unresolved Java AST  // arguably should be removed
-     * 6. construct and return a value
-     * </pre>
-     *
-     * There are some other types of work here (mostly metrics and data gathering) which do not
-     * serve the goal of output program construction. This work should really be moved into
-     * subclasses or some sort of callback or plugin system so as not to visually pollute the real
-     * compile logic.<br />
-     *
-     * Significant amounts of visitors implementing the intended above stages are triggered here but
-     * in the wrong order. They have been noted for future cleanup.
-     */
-    protected final UnifiedAst precompile(String[] additionalRootTypes, boolean singlePermutation,
-        PrecompilationMetricsArtifact precompilationMetrics) throws UnableToCompleteException {
-      try {
-        /*
-         * Do not introduce any new pass here unless it is logically a part of one of the 6 defined
-         * stages and is physically located in that stage.
-         */
-
-        // (1) Initialize local state
-        createJProgram(compilerContext);
-        // Synchronize JTypeOracle with compile optimization behavior.
-        jprogram.typeOracle.setOptimize(
-            options.getOptimizationLevel() > OptionOptimize.OPTIMIZE_LEVEL_DRAFT);
-        jprogram.typeOracle.setJsInteropMode(options.getJsInteropMode());
-
-        jsProgram = new JsProgram();
-        if (additionalRootTypes == null) {
-          additionalRootTypes = Empty.STRINGS;
-        }
-
-        // (2) Assert preconditions
-        checkEntryPoints(additionalRootTypes);
-
-        // (3) Construct and unify the unresolved Java AST
-        CompilationState compilationState = constructJavaAst(additionalRootTypes);
-
-        // TODO(stalcup): hide metrics gathering in a callback or subclass
-        JsniRestrictionChecker.exec(logger, jprogram);
-        JsInteropRestrictionChecker.exec(logger, jprogram, getMinimalRebuildCache());
-        logTypeOracleMetrics(precompilationMetrics, compilationState);
-        Memory.maybeDumpMemory("AstOnly");
-        AstDumper.maybeDumpAST(jprogram);
-
-        // TODO(stalcup): is in wrong place, move to optimization stage
-        obfuscateEnums();
-
-        // (4) Normalize the unresolved Java AST
-        // Replace defender method references
-        ReplaceDefenderMethodReferences.exec(jprogram);
-
-        FixAssignmentsToUnboxOrCast.exec(jprogram);
-        if (options.isEnableAssertions()) {
-          AssertionNormalizer.exec(jprogram);
-        } else {
-          AssertionRemover.exec(jprogram);
-        }
-        if (module != null && options.isRunAsyncEnabled()) {
-          ReplaceRunAsyncs.exec(logger, jprogram);
-          ConfigProps config = new ConfigProps(module);
-          CodeSplitters.pickInitialLoadSequence(logger, jprogram, config);
-        }
-        ImplementClassLiteralsAsFields.exec(jprogram);
-
-        // (5) Optimize the unresolved Java AST
-        optimizeJava(singlePermutation);
-
-        // TODO(stalcup): hide metrics gathering in a callback or subclass
-        logAstTypeMetrics(precompilationMetrics);
-
-        // (6) Construct and return a value.
-        Event createUnifiedAstEvent = SpeedTracerLogger.start(CompilerEventType.CREATE_UNIFIED_AST);
-        UnifiedAst result = new UnifiedAst(
-            options, new AST(jprogram, jsProgram), singlePermutation, RecordRebinds.exec(jprogram));
-        createUnifiedAstEvent.end();
-        return result;
-      } catch (Throwable e) {
-        throw CompilationProblemReporter.logAndTranslateException(logger, e);
-      } finally {
-        logTrackingStats();
-      }
-    }
-
-    /**
-     * Creates (and returns the name for) a new class to serve as the container for the invocation
-     * of registered entry point methods as part of module bootstrapping.<br />
-     *
-     * The resulting class will be invoked during bootstrapping like FooEntryMethodHolder.init(). By
-     * generating the class on the fly and naming it to match the current module, the resulting
-     * holder class can work in both monolithic and separate compilation schemes.
-     */
-    private String buildEntryMethodHolder(StandardGeneratorContext context,
-        Set<String> allRootTypes) throws UnableToCompleteException {
-      // If there are no entry points.
-      if (entryPointTypeNames.length == 0) {
-        // Then there's no need to generate an EntryMethodHolder class to launch them.
-        return null;
+      if (options.isIncrementalCompileEnabled()) {
+        // Per file compilation reuses type JS even as references (like casts) in other files
+        // change, which means all legal casts need to be allowed now before they are actually
+        // used later.
+        ComputeExhaustiveCastabilityInformation.exec(jprogram);
+      } else {
+        // If trivial casts are pruned then one can use smaller runtime castmaps.
+        ComputeCastabilityInformation.exec(jprogram, options.isCastCheckingDisabled(),
+            !shouldOptimize() /* recordTrivialCasts */);
       }
 
-      EntryMethodHolderGenerator entryMethodHolderGenerator = new EntryMethodHolderGenerator();
-      String entryMethodHolderTypeName =
-          entryMethodHolderGenerator.generate(logger, context, module.getCanonicalName());
-      context.finish(logger);
-      // Ensures that unification traverses and keeps the class.
-      allRootTypes.add(entryMethodHolderTypeName);
-      // Ensures that JProgram knows to index this class's methods so that later bootstrap
-      // construction code is able to locate the FooEntryMethodHolder.init() function.
-      jprogram.addIndexedTypeName(entryMethodHolderTypeName);
-      return entryMethodHolderTypeName;
-    }
+      ComputeInstantiatedJsoInterfaces.exec(jprogram);
+      ImplementCastsAndTypeChecks.exec(jprogram, options.isCastCheckingDisabled(),
+          shouldOptimize() /* pruneTrivialCasts */);
+      ArrayNormalizer.exec(jprogram, options.isCastCheckingDisabled());
+      EqualityNormalizer.exec(jprogram);
 
-    private CompilationState constructJavaAst(String[] additionalRootTypes)
-        throws UnableToCompleteException {
-      Set<String> allRootTypes = new TreeSet<String>();
-      CompilationState compilationState = rpo.getCompilationState();
-      Memory.maybeDumpMemory("CompStateBuilt");
-      recordJsoTypes(compilationState.getTypeOracle());
-      populateRootTypes(allRootTypes, additionalRootTypes, compilationState);
-      String entryMethodHolderTypeName =
-          buildEntryMethodHolder(rpo.getGeneratorContext(), allRootTypes);
-      beforeUnifyAst(allRootTypes);
-      unifyJavaAst(allRootTypes, entryMethodHolderTypeName);
-      if (options.isSoycEnabled() || options.isJsonSoycEnabled()) {
-        SourceInfoCorrelator.exec(jprogram);
-      }
-
-      // Free up memory.
-      rpo.clear();
-      Set<String> deletedTypeNames = options.isIncrementalCompileEnabled()
-          ? getMinimalRebuildCache().computeDeletedTypeNames() : Sets.<String> newHashSet();
-      jprogram.typeOracle.computeBeforeAST(StandardTypes.createFrom(jprogram),
-          jprogram.getDeclaredTypes(), jprogram.getModuleDeclaredTypes(), deletedTypeNames);
-      return compilationState;
-    }
-
-    /**
-     * This method can be used to fetch the list of referenced class.
-     *
-     * This method is intended to support compiler metrics.
-     */
-    private String[] getReferencedJavaClasses() {
-      class ClassNameVisitor extends JVisitor {
-        List<String> classNames = new ArrayList<String>();
-        @Override
-        public boolean visit(JClassType x, Context ctx) {
-          classNames.add(x.getName());
-          return true;
-        }
-      }
-      ClassNameVisitor v = new ClassNameVisitor();
-      v.accept(jprogram);
-      return v.classNames.toArray(new String[v.classNames.size()]);
-    }
-
-    private void logAstTypeMetrics(PrecompilationMetricsArtifact precompilationMetrics) {
-      if (options.isCompilerMetricsEnabled()) {
-        precompilationMetrics.setAstTypes(getReferencedJavaClasses());
-      }
-    }
-
-    private void logTypeOracleMetrics(
-        PrecompilationMetricsArtifact precompilationMetrics, CompilationState compilationState) {
-      if (precompilationMetrics != null) {
-        List<String> finalTypeOracleTypes = Lists.newArrayList();
-        for (com.google.gwt.core.ext.typeinfo.JClassType type :
-            compilationState.getTypeOracle().getTypes()) {
-          finalTypeOracleTypes.add(type.getPackage().getName() + "." + type.getName());
-        }
-        precompilationMetrics.setFinalTypeOracleTypes(finalTypeOracleTypes);
-      }
-    }
-
-    private void obfuscateEnums() {
-      // See if we should run the EnumNameObfuscator
-      if (module != null) {
-        ConfigProps config = new ConfigProps(module);
-        List<String> enumObfProps = config.getStrings(ENUM_NAME_OBFUSCATION_PROPERTY);
-        String enumObfProp = enumObfProps != null ? enumObfProps.get(0) : null;
-        if (!"false".equals(enumObfProp)) {
-          EnumNameObfuscator.exec(jprogram, logger,
-              config.getCommaSeparatedStrings(
-                  ENUM_NAME_OBFUSCATION_BLACKLIST_PROPERTY),
-              "closure".equals(enumObfProp));
-        }
-      }
-    }
-
-    private void optimizeJava(boolean singlePermutation) throws InterruptedException {
-      if (options.getOptimizationLevel() > OptionOptimize.OPTIMIZE_LEVEL_DRAFT
-          && !singlePermutation) {
-        if (options.isOptimizePrecompile()) {
-          /*
-           * Go ahead and optimize early, so that each permutation will run faster. This code path
-           * is used by the Compiler entry point. We assume that we will not be able to perfectly
-           * parallelize the permutation compiles, so let's optimize as much as possible the common
-           * AST. In some cases, this might also have the side benefit of reducing the total
-           * permutation count.
-           */
-          optimizeJavaToFixedPoint();
-        } else {
-          /*
-           * Do only minimal early optimizations. This code path is used by the Precompile entry
-           * point. The external system might be able to perfectly parallelize the permutation
-           * compiles, so let's avoid doing potentially superlinear optimizations on the unified
-           * AST.
-           */
-          optimizeJavaOneTime("Early Optimization", jprogram.getNodeCount(),
-              new FullOptimizerContext(jprogram));
-        }
-      }
-    }
-
-    private void populateRootTypes(Set<String> allRootTypes, String[] additionalRootTypes,
-        CompilationState compilationState) {
-      if (jprogram.typeOracle.isJsInteropEnabled()) {
-        Iterables.addAll(allRootTypes, compilationState.getQualifiedJsInteropRootTypesNames());
-      }
-      Collections.addAll(allRootTypes, entryPointTypeNames);
-      Collections.addAll(allRootTypes, additionalRootTypes);
-      allRootTypes.addAll(JProgram.CODEGEN_TYPES_SET);
-      allRootTypes.addAll(jprogram.getTypeNamesToIndex());
-      /*
-       * Add all SingleJsoImpl types that we know about. It's likely that the concrete types are
-       * never explicitly referenced.
-       */
-      TypeOracle typeOracle = compilationState.getTypeOracle();
-      for (com.google.gwt.core.ext.typeinfo.JClassType singleJsoIntf :
-          typeOracle.getSingleJsoImplInterfaces()) {
-        allRootTypes.add(typeOracle.getSingleJsoImpl(singleJsoIntf).getQualifiedSourceName());
-      }
-    }
-
-    private void recordJsoTypes(TypeOracle typeOracle) {
-      if (!options.isIncrementalCompileEnabled()) {
-        return;
-      }
-
-      // Add names of JSO subtypes.
-      Set<String> jsoTypeNames = Sets.newHashSet();
-      for (com.google.gwt.dev.javac.typemodel.JClassType subtype :
-          typeOracle.getJavaScriptObject().getSubtypes()) {
-        jsoTypeNames.add(subtype.getQualifiedBinaryName());
-      }
-
-      // Add names of interfaces that are always of a JSO (aka there are no non-JSO implementors).
-      Set<String> singleJsoImplInterfaceNames = Sets.newHashSet();
-      for (com.google.gwt.core.ext.typeinfo.JClassType singleJsoImplInterface :
-          typeOracle.getSingleJsoImplInterfaces()) {
-        singleJsoImplInterfaceNames.add(singleJsoImplInterface.getQualifiedBinaryName());
-      }
-
-      // Add names of interfaces that are only sometimes a JSO (aka there are both JSO and non-JSO
-      // imlementors).
-      Set<String> dualJsoImplInterfaceNames = Sets.newHashSet();
-      for (com.google.gwt.core.ext.typeinfo.JClassType dualJsoImplInterface :
-          typeOracle.getDualJsoImplInterfaces()) {
-        dualJsoImplInterfaceNames.add(dualJsoImplInterface.getQualifiedBinaryName());
-      }
-
-      compilerContext.getMinimalRebuildCache().setJsoTypeNames(jsoTypeNames,
-          singleJsoImplInterfaceNames, dualJsoImplInterfaceNames);
-    }
-
-    private void synthesizeEntryMethodHolderInit(UnifyAst unifyAst,
-        String entryMethodHolderTypeName) throws UnableToCompleteException {
-      // Get type references.
-      JDeclaredType entryMethodHolderType =
-          unifyAst.findType(entryMethodHolderTypeName, unifyAst.getSourceNameBasedTypeLocator());
-      JDeclaredType gwtType = unifyAst.findType("com.google.gwt.core.client.GWT",
-          unifyAst.getSourceNameBasedTypeLocator());
-      JDeclaredType entryPointType = unifyAst.findType("com.google.gwt.core.client.EntryPoint",
-          unifyAst.getSourceNameBasedTypeLocator());
-
-      // Get method references.
-      JMethod initMethod = entryMethodHolderType.findMethod("init()V", false);
-      JMethod gwtCreateMethod =
-          gwtType.findMethod("create(Ljava/lang/Class;)Ljava/lang/Object;", false);
-
-      // Synthesize all onModuleLoad() calls.
-      JBlock initMethodBlock = ((JMethodBody) initMethod.getBody()).getBlock();
-      SourceInfo origin = initMethodBlock.getSourceInfo().makeChild();
-      for (String entryPointTypeName : entryPointTypeNames) {
-        // Get type and onModuleLoad function for the current entryPointTypeName.
-        JDeclaredType specificEntryPointType =
-            unifyAst.findType(entryPointTypeName, unifyAst.getSourceNameBasedTypeLocator());
-        if (specificEntryPointType == null) {
-          logger.log(TreeLogger.ERROR,
-              "Could not find module entry point class '" + entryPointTypeName + "'", null);
-          throw new UnableToCompleteException();
-        }
-        JMethod onModuleLoadMethod =
-            entryPointType.findMethod("onModuleLoad()V", true);
-        JMethod specificOnModuleLoadMethod =
-            specificEntryPointType.findMethod("onModuleLoad()V", true);
-
-        if (specificOnModuleLoadMethod != null && specificOnModuleLoadMethod.isStatic()) {
-          // Synthesize a static invocation FooEntryPoint.onModuleLoad(); call.
-          JMethodCall staticOnModuleLoadCall =
-              new JMethodCall(origin, null, specificOnModuleLoadMethod);
-          initMethodBlock.addStmt(staticOnModuleLoadCall.makeStatement());
-        } else {
-          // Synthesize ((EntryPoint)GWT.create(FooEntryPoint.class)).onModuleLoad();
-          JClassLiteral entryPointTypeClassLiteral =
-              new JClassLiteral(origin, specificEntryPointType);
-          JMethodCall createInstanceCall =
-              new JMethodCall(origin, null, gwtCreateMethod, entryPointTypeClassLiteral);
-          JCastOperation castToEntryPoint =
-              new JCastOperation(origin, entryPointType, createInstanceCall);
-          JMethodCall instanceOnModuleLoadCall =
-              new JMethodCall(origin, castToEntryPoint, onModuleLoadMethod);
-          initMethodBlock.addStmt(instanceOnModuleLoadCall.makeStatement());
-        }
-      }
-    }
-
-    private void unifyJavaAst(Set<String> allRootTypes, String entryMethodHolderTypeName)
-        throws UnableToCompleteException {
-
-      Event event = SpeedTracerLogger.start(CompilerEventType.UNIFY_AST);
-
-      UnifyAst unifyAst = new UnifyAst(logger, compilerContext, jprogram, jsProgram, rpo);
-      // Makes JProgram aware of these types so they can be accessed via index.
-      unifyAst.addRootTypes(allRootTypes);
-      // Must synthesize entryPoint.onModuleLoad() calls because some EntryPoint classes are
-      // private.
-      if (entryMethodHolderTypeName != null) {
-        // Only synthesize the init method in the EntryMethodHolder class, if there is an
-        // EntryMethodHolder class.
-        synthesizeEntryMethodHolderInit(unifyAst, entryMethodHolderTypeName);
-      }
-      if (entryMethodHolderTypeName != null) {
-        // Only register the init method in the EntryMethodHolder class as an entry method, if there
-        // is an EntryMethodHolder class.
-        jprogram.addEntryMethod(jprogram.getIndexedMethod(
-            SourceName.getShortClassName(entryMethodHolderTypeName) + ".init"));
-      }
-      unifyAst.exec();
-
+      TypeMapper<?> typeMapper = getTypeMapper();
+      ResolveRuntimeTypeReferences.exec(jprogram, typeMapper, getTypeOrder());
+      return typeMapper;
+    } finally {
       event.end();
     }
   }
 
+  private void optimizeJava() throws InterruptedException {
+    if (shouldOptimize()) {
+      optimizeJavaToFixedPoint();
+      RemoveEmptySuperCalls.exec(jprogram);
+    }
+  }
+
+  private void optimizeJs(Set<JsNode> inlinableJsFunctions) throws InterruptedException {
+    if (shouldOptimize()) {
+      optimizeJsLoop(inlinableJsFunctions);
+      JsDuplicateCaseFolder.exec(jsProgram);
+    }
+  }
+
+  private void postNormalizationOptimizeJava() {
+    Event event = SpeedTracerLogger.start(CompilerEventType.JAVA_POST_NORMALIZER_OPTIMIZERS);
+    try {
+      if (shouldOptimize()) {
+        RemoveSpecializations.exec(jprogram);
+        Pruner.exec(jprogram, false);
+      }
+      ReplaceGetClassOverrides.exec(jprogram);
+    } finally {
+      event.end();
+    }
+  }
+
+  private Map<JsName, JsLiteral> runDetailedNamer(ConfigProps config)
+      throws IllegalNameException {
+    Map<JsName, JsLiteral> internedTextByVariableName = null;
+    if (shouldOptimize()) {
+      // Only perform the interning optimization when optimizations are enabled.
+      internedTextByVariableName =
+          JsLiteralInterner.exec(jprogram, jsProgram, (byte) (JsLiteralInterner.INTERN_ALL
+              & (byte) (jprogram.typeOracle.isJsInteropEnabled()
+              ? ~JsLiteralInterner.INTERN_STRINGS : ~0)));
+    }
+    JsVerboseNamer.exec(jsProgram, config);
+    return internedTextByVariableName;
+  }
+
+  private Pair<SyntheticArtifact, MultipleDependencyGraphRecorder> splitJsIntoFragments(
+      PermProps props, int permutationId, JavaToJavaScriptMap jjsmap) {
+    Pair<SyntheticArtifact, MultipleDependencyGraphRecorder> dependenciesAndRecorder;
+    MultipleDependencyGraphRecorder dependencyRecorder = null;
+    SyntheticArtifact dependencies = null;
+    if (options.isRunAsyncEnabled()) {
+      ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+      int expectedFragmentCount = options.getFragmentCount();
+      // -1 is the default value, we trap 0 just in case (0 is not a legal value in any case)
+      if (expectedFragmentCount <= 0) {
+        // Fragment count not set check fragments merge.
+        int numberOfMerges = options.getFragmentsMerge();
+        if (numberOfMerges > 0) {
+          // + 1 for left over, + 1 for initial gave us the total number
+          // of fragments without splitting.
+          expectedFragmentCount =
+              Math.max(0, jprogram.getRunAsyncs().size() + 2 - numberOfMerges);
+        }
+      }
+
+      int minFragmentSize = props.getConfigProps().getInteger(CodeSplitters.MIN_FRAGMENT_SIZE, 0);
+
+      dependencyRecorder = chooseDependencyRecorder(baos);
+      CodeSplitter.exec(logger, jprogram, jsProgram, jjsmap, expectedFragmentCount,
+          minFragmentSize, dependencyRecorder);
+
+      if (baos.size() == 0) {
+        dependencyRecorder = recordNonSplitDependencies(baos);
+      }
+      if (baos.size() > 0) {
+        dependencies = new SyntheticArtifact(
+            SoycReportLinker.class, "dependencies" + permutationId + ".xml.gz",
+            baos.toByteArray());
+      }
+    } else if (options.isSoycEnabled() || options.isJsonSoycEnabled()) {
+      dependencyRecorder = recordNonSplitDependencies(new ByteArrayOutputStream());
+    }
+    dependenciesAndRecorder = Pair.create(
+        dependencies, dependencyRecorder);
+
+    // No new JsNames or references to JSNames can be introduced after this
+    // point.
+    HandleCrossFragmentReferences.exec(jsProgram, props);
+
+    return dependenciesAndRecorder;
+  }
+
+  private MultipleDependencyGraphRecorder chooseDependencyRecorder(OutputStream out) {
+    MultipleDependencyGraphRecorder dependencyRecorder =
+        MultipleDependencyGraphRecorder.NULL_RECORDER;
+    if (options.isSoycEnabled() && options.isJsonSoycEnabled()) {
+      dependencyRecorder = new DependencyGraphRecorder(out, jprogram);
+    } else if (options.isSoycEnabled()) {
+      dependencyRecorder = new DependencyRecorder(out);
+    } else if (options.isJsonSoycEnabled()) {
+      dependencyRecorder = new DependencyGraphRecorder(out, jprogram);
+    }
+    return dependencyRecorder;
+  }
+
+  /**
+   * Dependency information is normally recorded during code splitting, and it results in multiple
+   * dependency graphs. If the code splitter doesn't run, then this method can be used instead to
+   * record a single dependency graph for the whole program.
+   */
+  private DependencyRecorder recordNonSplitDependencies(OutputStream out) {
+    DependencyRecorder deps;
+    if (options.isSoycEnabled() && options.isJsonSoycEnabled()) {
+      deps = new DependencyGraphRecorder(out, jprogram);
+    } else if (options.isSoycEnabled()) {
+      deps = new DependencyRecorder(out);
+    } else if (options.isJsonSoycEnabled()) {
+      deps = new DependencyGraphRecorder(out, jprogram);
+    } else {
+      return null;
+    }
+    deps.open();
+    deps.startDependencyGraph("initial", null);
+
+    ControlFlowAnalyzer cfa = new ControlFlowAnalyzer(jprogram);
+    cfa.setDependencyRecorder(deps);
+    cfa.traverseEntryMethods();
+    deps.endDependencyGraph();
+    deps.close();
+    return deps;
+  }
+
+  private CompilationMetricsArtifact addCompilerMetricsArtifact(UnifiedAst unifiedAst,
+      Permutation permutation, long startTimeMs, SizeBreakdown[] sizeBreakdowns,
+      PermutationResult permutationResult) {
+    CompilationMetricsArtifact compilationMetrics = null;
+    // TODO: enable this when ClosureCompiler is enabled
+    if (options.isCompilerMetricsEnabled()) {
+      if (options.isClosureCompilerEnabled()) {
+        logger.log(TreeLogger.WARN, "Incompatible options: -XenableClosureCompiler and "
+            + "-XcompilerMetric; ignoring -XcompilerMetric.");
+      } else {
+        compilationMetrics = new CompilationMetricsArtifact(permutation.getId());
+        compilationMetrics.setCompileElapsedMilliseconds(
+            System.currentTimeMillis() - startTimeMs);
+        compilationMetrics.setElapsedMilliseconds(
+            System.currentTimeMillis() - ManagementFactory.getRuntimeMXBean().getStartTime());
+        compilationMetrics.setJsSize(sizeBreakdowns);
+        compilationMetrics.setPermutationDescription(permutation.getProps().prettyPrint());
+        permutationResult.addArtifacts(Lists.newArrayList(
+            unifiedAst.getModuleMetrics(), unifiedAst.getPrecompilationMetrics(),
+            compilationMetrics));
+      }
+    }
+    return compilationMetrics;
+  }
+
+  private void addSourceMapArtifacts(int permutationId, JavaToJavaScriptMap jjsmap,
+      Pair<SyntheticArtifact, MultipleDependencyGraphRecorder> dependenciesAndRecorder,
+      boolean isSourceMapsEnabled, SizeBreakdown[] sizeBreakdowns,
+      List<JsSourceMap> sourceInfoMaps, PermutationResult permutationResult) {
+    if (options.isJsonSoycEnabled()) {
+      // TODO: enable this when ClosureCompiler is enabled
+      if (options.isClosureCompilerEnabled()) {
+        logger.log(TreeLogger.WARN, "Incompatible options: -XenableClosureCompiler and "
+            + "-XjsonSoyc; ignoring -XjsonSoyc.");
+      } else {
+        // Is a super set of SourceMapRecorder.makeSourceMapArtifacts().
+        permutationResult.addArtifacts(EntityRecorder.makeSoycArtifacts(
+            permutationId, sourceInfoMaps, options.getSourceMapFilePrefix(),
+            jjsmap, sizeBreakdowns,
+            ((DependencyGraphRecorder) dependenciesAndRecorder.getRight()), jprogram));
+      }
+    } else if (isSourceMapsEnabled) {
+      // TODO: enable this when ClosureCompiler is enabled
+      if (options.isClosureCompilerEnabled()) {
+        logger.log(TreeLogger.WARN, "Incompatible options: -XenableClosureCompiler and "
+            + "compiler.useSourceMaps=true; ignoring compiler.useSourceMaps=true.");
+      } else {
+        logger.log(TreeLogger.INFO, "Source Maps Enabled");
+        permutationResult.addArtifacts(SourceMapRecorder.exec(permutationId, sourceInfoMaps,
+            options.getSourceMapFilePrefix()));
+      }
+    }
+  }
+
+  /**
+   * Adds generated artifacts from previous compiles when doing per-file compiles. <p> All
+   * generators are run on first compile but only some very small subset are rerun on recompiles.
+   * Care must be taken to ensure that all generated artifacts (such as png/html/css files) are
+   * still registered for output even when no generators are run in the current compile.
+   */
+  private void maybeAddGeneratedArtifacts(PermutationResult permutationResult) {
+    if (options.isIncrementalCompileEnabled()) {
+      permutationResult.addArtifacts(
+          compilerContext.getMinimalRebuildCache().getGeneratedArtifacts());
+    }
+  }
+
+  private void addSoycArtifacts(UnifiedAst unifiedAst, int permutationId,
+      JavaToJavaScriptMap jjsmap,
+      Pair<SyntheticArtifact, MultipleDependencyGraphRecorder> dependenciesAndRecorder,
+      Map<JsName, JsLiteral> internedLiteralByVariableName, String[] js,
+      SizeBreakdown[] sizeBreakdowns,
+      List<JsSourceMap> sourceInfoMaps, PermutationResult permutationResult,
+      CompilationMetricsArtifact compilationMetrics)
+      throws IOException, UnableToCompleteException {
+    // TODO: enable this when ClosureCompiler is enabled
+    if (options.isClosureCompilerEnabled()) {
+      if (options.isSoycEnabled()) {
+        logger.log(TreeLogger.WARN, "Incompatible options: -XenableClosureCompiler and "
+            + "-compileReport; ignoring -compileReport.");
+      }
+    } else {
+      permutationResult.addArtifacts(makeSoycArtifacts(permutationId, js, sizeBreakdowns,
+          options.isSoycExtra() ? sourceInfoMaps : null, dependenciesAndRecorder.getLeft(),
+          jjsmap, internedLiteralByVariableName, unifiedAst.getModuleMetrics(),
+          unifiedAst.getPrecompilationMetrics(), compilationMetrics,
+          options.isSoycHtmlDisabled()));
+    }
+  }
+
+  private void addSyntheticArtifacts(UnifiedAst unifiedAst, Permutation permutation,
+      long startTimeMs, int permutationId, JavaToJavaScriptMap jjsmap,
+      Pair<SyntheticArtifact, MultipleDependencyGraphRecorder> dependenciesAndRecorder,
+      Map<JsName, JsLiteral> internedLiteralByVariableName, boolean isSourceMapsEnabled,
+      String[] jsFragments, SizeBreakdown[] sizeBreakdowns,
+      List<JsSourceMap> sourceInfoMaps, PermutationResult permutationResult)
+      throws IOException, UnableToCompleteException {
+
+    assert internedLiteralByVariableName != null;
+
+    Event event = SpeedTracerLogger.start(CompilerEventType.PERMUTATION_ARTIFACTS);
+
+    CompilationMetricsArtifact compilationMetrics = addCompilerMetricsArtifact(
+        unifiedAst, permutation, startTimeMs, sizeBreakdowns, permutationResult);
+    addSoycArtifacts(unifiedAst, permutationId, jjsmap, dependenciesAndRecorder,
+        internedLiteralByVariableName, jsFragments, sizeBreakdowns, sourceInfoMaps,
+        permutationResult, compilationMetrics);
+    addSourceMapArtifacts(permutationId, jjsmap, dependenciesAndRecorder, isSourceMapsEnabled,
+        sizeBreakdowns, sourceInfoMaps, permutationResult);
+    maybeAddGeneratedArtifacts(permutationResult);
+
+    event.end();
+  }
+
+  /**
+   * Generate Js code from the given Js ASTs. Also produces information about that transformation.
+   */
+  private void generateJavaScriptCode(JavaToJavaScriptMap jjsMap, String[] jsFragments,
+      StatementRanges[] ranges, SizeBreakdown[] sizeBreakdowns,
+      List<JsSourceMap> sourceInfoMaps, boolean sourceMapsEnabled) {
+
+    Event generateJavascriptEvent =
+        SpeedTracerLogger.start(CompilerEventType.GENERATE_JAVASCRIPT);
+
+    boolean useClosureCompiler = options.isClosureCompilerEnabled();
+    if (useClosureCompiler) {
+      ClosureJsRunner runner = new ClosureJsRunner();
+      runner.compile(jprogram, jsProgram, jsFragments, options.getOutput());
+      generateJavascriptEvent.end();
+      return;
+    }
+
+    for (int i = 0; i < jsFragments.length; i++) {
+      DefaultTextOutput out = new DefaultTextOutput(options.getOutput().shouldMinimize());
+      JsReportGenerationVisitor v = new JsReportGenerationVisitor(out, jjsMap,
+          options.isJsonSoycEnabled());
+      v.accept(jsProgram.getFragmentBlock(i));
+
+      StatementRanges statementRanges = v.getStatementRanges();
+      String code = out.toString();
+      JsSourceMap infoMap = (sourceInfoMaps != null) ? v.getSourceInfoMap() : null;
+
+      JsAbstractTextTransformer transformer =
+          new JsNoopTransformer(code, statementRanges, infoMap);
+
+      /**
+       * Cut generated JS up on class boundaries and re-link the source (possibly making use of
+       * source from previous compiles, thus making it possible to perform partial recompiles).
+       */
+      if (options.isIncrementalCompileEnabled()) {
+        transformer = new JsTypeLinker(logger, transformer, v.getClassRanges(),
+            v.getProgramClassRange(), getMinimalRebuildCache(), jprogram.typeOracle);
+        transformer.exec();
+      }
+
+      /**
+       * Reorder function decls to improve compression ratios. Also restructures the top level
+       * blocks into sub-blocks if they exceed 32767 statements.
+       */
+      Event functionClusterEvent = SpeedTracerLogger.start(CompilerEventType.FUNCTION_CLUSTER);
+      // TODO(cromwellian) move to the Js AST optimization, re-enable sourcemaps + clustering
+      if (!sourceMapsEnabled && !options.isClosureCompilerFormatEnabled()
+          && options.shouldClusterSimilarFunctions()
+          && options.getNamespace() == JsNamespaceOption.NONE
+          && options.getOutput() == JsOutputOption.OBFUSCATED) {
+        transformer = new JsFunctionClusterer(transformer);
+        transformer.exec();
+      }
+      functionClusterEvent.end();
+
+      jsFragments[i] = transformer.getJs();
+      ranges[i] = transformer.getStatementRanges();
+      if (sizeBreakdowns != null) {
+        sizeBreakdowns[i] = v.getSizeBreakdown();
+      }
+      if (sourceInfoMaps != null) {
+        sourceInfoMaps.add(transformer.getSourceInfoMap());
+      }
+    }
+
+    generateJavascriptEvent.end();
+  }
+
+  private Collection<? extends Artifact<?>> makeSoycArtifacts(int permutationId, String[] js,
+      SizeBreakdown[] sizeBreakdowns, List<JsSourceMap> sourceInfoMaps,
+      SyntheticArtifact dependencies, JavaToJavaScriptMap jjsmap,
+      Map<JsName, JsLiteral> internedLiteralByVariableName,
+      ModuleMetricsArtifact moduleMetricsArtifact,
+      PrecompilationMetricsArtifact precompilationMetricsArtifact,
+      CompilationMetricsArtifact compilationMetrics, boolean htmlReportsDisabled)
+      throws IOException, UnableToCompleteException {
+    Memory.maybeDumpMemory("makeSoycArtifactsStart");
+    List<SyntheticArtifact> soycArtifacts = new ArrayList<SyntheticArtifact>();
+
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+    Event soycEvent = SpeedTracerLogger.start(CompilerEventType.MAKE_SOYC_ARTIFACTS);
+
+    Event recordSplitPoints = SpeedTracerLogger.start(
+        CompilerEventType.MAKE_SOYC_ARTIFACTS, "phase", "recordSplitPoints");
+    SplitPointRecorder.recordSplitPoints(jprogram, baos, logger);
+    SyntheticArtifact splitPoints = new SyntheticArtifact(
+        SoycReportLinker.class, "splitPoints" + permutationId + ".xml.gz", baos.toByteArray());
+    soycArtifacts.add(splitPoints);
+    recordSplitPoints.end();
+
+    SyntheticArtifact sizeMaps = null;
+    if (sizeBreakdowns != null) {
+      Event recordSizeMap = SpeedTracerLogger.start(
+          CompilerEventType.MAKE_SOYC_ARTIFACTS, "phase", "recordSizeMap");
+      baos.reset();
+      SizeMapRecorder.recordMap(logger, baos, sizeBreakdowns, jjsmap,
+          internedLiteralByVariableName);
+      sizeMaps = new SyntheticArtifact(
+          SoycReportLinker.class, "stories" + permutationId + ".xml.gz", baos.toByteArray());
+      soycArtifacts.add(sizeMaps);
+      recordSizeMap.end();
+    }
+
+    if (sourceInfoMaps != null) {
+      Event recordStories = SpeedTracerLogger.start(
+          CompilerEventType.MAKE_SOYC_ARTIFACTS, "phase", "recordStories");
+      baos.reset();
+      StoryRecorder.recordStories(logger, baos, sourceInfoMaps, js);
+      soycArtifacts.add(new SyntheticArtifact(
+          SoycReportLinker.class, "detailedStories" + permutationId + ".xml.gz",
+          baos.toByteArray()));
+      recordStories.end();
+    }
+
+    if (dependencies != null) {
+      soycArtifacts.add(dependencies);
+    }
+
+    // Set all of the main SOYC artifacts private.
+    for (SyntheticArtifact soycArtifact : soycArtifacts) {
+      soycArtifact.setVisibility(Visibility.Private);
+    }
+
+    if (!htmlReportsDisabled && sizeBreakdowns != null) {
+      Event generateCompileReport = SpeedTracerLogger.start(
+          CompilerEventType.MAKE_SOYC_ARTIFACTS, "phase", "generateCompileReport");
+      ArtifactsOutputDirectory outDir = new ArtifactsOutputDirectory();
+      SoycDashboard dashboard = new SoycDashboard(outDir);
+      dashboard.startNewPermutation(Integer.toString(permutationId));
+      try {
+        dashboard.readSplitPoints(openWithGunzip(splitPoints));
+        if (sizeMaps != null) {
+          dashboard.readSizeMaps(openWithGunzip(sizeMaps));
+        }
+        if (dependencies != null) {
+          dashboard.readDependencies(openWithGunzip(dependencies));
+        }
+        Memory.maybeDumpMemory("soycReadDependenciesEnd");
+      } catch (ParserConfigurationException e) {
+        throw new InternalCompilerException(
+            "Error reading compile report information that was just generated", e);
+      } catch (SAXException e) {
+        throw new InternalCompilerException(
+            "Error reading compile report information that was just generated", e);
+      }
+      dashboard.generateForOnePermutation();
+      if (moduleMetricsArtifact != null && precompilationMetricsArtifact != null
+          && compilationMetrics != null) {
+        dashboard.generateCompilerMetricsForOnePermutation(
+            moduleMetricsArtifact, precompilationMetricsArtifact, compilationMetrics);
+      }
+      soycArtifacts.addAll(outDir.getArtifacts());
+      generateCompileReport.end();
+    }
+
+    soycEvent.end();
+
+    return soycArtifacts;
+  }
+
+  private SymbolData[] makeSymbolMap(Map<StandardSymbolData, JsName> symbolTable) {
+    // Keep tracks of a list of referenced name. If it is not used, don't
+    // add it to symbol map.
+    final Set<String> nameUsed = new HashSet<String>();
+    final Map<JsName, Integer> nameToFragment = new HashMap<JsName, Integer>();
+
+    for (int i = 0; i < jsProgram.getFragmentCount(); i++) {
+      final Integer fragId = i;
+      new JsVisitor() {
+        @Override
+        public void endVisit(JsForIn x, JsContext ctx) {
+          if (x.getIterVarName() != null) {
+            nameUsed.add(x.getIterVarName().getIdent());
+          }
+        }
+
+        @Override
+        public void endVisit(JsFunction x, JsContext ctx) {
+          if (x.getName() != null) {
+            nameToFragment.put(x.getName(), fragId);
+            nameUsed.add(x.getName().getIdent());
+          }
+        }
+
+        @Override
+        public void endVisit(JsLabel x, JsContext ctx) {
+          nameUsed.add(x.getName().getIdent());
+        }
+
+        @Override
+        public void endVisit(JsNameOf x, JsContext ctx) {
+          if (x.getName() != null) {
+            nameUsed.add(x.getName().getIdent());
+          }
+        }
+
+        @Override
+        public void endVisit(JsNameRef x, JsContext ctx) {
+          // Obviously this isn't even that accurate. Some of them are
+          // variable names, some of the are property. At least this
+          // this give us a safe approximation. Ideally we need
+          // the code removal passes to remove stuff in the scope objects.
+          if (x.isResolved()) {
+            nameUsed.add(x.getName().getIdent());
+          }
+        }
+
+        @Override
+        public void endVisit(JsParameter x, JsContext ctx) {
+          nameUsed.add(x.getName().getIdent());
+        }
+
+        @Override
+
+        public void endVisit(JsVars.JsVar x, JsContext ctx) {
+          nameUsed.add(x.getName().getIdent());
+        }
+      }.accept(jsProgram.getFragmentBlock(i));
+    }
+
+    // TODO(acleung): This is a temp fix. Once we know this is safe. We
+    // new to rewrite it to avoid extra ArrayList creations.
+    // Or we should just consider serializing it as an ArrayList if
+    // it is that much trouble to determine the true size.
+    List<SymbolData> result = new ArrayList<SymbolData>();
+
+    for (Map.Entry<StandardSymbolData, JsName> entry : symbolTable.entrySet()) {
+      StandardSymbolData symbolData = entry.getKey();
+      symbolData.setSymbolName(entry.getValue().getShortIdent());
+      Integer fragNum = nameToFragment.get(entry.getValue());
+      if (fragNum != null) {
+        symbolData.setFragmentNumber(fragNum);
+      }
+      if (nameUsed.contains(entry.getValue().getIdent()) || entry.getKey().isClass()) {
+        result.add(symbolData);
+      }
+    }
+
+    return result.toArray(new SymbolData[result.size()]);
+  }
+
+  /**
+   * Open an emitted artifact and gunzip its contents.
+   */
+  private InputStream openWithGunzip(EmittedArtifact artifact)
+      throws IOException, UnableToCompleteException {
+    return new BufferedInputStream(new GZIPInputStream(artifact.getContents(TreeLogger.NULL)));
+  }
+
+  private void optimizeJsLoop(Collection<JsNode> toInline) throws InterruptedException {
+    int optimizationLevel = options.getOptimizationLevel();
+    List<OptimizerStats> allOptimizerStats = Lists.newArrayList();
+    int counter = 0;
+    while (true) {
+      counter++;
+      if (Thread.interrupted()) {
+        throw new InterruptedException();
+      }
+      Event optimizeJsEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE_JS);
+
+      OptimizerStats stats = new OptimizerStats("Pass " + counter);
+
+      // Remove unused functions if possible.
+      stats.add(JsStaticEval.exec(jsProgram));
+      // Inline Js function invocations
+      stats.add(JsInliner.exec(jsProgram, toInline));
+      // Remove unused functions if possible.
+      stats.add(JsUnusedFunctionRemover.exec(jsProgram));
+
+      // Save the stats to print out after optimizers finish.
+      allOptimizerStats.add(stats);
+
+      optimizeJsEvent.end();
+      if ((optimizationLevel < OptionOptimize.OPTIMIZE_LEVEL_MAX && counter > optimizationLevel)
+          || !stats.didChange()) {
+        break;
+      }
+    }
+
+    if (optimizationLevel > OptionOptimize.OPTIMIZE_LEVEL_DRAFT) {
+      DuplicateExecuteOnceRemover.exec(jsProgram);
+    }
+  }
+
+  private Map<JsName, JsLiteral> renameJsSymbols(PermProps props, JavaToJavaScriptMap jjsmap)
+      throws UnableToCompleteException {
+    Map<JsName, JsLiteral> internedLiteralByVariableName;
+    try {
+      switch (options.getOutput()) {
+        case OBFUSCATED:
+          internedLiteralByVariableName = runObfuscateNamer(props);
+          break;
+        case PRETTY:
+          internedLiteralByVariableName = runPrettyNamer(props.getConfigProps(), jjsmap);
+          break;
+        case DETAILED:
+          internedLiteralByVariableName = runDetailedNamer(props.getConfigProps());
+          break;
+        default:
+          throw new InternalCompilerException("Unknown output mode");
+      }
+    } catch (IllegalNameException e) {
+      logger.log(TreeLogger.ERROR, e.getMessage(), e);
+      throw new UnableToCompleteException();
+    }
+    return internedLiteralByVariableName == null ?
+        ImmutableMap.<JsName, JsLiteral>of() : internedLiteralByVariableName;
+  }
+
+  private Map<JsName, JsLiteral> runObfuscateNamer(PermProps props) throws IllegalNameException {
+    Map<JsName, JsLiteral> internedLiteralByVariableName =
+        JsLiteralInterner.exec(jprogram, jsProgram, (byte) (JsLiteralInterner.INTERN_ALL
+            & (byte) (jprogram.typeOracle.isJsInteropEnabled()
+            ? ~JsLiteralInterner.INTERN_STRINGS : ~0)));
+    FreshNameGenerator freshNameGenerator = JsObfuscateNamer.exec(jsProgram,
+        props.getConfigProps());
+    if (options.shouldRemoveDuplicateFunctions()
+        && JsStackEmulator.getStackMode(props) == JsStackEmulator.StackMode.STRIP) {
+      JsDuplicateFunctionRemover.exec(jsProgram, freshNameGenerator);
+    }
+    return internedLiteralByVariableName;
+  }
+
+  private Map<JsName, JsLiteral> runPrettyNamer(ConfigProps config, JavaToJavaScriptMap jjsmap)
+      throws IllegalNameException {
+    if (compilerContext.getOptions().isIncrementalCompileEnabled()) {
+      JsIncrementalNamer.exec(jsProgram, config,
+          compilerContext.getMinimalRebuildCache().getPersistentPrettyNamerState(), jjsmap);
+      return null;
+    }
+
+    // We don't intern strings in pretty mode to improve readability
+    Map<JsName, JsLiteral> internedLiteralByVariableName = JsLiteralInterner.exec(
+        jprogram, jsProgram,
+        (byte) (JsLiteralInterner.INTERN_ALL & ~JsLiteralInterner.INTERN_STRINGS));
+
+    JsPrettyNamer.exec(jsProgram, config);
+    return internedLiteralByVariableName;
+  }
+
+  /**
+   * Takes as input a CompilationState and transforms that into a unified by not yet resolved Java
+   * AST (a Java AST wherein cross-class references have been connected and all rebind result
+   * classes are available and have not yet been pruned down to the set applicable for a particular
+   * permutation). This AST is packaged into a UnifiedAst instance and then returned.
+   *
+   * Precompilation is INTENDED to progress as a series of stages:
+   *
+   * <pre>
+   * 1. initialize local state
+   * 2. assert preconditions
+   * 3. construct and unify the unresolved Java AST
+   * 4. normalize the unresolved Java AST  // arguably should be removed
+   * 5. optimize the unresolved Java AST  // arguably should be removed
+   * 6. construct and return a value
+   * </pre>
+   *
+   * There are some other types of work here (mostly metrics and data gathering) which do not serve
+   * the goal of output program construction. This work should really be moved into subclasses or
+   * some sort of callback or plugin system so as not to visually pollute the real compile logic.<br
+   * />
+   *
+   * Significant amounts of visitors implementing the intended above stages are triggered here but
+   * in the wrong order. They have been noted for future cleanup.
+   */
+  private UnifiedAst precompile(RebindPermutationOracle rpo, String[] entryPointTypeNames,
+      String[] additionalRootTypes, boolean singlePermutation,
+      PrecompilationMetricsArtifact precompilationMetrics) throws UnableToCompleteException {
+    try {
+      /*
+       * Do not introduce any new pass here unless it is logically a part of one of the 6 defined
+       * stages and is physically located in that stage.
+       */
+
+      // (1) Initialize local state
+      jprogram = new JProgram(compilerContext.getMinimalRebuildCache());
+      // Synchronize JTypeOracle with compile optimization behavior.
+      jprogram.typeOracle.setOptimize(
+          options.getOptimizationLevel() > OptionOptimize.OPTIMIZE_LEVEL_DRAFT);
+      jprogram.typeOracle.setJsInteropMode(options.getJsInteropMode());
+
+      jsProgram = new JsProgram();
+      if (additionalRootTypes == null) {
+        additionalRootTypes = Empty.STRINGS;
+      }
+
+      // (2) Assert preconditions
+      if (entryPointTypeNames.length + additionalRootTypes.length == 0) {
+        throw new IllegalArgumentException("entry point(s) required");
+      }
+
+      // (3) Construct and unify the unresolved Java AST
+      CompilationState compilationState =
+          constructJavaAst(rpo, entryPointTypeNames, additionalRootTypes);
+
+      // TODO(stalcup): hide metrics gathering in a callback or subclass
+      JsniRestrictionChecker.exec(logger, jprogram);
+      JsInteropRestrictionChecker.exec(logger, jprogram, getMinimalRebuildCache());
+      logTypeOracleMetrics(precompilationMetrics, compilationState);
+      Memory.maybeDumpMemory("AstOnly");
+      AstDumper.maybeDumpAST(jprogram);
+
+      // TODO(stalcup): is in wrong place, move to optimization stage
+      ConfigProps configProps = new ConfigProps(module);
+      EnumNameObfuscator.exec(jprogram, logger,configProps);
+
+      // (4) Normalize the unresolved Java AST
+      // Replace defender method references
+      ReplaceDefenderMethodReferences.exec(jprogram);
+
+      FixAssignmentsToUnboxOrCast.exec(jprogram);
+      if (options.isEnableAssertions()) {
+        AssertionNormalizer.exec(jprogram);
+      } else {
+        AssertionRemover.exec(jprogram);
+      }
+      if (module != null && options.isRunAsyncEnabled()) {
+        ReplaceRunAsyncs.exec(logger, jprogram);
+        ConfigProps config = new ConfigProps(module);
+        CodeSplitters.pickInitialLoadSequence(logger, jprogram, config);
+      }
+      ImplementClassLiteralsAsFields.exec(jprogram);
+
+      // (5) Optimize the unresolved Java AST
+      optimizeJava(singlePermutation);
+
+      // TODO(stalcup): hide metrics gathering in a callback or subclass
+      logAstTypeMetrics(precompilationMetrics);
+
+      // (6) Construct and return a value.
+      Event createUnifiedAstEvent = SpeedTracerLogger.start(CompilerEventType.CREATE_UNIFIED_AST);
+      UnifiedAst result = new UnifiedAst(
+          options, new AST(jprogram, jsProgram), singlePermutation, RecordRebinds.exec(jprogram));
+      createUnifiedAstEvent.end();
+      return result;
+    } catch (Throwable e) {
+      throw CompilationProblemReporter.logAndTranslateException(logger, e);
+    }
+  }
+
+  /**
+   * Creates (and returns the name for) a new class to serve as the container for the invocation of
+   * registered entry point methods as part of module bootstrapping.<br />
+   *
+   * The resulting class will be invoked during bootstrapping like FooEntryMethodHolder.init(). By
+   * generating the class on the fly and naming it to match the current module, the resulting holder
+   * class can work in both monolithic and separate compilation schemes.
+   */
+  private String buildEntryMethodHolder(StandardGeneratorContext context,
+      String[] entryPointTypeNames, Set<String> allRootTypes)
+      throws UnableToCompleteException {
+    // If there are no entry points.
+    if (entryPointTypeNames.length == 0) {
+      // Then there's no need to generate an EntryMethodHolder class to launch them.
+      return null;
+    }
+
+    EntryMethodHolderGenerator entryMethodHolderGenerator = new EntryMethodHolderGenerator();
+    String entryMethodHolderTypeName =
+        entryMethodHolderGenerator.generate(logger, context, module.getCanonicalName());
+    context.finish(logger);
+    // Ensures that unification traverses and keeps the class.
+    allRootTypes.add(entryMethodHolderTypeName);
+    // Ensures that JProgram knows to index this class's methods so that later bootstrap
+    // construction code is able to locate the FooEntryMethodHolder.init() function.
+    jprogram.addIndexedTypeName(entryMethodHolderTypeName);
+    return entryMethodHolderTypeName;
+  }
+
+  private CompilationState constructJavaAst(RebindPermutationOracle rpo,
+      String[] entryPointTypeNames, String[] additionalRootTypes)
+      throws UnableToCompleteException {
+    Set<String> allRootTypes = Sets.newTreeSet();
+    CompilationState compilationState = rpo.getCompilationState();
+    Memory.maybeDumpMemory("CompStateBuilt");
+    recordJsoTypes(compilationState.getTypeOracle());
+    populateRootTypes(allRootTypes, entryPointTypeNames, additionalRootTypes, compilationState);
+    String entryMethodHolderTypeName =
+        buildEntryMethodHolder(rpo.getGeneratorContext(), entryPointTypeNames, allRootTypes);
+    unifyJavaAst(rpo, entryPointTypeNames, allRootTypes, entryMethodHolderTypeName);
+    if (options.isSoycEnabled() || options.isJsonSoycEnabled()) {
+      SourceInfoCorrelator.exec(jprogram);
+    }
+
+    // Free up memory.
+    rpo.clear();
+    Set<String> deletedTypeNames = options.isIncrementalCompileEnabled()
+        ? getMinimalRebuildCache().computeDeletedTypeNames() : Sets.<String>newHashSet();
+    jprogram.typeOracle.computeBeforeAST(StandardTypes.createFrom(jprogram),
+        jprogram.getDeclaredTypes(), jprogram.getModuleDeclaredTypes(), deletedTypeNames);
+    return compilationState;
+  }
+
+  /**
+   * This method can be used to fetch the list of referenced class.
+   *
+   * This method is intended to support compiler metrics.
+   */
+  private String[] getReferencedJavaClasses() {
+    class ClassNameVisitor extends JVisitor {
+      List<String> classNames = new ArrayList<String>();
+
+      @Override
+      public boolean visit(JClassType x, Context ctx) {
+        classNames.add(x.getName());
+        return true;
+      }
+    }
+    ClassNameVisitor v = new ClassNameVisitor();
+    v.accept(jprogram);
+    return v.classNames.toArray(new String[v.classNames.size()]);
+  }
+
+  private void logAstTypeMetrics(PrecompilationMetricsArtifact precompilationMetrics) {
+    if (options.isCompilerMetricsEnabled()) {
+      precompilationMetrics.setAstTypes(getReferencedJavaClasses());
+    }
+  }
+
+  private void logTypeOracleMetrics(
+      PrecompilationMetricsArtifact precompilationMetrics, CompilationState compilationState) {
+    if (precompilationMetrics != null) {
+      List<String> finalTypeOracleTypes = Lists.newArrayList();
+      for (com.google.gwt.core.ext.typeinfo.JClassType type :
+          compilationState.getTypeOracle().getTypes()) {
+        finalTypeOracleTypes.add(type.getPackage().getName() + "." + type.getName());
+      }
+      precompilationMetrics.setFinalTypeOracleTypes(finalTypeOracleTypes);
+    }
+  }
+
+  private void optimizeJava(boolean singlePermutation) throws InterruptedException {
+    if (options.getOptimizationLevel() > OptionOptimize.OPTIMIZE_LEVEL_DRAFT
+        && !singlePermutation) {
+      if (options.isOptimizePrecompile()) {
+        /*
+         * Go ahead and optimize early, so that each permutation will run faster. This code path
+         * is used by the Compiler entry point. We assume that we will not be able to perfectly
+         * parallelize the permutation compiles, so let's optimize as much as possible the common
+         * AST. In some cases, this might also have the side benefit of reducing the total
+         * permutation count.
+         */
+        optimizeJavaToFixedPoint();
+      } else {
+        /*
+         * Do only minimal early optimizations. This code path is used by the Precompile entry
+         * point. The external system might be able to perfectly parallelize the permutation
+         * compiles, so let's avoid doing potentially superlinear optimizations on the unified
+         * AST.
+         */
+        optimizeJavaOneTime("Early Optimization", jprogram.getNodeCount(),
+            new FullOptimizerContext(jprogram));
+      }
+    }
+  }
+
+  private void populateRootTypes(Set<String> allRootTypes, String[] entryPointTypeNames,
+      String[] additionalRootTypes, CompilationState compilationState) {
+    if (jprogram.typeOracle.isJsInteropEnabled()) {
+      Iterables.addAll(allRootTypes, compilationState.getQualifiedJsInteropRootTypesNames());
+    }
+    Collections.addAll(allRootTypes, entryPointTypeNames);
+    Collections.addAll(allRootTypes, additionalRootTypes);
+    allRootTypes.addAll(JProgram.CODEGEN_TYPES_SET);
+    allRootTypes.addAll(jprogram.getTypeNamesToIndex());
+    /*
+     * Add all SingleJsoImpl types that we know about. It's likely that the concrete types are
+     * never explicitly referenced.
+     */
+    TypeOracle typeOracle = compilationState.getTypeOracle();
+    for (com.google.gwt.core.ext.typeinfo.JClassType singleJsoIntf :
+        typeOracle.getSingleJsoImplInterfaces()) {
+      allRootTypes.add(typeOracle.getSingleJsoImpl(singleJsoIntf).getQualifiedSourceName());
+    }
+  }
+
+  private void recordJsoTypes(TypeOracle typeOracle) {
+    if (!options.isIncrementalCompileEnabled()) {
+      return;
+    }
+
+    // Add names of JSO subtypes.
+    Set<String> jsoTypeNames = Sets.newHashSet();
+    for (com.google.gwt.dev.javac.typemodel.JClassType subtype :
+        typeOracle.getJavaScriptObject().getSubtypes()) {
+      jsoTypeNames.add(subtype.getQualifiedBinaryName());
+    }
+
+    // Add names of interfaces that are always of a JSO (aka there are no non-JSO implementors).
+    Set<String> singleJsoImplInterfaceNames = Sets.newHashSet();
+    for (com.google.gwt.core.ext.typeinfo.JClassType singleJsoImplInterface :
+        typeOracle.getSingleJsoImplInterfaces()) {
+      singleJsoImplInterfaceNames.add(singleJsoImplInterface.getQualifiedBinaryName());
+    }
+
+    // Add names of interfaces that are only sometimes a JSO (aka there are both JSO and non-JSO
+    // imlementors).
+    Set<String> dualJsoImplInterfaceNames = Sets.newHashSet();
+    for (com.google.gwt.core.ext.typeinfo.JClassType dualJsoImplInterface :
+        typeOracle.getDualJsoImplInterfaces()) {
+      dualJsoImplInterfaceNames.add(dualJsoImplInterface.getQualifiedBinaryName());
+    }
+
+    compilerContext.getMinimalRebuildCache().setJsoTypeNames(jsoTypeNames,
+        singleJsoImplInterfaceNames, dualJsoImplInterfaceNames);
+  }
+
+  private void synthesizeEntryMethodHolderInit(UnifyAst unifyAst, String[] entryPointTypeNames,
+      String entryMethodHolderTypeName) throws UnableToCompleteException {
+    // Get type references.
+    JDeclaredType entryMethodHolderType =
+        unifyAst.findType(entryMethodHolderTypeName, unifyAst.getSourceNameBasedTypeLocator());
+    JDeclaredType gwtType = unifyAst.findType("com.google.gwt.core.client.GWT",
+        unifyAst.getSourceNameBasedTypeLocator());
+    JDeclaredType entryPointType = unifyAst.findType("com.google.gwt.core.client.EntryPoint",
+        unifyAst.getSourceNameBasedTypeLocator());
+
+    // Get method references.
+    JMethod initMethod = entryMethodHolderType.findMethod("init()V", false);
+    JMethod gwtCreateMethod =
+        gwtType.findMethod("create(Ljava/lang/Class;)Ljava/lang/Object;", false);
+
+    // Synthesize all onModuleLoad() calls.
+    JBlock initMethodBlock = ((JMethodBody) initMethod.getBody()).getBlock();
+    SourceInfo origin = initMethodBlock.getSourceInfo().makeChild();
+    for (String entryPointTypeName : entryPointTypeNames) {
+      // Get type and onModuleLoad function for the current entryPointTypeName.
+      JDeclaredType specificEntryPointType =
+          unifyAst.findType(entryPointTypeName, unifyAst.getSourceNameBasedTypeLocator());
+      if (specificEntryPointType == null) {
+        logger.log(TreeLogger.ERROR,
+            "Could not find module entry point class '" + entryPointTypeName + "'", null);
+        throw new UnableToCompleteException();
+      }
+      JMethod onModuleLoadMethod =
+          entryPointType.findMethod("onModuleLoad()V", true);
+      JMethod specificOnModuleLoadMethod =
+          specificEntryPointType.findMethod("onModuleLoad()V", true);
+
+      if (specificOnModuleLoadMethod != null && specificOnModuleLoadMethod.isStatic()) {
+        // Synthesize a static invocation FooEntryPoint.onModuleLoad(); call.
+        JMethodCall staticOnModuleLoadCall =
+            new JMethodCall(origin, null, specificOnModuleLoadMethod);
+        initMethodBlock.addStmt(staticOnModuleLoadCall.makeStatement());
+      } else {
+        // Synthesize ((EntryPoint)GWT.create(FooEntryPoint.class)).onModuleLoad();
+        JClassLiteral entryPointTypeClassLiteral =
+            new JClassLiteral(origin, specificEntryPointType);
+        JMethodCall createInstanceCall =
+            new JMethodCall(origin, null, gwtCreateMethod, entryPointTypeClassLiteral);
+        JCastOperation castToEntryPoint =
+            new JCastOperation(origin, entryPointType, createInstanceCall);
+        JMethodCall instanceOnModuleLoadCall =
+            new JMethodCall(origin, castToEntryPoint, onModuleLoadMethod);
+        initMethodBlock.addStmt(instanceOnModuleLoadCall.makeStatement());
+      }
+    }
+  }
+
+  private void unifyJavaAst(RebindPermutationOracle rpo, String[] entryPointTypeNames,
+      Set<String> allRootTypes, String entryMethodHolderTypeName)
+      throws UnableToCompleteException {
+
+    Event event = SpeedTracerLogger.start(CompilerEventType.UNIFY_AST);
+
+    UnifyAst unifyAst = new UnifyAst(logger, compilerContext, jprogram, jsProgram, rpo);
+    // Makes JProgram aware of these types so they can be accessed via index.
+    unifyAst.addRootTypes(allRootTypes);
+    // Must synthesize entryPoint.onModuleLoad() calls because some EntryPoint classes are
+    // private.
+    if (entryMethodHolderTypeName != null) {
+      // Only synthesize the init method in the EntryMethodHolder class, if there is an
+      // EntryMethodHolder class.
+      synthesizeEntryMethodHolderInit(unifyAst, entryPointTypeNames, entryMethodHolderTypeName);
+    }
+    if (entryMethodHolderTypeName != null) {
+      // Only register the init method in the EntryMethodHolder class as an entry method, if there
+      // is an EntryMethodHolder class.
+      jprogram.addEntryMethod(jprogram.getIndexedMethod(
+          SourceName.getShortClassName(entryMethodHolderTypeName) + ".init"));
+    }
+    unifyAst.exec();
+
+    event.end();
+  }
+
+  private void optimizeJavaToFixedPoint() throws InterruptedException {
+    Event optimizeEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE);
+
+    List<OptimizerStats> allOptimizerStats = Lists.newArrayList();
+    int passCount = 0;
+    int nodeCount = jprogram.getNodeCount();
+    int lastNodeCount;
+
+    boolean atMaxLevel = options.getOptimizationLevel() == OptionOptimize.OPTIMIZE_LEVEL_MAX;
+    int passLimit = atMaxLevel ? MAX_PASSES : options.getOptimizationLevel();
+    float minChangeRate = atMaxLevel ? FIXED_POINT_CHANGE_RATE : EFFICIENT_CHANGE_RATE;
+    OptimizerContext optimizerCtx = new FullOptimizerContext(jprogram);
+    while (true) {
+      passCount++;
+      if (passCount > passLimit) {
+        break;
+      }
+      if (Thread.interrupted()) {
+        optimizeEvent.end();
+        throw new InterruptedException();
+      }
+      AstDumper.maybeDumpAST(jprogram);
+      OptimizerStats stats = optimizeJavaOneTime("Pass " + passCount, nodeCount, optimizerCtx);
+      allOptimizerStats.add(stats);
+      lastNodeCount = nodeCount;
+      nodeCount = jprogram.getNodeCount();
+
+      float nodeChangeRate = stats.getNumMods() / (float) lastNodeCount;
+      float sizeChangeRate = (lastNodeCount - nodeCount) / (float) lastNodeCount;
+      if (nodeChangeRate <= minChangeRate && sizeChangeRate <= minChangeRate) {
+        break;
+      }
+    }
+
+    if (options.shouldOptimizeDataflow()) {
+      // Just run it once, because it is very time consuming
+      allOptimizerStats.add(DataflowOptimizer.exec(jprogram));
+    }
+
+    optimizeEvent.end();
+  }
+
+  private boolean shouldOptimize() {
+    return options.getOptimizationLevel() > OptionOptimize.OPTIMIZE_LEVEL_DRAFT;
+  }
+
+  private TypeMapper getTypeMapper() {
+
+    // Used to stabilize output for DeltaJS
+    if (JjsUtils.closureStyleLiteralsNeeded(this.options)) {
+      return new ClosureUniqueIdTypeMapper(jprogram);
+    }
+
+    if (this.options.useDetailedTypeIds()) {
+      return new StringTypeMapper();
+    }
+    return this.options.isIncrementalCompileEnabled() ?
+        compilerContext.getMinimalRebuildCache().getTypeMapper() :
+        new IntTypeMapper();
+  }
+
+  private TypeOrder getTypeOrder() {
+
+    // Used to stabilize output for DeltaJS
+    if (JjsUtils.closureStyleLiteralsNeeded(this.options)) {
+      return TypeOrder.ALPHABETICAL;
+    }
+
+    if (this.options.useDetailedTypeIds()) {
+      return TypeOrder.NONE;
+    }
+    return this.options.isIncrementalCompileEnabled() ? TypeOrder.ALPHABETICAL
+        : TypeOrder.FREQUENCY;
+  }
+
+  private OptimizerStats optimizeJavaOneTime(String passName, int numNodes,
+      OptimizerContext optimizerCtx) {
+    Event optimizeEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE, "phase", "loop");
+    // Clinits might have become empty become empty.
+    jprogram.typeOracle.recomputeAfterOptimizations(jprogram.getDeclaredTypes());
+    OptimizerStats stats = new OptimizerStats(passName);
+    JavaAstVerifier.assertProgramIsConsistent(jprogram);
+    stats.add(Pruner.exec(jprogram, true, optimizerCtx).recordVisits(numNodes));
+    stats.add(Finalizer.exec(jprogram, optimizerCtx).recordVisits(numNodes));
+    stats.add(MakeCallsStatic.exec(jprogram, options.shouldAddRuntimeChecks(), optimizerCtx)
+        .recordVisits(numNodes));
+    stats.add(TypeTightener.exec(jprogram, optimizerCtx).recordVisits(numNodes));
+    stats.add(MethodCallTightener.exec(jprogram, optimizerCtx).recordVisits(numNodes));
+    // Note: Specialization should be done before inlining.
+    stats.add(MethodCallSpecializer.exec(jprogram, optimizerCtx).recordVisits(numNodes));
+    stats.add(DeadCodeElimination.exec(jprogram, optimizerCtx).recordVisits(numNodes));
+    stats.add(MethodInliner.exec(jprogram, optimizerCtx).recordVisits(numNodes));
+    if (options.shouldInlineLiteralParameters()) {
+      stats.add(SameParameterValueOptimizer.exec(jprogram, optimizerCtx).recordVisits(numNodes));
+    }
+    if (options.shouldOrdinalizeEnums()) {
+      stats.add(EnumOrdinalizer.exec(jprogram, optimizerCtx).recordVisits(numNodes));
+    }
+    optimizeEvent.end();
+    return stats;
+  }
+
+  private MinimalRebuildCache getMinimalRebuildCache() {
+    return compilerContext.getMinimalRebuildCache();
+  }
+
   private static class PermutationResultImpl implements PermutationResult {
 
     private final ArtifactSet artifacts = new ArtifactSet();
@@ -1303,147 +1599,4 @@
       return statementRanges;
     }
   }
-
-  /**
-   * Ending optimization passes when the rate of change has reached this value results in gaining
-   * nearly all of the impact while avoiding the long tail of costly but low-impact passes.
-   */
-  private static final float EFFICIENT_CHANGE_RATE = 0.01f;
-
-  private static final String ENUM_NAME_OBFUSCATION_PROPERTY = "compiler.enum.obfuscate.names";
-
-  private static final String ENUM_NAME_OBFUSCATION_BLACKLIST_PROPERTY = "compiler.enum.obfuscate.names.blacklist";
-
-  /**
-   * Continuing to apply optimizations till the rate of change reaches this value causes the AST to
-   * reach a fixed point.
-   */
-  private static final int FIXED_POINT_CHANGE_RATE = 0;
-
-  /**
-   * Limits the number of optimization passes against the possible danger of an AST that does not
-   * converge.
-   */
-  private static final int MAX_PASSES = 100;
-
-  static {
-    InternalCompilerException.preload();
-  }
-
-  protected final CompilerContext compilerContext;
-
-  protected JsProgram jsProgram;
-
-  protected final TreeLogger logger;
-
-  protected final ModuleDef module;
-
-  protected final PrecompileTaskOptions options;
-
-  @VisibleForTesting
-  JProgram jprogram;
-
-  public JavaToJavaScriptCompiler(TreeLogger logger, CompilerContext compilerContext) {
-    this.logger = logger;
-    this.compilerContext = compilerContext;
-    this.module = compilerContext.getModule();
-    this.options = compilerContext.getOptions();
-  }
-
-  /**
-   * Compiles and returns a particular permutation, based on a precompiled unified AST.
-   */
-  public abstract PermutationResult compilePermutation(
-      UnifiedAst unifiedAst, Permutation permutation) throws UnableToCompleteException;
-
-  /**
-   * Performs a precompilation, returning a unified AST.
-   */
-  public abstract UnifiedAst precompile(RebindPermutationOracle rpo, String[] entryPointTypeNames,
-      String[] additionalRootTypes, boolean singlePermutation,
-      PrecompilationMetricsArtifact precompilationMetrics) throws UnableToCompleteException;
-
-  protected final void optimizeJavaToFixedPoint() throws InterruptedException {
-    Event optimizeEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE);
-
-    List<OptimizerStats> allOptimizerStats = new ArrayList<OptimizerStats>();
-    int passCount = 0;
-    int nodeCount = jprogram.getNodeCount();
-    int lastNodeCount;
-
-    boolean atMaxLevel = options.getOptimizationLevel() == OptionOptimize.OPTIMIZE_LEVEL_MAX;
-    int passLimit = atMaxLevel ? MAX_PASSES : options.getOptimizationLevel();
-    float minChangeRate = atMaxLevel ? FIXED_POINT_CHANGE_RATE : EFFICIENT_CHANGE_RATE;
-    OptimizerContext optimizerCtx = new FullOptimizerContext(jprogram);
-    while (true) {
-      passCount++;
-      if (passCount > passLimit) {
-        break;
-      }
-      if (Thread.interrupted()) {
-        optimizeEvent.end();
-        throw new InterruptedException();
-      }
-      AstDumper.maybeDumpAST(jprogram);
-      OptimizerStats stats = optimizeJavaOneTime("Pass " + passCount, nodeCount, optimizerCtx);
-      allOptimizerStats.add(stats);
-      lastNodeCount = nodeCount;
-      nodeCount = jprogram.getNodeCount();
-
-      float nodeChangeRate = stats.getNumMods() / (float) lastNodeCount;
-      float sizeChangeRate = (lastNodeCount - nodeCount) / (float) lastNodeCount;
-      if (nodeChangeRate <= minChangeRate && sizeChangeRate <= minChangeRate) {
-        break;
-      }
-    }
-
-    if (options.shouldOptimizeDataflow()) {
-      // Just run it once, because it is very time consuming
-      allOptimizerStats.add(DataflowOptimizer.exec(jprogram));
-    }
-
-    optimizeEvent.end();
-  }
-
-  /*
-   * This method is intended as a central location for producing optional tracking output. This will
-   * be called after all optimization/normalization passes have completed.
-   */
-  private void logTrackingStats() {
-    EnumOrdinalizer.Tracker eot = EnumOrdinalizer.getTracker();
-    if (eot != null) {
-      eot.logResultsDetailed(logger, TreeLogger.WARN);
-    }
-  }
-
-  private OptimizerStats optimizeJavaOneTime(String passName, int numNodes,
-      OptimizerContext optimizerCtx) {
-    Event optimizeEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE, "phase", "loop");
-    // Clinits might have become empty become empty.
-    jprogram.typeOracle.recomputeAfterOptimizations(jprogram.getDeclaredTypes());
-    OptimizerStats stats = new OptimizerStats(passName);
-    JavaAstVerifier.assertProgramIsConsistent(jprogram);
-    stats.add(Pruner.exec(jprogram, true, optimizerCtx).recordVisits(numNodes));
-    stats.add(Finalizer.exec(jprogram, optimizerCtx).recordVisits(numNodes));
-    stats.add(MakeCallsStatic.exec(jprogram, options.shouldAddRuntimeChecks(), optimizerCtx)
-        .recordVisits(numNodes));
-    stats.add(TypeTightener.exec(jprogram, optimizerCtx).recordVisits(numNodes));
-    stats.add(MethodCallTightener.exec(jprogram, optimizerCtx).recordVisits(numNodes));
-    // Note: Specialization should be done before inlining.
-    stats.add(MethodCallSpecializer.exec(jprogram, optimizerCtx).recordVisits(numNodes));
-    stats.add(DeadCodeElimination.exec(jprogram, optimizerCtx).recordVisits(numNodes));
-    stats.add(MethodInliner.exec(jprogram, optimizerCtx).recordVisits(numNodes));
-    if (options.shouldInlineLiteralParameters()) {
-      stats.add(SameParameterValueOptimizer.exec(jprogram, optimizerCtx).recordVisits(numNodes));
-    }
-    if (options.shouldOrdinalizeEnums()) {
-      stats.add(EnumOrdinalizer.exec(jprogram, optimizerCtx).recordVisits(numNodes));
-    }
-    optimizeEvent.end();
-    return stats;
-  }
-
-  private MinimalRebuildCache getMinimalRebuildCache() {
-    return compilerContext.getMinimalRebuildCache();
-  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/MonolithicJavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/MonolithicJavaToJavaScriptCompiler.java
deleted file mode 100644
index 6db5b3b..0000000
--- a/dev/core/src/com/google/gwt/dev/jjs/MonolithicJavaToJavaScriptCompiler.java
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
- * 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;
-
-import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.core.ext.linker.PrecompilationMetricsArtifact;
-import com.google.gwt.core.ext.linker.SyntheticArtifact;
-import com.google.gwt.core.ext.soyc.coderef.DependencyGraphRecorder;
-import com.google.gwt.core.ext.soyc.impl.DependencyRecorder;
-import com.google.gwt.core.linker.SoycReportLinker;
-import com.google.gwt.dev.CompilerContext;
-import com.google.gwt.dev.Permutation;
-import com.google.gwt.dev.cfg.ConfigProps;
-import com.google.gwt.dev.cfg.PermProps;
-import com.google.gwt.dev.jdt.RebindPermutationOracle;
-import com.google.gwt.dev.jjs.ast.JProgram;
-import com.google.gwt.dev.jjs.impl.ArrayNormalizer;
-import com.google.gwt.dev.jjs.impl.CatchBlockNormalizer;
-import com.google.gwt.dev.jjs.impl.ComputeCastabilityInformation;
-import com.google.gwt.dev.jjs.impl.ComputeExhaustiveCastabilityInformation;
-import com.google.gwt.dev.jjs.impl.ComputeInstantiatedJsoInterfaces;
-import com.google.gwt.dev.jjs.impl.ControlFlowAnalyzer;
-import com.google.gwt.dev.jjs.impl.Devirtualizer;
-import com.google.gwt.dev.jjs.impl.EqualityNormalizer;
-import com.google.gwt.dev.jjs.impl.HandleCrossFragmentReferences;
-import com.google.gwt.dev.jjs.impl.ImplementCastsAndTypeChecks;
-import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap;
-import com.google.gwt.dev.jjs.impl.JjsUtils;
-import com.google.gwt.dev.jjs.impl.LongCastNormalizer;
-import com.google.gwt.dev.jjs.impl.LongEmulationNormalizer;
-import com.google.gwt.dev.jjs.impl.PostOptimizationCompoundAssignmentNormalizer;
-import com.google.gwt.dev.jjs.impl.Pruner;
-import com.google.gwt.dev.jjs.impl.RemoveEmptySuperCalls;
-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.ClosureUniqueIdTypeMapper;
-import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.IntTypeMapper;
-import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.StringTypeMapper;
-import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.TypeMapper;
-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;
-import com.google.gwt.dev.jjs.impl.codesplitter.CodeSplitters;
-import com.google.gwt.dev.jjs.impl.codesplitter.MultipleDependencyGraphRecorder;
-import com.google.gwt.dev.js.JsDuplicateCaseFolder;
-import com.google.gwt.dev.js.JsLiteralInterner;
-import com.google.gwt.dev.js.JsNamer.IllegalNameException;
-import com.google.gwt.dev.js.JsVerboseNamer;
-import com.google.gwt.dev.js.ast.JsLiteral;
-import com.google.gwt.dev.js.ast.JsName;
-import com.google.gwt.dev.js.ast.JsNode;
-import com.google.gwt.dev.util.Pair;
-import com.google.gwt.dev.util.arg.OptionOptimize;
-import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
-import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
-import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
-
-import java.io.ByteArrayOutputStream;
-import java.io.OutputStream;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Compiles the Java <code>JProgram</code> representation into its corresponding Js source.
- */
-public class MonolithicJavaToJavaScriptCompiler extends JavaToJavaScriptCompiler {
-
-  private class MonolithicPermutationCompiler extends PermutationCompiler {
-
-    public MonolithicPermutationCompiler(Permutation permutation) {
-      super(permutation);
-    }
-
-    @Override
-    protected TypeMapper<?> normalizeSemantics() {
-      Event event = SpeedTracerLogger.start(CompilerEventType.JAVA_NORMALIZERS);
-      try {
-        Devirtualizer.exec(jprogram);
-        CatchBlockNormalizer.exec(jprogram);
-        PostOptimizationCompoundAssignmentNormalizer.exec(jprogram);
-        LongCastNormalizer.exec(jprogram);
-        LongEmulationNormalizer.exec(jprogram);
-        TypeCoercionNormalizer.exec(jprogram);
-
-        if (options.isIncrementalCompileEnabled()) {
-          // Per file compilation reuses type JS even as references (like casts) in other files
-          // change, which means all legal casts need to be allowed now before they are actually
-          // used later.
-          ComputeExhaustiveCastabilityInformation.exec(jprogram);
-        } else {
-          // If trivial casts are pruned then one can use smaller runtime castmaps.
-          ComputeCastabilityInformation.exec(jprogram, options.isCastCheckingDisabled(),
-              !shouldOptimize() /* recordTrivialCasts */);
-        }
-
-        ComputeInstantiatedJsoInterfaces.exec(jprogram);
-        ImplementCastsAndTypeChecks.exec(jprogram, options.isCastCheckingDisabled(),
-            shouldOptimize() /* pruneTrivialCasts */);
-        ArrayNormalizer.exec(jprogram, options.isCastCheckingDisabled());
-        EqualityNormalizer.exec(jprogram);
-
-        TypeMapper<?> typeMapper = getTypeMapper();
-        ResolveRuntimeTypeReferences.exec(jprogram, typeMapper, getTypeOrder());
-        return typeMapper;
-      } finally {
-        event.end();
-      }
-    }
-
-    @Override
-    protected void optimizeJava() throws InterruptedException {
-      if (shouldOptimize()) {
-        optimizeJavaToFixedPoint();
-        RemoveEmptySuperCalls.exec(jprogram);
-      }
-    }
-
-    @Override
-    protected void optimizeJs(Set<JsNode> inlinableJsFunctions) throws InterruptedException {
-      if (shouldOptimize()) {
-        optimizeJsLoop(inlinableJsFunctions);
-        JsDuplicateCaseFolder.exec(jsProgram);
-      }
-    }
-
-    @Override
-    protected void postNormalizationOptimizeJava() {
-      Event event = SpeedTracerLogger.start(CompilerEventType.JAVA_POST_NORMALIZER_OPTIMIZERS);
-      try {
-        if (shouldOptimize()) {
-          RemoveSpecializations.exec(jprogram);
-          Pruner.exec(jprogram, false);
-        }
-        ReplaceGetClassOverrides.exec(jprogram);
-      } finally {
-        event.end();
-      }
-    }
-
-    @Override
-    protected Map<JsName, JsLiteral> runDetailedNamer(ConfigProps config)
-        throws IllegalNameException {
-      Map<JsName, JsLiteral> internedTextByVariableName = null;
-      if (shouldOptimize()) {
-        // Only perform the interning optimization when optimizations are enabled.
-        internedTextByVariableName =
-            JsLiteralInterner.exec(jprogram, jsProgram, (byte) (JsLiteralInterner.INTERN_ALL
-                & (byte) (jprogram.typeOracle.isJsInteropEnabled()
-                ? ~JsLiteralInterner.INTERN_STRINGS : ~0)));
-      }
-      JsVerboseNamer.exec(jsProgram, config);
-      return internedTextByVariableName;
-    }
-
-    @Override
-    protected Pair<SyntheticArtifact, MultipleDependencyGraphRecorder> splitJsIntoFragments(
-        PermProps props, int permutationId, JavaToJavaScriptMap jjsmap) {
-      Pair<SyntheticArtifact, MultipleDependencyGraphRecorder> dependenciesAndRecorder;
-      MultipleDependencyGraphRecorder dependencyRecorder = null;
-      SyntheticArtifact dependencies = null;
-      if (options.isRunAsyncEnabled()) {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-
-        int expectedFragmentCount = options.getFragmentCount();
-        // -1 is the default value, we trap 0 just in case (0 is not a legal value in any case)
-        if (expectedFragmentCount <= 0) {
-          // Fragment count not set check fragments merge.
-          int numberOfMerges = options.getFragmentsMerge();
-          if (numberOfMerges > 0) {
-            // + 1 for left over, + 1 for initial gave us the total number
-            // of fragments without splitting.
-            expectedFragmentCount =
-                Math.max(0, jprogram.getRunAsyncs().size() + 2 - numberOfMerges);
-          }
-        }
-
-        int minFragmentSize = props.getConfigProps().getInteger(CodeSplitters.MIN_FRAGMENT_SIZE, 0);
-
-        dependencyRecorder = chooseDependencyRecorder(baos);
-        CodeSplitter.exec(logger, jprogram, jsProgram, jjsmap, expectedFragmentCount,
-            minFragmentSize, dependencyRecorder);
-
-        if (baos.size() == 0) {
-          dependencyRecorder = recordNonSplitDependencies(baos);
-        }
-        if (baos.size() > 0) {
-          dependencies = new SyntheticArtifact(
-              SoycReportLinker.class, "dependencies" + permutationId + ".xml.gz",
-              baos.toByteArray());
-        }
-      } else if (options.isSoycEnabled() || options.isJsonSoycEnabled()) {
-        dependencyRecorder = recordNonSplitDependencies(new ByteArrayOutputStream());
-      }
-      dependenciesAndRecorder = Pair.<SyntheticArtifact, MultipleDependencyGraphRecorder> create(
-          dependencies, dependencyRecorder);
-
-      // No new JsNames or references to JSNames can be introduced after this
-      // point.
-      HandleCrossFragmentReferences.exec(jsProgram, props);
-
-      return dependenciesAndRecorder;
-    }
-
-    private MultipleDependencyGraphRecorder chooseDependencyRecorder(OutputStream out) {
-      MultipleDependencyGraphRecorder dependencyRecorder =
-          MultipleDependencyGraphRecorder.NULL_RECORDER;
-      if (options.isSoycEnabled() && options.isJsonSoycEnabled()) {
-        dependencyRecorder = new DependencyGraphRecorder(out, jprogram);
-      } else if (options.isSoycEnabled()) {
-        dependencyRecorder = new DependencyRecorder(out);
-      } else if (options.isJsonSoycEnabled()) {
-        dependencyRecorder = new DependencyGraphRecorder(out, jprogram);
-      }
-      return dependencyRecorder;
-    }
-
-    /**
-     * Dependency information is normally recorded during code splitting, and it results in multiple
-     * dependency graphs. If the code splitter doesn't run, then this method can be used instead to
-     * record a single dependency graph for the whole program.
-     */
-    private DependencyRecorder recordNonSplitDependencies(OutputStream out) {
-      DependencyRecorder deps;
-      if (options.isSoycEnabled() && options.isJsonSoycEnabled()) {
-        deps = new DependencyGraphRecorder(out, jprogram);
-      } else if (options.isSoycEnabled()) {
-        deps = new DependencyRecorder(out);
-      } else if (options.isJsonSoycEnabled()) {
-        deps = new DependencyGraphRecorder(out, jprogram);
-      } else {
-        return null;
-      }
-      deps.open();
-      deps.startDependencyGraph("initial", null);
-
-      ControlFlowAnalyzer cfa = new ControlFlowAnalyzer(jprogram);
-      cfa.setDependencyRecorder(deps);
-      cfa.traverseEntryMethods();
-      deps.endDependencyGraph();
-      deps.close();
-      return deps;
-    }
-  }
-
-  private class MonolithicPrecompiler extends Precompiler {
-
-    public MonolithicPrecompiler(RebindPermutationOracle rpo, String[] entryPointTypeNames) {
-      super(rpo, entryPointTypeNames);
-    }
-
-    @Override
-    protected void beforeUnifyAst(Set<String> allRootTypes)
-        throws UnableToCompleteException {
-    }
-
-    @Override
-    protected void checkEntryPoints(String[] additionalRootTypes) {
-      if (entryPointTypeNames.length + additionalRootTypes.length == 0) {
-        throw new IllegalArgumentException("entry point(s) required");
-      }
-    }
-
-    @Override
-    protected void createJProgram(CompilerContext compilerContext) {
-      jprogram = new JProgram(compilerContext.getMinimalRebuildCache());
-    }
-  }
-
-  /**
-   * Constructs a JavaToJavaScriptCompiler with customizations for compiling independent libraries.
-   */
-  public MonolithicJavaToJavaScriptCompiler(TreeLogger logger, CompilerContext compilerContext) {
-    super(logger, compilerContext);
-  }
-
-  @Override
-  public PermutationResult compilePermutation(UnifiedAst unifiedAst, Permutation permutation)
-      throws UnableToCompleteException {
-    return new MonolithicPermutationCompiler(permutation).compilePermutation(unifiedAst);
-  }
-
-  @Override
-  public UnifiedAst precompile(RebindPermutationOracle rpo, String[] entryPointTypeNames,
-      String[] additionalRootTypes, boolean singlePermutation,
-      PrecompilationMetricsArtifact precompilationMetrics) throws UnableToCompleteException {
-    return new MonolithicPrecompiler(rpo, entryPointTypeNames).precompile(additionalRootTypes,
-        singlePermutation, precompilationMetrics);
-  }
-
-  private boolean shouldOptimize() {
-    return options.getOptimizationLevel() > OptionOptimize.OPTIMIZE_LEVEL_DRAFT;
-  }
-
-  private TypeMapper getTypeMapper() {
-
-    // Used to stabilize output for DeltaJS
-    if (JjsUtils.closureStyleLiteralsNeeded(this.options)) {
-      return new ClosureUniqueIdTypeMapper(jprogram);
-    }
-
-    if (this.options.useDetailedTypeIds()) {
-      return new StringTypeMapper();
-    }
-    return this.options.isIncrementalCompileEnabled() ?
-        compilerContext.getMinimalRebuildCache().getTypeMapper() :
-        new IntTypeMapper();
-  }
-
-  private TypeOrder getTypeOrder() {
-
-    // Used to stabilize output for DeltaJS
-    if (JjsUtils.closureStyleLiteralsNeeded(this.options)) {
-      return TypeOrder.ALPHABETICAL;
-    }
-
-    if (this.options.useDetailedTypeIds()) {
-      return TypeOrder.NONE;
-    }
-    return this.options.isIncrementalCompileEnabled() ? TypeOrder.ALPHABETICAL : TypeOrder.FREQUENCY;
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/UnifiedAst.java b/dev/core/src/com/google/gwt/dev/jjs/UnifiedAst.java
index e21cafd..ae52af2 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/UnifiedAst.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/UnifiedAst.java
@@ -15,12 +15,8 @@
  */
 package com.google.gwt.dev.jjs;
 
-import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.linker.ModuleMetricsArtifact;
 import com.google.gwt.core.ext.linker.PrecompilationMetricsArtifact;
-import com.google.gwt.dev.CompilerContext;
-import com.google.gwt.dev.Permutation;
 import com.google.gwt.dev.PrecompileTaskOptions;
 import com.google.gwt.dev.PrecompileTaskOptionsImpl;
 import com.google.gwt.dev.jjs.ast.JProgram;
@@ -113,24 +109,6 @@
   }
 
   /**
-   * Compiles a particular permutation.
-   *
-   * @param logger the logger to use
-   * @param compilerContext shared read only compiler state
-   * @param permutation the permutation to compile
-   * @return the permutation result
-   * @throws UnableToCompleteException if an error other than
-   *           {@link OutOfMemoryError} occurs
-   */
-  public PermutationResult compilePermutation(
-      TreeLogger logger, CompilerContext compilerContext, Permutation permutation)
-      throws UnableToCompleteException {
-    JavaToJavaScriptCompiler javaToJavaScriptCompiler =
-        new MonolithicJavaToJavaScriptCompiler(logger, compilerContext);
-    return javaToJavaScriptCompiler.compilePermutation(this, permutation);
-  }
-
-  /**
    * Return the current AST so that clients can explicitly walk the Java or
    * JavaScript parse trees.
    *
diff --git a/dev/core/src/com/google/gwt/dev/jjs/EnumNameObfuscator.java b/dev/core/src/com/google/gwt/dev/jjs/impl/EnumNameObfuscator.java
similarity index 62%
rename from dev/core/src/com/google/gwt/dev/jjs/EnumNameObfuscator.java
rename to dev/core/src/com/google/gwt/dev/jjs/impl/EnumNameObfuscator.java
index 74b30eb..3089112 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/EnumNameObfuscator.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/EnumNameObfuscator.java
@@ -13,9 +13,10 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.dev.jjs;
+package com.google.gwt.dev.jjs.impl;
 
 import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.cfg.ConfigProps;
 import com.google.gwt.dev.jjs.ast.Context;
 import com.google.gwt.dev.jjs.ast.JClassType;
 import com.google.gwt.dev.jjs.ast.JConstructor;
@@ -29,8 +30,8 @@
 import com.google.gwt.dev.jjs.ast.JParameter;
 import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.jjs.ast.JVisitor;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
 
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -38,6 +39,11 @@
  */
 public class EnumNameObfuscator {
 
+  private static final String ENUM_NAME_OBFUSCATION_PROPERTY =
+      "compiler.enum.obfuscate.names";
+  private static final String ENUM_NAME_OBFUSCATION_BLACKLIST_PROPERTY =
+      "compiler.enum.obfuscate.names.blacklist";
+
   private static class EnumNameCallChecker extends JVisitor {
 
     private final JDeclaredType classType;
@@ -49,7 +55,7 @@
     private final List<String> blacklistedEnums;
     private final JDeclaredType stringType;
 
-    public EnumNameCallChecker(JProgram jprogram, TreeLogger logger,
+    private EnumNameCallChecker(JProgram jprogram, TreeLogger logger,
         List<String> blacklistedEnums) {
       this.logger = logger;
       this.blacklistedEnums = blacklistedEnums;
@@ -73,13 +79,12 @@
       JMethod foundMethod = null;
       List<JMethod> enumMethods = enumType.getMethods();
       for (JMethod enumMethod : enumMethods) {
-        if ("valueOf".equals(enumMethod.getName())) {
-          List<JParameter> jParameters = enumMethod.getParams();
-          if (jParameters.size() == 2 && jParameters.get(0).getType() == classType
-              && jParameters.get(1).getType() == stringType) {
-            foundMethod = enumMethod;
-            break;
-          }
+        List<JParameter> params = enumMethod.getParams();
+        if (enumMethod.getName().equals("valueOf") &&
+            params.size() == 2 && params.get(0).getType() == classType
+            && params.get(1).getType() == stringType) {
+          foundMethod = enumMethod;
+          break;
         }
       }
       this.enumValueOfMethod = foundMethod;
@@ -90,27 +95,30 @@
       JMethod target = x.getTarget();
       JDeclaredType type = target.getEnclosingType();
 
-      if (type instanceof JClassType) {
-        JClassType cType = (JClassType) type;
-        if (typeInBlacklist(blacklistedEnums, cType)) {
-          return;
-        }
-        if (target == enumNameMethod || target == enumToStringMethod || target == enumValueOfMethod) {
-          warn(x);
-        } else if (cType.isEnumOrSubclass() != null) {
-          if ("valueOf".equals(target.getName())) {
-            /*
-             * Check for calls to the auto-generated
-             * EnumSubType.valueOf(String). Note, the check of the signature for
-             * the single String arg version is to avoid flagging user-defined
-             * overloaded versions of the method, which are presumably ok.
-             */
-            List<JParameter> jParameters = target.getParams();
-            if (jParameters.size() == 1 && jParameters.get(0).getType() == stringType) {
-              warn(x);
-            }
-          }
-        }
+      if (!(type instanceof JClassType)) {
+        return;
+      }
+      JClassType cType = (JClassType) type;
+      if (typeInBlacklist(blacklistedEnums, cType)) {
+        return;
+      }
+      if (target == enumNameMethod || target == enumToStringMethod || target == enumValueOfMethod) {
+        warn(x);
+        return;
+      }
+      if (cType.isEnumOrSubclass() != null) {
+        return;
+      }
+      /*
+       * Check for calls to the auto-generated
+       * EnumSubType.valueOf(String). Note, the check of the signature for
+       * the single String arg version is to avoid flagging user-defined
+       * overloaded versions of the method, which are presumably ok.
+       */
+      List<JParameter> params = target.getParams();
+      if (target.getName().equals("valueOf") &&
+          params.size() == 1 && params.get(0).getType() == stringType) {
+        warn(x);
       }
     }
 
@@ -143,7 +151,7 @@
     private boolean closureMode;
     private final TreeLogger logger;
 
-    public EnumNameReplacer(JProgram jprogram, TreeLogger logger,
+    private EnumNameReplacer(JProgram jprogram, TreeLogger logger,
         List<String> blacklistedEnums, boolean closureMode) {
       this.logger = logger;
       this.jprogram = jprogram;
@@ -152,11 +160,12 @@
       this.makeEnumName = jprogram.getIndexedMethod("Util.makeEnumName");
     }
 
-    public void exec() {
+    private void exec() {
       accept(jprogram);
     }
 
-    @Override public boolean visit(JClassType x, Context ctx) {
+    @Override
+    public boolean visit(JClassType x, Context ctx) {
       if (x.isEnumOrSubclass() == null || typeInBlacklist(blacklistedEnums, x)) {
         return false;
       }
@@ -164,31 +173,31 @@
       return true;
     }
 
-    @Override public void endVisit(JNewInstance x, Context ctx) {
+    @Override
+    public void endVisit(JNewInstance x, Context ctx) {
       JConstructor target = x.getTarget();
       JClassType enclosingType = target.getEnclosingType();
-      if (enclosingType.isEnumOrSubclass() != null) {
-        if (!typeInBlacklist(blacklistedEnums, enclosingType)) {
-          JNewInstance newEnum = new JNewInstance(x);
-          // replace first argument with null
-          List<JExpression> args = new ArrayList<JExpression>(x.getArgs());
-          if (closureMode) {
-            JMethodCall makeEnum = new JMethodCall(x.getSourceInfo(), null, makeEnumName, args.get(0));
-            args.set(0, makeEnum);
-          } else {
-            args.set(0, JNullLiteral.INSTANCE);
-          }
-          newEnum.addArgs(args);
-          ctx.replaceMe(newEnum);
-        }
+      if (enclosingType.isEnumOrSubclass() == null) {
+        return;
       }
+
+      if (typeInBlacklist(blacklistedEnums, enclosingType)) {
+        return;
+      }
+
+      JNewInstance newEnum = new JNewInstance(x);
+      List<JExpression> args = Lists.newArrayList(x.getArgs());
+      // replace first argument with null or the closure obfuscation function.
+      args.set(0, closureMode ?
+            new JMethodCall(x.getSourceInfo(), null, makeEnumName, args.get(0)) :
+            JNullLiteral.INSTANCE);
+      newEnum.addArgs(args);
+      ctx.replaceMe(newEnum);
     }
 
     private void trace(JClassType x) {
       if (logger.isLoggable(TreeLogger.TRACE)) {
-        logger.log(TreeLogger.TRACE,
-            "Obfuscating enum " + x
-            + x.getSourceInfo().getFileName() + ":"
+        logger.log(TreeLogger.TRACE, "Obfuscating enum " + x + x.getSourceInfo().getFileName() + ":"
             + x.getSourceInfo().getStartLine());
       }
     }
@@ -198,8 +207,16 @@
     return blacklistedEnums.contains(cType.getName().replace('$', '.'));
   }
 
-  public static void exec(JProgram jprogram, TreeLogger logger,
-      List<String> blacklistedEnums, boolean closureMode) {
+  public static void exec(JProgram jprogram, TreeLogger logger, ConfigProps configProps) {
+    String enumObfucationProperty = configProps.getString(
+        EnumNameObfuscator.ENUM_NAME_OBFUSCATION_PROPERTY, "false");
+    // TODO(rluble): Replace configuration property by the command line option.
+    boolean closureMode = enumObfucationProperty.equals("closure");
+    if (enumObfucationProperty.equals("false")) {
+      return;
+    }
+    List<String> blacklistedEnums = configProps.getCommaSeparatedStrings(
+        EnumNameObfuscator.ENUM_NAME_OBFUSCATION_BLACKLIST_PROPERTY);
     new EnumNameCallChecker(jprogram, logger, blacklistedEnums).accept(jprogram);
     new EnumNameReplacer(jprogram, logger, blacklistedEnums, closureMode).exec();
   }