Optimize initializing fields at the top scope.

Change-Id: I97a06eb36396a8b8659ce9a025b21a9cf93d0500
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 240f6a0..7ec2231 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
@@ -15,10 +15,10 @@
  */
 package com.google.gwt.dev.jjs.impl;
 
+import com.google.gwt.core.ext.PropertyOracle;
 import com.google.gwt.core.ext.linker.CastableTypeMap;
 import com.google.gwt.core.ext.linker.impl.StandardCastableTypeMap;
 import com.google.gwt.core.ext.linker.impl.StandardSymbolData;
-import com.google.gwt.core.ext.PropertyOracle;
 import com.google.gwt.dev.jjs.HasSourceInfo;
 import com.google.gwt.dev.jjs.InternalCompilerException;
 import com.google.gwt.dev.jjs.JsOutputOption;
@@ -859,7 +859,7 @@
       JVariable target = x.getVariableRef().getTarget();
       if (target instanceof JField) {
         JField field = (JField) target;
-        if (field.getLiteralInitializer() != null && (field.isStatic() || field.isFinal())) {
+        if (initializeAtTopScope(field)) {
           // Will initialize at top scope; no need to double-initialize.
           push(null);
           return;
@@ -893,7 +893,7 @@
     @Override
     public void endVisit(JField x, Context ctx) {
       // if we need an initial value, create an assignment
-      if (x.getLiteralInitializer() != null && (x.isFinal() || x.isStatic())) {
+      if (initializeAtTopScope(x)) {
         // setup the constant value
         accept(x.getLiteralInitializer());
       } else if (x.getEnclosingType() == program.getTypeJavaLangObject()) {
@@ -2207,6 +2207,39 @@
       jsInvocation.setQualifier(names.get(clinitMethod).makeRef(sourceInfo));
       return jsInvocation;
     }
+
+    /**
+     * If a field is a literal, we can potentially treat it as immutable and assign it once on the
+     * prototype, to be reused by all instances of the class, instead of re-assigning the same
+     * literal in each constructor.
+     * 
+     * Technically, to match JVM semantics, we should only do this for final or static fields. For
+     * non-final/non-static fields, a super class's cstr, when it calls a polymorphic method that is
+     * overridden in the subclass, should actually see default values (not the literal initializer)
+     * before the subclass's cstr runs.
+     * 
+     * However, cstr's calling polymorphic methods is admittedly an uncommon case, so we apply some
+     * heuristics to see if we can initialize the field on the prototype anyway.
+     */
+    private boolean initializeAtTopScope(JField x) {
+      if (x.getLiteralInitializer() == null) {
+        return false;
+      }
+      if (x.isFinal() || x.isStatic()) {
+        // we can definitely initialize at top-scope, as JVM does so as well
+        return true;
+      }
+      // if the superclass can observe the field, we need to leave the default value
+      JDeclaredType current = x.getEnclosingType().getSuperClass();
+      while (current != null) {
+        if (canObserveSubclassFields.contains(current)) {
+          return false;
+        }
+        current = current.getSuperClass();
+      }
+      // should be safe to initialize at top-scope, as no one can observe the difference
+      return true;
+    }
   }
 
   private static class JavaToJsOperatorMap {
@@ -2266,6 +2299,69 @@
     }
   }
 
+  /**
+   * Determines which classes can potentially see uninitialized values of their subclasses' fields.
+   * 
+   * If a class can not observe subclass uninitialized fields then the initialization of those could
+   * be hoisted to the prototype.
+   */
+  private class CanObserveSubclassUninitializedFieldsVisitor extends JVisitor {
+    private JDeclaredType currentClass;
+
+    @Override
+    public boolean visit(JConstructor x, Context ctx) {
+      // Only look at constructor bodies.
+      assert currentClass == null;
+      currentClass = x.getEnclosingType();
+      return true;
+    }
+
+    @Override
+    public void endVisit(JConstructor x, Context ctx) {
+      currentClass = null;
+    }
+
+    @Override
+    public boolean visit(JMethod x, Context ctx) {
+      if (x.getName().equals("$$init")) {
+        assert currentClass == null;
+        currentClass = x.getEnclosingType();
+        return true;
+      }
+      // Do not traverse the method body if it is not a constructor.
+      return false;
+    }
+
+    @Override
+    public void endVisit(JMethod x, Context ctx) {
+      currentClass = null;
+    }
+
+    @Override
+    public void endVisit(JMethodCall x, Context ctx) {
+      // This is a method call inside a constructor.
+      assert currentClass != null;
+      // Calls to this/super constructors are okay, as they will also get examined
+      if (x.getTarget().isConstructor() && x.getInstance() instanceof JThisRef) {
+        return;
+      }
+      // Calls to the instance initializer are okay, as execution will not escape it
+      if (x.getTarget().getName().equals("$$init")) {
+        return;
+      }
+      // Calls to static methods with no arguments are safe, because they have no
+      // way to trace back into our new instance.
+      if (x.getTarget().isStatic() && x.getTarget().getOriginalParamTypes().size() == 0) {
+        return;
+      }
+      // Technically we could get fancier about trying to track the "this" reference
+      // through other variable assignments, field assignments, and methods calls, to
+      // see if it calls a polymorphic method against ourself (which would let subclasses
+      // observe their fields), but that gets tricky, so we'll use the above heuristics
+      // for now.
+      canObserveSubclassFields.add(currentClass);
+    }
+  }
 
   private class RecordCrossClassCallsAndJSInlinableMethods extends JVisitor {
 
@@ -2419,6 +2515,12 @@
   private final Set<JConstructor> liveCtors = new IdentityHashSet<JConstructor>();
 
   /**
+   * Classes that could potentially see uninitialized values for fields that are initialized in the
+   * declaration.
+   */
+  private final Set<JDeclaredType> canObserveSubclassFields = new HashSet<JDeclaredType>();
+
+  /**
    * Sorted to avoid nondeterministic iteration.
    */
   private final Map<Long, JsName> longLits = new TreeMap<Long, JsName>();
@@ -2657,6 +2759,9 @@
   }
 
   private Pair<JavaToJavaScriptMap, Set<JsNode>> execImpl() {
+    CanObserveSubclassUninitializedFieldsVisitor canObserve =
+        new CanObserveSubclassUninitializedFieldsVisitor();
+    canObserve.accept(program);
     SortVisitor sorter = new SortVisitor();
     sorter.accept(program);
     RecordCrossClassCallsAndJSInlinableMethods recorder =