Support translation of runtime type literals to closure id generators

Background:
Google uses a system of Javascript diffing and patching called DeltaJS.
This works by storing old copies of previous build versions and
computing vcdiffs between the JS layout, sending only the changed JS
to the client.

In order for this to work efficiently, GWT's output needs to be stable,
and it needs to be run through Closure Compiler to rename variables
according to Closure's Stable-Renaming-Map. This ensures new builds get
the same obfuscated identifiers as previous builds.
JRuntimeTypeLiterals currently either translate to strings, or to
numeric type ids. This patch adds a third option equivalent to using
strings which are replaced by the Closure Compiler with a more
compact representation.

See https://code.google.com/p/closure-compiler/wiki/IdGeneratorAnnotations

When this mode is enabled via -XclosureFormattedOutput, and JsInteropMode
is JS, a runtime type like 'Serializable' will be written in
cast maps and instanceof expressions as
jsinterop.closure.getUniqueId('Serializable')

After Closure Compiler compilation, this will be replaced with a short
static string that is stable across compilations.
Note that usage of this requires your linker to provide an implementation
of jsinterop.closure.getUniqueId

This is highly experimental and specialized for use by internal hybrid
Closure/GWT apps, so it is not recommended for use and will eventually
be replaced by a more official mechanism for GWT and Closure to
operate together.

This patch was originally reviewed in
https://gwt-review.googlesource.com/#/c/10350/

Change-Id: I7e965f4627af027a9e82352b2f316edb3fc72efa
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/CompilerOptionsImpl.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/CompilerOptionsImpl.java
index 6e7c460..66bf6cb 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/CompilerOptionsImpl.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/CompilerOptionsImpl.java
@@ -48,6 +48,7 @@
   private final OptionJsInteropMode.Mode jsInteropMode;
   private final OptionMethodNameDisplayMode.Mode methodNameDisplayMode;
   private final ListMultimap<String, String> properties;
+  private final boolean closureFormattedOutput;
 
   CompilerOptionsImpl(CompileDir compileDir, String moduleName, Options options) {
     this.compileDir = compileDir;
@@ -61,6 +62,7 @@
     this.jsInteropMode = options.getJsInteropMode();
     this.methodNameDisplayMode = options.getMethodNameDisplayMode();
     this.properties = LinkedListMultimap.create(options.getProperties());
+    this.closureFormattedOutput = options.isClosureFormattedOutput();
   }
 
   @Override
@@ -336,4 +338,9 @@
   public boolean warnOverlappingSource() {
     return false;
   }
+
+  @Override
+  public boolean isClosureCompilerFormatEnabled() {
+    return closureFormattedOutput;
+  }
 }
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/Options.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/Options.java
index 66e5348..778d601 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/Options.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/Options.java
@@ -19,12 +19,14 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.dev.ArgProcessorBase;
 import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.util.arg.ArgHandlerClosureFormattedOutput;
 import com.google.gwt.dev.util.arg.ArgHandlerIncrementalCompile;
 import com.google.gwt.dev.util.arg.ArgHandlerJsInteropMode;
 import com.google.gwt.dev.util.arg.ArgHandlerLogLevel;
 import com.google.gwt.dev.util.arg.ArgHandlerMethodNameDisplayMode;
 import com.google.gwt.dev.util.arg.ArgHandlerSetProperties;
 import com.google.gwt.dev.util.arg.ArgHandlerSourceLevel;
+import com.google.gwt.dev.util.arg.OptionClosureFormattedOutput;
 import com.google.gwt.dev.util.arg.OptionIncrementalCompile;
 import com.google.gwt.dev.util.arg.OptionJsInteropMode;
 import com.google.gwt.dev.util.arg.OptionLogLevel;
@@ -85,6 +87,7 @@
   private OptionJsInteropMode.Mode jsInteropMode = OptionJsInteropMode.Mode.NONE;
   private OptionMethodNameDisplayMode.Mode methodNameDisplayMode =
       OptionMethodNameDisplayMode.Mode.NONE;
+  private boolean closureFormattedOutput = false;
 
   private final ListMultimap<String, String> properties = LinkedListMultimap.create();
 
@@ -309,6 +312,10 @@
     return properties;
   }
 
+  public boolean isClosureFormattedOutput() {
+    return closureFormattedOutput;
+  }
+
   private class ArgProcessor extends ArgProcessorBase {
 
     public ArgProcessor() {
@@ -392,6 +399,17 @@
         }
       }) {
       });
+      registerHandler(new ArgHandlerClosureFormattedOutput(new OptionClosureFormattedOutput() {
+        @Override
+        public boolean isClosureCompilerFormatEnabled() {
+          return Options.this.closureFormattedOutput;
+        }
+
+        @Override
+        public void setClosureCompilerFormatEnabled(boolean enabled) {
+          Options.this.closureFormattedOutput = enabled;
+        }
+      }));
     }
 
     @Override
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/UnmodifiableCompilerOptions.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/UnmodifiableCompilerOptions.java
index 9576991..54240b4 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/UnmodifiableCompilerOptions.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/UnmodifiableCompilerOptions.java
@@ -304,4 +304,10 @@
   public void setPropertyValues(String name, Iterable<String> value) {
     throw new UnsupportedOperationException();
   }
+
+
+  @Override
+  public void setClosureCompilerFormatEnabled(boolean enabled) {
+    throw new UnsupportedOperationException();
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/DevMode.java b/dev/core/src/com/google/gwt/dev/DevMode.java
index f2aeea7..760dec4 100644
--- a/dev/core/src/com/google/gwt/dev/DevMode.java
+++ b/dev/core/src/com/google/gwt/dev/DevMode.java
@@ -381,6 +381,15 @@
     private void updateModuleBaseDir() {
       this.moduleBaseDir = new File(warDir, modulePathPrefix);
     }
+
+    @Override
+    public boolean isClosureCompilerFormatEnabled() {
+      return false;
+    }
+
+    @Override
+    public void setClosureCompilerFormatEnabled(boolean enabled) {
+    }
   }
 
   /**
diff --git a/dev/core/src/com/google/gwt/dev/PrecompileTaskArgProcessor.java b/dev/core/src/com/google/gwt/dev/PrecompileTaskArgProcessor.java
index 0e2dc622..697c536 100644
--- a/dev/core/src/com/google/gwt/dev/PrecompileTaskArgProcessor.java
+++ b/dev/core/src/com/google/gwt/dev/PrecompileTaskArgProcessor.java
@@ -16,6 +16,7 @@
 
 package com.google.gwt.dev;
 
+import com.google.gwt.dev.util.arg.ArgHandlerClosureFormattedOutput;
 import com.google.gwt.dev.util.arg.ArgHandlerCompileReport;
 import com.google.gwt.dev.util.arg.ArgHandlerCompilerMetrics;
 import com.google.gwt.dev.util.arg.ArgHandlerDisableCastChecking;
@@ -56,6 +57,7 @@
 class PrecompileTaskArgProcessor extends CompileArgProcessor {
   public PrecompileTaskArgProcessor(PrecompileTaskOptions options) {
     super(options);
+    registerHandler(new ArgHandlerClosureFormattedOutput(options));
     registerHandler(new ArgHandlerCompileReport(options));
     registerHandler(new ArgHandlerCompilerMetrics(options));
     registerHandler(new ArgHandlerDisableCastChecking(options));
diff --git a/dev/core/src/com/google/gwt/dev/PrecompileTaskOptionsImpl.java b/dev/core/src/com/google/gwt/dev/PrecompileTaskOptionsImpl.java
index a958425..e1eeef9 100644
--- a/dev/core/src/com/google/gwt/dev/PrecompileTaskOptionsImpl.java
+++ b/dev/core/src/com/google/gwt/dev/PrecompileTaskOptionsImpl.java
@@ -498,4 +498,14 @@
   public boolean warnOverlappingSource() {
     return warnOverlappingSource;
   }
+
+  @Override
+  public boolean isClosureCompilerFormatEnabled() {
+    return jjsOptions.isClosureCompilerFormatEnabled();
+  }
+
+  @Override
+  public void setClosureCompilerFormatEnabled(boolean enabled) {
+    jjsOptions.setClosureCompilerFormatEnabled(enabled);
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java b/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java
index 7b96592..0fb4b05 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.dev.util.arg.OptionAllowJDTConstantInlining;
 import com.google.gwt.dev.util.arg.OptionCheckedMode;
+import com.google.gwt.dev.util.arg.OptionClosureFormattedOutput;
 import com.google.gwt.dev.util.arg.OptionClusterSimilarFunctions;
 import com.google.gwt.dev.util.arg.OptionDisableCastChecking;
 import com.google.gwt.dev.util.arg.OptionDisableClassMetadata;
@@ -58,5 +59,6 @@
     OptionStrictPublicResources, OptionSoycHtmlDisabled, OptionEnableClosureCompiler,
     OptionFragmentsMerge, OptionFragmentCount, OptionSourceLevel, OptionNamespace,
     OptionCheckedMode, OptionJsInteropMode, OptionUseDetailedTypeIds,
-    OptionAllowJDTConstantInlining, OptionMethodNameDisplayMode {
+    OptionAllowJDTConstantInlining, OptionMethodNameDisplayMode,
+    OptionClosureFormattedOutput {
 }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java b/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java
index 236a215..957ed60 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java
@@ -59,6 +59,7 @@
   private boolean useDetailedTypeIds = false;
   private OptionMethodNameDisplayMode.Mode methodNameDisplayMode =
       OptionMethodNameDisplayMode.Mode.NONE;
+  private boolean closureFormatEnabled = false;
 
   public JJSOptionsImpl() {
   }
@@ -97,6 +98,7 @@
     setJsInteropMode(other.getJsInteropMode());
     setUseDetailedTypeIds(other.useDetailedTypeIds());
     setMethodNameDisplayMode(other.getMethodNameDisplayMode());
+    setClosureCompilerFormatEnabled(other.isClosureCompilerFormatEnabled());
   }
 
   @Override
@@ -407,4 +409,14 @@
   public boolean useDetailedTypeIds() {
     return useDetailedTypeIds;
   }
+
+  @Override
+  public boolean isClosureCompilerFormatEnabled() {
+    return closureFormatEnabled;
+  }
+
+  @Override
+  public void setClosureCompilerFormatEnabled(boolean enabled) {
+    closureFormatEnabled = enabled;
+  }
 }
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 ccaa3db..e89bf9e 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -190,7 +190,7 @@
    */
   protected abstract class PermutationCompiler {
 
-    private Permutation permutation;
+    protected Permutation permutation;
 
     public PermutationCompiler(Permutation permutation) {
       this.permutation = permutation;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/MonolithicJavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/MonolithicJavaToJavaScriptCompiler.java
index 2b62e86..79076a3 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/MonolithicJavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/MonolithicJavaToJavaScriptCompiler.java
@@ -37,6 +37,7 @@
 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;
@@ -45,6 +46,7 @@
 import com.google.gwt.dev.jjs.impl.RemoveSpecializations;
 import com.google.gwt.dev.jjs.impl.ReplaceGetClassOverrides;
 import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences;
+import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.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;
@@ -309,18 +311,30 @@
   }
 
   private TypeMapper getTypeMapper() {
-    if (options.useDetailedTypeIds()) {
+
+    // Used to stabilize output for DeltaJS
+    if (JjsUtils.closureStyleLiteralsNeeded(this.options)) {
+      return new ClosureUniqueIdTypeMapper(jprogram);
+    }
+
+    if (this.options.useDetailedTypeIds()) {
       return new StringTypeMapper();
     }
-    return options.isIncrementalCompileEnabled() ?
+    return this.options.isIncrementalCompileEnabled() ?
         compilerContext.getMinimalRebuildCache().getTypeMapper() :
         new IntTypeMapper();
   }
 
   private TypeOrder getTypeOrder() {
-    if (options.useDetailedTypeIds()) {
+
+    // Used to stabilize output for DeltaJS
+    if (JjsUtils.closureStyleLiteralsNeeded(this.options)) {
+      return TypeOrder.ALPHABETICAL;
+    }
+
+    if (this.options.useDetailedTypeIds()) {
       return TypeOrder.NONE;
     }
-    return options.isIncrementalCompileEnabled() ? TypeOrder.ALPHABETICAL : TypeOrder.FREQUENCY;
+    return this.options.isIncrementalCompileEnabled() ? TypeOrder.ALPHABETICAL : TypeOrder.FREQUENCY;
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
index 1dd41e8..ed26448 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -125,7 +125,6 @@
 import com.google.gwt.dev.js.ast.JsIf;
 import com.google.gwt.dev.js.ast.JsInvocation;
 import com.google.gwt.dev.js.ast.JsLabel;
-import com.google.gwt.dev.js.ast.JsLiteral;
 import com.google.gwt.dev.js.ast.JsModVisitor;
 import com.google.gwt.dev.js.ast.JsName;
 import com.google.gwt.dev.js.ast.JsNameOf;
@@ -1387,7 +1386,7 @@
 
         JsInvocation getPrototypeCall = constructInvocation(x.getSourceInfo(),
             "JavaClassHierarchySetupUtil.getClassPrototype",
-            JjsUtils.translateLiteral(program.getLiteral(typeMapper.get(superMethodTargetType))));
+            convertJavaLiteral(typeMapper.get(superMethodTargetType)));
 
         JsNameRef methodNameRef = polymorphicNames.get(method).makeRef(x.getSourceInfo());
         methodNameRef.setQualifier(getPrototypeCall);
@@ -1402,6 +1401,8 @@
         if (program.isJsTypePrototype(method.getEnclosingType())) {
           result = dispatchToSuperPrototype(x, method, qualifier, methodNameRef, jsInvocation);
         }
+        // getClassPrototype is a native method call, so we enabling inlining
+        methodsForJsInlining.add(currentMethod);
       } else {
         JsName polyName = polymorphicNames.get(method);
         // potentially replace method call with property access
@@ -2214,20 +2215,47 @@
       return false;
     }
 
-    private JsObjectLiteral buildJsCastMapLiteral(List<JsExpression> runtimeTypeIdLiterals,
+    private JsExpression buildJsCastMapLiteral(
+        List<JsExpression> runtimeTypeIdLiterals,
         SourceInfo sourceInfo) {
+      if (JjsUtils.closureStyleLiteralsNeeded(compilePerFile, jsInteropMode,
+          jsExportClosureStyle)) {
+        return buildCastMapFromArrayLiteral(runtimeTypeIdLiterals, sourceInfo);
+      } else {
+        return buildCastMapAsObjectLiteral(runtimeTypeIdLiterals, sourceInfo);
+      }
+    }
+
+    private JsExpression buildCastMapAsObjectLiteral(
+        List<JsExpression> runtimeTypeIdLiterals, SourceInfo sourceInfo) {
       JsObjectLiteral objLit = new JsObjectLiteral(sourceInfo);
       objLit.setInternable();
-      List<JsPropertyInitializer> propInitializers = objLit.getPropertyInitializers();
+      List<JsPropertyInitializer> propInitializers =
+          objLit.getPropertyInitializers();
       JsNumberLiteral one = new JsNumberLiteral(sourceInfo, 1);
       for (JsExpression runtimeTypeIdLiteral : runtimeTypeIdLiterals) {
-        JsPropertyInitializer propInitializer = new JsPropertyInitializer(sourceInfo,
-            runtimeTypeIdLiteral, one);
+        JsPropertyInitializer propInitializer =
+            new JsPropertyInitializer(sourceInfo,
+                runtimeTypeIdLiteral, one);
         propInitializers.add(propInitializer);
       }
       return objLit;
     }
 
+    private JsExpression buildCastMapFromArrayLiteral(
+        List<JsExpression> runtimeTypeIdLiterals, SourceInfo sourceInfo) {
+      JsArrayLiteral castExprs = new JsArrayLiteral(sourceInfo);
+      for (JsExpression expr : runtimeTypeIdLiterals) {
+        castExprs.getExpressions().add(expr);
+      }
+      castExprs.setInternable();
+      JsInvocation jsInvocation = new JsInvocation(sourceInfo,
+          indexedFunctions
+              .get("JavaClassHierarchySetupUtil.makeCastMapFromArray"),
+          castExprs);
+      return jsInvocation;
+    }
+
     private void checkForDupMethods(JDeclaredType x) {
       // Sanity check to see that all methods are uniquely named.
       List<JMethod> methods = x.getMethods();
@@ -2513,17 +2541,24 @@
       }
     }
 
-    private JsLiteral convertJavaLiteral(JLiteral javaLiteral) {
-      return JjsUtils.translateLiteral(javaLiteral);
+    private JsExpression convertJavaLiteral(Object javaLiteral) {
+      if (javaLiteral instanceof JLiteral) {
+        return JjsUtils.translateLiteral((JLiteral) javaLiteral);
+      } else if (javaLiteral instanceof JExpression) {
+        accept((JExpression) javaLiteral);
+        return (JsExpression) pop();
+      } else {
+        return JjsUtils.translateLiteral(program.getLiteral(javaLiteral));
+      }
     }
 
     private void generateClassDefinition(JClassType x, List<JsStatement> globalStmts) {
       SourceInfo sourceInfo = x.getSourceInfo();
       assert x != program.getTypeJavaLangString();
 
-      JLiteral typeId = getRuntimeTypeReference(x);
+      JExpression typeId = getRuntimeTypeReference(x);
       JClassType superClass = x.getSuperClass();
-      JLiteral superTypeId = (superClass == null) ? JNullLiteral.INSTANCE :
+      JExpression superTypeId = (superClass == null) ? JNullLiteral.INSTANCE :
           getRuntimeTypeReference(x.getSuperClass());
       // check if there's an overriding prototype
       JInterfaceType jsPrototypeIntf = JProgram.maybeGetJsTypeFromPrototype(superClass);
@@ -2864,7 +2899,8 @@
           JsExprStmt provideStat = createAssignment(globalTemp.makeRef(x.getSourceInfo()),
               provideCall).makeStmt();
           globalStmts.add(provideStat);
-        } else if (jsInteropMode == OptionJsInteropMode.Mode.CLOSURE) {
+        } else if (jsInteropMode == OptionJsInteropMode.Mode.JS &&
+                   jsExportClosureStyle) {
           // goog.provide statements prepended by linker, so namespace already exists
           // but enclosing constructor exports may have overwritten them
           // so write foo.bar.Baz = foo.bar.Baz || {}
@@ -3397,7 +3433,7 @@
 
     this.stripStack = JsStackEmulator.getStackMode(props) == JsStackEmulator.StackMode.STRIP;
     this.jsExportClosureStyle = compilerContext.getOptions().getJsInteropMode()
-        == OptionJsInteropMode.Mode.CLOSURE;
+        == OptionJsInteropMode.Mode.JS && compilerContext.getOptions().isClosureCompilerFormatEnabled();
     this.jsInteropMode = compilerContext.getOptions().getJsInteropMode();
 
     /*
@@ -3448,11 +3484,14 @@
   /**
    * Retrieves the runtime typeId for {@code type}.
    */
-  JLiteral getRuntimeTypeReference(JReferenceType type) {
+  JExpression getRuntimeTypeReference(JReferenceType type) {
     Object typeId = typeMapper.get(type);
     if (typeId == null) {
       return null;
     }
+    if (typeId instanceof JMethodCall) {
+      return (JMethodCall) typeId;
+    }
     return program.getLiteral(typeId);
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JjsUtils.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JjsUtils.java
index feeba91..84323bf 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JjsUtils.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JjsUtils.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.dev.jjs.impl;
 
+import com.google.gwt.dev.PrecompileTaskOptions;
 import com.google.gwt.dev.jjs.SourceInfo;
 import com.google.gwt.dev.jjs.SourceOrigin;
 import com.google.gwt.dev.jjs.ast.JBooleanLiteral;
@@ -36,6 +37,7 @@
 import com.google.gwt.dev.js.ast.JsNumberLiteral;
 import com.google.gwt.dev.js.ast.JsObjectLiteral;
 import com.google.gwt.dev.js.ast.JsStringLiteral;
+import com.google.gwt.dev.util.arg.OptionJsInteropMode.Mode;
 import com.google.gwt.lang.LongLib;
 import com.google.gwt.thirdparty.guava.common.base.Predicate;
 import com.google.gwt.thirdparty.guava.common.base.Predicates;
@@ -107,6 +109,16 @@
     return translatorByLiteralClass.get(literal.getClass()).translate(literal);
   }
 
+  public static boolean closureStyleLiteralsNeeded(PrecompileTaskOptions options) {
+    return closureStyleLiteralsNeeded(options.isIncrementalCompileEnabled(),
+        options.getJsInteropMode(), options.isClosureCompilerFormatEnabled());
+  }
+
+  public static boolean closureStyleLiteralsNeeded(boolean incremental, Mode jsInteropMode,
+      boolean closureOutputFormat) {
+    return !incremental && jsInteropMode == Mode.JS && closureOutputFormat;
+  }
+
   private static Map<Class<? extends JLiteral>, LiteralTranslators> translatorByLiteralClass =
       new ImmutableMap.Builder()
           .put(JBooleanLiteral.class, LiteralTranslators.BOOLEAN_LITERAL_TRANSLATOR)
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ResolveRuntimeTypeReferences.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ResolveRuntimeTypeReferences.java
index 77e8f56..1d89caf 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ResolveRuntimeTypeReferences.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ResolveRuntimeTypeReferences.java
@@ -18,7 +18,8 @@
 import com.google.gwt.dev.jjs.ast.Context;
 import com.google.gwt.dev.jjs.ast.HasName;
 import com.google.gwt.dev.jjs.ast.JCastMap;
-import com.google.gwt.dev.jjs.ast.JLiteral;
+import com.google.gwt.dev.jjs.ast.JExpression;
+import com.google.gwt.dev.jjs.ast.JMethod;
 import com.google.gwt.dev.jjs.ast.JMethodCall;
 import com.google.gwt.dev.jjs.ast.JModVisitor;
 import com.google.gwt.dev.jjs.ast.JProgram;
@@ -132,6 +133,38 @@
   }
 
   /**
+   * Predictably creates String type id literals for castable and instantiable types
+   * by using closure uniqueid generation in JsInterop CLOSURE mode.
+   */
+  public static class ClosureUniqueIdTypeMapper implements TypeMapper<JMethodCall> {
+
+    private JProgram program;
+
+    public ClosureUniqueIdTypeMapper(JProgram program) {
+      this.program = program;
+    }
+
+    @Override
+    public void copyFrom(TypeMapper<JMethodCall> that) {
+      if (!(that instanceof ClosureUniqueIdTypeMapper)) {
+        throw new IllegalArgumentException("Can only copy from ClosureUniqueIdTypeMapper");
+      }
+    }
+
+    @Override
+    public JMethodCall getOrCreateTypeId(JType type) {
+      return get(type);
+    }
+
+    @Override
+    public JMethodCall get(JType type) {
+      JMethod getUniqueId = program.getIndexedMethod("JavaClassHierarchySetupUtil.uniqueId");
+     return new JMethodCall(type.getSourceInfo(), null,
+          getUniqueId, program.getStringLiteral(type.getSourceInfo(), type.getName()));
+    }
+  }
+
+  /**
    * Collects all types that need an id at runtime.
    */
   // TODO(rluble): Maybe this pass should insert the defineClass in Java.
@@ -206,7 +239,7 @@
         break;
     }
 
-    for (JType type :types) {
+    for (JType type : types) {
       typeMapper.getOrCreateTypeId(type);
     }
   }
@@ -239,8 +272,9 @@
     }
   }
 
-  private JLiteral getTypeIdLiteral(JType type) {
-    return program.getLiteral(typeMapper.getOrCreateTypeId(type));
+  private JExpression getTypeIdLiteral(JType type) {
+    Object typeId = typeMapper.getOrCreateTypeId(type);
+    return typeId instanceof JMethodCall ? (JExpression) typeId : program.getLiteral(typeId);
   }
 
   public static void exec(JProgram program, TypeMapper<?> typeMapper, TypeOrder typeOrder) {
diff --git a/dev/core/src/com/google/gwt/dev/js/JsInliner.java b/dev/core/src/com/google/gwt/dev/js/JsInliner.java
index 6fbf5c4..e1e3755 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsInliner.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsInliner.java
@@ -61,6 +61,7 @@
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
 import com.google.gwt.thirdparty.guava.common.collect.HashMultiset;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
 import com.google.gwt.thirdparty.guava.common.collect.Lists;
 import com.google.gwt.thirdparty.guava.common.collect.Maps;
 import com.google.gwt.thirdparty.guava.common.collect.Multiset;
@@ -636,10 +637,23 @@
 
     private JsProgram program;
 
+    private final Set<JsName> safeToInlineAtTopLevel;
+
     public InliningVisitor(JsProgram program, Set<JsNode> whitelist) {
       this.program = program;
       this.whitelist = whitelist;
       invocationCountingVisitor.accept(program);
+      JsName defineClass = getFunctionName(program, "JavaClassHierarchySetupUtil.defineClass");
+      JsName defineClassProto = getFunctionName(program,
+          "JavaClassHierarchySetupUtil.defineClassWithPrototype");
+      // JsInlinerTest doesn't have these functions, but doesn't need them
+      safeToInlineAtTopLevel = defineClass != null ? ImmutableSet.of(
+          defineClass, defineClassProto) : ImmutableSet.<JsName>of();
+    }
+
+    private static JsName getFunctionName(JsProgram program, String name) {
+      JsFunction func = program.getIndexedFunction(name);
+      return func != null ? func.getName() : null;
     }
 
     /**
@@ -745,9 +759,6 @@
 
     @Override
     public void endVisit(JsInvocation x, JsContext ctx) {
-      if (functionStack.isEmpty()) {
-        return;
-      }
       JsFunction callerFunction = functionStack.peek();
 
       /*
@@ -829,9 +840,10 @@
     @Override
     public boolean visit(JsExprStmt x, JsContext ctx) {
       if (functionStack.peek() == programFunction) {
-        /* Don't inline top-level invocations. */
+        /* Don't inline most top-level invocations. */
         if (x.getExpression() instanceof JsInvocation) {
-          return false;
+          return safeToInlineAtTopLevel.contains(
+              JsUtils.maybeGetFunctionName(x.getExpression()));
         }
       }
       return true;
diff --git a/dev/core/src/com/google/gwt/dev/js/JsUtils.java b/dev/core/src/com/google/gwt/dev/js/JsUtils.java
index a940376..fa8bbd6 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsUtils.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsUtils.java
@@ -18,6 +18,7 @@
 import com.google.gwt.dev.js.ast.JsExpression;
 import com.google.gwt.dev.js.ast.JsFunction;
 import com.google.gwt.dev.js.ast.JsInvocation;
+import com.google.gwt.dev.js.ast.JsName;
 import com.google.gwt.dev.js.ast.JsNameRef;
 import com.google.gwt.dev.js.ast.JsNode;
 import com.google.gwt.dev.util.StringInterner;
@@ -63,6 +64,21 @@
     return null;
   }
 
+  /**
+   * Similar to {@link #isFunction(com.google.gwt.dev.js.ast.JsExpression)} but
+   * retrieves the JsName of an invocation if the qualifier is a name ref.
+   */
+  public static JsName maybeGetFunctionName(JsExpression expression) {
+    if (expression instanceof JsInvocation) {
+      JsInvocation jsInvoke = (JsInvocation) expression;
+      if (jsInvoke.getQualifier() instanceof JsNameRef) {
+        JsNameRef nameRef = (JsNameRef) jsInvoke.getQualifier();
+        return nameRef.getName();
+      }
+    }
+    return null;
+  }
+
   private static final String CALL_STRING = StringInterner.get().intern("call");
   private JsUtils() {
   }
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerClosureFormattedOutput.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerClosureFormattedOutput.java
new file mode 100644
index 0000000..1cf276a
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerClosureFormattedOutput.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.util.arg;
+
+import com.google.gwt.util.tools.ArgHandlerFlag;
+
+/**
+ * Enables Javascript output suitable for post compilation by closure.
+ */
+public class ArgHandlerClosureFormattedOutput extends ArgHandlerFlag {
+
+  private final OptionClosureFormattedOutput option;
+
+  public ArgHandlerClosureFormattedOutput(OptionClosureFormattedOutput option) {
+    this.option = option;
+
+    addTagValue("-XclosureFormattedOutput", true);
+  }
+
+  @Override
+  public String getPurposeSnippet() {
+    return "Enables Javascript output suitable for post-compilation by Closure Compiler";
+  }
+
+  @Override
+  public String getLabel() {
+    return "closureFormattedOutput";
+  }
+
+  @Override
+  public boolean setFlag(boolean value) {
+    option.setClosureCompilerFormatEnabled(value);
+    return true;
+  }
+
+  @Override
+  public boolean isExperimental() {
+    return true;
+  }
+
+  @Override
+  public boolean getDefaultValue() {
+    return option.isClosureCompilerFormatEnabled();
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/OptionClosureFormattedOutput.java b/dev/core/src/com/google/gwt/dev/util/arg/OptionClosureFormattedOutput.java
new file mode 100644
index 0000000..30d1663
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/OptionClosureFormattedOutput.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.util.arg;
+
+/**
+ * Encapsulates a compiler option to enable JavaScript output to be
+ * formatted for post-compilation by Closure Compiler.
+ */
+public interface OptionClosureFormattedOutput {
+  boolean isClosureCompilerFormatEnabled();
+
+  void setClosureCompilerFormatEnabled(boolean enabled);
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/OptionJsInteropMode.java b/dev/core/src/com/google/gwt/dev/util/arg/OptionJsInteropMode.java
index e25cea8..a9aa9bf 100644
--- a/dev/core/src/com/google/gwt/dev/util/arg/OptionJsInteropMode.java
+++ b/dev/core/src/com/google/gwt/dev/util/arg/OptionJsInteropMode.java
@@ -21,7 +21,7 @@
 public interface OptionJsInteropMode {
   /**
    * Specifies which level of JsInterop support is enabled in the compiler,
-   * NONE, JS, and CLOSURE.
+   * NONE, JS.
    */
   public enum Mode {
     /**
@@ -31,11 +31,7 @@
     /**
      * For hand coded, external JS, not run through an external compiler.
      */
-    JS,
-    /**
-     * For cases where GWT code is post-optimized and checked with the Closure Compiler.
-     */
-    CLOSURE
+    JS
   }
 
   Mode getJsInteropMode();
diff --git a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/JavaClassHierarchySetupUtil.java b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/JavaClassHierarchySetupUtil.java
index 4c6a8c8..8ddaff6 100644
--- a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/JavaClassHierarchySetupUtil.java
+++ b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/JavaClassHierarchySetupUtil.java
@@ -127,6 +127,19 @@
         portableObjCreate(Lcom/google/gwt/core/client/JavaScriptObject;)(prototypesByTypeId[superTypeId]);
   }-*/;
 
+  public static native JavaScriptObject makeCastMapFromArray(JavaScriptObject array) /*-{
+    if (array.castMap) {
+        // already created on interned array
+        return array.castMap;
+    }
+    var castMap = {};
+    for (var i = 0; i < array.length; i++) {
+        castMap[array[i]] = 1;
+    }
+    array.castMap = castMap;
+    return castMap;
+  }-*/;
+
   /**
    * Retrieves the class literal if stored in a place holder, {@code null} otherwise.
    */
@@ -269,4 +282,8 @@
    */
   static native void emptyMethod() /*-{
   }-*/;
+
+  static native JavaScriptObject uniqueId(String id) /*-{
+    return jsinterop.closure.getUniqueId(id);
+  }-*/;
 }