This addresses issue #622. The problem is that CastOptimizer leaves the
tree in a particular state, namely:
"Cast.throwClassCastExceptionUnlessNull(value).value". The expectation is
that FixDanglignRefsVisitor would replace the ".value" field reference
with ".nullField". The problem is, however, that only TightenTypesVisitor
can force FDRV to run; CastOptimizer cannot.
My fix is to eliminate CastOptimizer altogether and move its work (mostly)
into TypeTightener. Impossible casts are replaced in the tree with a
(<null>) cast, which becomes a throwClassCastExceptionUnlessNull() in
CastNormalizer.
Review by: mmendez
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@403 8db76d5a-ed1c-0410-87a9-c151d255dfc7
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 51988d1..5d887e7 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -34,7 +34,6 @@
import com.google.gwt.dev.jjs.impl.ArrayNormalizer;
import com.google.gwt.dev.jjs.impl.BuildTypeMap;
import com.google.gwt.dev.jjs.impl.CastNormalizer;
-import com.google.gwt.dev.jjs.impl.CastOptimizer;
import com.google.gwt.dev.jjs.impl.CatchBlockNormalizer;
import com.google.gwt.dev.jjs.impl.CompoundAssignmentNormalizer;
import com.google.gwt.dev.jjs.impl.DeadCodeElimination;
@@ -336,14 +335,12 @@
// - params based on assignment and call sites
// - method bodies based on return statements
// - polymorphic methods based on return types of all implementors
+ // - optimize casts and instanceof
didChange = TypeTightener.exec(jprogram) || didChange;
// tighten method call bindings
didChange = MethodCallTightener.exec(jprogram) || didChange;
- // remove unnecessary casts / optimize instanceof
- didChange = CastOptimizer.exec(jprogram) || didChange;
-
// dead code removal??
didChange = DeadCodeElimination.exec(jprogram) || didChange;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JCastOperation.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JCastOperation.java
index 2adf61d..417a3e1 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JCastOperation.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JCastOperation.java
@@ -43,10 +43,11 @@
}
public boolean hasSideEffects() {
- // technically this isn't true, but since the same cast on the same
- // expression always evaluates the same way, it effectively has no side
- // effects
- return getExpr().hasSideEffects();
+ // Any live cast operations might throw a ClassCastException
+ //
+ // TODO: revisit this when we support the concept of whether something
+ // can/must complete normally!
+ return true;
}
public void traverse(JVisitor visitor, Context ctx) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java
index 7368151..0a9f81d 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java
@@ -155,8 +155,9 @@
// @Override
public void endVisit(JCastOperation x, Context ctx) {
- assert (x.getCastType() != program.getTypeNull());
- recordCast(x.getCastType(), x.getExpr());
+ if (x.getCastType() != program.getTypeNull()) {
+ recordCast(x.getCastType(), x.getExpr());
+ }
}
// @Override
@@ -351,7 +352,23 @@
public void endVisit(JCastOperation x, Context ctx) {
JExpression replaceExpr;
JType toType = x.getCastType();
- if (toType instanceof JReferenceType) {
+ if (toType instanceof JNullType) {
+ /*
+ * Magic: a null type cast means the user tried a cast that couldn't
+ * possibly work. Typically this means either the statically resolvable
+ * arg type is incompatible with the target type, or the target type was
+ * globally uninstantiable. We handle this cast by throwing a
+ * ClassCastException, unless the argument is null.
+ */
+ JMethod method = program.getSpecialMethod("Cast.throwClassCastExceptionUnlessNull");
+ /*
+ * Override the type of the magic method with the null type.
+ */
+ JMethodCall call = new JMethodCall(program, x.getSourceInfo(), null,
+ method, program.getTypeNull());
+ call.getArgs().add(x.getExpr());
+ replaceExpr = call;
+ } else if (toType instanceof JReferenceType) {
JExpression curExpr = x.getExpr();
JReferenceType refType = (JReferenceType) toType;
JType argType = x.getExpr().getType();
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CastOptimizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CastOptimizer.java
deleted file mode 100644
index 517e037..0000000
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CastOptimizer.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright 2006 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.impl;
-
-import com.google.gwt.dev.jjs.ast.Context;
-import com.google.gwt.dev.jjs.ast.JBinaryOperation;
-import com.google.gwt.dev.jjs.ast.JBinaryOperator;
-import com.google.gwt.dev.jjs.ast.JCastOperation;
-import com.google.gwt.dev.jjs.ast.JInstanceOf;
-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.JNullLiteral;
-import com.google.gwt.dev.jjs.ast.JProgram;
-import com.google.gwt.dev.jjs.ast.JReferenceType;
-import com.google.gwt.dev.jjs.ast.JType;
-import com.google.gwt.dev.jjs.ast.JTypeOracle;
-
-/**
- * Optimizer that will remove all trivially computable casts and instanceof
- * operations.
- */
-public class CastOptimizer {
-
- /**
- * Replaces all trivially computable casts and instanceof operations.
- */
- private class ReplaceTrivialCastsVisitor extends JModVisitor {
-
- // @Override
- public void endVisit(JCastOperation x, Context ctx) {
- JType argType = x.getExpr().getType();
- if (!(x.getCastType() instanceof JReferenceType)
- || !(argType instanceof JReferenceType)) {
- return;
- }
-
- JReferenceType toType = (JReferenceType) x.getCastType();
- JReferenceType fromType = (JReferenceType) argType;
-
- boolean triviallyTrue = false;
- boolean triviallyFalse = false;
-
- JTypeOracle typeOracle = program.typeOracle;
- if (typeOracle.canTriviallyCast(fromType, toType)) {
- triviallyTrue = true;
- } else if (!typeOracle.isInstantiatedType(toType)) {
- triviallyFalse = true;
- } else if (!typeOracle.canTheoreticallyCast(fromType, toType)) {
- triviallyFalse = true;
- }
-
- if (triviallyTrue) {
- // remove the cast operation
- ctx.replaceMe(x.getExpr());
- } else if (triviallyFalse) {
- // throw a ClassCastException unless the argument is null
- JMethod method = program.getSpecialMethod("Cast.throwClassCastExceptionUnlessNull");
- /*
- * Override the type of the called method with the null type. Null flow
- * will proceedeth forth from this cast operation. Assuredly, if the
- * call completes normally it will return null.
- */
- JMethodCall call = new JMethodCall(program, x.getSourceInfo(), null,
- method, program.getTypeNull());
- call.getArgs().add(x.getExpr());
- ctx.replaceMe(call);
- }
- }
-
- // @Override
- public void endVisit(JInstanceOf x, Context ctx) {
- JType argType = x.getExpr().getType();
- if (!(argType instanceof JReferenceType)) {
- return;
- }
-
- JReferenceType toType = x.getTestType();
- JReferenceType fromType = (JReferenceType) argType;
-
- boolean triviallyTrue = false;
- boolean triviallyFalse = false;
-
- JTypeOracle typeOracle = program.typeOracle;
- if (fromType == program.getTypeNull()) {
- // null is never instanceOf anything
- triviallyFalse = true;
- } else if (typeOracle.canTriviallyCast(fromType, toType)) {
- triviallyTrue = true;
- } else if (!typeOracle.isInstantiatedType(toType)) {
- triviallyFalse = true;
- } else if (!typeOracle.canTheoreticallyCast(fromType, toType)) {
- triviallyFalse = true;
- }
-
- if (triviallyTrue) {
- // replace with a simple null test
- JNullLiteral nullLit = program.getLiteralNull();
- JBinaryOperation neq = new JBinaryOperation(program, x.getSourceInfo(),
- program.getTypePrimitiveBoolean(), JBinaryOperator.NEQ,
- x.getExpr(), nullLit);
- ctx.replaceMe(neq);
- } else if (triviallyFalse) {
- // replace with a false literal
- ctx.replaceMe(program.getLiteralBoolean(false));
- }
- }
- }
-
- public static boolean exec(JProgram program) {
- return new CastOptimizer(program).execImpl();
- }
-
- private final JProgram program;
-
- private CastOptimizer(JProgram program) {
- this.program = program;
- }
-
- private boolean execImpl() {
- ReplaceTrivialCastsVisitor replacer = new ReplaceTrivialCastsVisitor();
- replacer.accept(program);
- return replacer.didChange();
- }
-}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
index 9ecd657..4e13025 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
@@ -19,10 +19,13 @@
import com.google.gwt.dev.jjs.ast.JArrayRef;
import com.google.gwt.dev.jjs.ast.JArrayType;
import com.google.gwt.dev.jjs.ast.JBinaryOperation;
+import com.google.gwt.dev.jjs.ast.JBinaryOperator;
+import com.google.gwt.dev.jjs.ast.JCastOperation;
import com.google.gwt.dev.jjs.ast.JClassType;
import com.google.gwt.dev.jjs.ast.JExpression;
import com.google.gwt.dev.jjs.ast.JField;
import com.google.gwt.dev.jjs.ast.JFieldRef;
+import com.google.gwt.dev.jjs.ast.JInstanceOf;
import com.google.gwt.dev.jjs.ast.JInterfaceType;
import com.google.gwt.dev.jjs.ast.JLocal;
import com.google.gwt.dev.jjs.ast.JLocalDeclarationStatement;
@@ -31,6 +34,7 @@
import com.google.gwt.dev.jjs.ast.JMethodCall;
import com.google.gwt.dev.jjs.ast.JModVisitor;
import com.google.gwt.dev.jjs.ast.JNewArray;
+import com.google.gwt.dev.jjs.ast.JNullLiteral;
import com.google.gwt.dev.jjs.ast.JNullType;
import com.google.gwt.dev.jjs.ast.JParameter;
import com.google.gwt.dev.jjs.ast.JParameterRef;
@@ -39,6 +43,7 @@
import com.google.gwt.dev.jjs.ast.JReturnStatement;
import com.google.gwt.dev.jjs.ast.JTryStatement;
import com.google.gwt.dev.jjs.ast.JType;
+import com.google.gwt.dev.jjs.ast.JTypeOracle;
import com.google.gwt.dev.jjs.ast.JVariable;
import com.google.gwt.dev.jjs.ast.JVariableRef;
import com.google.gwt.dev.jjs.ast.JVisitor;
@@ -58,7 +63,7 @@
* The purpose of this pass is to record "type flow" information and then use
* the information to infer places where "tighter" (that is, more specific)
* types can be inferred for locals, fields, parameters, and method return
- * types.
+ * types. We also optimize dynamic casts and instanceof operations.
*
* Type flow occurs automatically in most JExpressions. But locals, fields,
* parameters, and method return types serve as "way points" where type
@@ -162,6 +167,7 @@
}
}
}
+
/**
* Record "type flow" information. Variables receive type flow via assignment.
* As a special case, Parameters also receive type flow based on the types of
@@ -370,16 +376,51 @@
* to change the declared type of a field, local, parameter, or method to a
* more specific type.
*
- * We must iterate mutiple times because each way point we tighten creates
- * more opportunities to do additional tightening for the things that depend
- * on it.
+ * Also optimize dynamic casts and instanceof operations where possible.
*/
- public class TightenTypesVisitor extends JVisitor {
+ public class TightenTypesVisitor extends JModVisitor {
- private boolean didChange = false;
+ /**
+ * <code>true</code> if this visitor has changed the AST apart from calls
+ * to Context.
+ */
+ private boolean myDidChange = false;
public boolean didChange() {
- return didChange;
+ return myDidChange || super.didChange();
+ }
+
+ public void endVisit(JCastOperation x, Context ctx) {
+ JType argType = x.getExpr().getType();
+ if (!(x.getCastType() instanceof JReferenceType)
+ || !(argType instanceof JReferenceType)) {
+ return;
+ }
+
+ JReferenceType toType = (JReferenceType) x.getCastType();
+ JReferenceType fromType = (JReferenceType) argType;
+
+ boolean triviallyTrue = false;
+ boolean triviallyFalse = false;
+
+ JTypeOracle typeOracle = program.typeOracle;
+ if (typeOracle.canTriviallyCast(fromType, toType)) {
+ triviallyTrue = true;
+ } else if (!typeOracle.isInstantiatedType(toType)) {
+ triviallyFalse = true;
+ } else if (!typeOracle.canTheoreticallyCast(fromType, toType)) {
+ triviallyFalse = true;
+ }
+
+ if (triviallyTrue) {
+ // remove the cast operation
+ ctx.replaceMe(x.getExpr());
+ } else if (triviallyFalse) {
+ // replace with a magic NULL cast
+ JCastOperation newOp = new JCastOperation(program, x.getSourceInfo(),
+ program.getTypeNull(), x.getExpr());
+ ctx.replaceMe(newOp);
+ }
}
// @Override
@@ -388,6 +429,45 @@
}
// @Override
+ public void endVisit(JInstanceOf x, Context ctx) {
+ JType argType = x.getExpr().getType();
+ if (!(argType instanceof JReferenceType)) {
+ // TODO: is this even possible? Replace with assert maybe.
+ return;
+ }
+
+ JReferenceType toType = x.getTestType();
+ JReferenceType fromType = (JReferenceType) argType;
+
+ boolean triviallyTrue = false;
+ boolean triviallyFalse = false;
+
+ JTypeOracle typeOracle = program.typeOracle;
+ if (fromType == program.getTypeNull()) {
+ // null is never instanceOf anything
+ triviallyFalse = true;
+ } else if (typeOracle.canTriviallyCast(fromType, toType)) {
+ triviallyTrue = true;
+ } else if (!typeOracle.isInstantiatedType(toType)) {
+ triviallyFalse = true;
+ } else if (!typeOracle.canTheoreticallyCast(fromType, toType)) {
+ triviallyFalse = true;
+ }
+
+ if (triviallyTrue) {
+ // replace with a simple null test
+ JNullLiteral nullLit = program.getLiteralNull();
+ JBinaryOperation neq = new JBinaryOperation(program, x.getSourceInfo(),
+ program.getTypePrimitiveBoolean(), JBinaryOperator.NEQ,
+ x.getExpr(), nullLit);
+ ctx.replaceMe(neq);
+ } else if (triviallyFalse) {
+ // replace with a false literal
+ ctx.replaceMe(program.getLiteralBoolean(false));
+ }
+ }
+
+ // @Override
public void endVisit(JLocal x, Context ctx) {
tighten(x);
}
@@ -410,7 +490,7 @@
// tighten based on non-instantiability
if (!program.typeOracle.isInstantiatedType(refType)) {
x.setType(typeNull);
- didChange = true;
+ myDidChange = true;
return;
}
@@ -443,7 +523,7 @@
resultType = program.strongerType(refType, resultType);
if (refType != resultType) {
x.setType(resultType);
- didChange = true;
+ myDidChange = true;
}
}
@@ -456,7 +536,7 @@
if (!program.typeOracle.isInstantiatedType((JReferenceType) leafType)) {
arrayType = program.getTypeArray(typeNull, arrayType.getDims());
x.setType(arrayType);
- didChange = true;
+ myDidChange = true;
}
}
}
@@ -501,7 +581,7 @@
// tighten based on non-instantiability
if (!program.typeOracle.isInstantiatedType(refType)) {
x.setType(typeNull);
- didChange = true;
+ myDidChange = true;
return;
}
@@ -551,7 +631,7 @@
resultType = program.strongerType(refType, resultType);
if (refType != resultType) {
x.setType(resultType);
- didChange = true;
+ myDidChange = true;
}
}
}
@@ -586,6 +666,12 @@
private boolean execImpl() {
RecordVisitor recorder = new RecordVisitor();
recorder.accept(program);
+
+ /*
+ * We must iterate mutiple times because each way point we tighten creates
+ * more opportunities to do additional tightening for the things that depend
+ * on it.
+ */
boolean madeChanges = false;
while (true) {
TightenTypesVisitor tightener = new TightenTypesVisitor();