Removes trivially-delegating clinits, plus some.

1) A trivial clinit that merely delegates to a superclass doesn't emit code; instead, call sites that would have targeted the subclass will target the superclass instead.

2) As an added bonus, in situations where you have two classes that sharing a supertype, where only the supertype has a clinit, calls between the two classes won't trigger clinit checks anymore.  This also works when a supetype calls into a subtype when only the supertype has a clinit, which is exactly the case that enums are currently tripping over.

http://gwt-code-reviews.appspot.com/184802/show
Review by: spoon


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7868 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JDeclaredType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JDeclaredType.java
index b1d67a0..8913b09 100755
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JDeclaredType.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JDeclaredType.java
@@ -52,10 +52,10 @@
   protected transient List<JMethod> methods = Lists.create();
 
   /**
-   * Tracks whether this class has a dynamic clinit. Defaults to true until
-   * shown otherwise.
+   * Tracks the target static initialization for this class. Default to self
+   * until removed or set to be a superclass.
    */
-  private boolean hasClinit = true;
+  private JDeclaredType clinitTarget = this;
 
   /**
    * This type's super class.
@@ -124,17 +124,12 @@
       // Target has no clinit (common case).
       return false;
     }
-
-    // See if I'm a subclass.
-    JClassType checkType = this.getSuperClass();
-    while (checkType != null) {
-      if (checkType == targetType) {
-        // I am a subclass.
-        return false;
-      }
-      checkType = checkType.getSuperClass();
-    }
-    return true;
+    /*
+     * The clinit for the source of the reference must already have run, so if
+     * it's the same as this one, there it must have already run. One example is
+     * a reference from a subclass to something in a superclass.
+     */
+    return this.getClinitTarget() != targetType.getClinitTarget();
   }
 
   public JAnnotation findAnnotation(String className) {
@@ -150,6 +145,14 @@
   }
 
   /**
+   * Returns the class that must be initialized to use this class. May be a
+   * superclass, or <code>null</code> if this class has no static initializer.
+   */
+  public JDeclaredType getClinitTarget() {
+    return clinitTarget;
+  }
+
+  /**
    * Returns this type's fields;does not include fields defined in a super type
    * unless they are overridden by this type.
    */
@@ -202,7 +205,7 @@
    * Returns <code>true</code> when this class's clinit must be run dynamically.
    */
   public boolean hasClinit() {
-    return hasClinit;
+    return clinitTarget != null;
   }
 
   /**
@@ -265,7 +268,7 @@
     annotations = (List<JAnnotation>) stream.readObject();
   }
 
-/**
+  /**
    * See {@link #writeMethodBodies(ObjectOutputStream).
    * 
    * @see #writeMethodBodies(ObjectOutputStream)
@@ -278,13 +281,19 @@
   }
 
   /**
-   * Called when this class's clinit is empty or can be run at the top level.
+   * Called to set this class's trivial initializer to point to a superclass.
    */
-  void removeClinit() {
-    assert hasClinit();
-    JMethod clinitMethod = methods.get(0);
-    assert JProgram.isClinit(clinitMethod);
-    hasClinit = false;
+  void setClinitTarget(JDeclaredType newClinitTarget) {
+    if (clinitTarget == newClinitTarget) {
+      return;
+    }
+    if (getClass().desiredAssertionStatus()) {
+      // Make sure this is a pure upgrade to a superclass or null.
+      for (JDeclaredType current = clinitTarget; current != newClinitTarget; current = current.getSuperClass()) {
+        assert current.getSuperClass() != null;
+      }
+    }
+    clinitTarget = newClinitTarget;
   }
 
   /**
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
index 835b20d..cdb44ec 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
@@ -222,6 +222,10 @@
    */
   private final Map<JInterfaceType, Set<JClassType>> isImplementedMap = new IdentityHashMap<JInterfaceType, Set<JClassType>>();
 
+  private JDeclaredType javaIoSerializable;
+
+  private JDeclaredType javaLangCloneable;
+
   /**
    * Caches the {@link Object} class.
    */
@@ -261,7 +265,6 @@
    * indirectly.
    */
   private final Map<JInterfaceType, Set<JInterfaceType>> superInterfaceMap = new IdentityHashMap<JInterfaceType, Set<JInterfaceType>>();
-
   /**
    * A map of all methods with virtual overrides, onto the collection of
    * overridden methods. Each key method's collections is a map of the set of
@@ -271,9 +274,6 @@
    */
   private final Map<JMethod, Map<JClassType, Set<JMethod>>> virtualUpRefMap = new IdentityHashMap<JMethod, Map<JClassType, Set<JMethod>>>();
 
-  private JDeclaredType javaIoSerializable;
-  private JDeclaredType javaLangCloneable;
-
   public JTypeOracle(JProgram program) {
     this.program = program;
   }
@@ -348,16 +348,6 @@
     return true;
   }
 
-  public boolean canTriviallyCast(JType type, JType qType) {
-    if (type instanceof JPrimitiveType && 
-        qType instanceof JPrimitiveType) {
-      return type == qType;
-    } else if (type instanceof JReferenceType && 
-        qType instanceof JReferenceType) {
-      return canTriviallyCast((JReferenceType) type, (JReferenceType) qType); 
-    }
-    return false;
-  }
   public boolean canTriviallyCast(JReferenceType type, JReferenceType qType) {
     if (type.canBeNull() && !qType.canBeNull()) {
       // Cannot reliably cast nullable to non-nullable
@@ -432,6 +422,16 @@
     return false;
   }
 
+  public boolean canTriviallyCast(JType type, JType qType) {
+    if (type instanceof JPrimitiveType && qType instanceof JPrimitiveType) {
+      return type == qType;
+    } else if (type instanceof JReferenceType
+        && qType instanceof JReferenceType) {
+      return canTriviallyCast((JReferenceType) type, (JReferenceType) qType);
+    }
+    return false;
+  }
+
   public void computeBeforeAST() {
     javaLangObject = program.getTypeJavaLangObject();
     javaIoSerializable = program.getFromTypeMap(Serializable.class.getName());
@@ -595,11 +595,8 @@
    */
   public void recomputeAfterOptimizations() {
     Set<JDeclaredType> computed = new IdentityHashSet<JDeclaredType>();
-    for (int i = 0; i < program.getDeclaredTypes().size(); ++i) {
-      JDeclaredType type = program.getDeclaredTypes().get(i);
-      if (type.hasClinit()) {
-        computeHasClinit(type, computed);
-      }
+    for (JDeclaredType type : program.getDeclaredTypes()) {
+      computeHasClinitTarget(type, computed);
     }
   }
 
@@ -638,16 +635,32 @@
     }
   }
 
-  private void computeHasClinit(JDeclaredType type, Set<JDeclaredType> computed) {
-    if (computeHasClinitRecursive(type, computed,
-        new IdentityHashSet<JDeclaredType>())) {
-      computed.add(type);
-    } else {
-      type.removeClinit();
+  private void computeHasClinitTarget(JDeclaredType type,
+      Set<JDeclaredType> computed) {
+    if (!type.hasClinit() || computed.contains(type)) {
+      return;
     }
+    if (type.getSuperClass() != null) {
+      /*
+       * Compute super first so that it's already been tightened to the tightest
+       * possible target; this ensures if we're tightened as well it's to the
+       * transitively tightest target.
+       */
+      computeHasClinitTarget(type.getSuperClass(), computed);
+    }
+    if (type.getClinitTarget() != type) {
+      // I already have a trivial clinit, just follow my super chain.
+      type.setClinitTarget(type.getSuperClass().getClinitTarget());
+    } else {
+      // I still have a real clinit, actually compute.
+      JDeclaredType target = computeHasClinitTargetRecursive(type, computed,
+          new IdentityHashSet<JDeclaredType>());
+      type.setClinitTarget(target);
+    }
+    computed.add(type);
   }
 
-  private boolean computeHasClinitRecursive(JDeclaredType type,
+  private JDeclaredType computeHasClinitTargetRecursive(JDeclaredType type,
       Set<JDeclaredType> computed, Set<JDeclaredType> alreadySeen) {
     // Track that we've been seen.
     alreadySeen.add(type);
@@ -657,9 +670,18 @@
     CheckClinitVisitor v = new CheckClinitVisitor();
     v.accept(method);
     if (v.hasLiveCode()) {
-      return true;
+      return type;
     }
-    for (JDeclaredType target : v.getClinitTargets()) {
+    // Check for trivial super clinit.
+    JDeclaredType[] clinitTargets = v.getClinitTargets();
+    if (clinitTargets.length == 1) {
+      JDeclaredType singleTarget = clinitTargets[0];
+      if (type instanceof JClassType && singleTarget instanceof JClassType
+          && isSuperClass((JClassType) type, (JClassType) singleTarget)) {
+        return singleTarget.getClinitTarget();
+      }
+    }
+    for (JDeclaredType target : clinitTargets) {
       if (!target.hasClinit()) {
         // A false result is always accurate.
         continue;
@@ -670,7 +692,7 @@
        * recomputed this run.
        */
       if (target.hasClinit() && computed.contains(target)) {
-        return true;
+        return type;
       }
 
       /*
@@ -681,15 +703,15 @@
         continue;
       }
 
-      if (computeHasClinitRecursive(target, computed, alreadySeen)) {
+      if (computeHasClinitTargetRecursive(target, computed, alreadySeen) != null) {
         // Calling a non-empty clinit means I am a real clinit.
-        return true;
+        return type;
       } else {
         // This clinit is okay, keep going.
         continue;
       }
     }
-    return false;
+    return null;
   }
 
   /**
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
index a81bfa8..66b6b5a 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.dev.jjs.impl;
 
+import com.google.gwt.dev.jjs.SourceInfo;
 import com.google.gwt.dev.jjs.ast.Context;
 import com.google.gwt.dev.jjs.ast.JBinaryOperation;
 import com.google.gwt.dev.jjs.ast.JBinaryOperator;
@@ -330,9 +331,9 @@
         multi.exprs.add(instance);
       }
 
-      JMethodCall clinit = maybeCreateClinitCall(x);
-      if (clinit != null) {
-        multi.exprs.add(clinit);
+      if (x.hasClinit()) {
+        multi.exprs.add(createClinitCall(x.getSourceInfo(),
+            x.getField().getEnclosingType()));
       }
 
       if (literal != null) {
@@ -415,6 +416,10 @@
         // Eliminate the call if the target is now empty.
         if (!targetType.hasClinit()) {
           ctx.replaceMe(program.getLiteralNull());
+        } else if (targetType != targetType.getClinitTarget()) {
+          // Tighten the target.
+          ctx.replaceMe(createClinitCall(x.getSourceInfo(),
+              targetType.getClinitTarget()));
         }
       }
     }
@@ -464,9 +469,9 @@
       // Replace the new operation with a multi.
       JMultiExpression multi = new JMultiExpression(x.getSourceInfo());
       multi.exprs.addAll(x.getArgs());
-      JMethodCall clinit = maybeCreateClinitCall(x);
-      if (clinit != null) {
-        multi.exprs.add(clinit);
+      if (x.hasClinit()) {
+        multi.exprs.add(createClinitCall(x.getSourceInfo(),
+            x.getTarget().getEnclosingType()));
       }
 
       ctx.replaceMe(accept(multi));
@@ -692,6 +697,13 @@
       return true;
     }
 
+    private JMethodCall createClinitCall(SourceInfo sourceInfo,
+        JDeclaredType targetType) {
+      JMethod clinit = targetType.getClinitTarget().getMethods().get(0);
+      assert (JProgram.isClinit(clinit));
+      return new JMethodCall(sourceInfo, null, clinit);
+    }
+
     private void evalConcat(JExpression lhs, JExpression rhs, Context ctx) {
       if (lhs instanceof JValueLiteral && rhs instanceof JValueLiteral) {
         Object lhsObj = ((JValueLiteral) lhs).getValueObj();
@@ -1204,24 +1216,6 @@
       return typeClassMap.get(type);
     }
 
-    private JMethodCall maybeCreateClinitCall(JFieldRef x) {
-      if (x.hasClinit()) {
-        JMethod clinit = x.getField().getEnclosingType().getMethods().get(0);
-        assert (JProgram.isClinit(clinit));
-        return new JMethodCall(x.getSourceInfo(), null, clinit);
-      }
-      return null;
-    }
-
-    private JMethodCall maybeCreateClinitCall(JNewInstance x) {
-      if (x.hasClinit()) {
-        JMethod clinit = x.getTarget().getEnclosingType().getMethods().get(0);
-        assert (JProgram.isClinit(clinit));
-        return new JMethodCall(x.getSourceInfo(), null, clinit);
-      }
-      return null;
-    }
-
     private int numRemovableExpressions(JMultiExpression x) {
       if (ignoringExpressionOutput.contains(x)) {
         // The result doesn't matter: all expressions can be removed.
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 95863a2..1c5e94c 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
@@ -634,7 +634,7 @@
       List<JsFunction> jsFuncs = popList(x.getMethods().size()); // methods
       List<JsNode> jsFields = popList(x.getFields().size()); // fields
 
-      if (x.hasClinit()) {
+      if (x.getClinitTarget() == x) {
         JsFunction superClinit = clinitMap.get(x.getSuperClass());
         JsFunction myClinit = jsFuncs.get(0);
         handleClinit(myClinit, superClinit);
@@ -898,7 +898,7 @@
       List<JsVar> jsFields = popList(x.getFields().size()); // fields
       List<JsStatement> globalStmts = jsProgram.getGlobalBlock().getStatements();
 
-      if (x.hasClinit()) {
+      if (x.getClinitTarget() == x) {
         JsFunction clinitFunc = jsFuncs.get(0);
         handleClinit(clinitFunc, null);
         globalStmts.add(clinitFunc.makeStmt());
@@ -1771,7 +1771,7 @@
         return null;
       }
 
-      JDeclaredType targetType = x.getEnclosingType();
+      JDeclaredType targetType = x.getEnclosingType().getClinitTarget();
       if (!currentMethod.getEnclosingType().checkClinitTo(targetType)) {
         return null;
       } else if (targetType.equals(program.getTypeClassLiteralHolder())) {
@@ -1802,7 +1802,7 @@
         return null;
       }
 
-      JMethod clinitMethod = enclosingType.getMethods().get(0);
+      JMethod clinitMethod = enclosingType.getClinitTarget().getMethods().get(0);
       SourceInfo sourceInfo = x.getSourceInfo().makeChild(
           GenerateJavaScriptVisitor.class, "clinit call");
       JsInvocation jsInvocation = new JsInvocation(sourceInfo);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java
index b2776c8..061772b 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java
@@ -155,8 +155,8 @@
     }
 
     private JMethodCall createClinitCall(JMethodCall x) {
-      JDeclaredType targetEnclosingType = x.getTarget().getEnclosingType();
-      if (!currentMethod.getEnclosingType().checkClinitTo(targetEnclosingType)) {
+      JDeclaredType targetType = x.getTarget().getEnclosingType().getClinitTarget();
+      if (!currentMethod.getEnclosingType().checkClinitTo(targetType)) {
         // Access from this class to the target class won't trigger a clinit
         return null;
       }
@@ -164,12 +164,12 @@
         // No clinit needed; target is really an instance method.
         return null;
       }
-      if (x.getTarget() == x.getTarget().getEnclosingType().getMethods().get(0)) {
+      if (JProgram.isClinit(x.getTarget())) {
         // This is a clinit call, doesn't need another clinit
         return null;
       }
 
-      JMethod clinit = targetEnclosingType.getMethods().get(0);
+      JMethod clinit = targetType.getMethods().get(0);
 
       // If the clinit is a non-native, empty body we can optimize it out here
       if (!clinit.isNative()