This change removes "dead loops" of clinit calls. This pattern that can occur when a supertype rebinds itself as a subtype in its own static initializer where everything is expected to inline and prune. The supertype winds up calling the subtype's clinit from its own, and vice versa. This breaks the logjam.
Review by: spoon (TBR)
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2235 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
index 413b98f..f00d929 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
@@ -108,6 +108,16 @@
return sb.toString();
}
+ public static boolean isClinit(JMethod method) {
+ JReferenceType enclosingType = method.getEnclosingType();
+ if ((enclosingType != null) && (method == enclosingType.methods.get(0))) {
+ assert (method.getName().equals("$clinit"));
+ return true;
+ } else {
+ return false;
+ }
+ }
+
public static boolean methodsDoMatch(JMethod method1, JMethod method2) {
// static methods cannot match each other
if (method1.isStatic() || method2.isStatic()) {
@@ -708,16 +718,6 @@
this.jsonTypeTable = jsonObjects;
}
- public boolean isClinit(JMethod method) {
- JReferenceType enclosingType = method.getEnclosingType();
- if ((enclosingType != null) && (method == enclosingType.methods.get(0))) {
- assert (method.getName().equals("$clinit"));
- return true;
- } else {
- return false;
- }
- }
-
public boolean isJavaScriptObject(JType type) {
if (type instanceof JClassType && typeSpecialJavaScriptObject != null) {
return typeOracle.canTriviallyCast((JClassType) type,
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 b42d688..4c4ebbd 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
@@ -15,6 +15,8 @@
*/
package com.google.gwt.dev.jjs.ast;
+import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -28,6 +30,77 @@
*/
public class JTypeOracle {
+ private static final class CheckClinitVisitor extends JVisitor {
+
+ private final Set<JReferenceType> clinitTargets = new HashSet<JReferenceType>();
+ private boolean hasNonClinitCalls = false;
+
+ public JReferenceType[] getClinitTargets() {
+ return clinitTargets.toArray(new JReferenceType[clinitTargets.size()]);
+ }
+
+ public boolean hasNonClinitCalls() {
+ return hasNonClinitCalls;
+ }
+
+ @Override
+ public boolean visit(JBlock x, Context ctx) {
+ for (JStatement stmt : x.statements) {
+ if (canContainClinitCalls(stmt)) {
+ accept(stmt);
+ } else {
+ hasNonClinitCalls = true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visit(JExpressionStatement x, Context ctx) {
+ JExpression expr = x.getExpr();
+ if (canContainClinitCalls(expr)) {
+ accept(expr);
+ } else {
+ hasNonClinitCalls = true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visit(JMethodCall x, Context ctx) {
+ JMethod target = x.getTarget();
+ if (JProgram.isClinit(target)) {
+ clinitTargets.add(target.getEnclosingType());
+ } else {
+ hasNonClinitCalls = true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visit(JMultiExpression x, Context ctx) {
+ for (JExpression expr : x.exprs) {
+ // Only a JMultiExpression or JMethodCall can contain clinit calls.
+ if (canContainClinitCalls(expr)) {
+ accept(expr);
+ } else {
+ hasNonClinitCalls = true;
+ }
+ }
+ return false;
+ }
+
+ private boolean canContainClinitCalls(JExpression expr) {
+ // Must have a visit method for every subtype that answers yes!
+ return expr instanceof JMultiExpression || expr instanceof JMethodCall;
+ }
+
+ private boolean canContainClinitCalls(JStatement stmt) {
+ // Must have a visit method for every subtype that answers yes!
+ return stmt instanceof JBlock || stmt instanceof JExpressionStatement;
+ }
+ }
+
private final Map<JInterfaceType, Set<JClassType>> couldBeImplementedMap = new IdentityHashMap<JInterfaceType, Set<JClassType>>();
private final Map<JClassType, Set<JInterfaceType>> couldImplementMap = new IdentityHashMap<JClassType, Set<JInterfaceType>>();
@@ -327,9 +400,10 @@
public void recomputeClinits() {
hasClinitSet.clear();
+ Set<JReferenceType> computed = new HashSet<JReferenceType>();
for (int i = 0; i < program.getDeclaredTypes().size(); ++i) {
JReferenceType type = program.getDeclaredTypes().get(i);
- computeHasClinit(type);
+ computeHasClinit(type, computed);
}
}
@@ -365,12 +439,49 @@
}
}
- private void computeHasClinit(JReferenceType type) {
- JMethod method = type.methods.get(0);
- JMethodBody body = (JMethodBody) method.getBody();
- if (!body.getStatements().isEmpty()) {
+ private void computeHasClinit(JReferenceType type,
+ Set<JReferenceType> computed) {
+ if (computeHasClinitRecursive(type, computed, new HashSet<JReferenceType>())) {
hasClinitSet.add(type);
}
+ computed.add(type);
+ }
+
+ private boolean computeHasClinitRecursive(JReferenceType type,
+ Set<JReferenceType> computed, Set<JReferenceType> alreadySeen) {
+ // Track that we've been seen.
+ alreadySeen.add(type);
+
+ // If we've been computed, hasClinitSet is accurate for me.
+ if (computed.contains(type)) {
+ return hasClinitSet.contains(type);
+ }
+
+ JMethod method = type.methods.get(0);
+ CheckClinitVisitor v = new CheckClinitVisitor();
+ v.accept(method);
+ if (v.hasNonClinitCalls()) {
+ return true;
+ }
+ for (JReferenceType target : v.getClinitTargets()) {
+ if (target == null) {
+ // Has an actual clinit.
+ return true;
+ }
+ if (alreadySeen.contains(target)) {
+ // Ignore this call for now.
+ continue;
+ }
+
+ if (computeHasClinitRecursive(target, computed, alreadySeen)) {
+ // Calling a non-empty clinit means I am a real clinit.
+ return true;
+ } else {
+ // This clinit is okay, keep going.
+ continue;
+ }
+ }
+ return false;
}
/**