Gflow framework with some local CFG-based optimizations.
Rietveld issues this was reviewed in:
http://gwt-code-reviews.appspot.com/112811
http://gwt-code-reviews.appspot.com/117805
http://gwt-code-reviews.appspot.com/130812
http://gwt-code-reviews.appspot.com/130813
http://gwt-code-reviews.appspot.com/130814
http://gwt-code-reviews.appspot.com/132813
http://gwt-code-reviews.appspot.com/132814
http://gwt-code-reviews.appspot.com/153807
http://gwt-code-reviews.appspot.com/154811
Review by: spoon@google.com
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7690 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 c4bbe8a..13e2845 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -90,6 +90,7 @@
import com.google.gwt.dev.jjs.impl.TypeMap;
import com.google.gwt.dev.jjs.impl.TypeTightener;
import com.google.gwt.dev.jjs.impl.CodeSplitter.MultipleDependencyGraphRecorder;
+import com.google.gwt.dev.jjs.impl.gflow.DataflowOptimizer;
import com.google.gwt.dev.js.EvalFunctionsAtTopScope;
import com.google.gwt.dev.js.JsBreakUpLargeVarStatements;
import com.google.gwt.dev.js.JsDuplicateFunctionRemover;
@@ -622,6 +623,12 @@
}
maybeDumpAST(jprogram);
} while (optimizeLoop(jprogram, options.isAggressivelyOptimize()));
+
+ if (options.isAggressivelyOptimize()) {
+ // Just run it once, because it is very time consuming
+ DataflowOptimizer.exec(jprogram);
+ }
+
PerfLogger.end();
}
@@ -631,6 +638,7 @@
// Recompute clinits each time, they can become empty.
jprogram.typeOracle.recomputeAfterOptimizations();
+ // jprogram.methodOracle = MethodOracleBuilder.buildMethodOracle(jprogram);
boolean didChange = false;
// Remove unreferenced types, fields, methods, [params, locals]
@@ -657,7 +665,7 @@
if (isAggressivelyOptimize) {
// inlining
didChange = MethodInliner.exec(jprogram) || didChange;
-
+
// remove same parameters value
didChange = SameParameterValueOptimizer.exec(jprogram) || didChange;
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/HasName.java b/dev/core/src/com/google/gwt/dev/jjs/ast/HasName.java
index de76765..b3e43d5 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/HasName.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/HasName.java
@@ -15,9 +15,26 @@
*/
package com.google.gwt.dev.jjs.ast;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
/**
* Interface implemented by named entities.
*/
public interface HasName {
String getName();
+
+ /**
+ * Collection of utilities to deal with HasName objects.
+ */
+ public static final class Util {
+ public static <T extends HasName> void sortByName(List<T> list) {
+ Collections.sort(list, new Comparator<T>() {
+ public int compare(T o1, T o2) {
+ return o1.getName().compareTo(o2.getName());
+ }
+ });
+ }
+ }
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JArrayRef.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JArrayRef.java
index e63722f..948122c 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JArrayRef.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JArrayRef.java
@@ -53,6 +53,7 @@
: arrayType.getElementType();
}
+ @Override
public boolean hasSideEffects() {
// TODO: make the last test better when we have null tracking.
return instance.hasSideEffects() || indexExpr.hasSideEffects()
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JBinaryOperation.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JBinaryOperation.java
index 61af84f..0ad22c3 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JBinaryOperation.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JBinaryOperation.java
@@ -58,6 +58,7 @@
}
}
+ @Override
public boolean hasSideEffects() {
return op.isAssignment() || getLhs().hasSideEffects()
|| getRhs().hasSideEffects();
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JBooleanLiteral.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JBooleanLiteral.java
index 8e0142f..f87ccf9 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JBooleanLiteral.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JBooleanLiteral.java
@@ -26,7 +26,7 @@
public static final JBooleanLiteral FALSE = new JBooleanLiteral(
SourceOrigin.UNKNOWN, false);
- private static final JBooleanLiteral TRUE = new JBooleanLiteral(
+ public static final JBooleanLiteral TRUE = new JBooleanLiteral(
SourceOrigin.UNKNOWN, true);
public static JBooleanLiteral get(boolean value) {
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 40c3a3b..cda2c46 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,6 +43,7 @@
return castType;
}
+ @Override
public boolean hasSideEffects() {
// Any live cast operations might throw a ClassCastException
//
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JConditional.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JConditional.java
index b8f4dff..a780f6e 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JConditional.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JConditional.java
@@ -52,6 +52,7 @@
return type;
}
+ @Override
public boolean hasSideEffects() {
return ifTest.hasSideEffects() || thenExpr.hasSideEffects()
|| elseExpr.hasSideEffects();
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JGwtCreate.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JGwtCreate.java
index 69ebac2..4784772 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JGwtCreate.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JGwtCreate.java
@@ -110,6 +110,7 @@
return type;
}
+ @Override
public boolean hasSideEffects() {
for (JExpression expr : instantiationExpressions) {
if (expr.hasSideEffects()) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JIfStatement.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JIfStatement.java
index e573ca5..2a053fb 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JIfStatement.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JIfStatement.java
@@ -50,10 +50,10 @@
if (visitor.visit(this, ctx)) {
ifExpr = visitor.accept(ifExpr);
if (thenStmt != null) {
- thenStmt = visitor.accept(thenStmt);
+ thenStmt = visitor.accept(thenStmt, true);
}
if (elseStmt != null) {
- elseStmt = visitor.accept(elseStmt);
+ elseStmt = visitor.accept(elseStmt, true);
}
}
visitor.endVisit(this, ctx);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JInstanceOf.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JInstanceOf.java
index 35fe3be..3018075 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JInstanceOf.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JInstanceOf.java
@@ -44,6 +44,7 @@
return JPrimitiveType.BOOLEAN;
}
+ @Override
public boolean hasSideEffects() {
return false;
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JLiteral.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JLiteral.java
index e67e247..3760e5e 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JLiteral.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JLiteral.java
@@ -26,6 +26,7 @@
super(sourceInfo);
}
+ @Override
public boolean hasSideEffects() {
return false;
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JLocalRef.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JLocalRef.java
index 196b8e6..0caf5c0 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JLocalRef.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JLocalRef.java
@@ -36,6 +36,7 @@
return local;
}
+ @Override
public boolean hasSideEffects() {
return false;
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
index 9245630..644bbaa 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
@@ -68,6 +68,7 @@
private List<JParameter> params = Collections.emptyList();
private JType returnType;
+ private List<JClassType> thrownExceptions = Collections.emptyList();
private boolean trace = false;
private boolean traceFirst = true;
@@ -111,6 +112,14 @@
public void addParam(JParameter x) {
params = Lists.add(params, x);
}
+
+ public void addThrownException(JClassType exceptionType) {
+ thrownExceptions = Lists.add(thrownExceptions, exceptionType);
+ }
+
+ public void addThrownExceptions(List<JClassType> exceptionTypes) {
+ thrownExceptions = Lists.addAll(thrownExceptions, exceptionTypes);
+ }
public JAnnotation findAnnotation(String className) {
return JAnnotation.findAnnotation(this, className);
@@ -165,6 +174,10 @@
return params;
}
+ public List<JClassType> getThrownExceptions() {
+ return thrownExceptions;
+ }
+
public JType getType() {
return returnType;
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JModVisitor.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JModVisitor.java
index a8e8919..41106de 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JModVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JModVisitor.java
@@ -182,28 +182,38 @@
}
private static class NodeContext implements Context {
+ boolean canRemove;
boolean didChange;
JNode node;
boolean replaced;
+ public NodeContext(boolean canRemove) {
+ this.canRemove = canRemove;
+ }
+
public boolean canInsert() {
return false;
}
public boolean canRemove() {
- return false;
+ return this.canRemove;
}
public void insertAfter(JNode node) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException("Can't insert after " + node);
}
public void insertBefore(JNode node) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException("Can't insert before " + node);
}
public void removeMe() {
- throw new UnsupportedOperationException();
+ if (!canRemove) {
+ throw new UnsupportedOperationException("Can't remove " + node);
+ }
+
+ this.node = null;
+ didChange = true;
}
public void replaceMe(JNode node) {
@@ -228,11 +238,17 @@
protected boolean didChange = false;
+ @Override
public JNode accept(JNode node) {
- NodeContext ctx = new NodeContext();
+ return accept(node, false);
+ }
+
+ @Override
+ public JNode accept(JNode node, boolean allowRemove) {
+ NodeContext ctx = new NodeContext(allowRemove);
try {
ctx.node = node;
- node.traverse(this, ctx);
+ traverse(node, ctx);
didChange |= ctx.didChange;
return ctx.node;
} catch (Throwable e) {
@@ -240,12 +256,14 @@
}
}
+ @Override
@SuppressWarnings("unchecked")
public <T extends JNode> void accept(List<T> list) {
- NodeContext ctx = new NodeContext();
+ NodeContext ctx = new NodeContext(false);
try {
for (int i = 0, c = list.size(); i < c; ++i) {
- (ctx.node = list.get(i)).traverse(this, ctx);
+ ctx.node = list.get(i);
+ traverse(ctx.node, ctx);
if (ctx.replaced) {
list.set(i, (T) ctx.node);
ctx.replaced = false;
@@ -260,10 +278,11 @@
@SuppressWarnings("unchecked")
@Override
public <T extends JNode> List<T> acceptImmutable(List<T> list) {
- NodeContext ctx = new NodeContext();
+ NodeContext ctx = new NodeContext(false);
try {
for (int i = 0, c = list.size(); i < c; ++i) {
- (ctx.node = list.get(i)).traverse(this, ctx);
+ ctx.node = list.get(i);
+ traverse(ctx.node, ctx);
if (ctx.replaced) {
list = Lists.set(list, i, (T) ctx.node);
ctx.replaced = false;
@@ -276,16 +295,22 @@
}
}
+ @Override
public <T extends JNode> void acceptWithInsertRemove(List<T> list) {
new ListContext<T>(list).traverse();
}
+ @Override
public <T extends JNode> List<T> acceptWithInsertRemoveImmutable(List<T> list) {
return new ListContextImmutable<T>(list).traverse();
}
+ @Override
public boolean didChange() {
return didChange;
}
+ protected void traverse(JNode node, Context context) {
+ node.traverse(this, context);
+ }
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JNullLiteral.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JNullLiteral.java
index 29dcd5d..501b053 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JNullLiteral.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JNullLiteral.java
@@ -31,6 +31,7 @@
}
@Override
+ protected
JValueLiteral cloneFrom(JValueLiteral value) {
throw new UnsupportedOperationException();
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JParameterRef.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JParameterRef.java
index f49b162..5d0ce4b 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JParameterRef.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JParameterRef.java
@@ -36,6 +36,7 @@
return param;
}
+ @Override
public boolean hasSideEffects() {
return false;
}
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 55edbf5..c45cbe6 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
@@ -405,6 +405,11 @@
public JClassType createClass(SourceInfo info, char[][] name,
boolean isAbstract, boolean isFinal) {
String sname = dotify(name);
+ return createClass(info, sname, isAbstract, isFinal);
+ }
+
+ public JClassType createClass(SourceInfo info, String sname,
+ boolean isAbstract, boolean isFinal) {
JClassType x = new JClassType(info, sname, isAbstract, isFinal);
allTypes.add(x);
@@ -526,12 +531,18 @@
public JMethod createMethod(SourceInfo info, char[] name,
JDeclaredType enclosingType, JType returnType, boolean isAbstract,
boolean isStatic, boolean isFinal, boolean isPrivate, boolean isNative) {
- String sname = String.valueOf(name);
- assert (sname != null);
+ return createMethod(info, String.valueOf(name), enclosingType, returnType,
+ isAbstract, isStatic, isFinal, isPrivate, isNative);
+ }
+
+ public JMethod createMethod(SourceInfo info, String name,
+ JDeclaredType enclosingType, JType returnType, boolean isAbstract,
+ boolean isStatic, boolean isFinal, boolean isPrivate, boolean isNative) {
+ assert (name != null);
assert (enclosingType != null);
assert (returnType != null);
assert (!isAbstract || !isNative);
- JMethod x = new JMethod(info, sname, enclosingType, returnType, isAbstract,
+ JMethod x = new JMethod(info, name, enclosingType, returnType, isAbstract,
isStatic, isFinal, isPrivate);
if (isNative) {
x.setBody(new JsniMethodBody(info));
@@ -540,7 +551,7 @@
}
if (!isPrivate && indexedTypes.containsValue(enclosingType)) {
- indexedMethods.put(enclosingType.getShortName() + '.' + sname, x);
+ indexedMethods.put(enclosingType.getShortName() + '.' + name, x);
}
enclosingType.addMethod(x);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JThisRef.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JThisRef.java
index 6f58f3b..dee0764 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JThisRef.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JThisRef.java
@@ -38,6 +38,7 @@
return type;
}
+ @Override
public boolean hasSideEffects() {
return false;
}
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 bfff3cb..8255315 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
@@ -339,6 +339,16 @@
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
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JUnaryOperation.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JUnaryOperation.java
index e9ff4a2..4334938 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JUnaryOperation.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JUnaryOperation.java
@@ -44,6 +44,7 @@
return arg.getType();
}
+ @Override
public boolean hasSideEffects() {
return getOp().isModifying() || arg.hasSideEffects();
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JValueLiteral.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JValueLiteral.java
index f729a67..1cbfe78 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JValueLiteral.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JValueLiteral.java
@@ -33,6 +33,6 @@
public abstract Object getValueObj();
- abstract JValueLiteral cloneFrom(JValueLiteral value);
+ protected abstract JValueLiteral cloneFrom(JValueLiteral value);
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JVisitor.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JVisitor.java
index a6a593c..99f7304 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JVisitor.java
@@ -81,6 +81,10 @@
}
public JNode accept(JNode node) {
+ return accept(node, false);
+ }
+
+ public JNode accept(JNode node, boolean allowRemove) {
try {
node.traverse(this, UNMODIFIABLE_CONTEXT);
return node;
@@ -90,7 +94,11 @@
}
public final JStatement accept(JStatement node) {
- return (JStatement) accept((JNode) node);
+ return accept(node, false);
+ }
+
+ public final JStatement accept(JStatement node, boolean allowRemove) {
+ return (JStatement) accept((JNode) node, allowRemove);
}
public <T extends JNode> void accept(List<T> list) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/js/JsonArray.java b/dev/core/src/com/google/gwt/dev/jjs/ast/js/JsonArray.java
index 641d997..46d9cff 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/js/JsonArray.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/js/JsonArray.java
@@ -43,6 +43,7 @@
return jsoType;
}
+ @Override
public boolean hasSideEffects() {
for (int i = 0, c = exprs.size(); i < c; ++i) {
if (exprs.get(i).hasSideEffects()) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/js/JsonObject.java b/dev/core/src/com/google/gwt/dev/jjs/ast/js/JsonObject.java
index a6fe9c6..ee749ca 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/js/JsonObject.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/js/JsonObject.java
@@ -67,6 +67,7 @@
return jsoType;
}
+ @Override
public boolean hasSideEffects() {
for (JsonPropInit propInit : propInits) {
if (propInit.labelExpr.hasSideEffects()
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java b/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
index 34c6efc..4c7bf5e 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
@@ -182,6 +182,7 @@
// user args
mapParameters(newMethod, ctorDecl);
+ addThrownExceptions(ctorDecl.binding, newMethod);
// original params are now frozen
info.addCorrelation(program.getCorrelator().by(newMethod));
@@ -310,6 +311,14 @@
return process(typeDeclaration);
}
+ private void addThrownExceptions(MethodBinding methodBinding,
+ JMethod method) {
+ for (ReferenceBinding thrownBinding : methodBinding.thrownExceptions) {
+ JClassType type = (JClassType) typeMap.get(thrownBinding.erasure());
+ method.addThrownException(type);
+ }
+ }
+
private JField createEnumField(SourceInfo info, FieldBinding binding,
JReferenceType enclosingType) {
JType type = (JType) typeMap.get(binding.type);
@@ -405,6 +414,8 @@
true, false, false);
synthetic.setSynthetic();
+ synthetic.addThrownExceptions(constructor.getThrownExceptions());
+
// new Foo() : Create the instance
JNewInstance newInstance = new JNewInstance(
type.getSourceInfo().makeChild(BuildDeclMapVisitor.class,
@@ -655,6 +666,7 @@
JMethod newMethod = program.createMethod(info, b.selector, enclosingType,
returnType, b.isAbstract(), b.isStatic(), b.isFinal(), b.isPrivate(),
b.isNative());
+ addThrownExceptions(b, newMethod);
if (b.isSynthetic()) {
newMethod.setSynthetic();
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CatchBlockNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CatchBlockNormalizer.java
index 7e544cf..d32ef57 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CatchBlockNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CatchBlockNormalizer.java
@@ -94,10 +94,7 @@
catchInfo, exVar));
JDeclarationStatement declaration = new JDeclarationStatement(
catchInfo, arg, new JLocalRef(catchInfo, exVar));
- if (!block.getStatements().isEmpty()) {
- // Only bother adding the assignment if the block is non-empty
- block.addStmt(0, declaration);
- }
+ block.addStmt(0, declaration);
// nest the previous as an else for me
cur = new JIfStatement(catchInfo, ifTest, block, cur);
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java
index 4723c42..bb4b516 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java
@@ -68,7 +68,6 @@
* </p>
*/
public abstract class CompoundAssignmentNormalizer {
-
/**
* Breaks apart certain complex assignments.
*/
@@ -373,7 +372,8 @@
*/
private final boolean reuseTemps;
- protected CompoundAssignmentNormalizer(JProgram program, boolean reuseTemps) {
+ protected CompoundAssignmentNormalizer(JProgram program,
+ boolean reuseTemps) {
this.program = program;
this.reuseTemps = reuseTemps;
cloner = new CloneExpressionVisitor(program);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
index ab86a8c..35d1616 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
@@ -709,6 +709,7 @@
private Map<JMethod, List<JMethod>> methodsThatOverrideMe;
private final JProgram program;
+
private Set<JReferenceType> referencedTypes = new HashSet<JReferenceType>();
private final RescueVisitor rescuer = new RescueVisitor();
private JMethod stringValueOfChar = 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 b39364c..7e4381c 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
@@ -286,11 +286,15 @@
// If false, replace do with do's body
if (!booleanLiteral.getValue()) {
- // Unless it contains break/continue statements
- FindBreakContinueStatementsVisitor visitor = new FindBreakContinueStatementsVisitor();
- visitor.accept(x.getBody());
- if (!visitor.hasBreakContinueStatements()) {
- ctx.replaceMe(x.getBody());
+ if (Simplifier.isEmpty(x.getBody())) {
+ ctx.removeMe();
+ } else {
+ // Unless it contains break/continue statements
+ FindBreakContinueStatementsVisitor visitor = new FindBreakContinueStatementsVisitor();
+ visitor.accept(x.getBody());
+ if (!visitor.hasBreakContinueStatements()) {
+ ctx.replaceMe(x.getBody());
+ }
}
}
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
index d1820bc..f217c96 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
@@ -2037,7 +2037,16 @@
}
}
- /**
+ private void addThrownExceptions(MethodBinding methodBinding,
+ JMethod method) {
+ for (ReferenceBinding exceptionReference :
+ methodBinding.thrownExceptions) {
+ method.addThrownException((JClassType)
+ typeMap.get(exceptionReference.erasure()));
+ }
+ }
+
+ /**
* Create a bridge method. It calls a same-named method with the same
* arguments, but with a different type signature.
*
@@ -2065,6 +2074,7 @@
paramType, true, false, bridgeMethod);
bridgeMethod.addParam(newParam);
}
+ addThrownExceptions(jdtBridgeMethod, bridgeMethod);
bridgeMethod.freezeParamTypes();
// create a call
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JavaPrecedenceVisitor.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JavaPrecedenceVisitor.java
index a18f36f..d6d2a53 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JavaPrecedenceVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JavaPrecedenceVisitor.java
@@ -44,6 +44,7 @@
import com.google.gwt.dev.jjs.ast.JStringLiteral;
import com.google.gwt.dev.jjs.ast.JThisRef;
import com.google.gwt.dev.jjs.ast.JVisitor;
+import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
/**
* See the Java Programming Language, 4th Edition, p. 750, Table 2. I just
@@ -58,7 +59,7 @@
JavaPrecedenceVisitor visitor = new JavaPrecedenceVisitor();
visitor.accept(expression);
if (visitor.answer < 0) {
- throw new InternalCompilerException("Precedence must be >= 0!");
+ throw new InternalCompilerException("Precedence must be >= 0 (" + expression + ") " + expression.getClass());
}
return visitor.answer;
}
@@ -172,6 +173,12 @@
}
@Override
+ public boolean visit(JMultiExpression x, Context ctx) {
+ answer = 14;
+ return false;
+ }
+
+ @Override
public boolean visit(JNewArray array, Context ctx) {
answer = 2;
return false;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JsoDevirtualizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JsoDevirtualizer.java
index 7199696..2216f98 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JsoDevirtualizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JsoDevirtualizer.java
@@ -144,6 +144,7 @@
oldParam.getType(), true, false, newMethod);
}
newMethod.freezeParamTypes();
+ newMethod.addThrownExceptions(objectMethod.getThrownExceptions());
// Build from bottom up.
JMethodCall condition = new JMethodCall(sourceInfo, null,
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java b/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
index 2b88423..158ab5f 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
@@ -156,6 +156,7 @@
CreateStaticImplsVisitor.class, "Devirtualized function"), newName,
enclosingType, returnType, false, true, true, x.isPrivate());
newMethod.setSynthetic();
+ newMethod.addThrownExceptions(x.getThrownExceptions());
// Setup parameters; map from the old params to the new params
JParameter thisParam = JParameter.create(sourceInfo.makeChild(
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/Simplifier.java b/dev/core/src/com/google/gwt/dev/jjs/impl/Simplifier.java
index de4e38b..c67a28f 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/Simplifier.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/Simplifier.java
@@ -308,6 +308,9 @@
}
private JStatement ensureBlock(JStatement stmt) {
+ if (stmt == null) {
+ return null;
+ }
if (!(stmt instanceof JBlock)) {
JBlock block = new JBlock(stmt.getSourceInfo());
block.addStmt(stmt);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/Analysis.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/Analysis.java
new file mode 100644
index 0000000..3fef9c9
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/Analysis.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2009 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.gflow;
+
+/**
+ * A simple, non-transforming flow analysis.
+ *
+ * @param <N> graph node type.
+ * @param <E> edge type.
+ * @param <G> graph type.
+ * @param <A> assumption type.
+ */
+public interface Analysis<N, E, G extends Graph<N, E, ?>,
+ A extends Assumption<A>> {
+ /**
+ * Gets analysis flow function.
+ */
+ FlowFunction<N, E, G, A> getFlowFunction();
+
+ /**
+ * Gets assumptions for graph to start approximation from.
+ */
+ void setInitialGraphAssumptions(G graph, AssumptionMap<E, A> assumptionMap);
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/AnalysisSolver.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/AnalysisSolver.java
new file mode 100644
index 0000000..08c3eb7
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/AnalysisSolver.java
@@ -0,0 +1,396 @@
+/*
+ * Copyright 2009 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.gflow;
+
+import com.google.gwt.dev.jjs.impl.gflow.TransformationFunction.Transformation;
+import com.google.gwt.dev.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A solver to solve all kinds of analyses defined in the package.
+ * Uses iterative worklist algorithm.
+ *
+ * Solver might be forward or backwards working. Both directions will always
+ * produce a valid fixed point, which depends on direction. As a rule,
+ * forward analysis benefits from forward direction, backwards - from the
+ * opposite.
+ *
+ * @param <N> graph node type.
+ * @param <E> graph edge type.
+ * @param <T> graph transformer type.
+ * @param <G> graph type.
+ * @param <A> assumption type.
+ */
+public class AnalysisSolver<N, E, T, G extends Graph<N, E, T>,
+ A extends Assumption<A>> {
+ /**
+ * Adapter from IntegratedFlowFunction to FlowFunction. If integrated function
+ * decides to perform transformation, replacement graph is recursively
+ * analyzed and result return without actually performing transformation,
+ */
+ private final class IntegratedFlowFunctionAdapter
+ implements FlowFunction<N, E, G, A> {
+ private IntegratedFlowFunction<N, E, T, G, A> flowFunction;
+
+ private IntegratedFlowFunctionAdapter(
+ IntegratedAnalysis<N, E, T, G, A> analysis) {
+ flowFunction = analysis.getIntegratedFlowFunction();
+ }
+
+ public void interpret(final N node, G graph,
+ final AssumptionMap<E, A> assumptionMap) {
+ final boolean[] mapWasModified = new boolean[1];
+ Transformation<T, G> transformation = flowFunction.interpretOrReplace(
+ node, graph, new AssumptionMap<E, A>() {
+ public A getAssumption(E edge) {
+ return assumptionMap.getAssumption(edge);
+ }
+
+ public void setAssumption(E edge, A assumption) {
+ mapWasModified[0] = true;
+ assumptionMap.setAssumption(edge, assumption);
+ }
+ });
+
+ if (transformation == null) {
+ return;
+ }
+
+ Preconditions.checkArgument(!mapWasModified[0]);
+
+ final G newSubgraph = transformation.getNewSubgraph();
+
+ if (debug) {
+ System.err.println("Applying transformation: " + transformation);
+ System.err.println("Replacing");
+ System.err.println(graph);
+ System.err.println("With");
+ System.err.println(newSubgraph);
+ }
+
+ final List<E> inEdges = graph.getInEdges(node);
+ final List<E> outEdges = graph.getOutEdges(node);
+
+ Preconditions.checkArgument(newSubgraph.getGraphInEdges().size() ==
+ inEdges.size());
+
+ Preconditions.checkArgument(newSubgraph.getGraphOutEdges().size() ==
+ outEdges.size());
+
+ iterate(newSubgraph,
+ new IntegratedAnalysis<N, E, T, G, A>() {
+ public IntegratedFlowFunction<N, E, T, G, A>
+ getIntegratedFlowFunction() {
+ return flowFunction;
+ }
+
+ public void setInitialGraphAssumptions(G graph,
+ AssumptionMap<E, A> newAssumptionMap) {
+ for (int i = 0; i < inEdges.size(); ++i) {
+ newAssumptionMap.setAssumption(newSubgraph.getGraphInEdges().get(i),
+ assumptionMap.getAssumption(inEdges.get(i)));
+ }
+
+ for (int i = 0; i < outEdges.size(); ++i) {
+ newAssumptionMap.setAssumption(newSubgraph.getGraphOutEdges().get(i),
+ assumptionMap.getAssumption(outEdges.get(i)));
+ }
+ }
+
+ });
+
+ for (int i = 0; i < inEdges.size(); ++i) {
+ assumptionMap.setAssumption(inEdges.get(i),
+ getEdgeAssumption(newSubgraph, newSubgraph.getGraphInEdges().get(i)));
+ }
+
+ for (int i = 0; i < outEdges.size(); ++i) {
+ assumptionMap.setAssumption(outEdges.get(i),
+ getEdgeAssumption(newSubgraph, newSubgraph.getGraphOutEdges().get(i)));
+ }
+ }
+ }
+
+ public static boolean debug = false;
+
+ /**
+ * Solve a non-integrated analysis.
+ *
+ * @param <N> graph node type.
+ * @param <E> graph edge type.
+ * @param <T> graph transformer type.
+ * @param <G> graph type.
+ * @param <A> assumption type.
+ */
+ public static <N, E, T, G extends Graph<N, E, T>, A extends Assumption<A>>
+ Map<E, A> solve(G g, Analysis<N, E, G, A> analysis, boolean forward) {
+ return new AnalysisSolver<N, E, T, G, A>(forward).solve(g, analysis);
+ }
+
+ /**
+ * Solve a integrated analysis.
+ *
+ * @param <N> graph node type.
+ * @param <E> graph edge type.
+ * @param <T> graph transformer type.
+ * @param <G> graph type.
+ * @param <A> assumption type.
+ */
+ public static <N, E, T, G extends Graph<N, E, T>, A extends Assumption<A>>
+ boolean solveIntegrated(G g, IntegratedAnalysis<N, E, T, G, A> analysis,
+ boolean forward) {
+ return new AnalysisSolver<N, E, T, G, A>(forward).solveIntegrated(g,
+ analysis);
+ }
+
+ /**
+ * If <code>true</code>, then we are moving forward. Moving backwards
+ * otherwise.
+ */
+ private final boolean forward;
+
+ /**
+ * @param forward <code>true</code> if solvers moves forward.
+ */
+ private AnalysisSolver(boolean forward) {
+ this.forward = forward;
+ }
+
+ /**
+ * Apply all transformations based on a found fixed point.
+ */
+ private boolean actualize(G graph,
+ final IntegratedAnalysis<N, E, T, G, A> analysis) {
+ TransformationFunction<N, E, T, G, A> function =
+ new TransformationFunction<N, E, T, G, A>() {
+ public Transformation<T, G> transform(final N node, final G graph,
+ AssumptionMap<E, A> assumptionMap) {
+ final boolean[] didAssumptionChange = new boolean[1];
+ Transformation<T, G> transformation = analysis.getIntegratedFlowFunction().interpretOrReplace(
+ node, graph, new AssumptionMap<E, A>() {
+ public A getAssumption(E edge) {
+ Preconditions.checkArgument(graph.getStart(edge) == node
+ || graph.getEnd(edge) == node);
+ return getEdgeAssumption(graph, edge);
+ }
+
+ public void setAssumption(E edge, A assumption) {
+ Preconditions.checkArgument(graph.getStart(edge) == node
+ || graph.getEnd(edge) == node);
+ didAssumptionChange[0] = true;
+ }
+ });
+ Preconditions.checkArgument(transformation == null ||
+ !didAssumptionChange[0]);
+ return transformation;
+ }
+ };
+ return applyTransformation(graph, function);
+ }
+
+ private boolean applyTransformation(final G graph,
+ TransformationFunction<N, E, T, G, A> transformationFunction) {
+ boolean didChange = false;
+
+ for (final N node : graph.getNodes()) {
+ Transformation<T, G> transformation = transformationFunction.transform(
+ node, graph, new AssumptionMap<E, A>() {
+ public A getAssumption(E edge) {
+ Preconditions.checkArgument(graph.getStart(edge) == node
+ || graph.getEnd(edge) == node);
+ return getEdgeAssumption(graph, edge);
+ }
+
+ public void setAssumption(E edge, A assumption) {
+ throw new IllegalStateException(
+ "Transformations should not change assumptions");
+ }
+ });
+ if (transformation != null) {
+ T actualizer = transformation.getGraphTransformer();
+ Preconditions.checkNotNull(actualizer, "Null actualizer from: %s",
+ transformationFunction);
+ didChange = graph.transform(node, actualizer) || didChange;
+ }
+ }
+
+ return didChange;
+ }
+
+ private LinkedHashSet<N> buildInitialWorklist(G g) {
+ ArrayList<N> nodes = new ArrayList<N>(g.getNodes());
+ LinkedHashSet<N> worklist = new LinkedHashSet<N>(nodes.size());
+ if (!forward) {
+ Collections.reverse(nodes);
+ }
+ worklist.addAll(nodes);
+ return worklist;
+ }
+
+ @SuppressWarnings("unchecked")
+ private A getEdgeAssumption(G graph, E edge) {
+ return (A) graph.getEdgeData(edge);
+ }
+
+ private void initGraphAssumptions(Analysis<N, E, G, A> analysis, final G graph) {
+ analysis.setInitialGraphAssumptions(graph, new AssumptionMap<E, A>() {
+ public A getAssumption(E edge) {
+ return getEdgeAssumption(graph, edge);
+ }
+
+ public void setAssumption(E edge, A assumption) {
+ setEdgeAssumption(graph, edge, assumption);
+ }
+ });
+ }
+
+ /**
+ * Find a fixed point of integrated analysis by wrapping it with
+ * IntegratedFlowFunctionAdapter and calling
+ * {@link #solveImpl(Graph, Analysis)}.
+ */
+ private void iterate(G graph,
+ final IntegratedAnalysis<N, E, T, G, A> integratedAnalysis) {
+ final IntegratedFlowFunctionAdapter adapter =
+ new IntegratedFlowFunctionAdapter(integratedAnalysis);
+
+ Analysis<N, E, G, A> analysis = new Analysis<N, E, G, A>() {
+ public FlowFunction<N, E, G, A> getFlowFunction() {
+ return adapter;
+ }
+
+ public void setInitialGraphAssumptions(G graph,
+ AssumptionMap<E, A> assumptionMap) {
+ integratedAnalysis.setInitialGraphAssumptions(graph, assumptionMap);
+ }
+ };
+
+ solveImpl(graph, analysis);
+ }
+
+ private void resetEdgeData(G graph) {
+ for (N node : graph.getNodes()) {
+ for (E e : graph.getInEdges(node)) {
+ graph.setEdgeData(e, null);
+ }
+ for (E e : graph.getOutEdges(node)) {
+ graph.setEdgeData(e, null);
+ }
+ }
+ for (E e : graph.getGraphOutEdges()) {
+ graph.setEdgeData(e, null);
+ }
+ for (E e : graph.getGraphInEdges()) {
+ graph.setEdgeData(e, null);
+ }
+ }
+
+ private void setEdgeAssumption(G graph, E edge, A assumption) {
+ graph.setEdgeData(edge, assumption);
+ }
+
+ /**
+ * Solve a non-integrated analysis.
+ */
+ private Map<E, A> solve(G g, Analysis<N, E, G, A> analysis) {
+ solveImpl(g, analysis);
+
+ Map<E, A> result = new HashMap<E, A>();
+
+ for (N n : g.getNodes()) {
+ for (E e : g.getInEdges(n)) {
+ result.put(e, getEdgeAssumption(g, e));
+ }
+ for (E e : g.getOutEdges(n)) {
+ result.put(e, getEdgeAssumption(g, e));
+ }
+ }
+ for (E e : g.getGraphInEdges()) {
+ result.put(e, getEdgeAssumption(g, e));
+ }
+ for (E e : g.getGraphOutEdges()) {
+ result.put(e, getEdgeAssumption(g, e));
+ }
+
+ return result;
+ }
+
+ /**
+ * Solve a non-integrated analysis.
+ */
+ private void solveImpl(final G graph, Analysis<N, E, G, A> analysis) {
+ FlowFunction<N, E, G, A> flowFunction = analysis.getFlowFunction();
+
+ final LinkedHashSet<N> worklist = buildInitialWorklist(graph);
+ resetEdgeData(graph);
+ initGraphAssumptions(analysis, graph);
+
+ while (!worklist.isEmpty()) {
+ Iterator<N> iterator = worklist.iterator();
+ final N node = iterator.next();
+ iterator.remove();
+
+ flowFunction.interpret(node, graph, new AssumptionMap<E, A>() {
+ public A getAssumption(E edge) {
+ Preconditions.checkArgument(graph.getStart(edge) == node
+ || graph.getEnd(edge) == node);
+ return getEdgeAssumption(graph, edge);
+ }
+
+ public void setAssumption(E edge, A assumption) {
+ N start = graph.getStart(edge);
+ N end = graph.getEnd(edge);
+ Preconditions.checkArgument(start == node || end == node);
+
+ if (!AssumptionUtil.equals(getEdgeAssumption(graph, edge), assumption)) {
+ setEdgeAssumption(graph, edge, assumption);
+
+ if (start == node) {
+ if (end != null) {
+ worklist.add(end);
+ }
+ } else if (end == node) {
+ if (start != null) {
+ worklist.add(start);
+ }
+ } else {
+ throw new IllegalStateException();
+ }
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Solve an integrated analysis.
+ *
+ * Finds a fixed point by using an IntegratedFlowFunctionAdapter and
+ * recursing into {@link #solve(Graph, Analysis)}. Applies analysis
+ * transformations based on the found fixed point.
+ */
+ private boolean solveIntegrated(G g, IntegratedAnalysis<N, E, T, G, A> analysis) {
+ iterate(g, analysis);
+ return actualize(g, analysis);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/Assumption.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/Assumption.java
new file mode 100644
index 0000000..4915ed9
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/Assumption.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2009 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.gflow;
+
+/**
+ * Assumptions are members of the lattice used in the analysis. Assumptions
+ * are associated with graph edges and depict analysis knowledge about the flow.
+ *
+ * So far we need only join operation to perform analysis.
+ *
+ * Lattice's bottom should be represented by <code>null</null>.
+ *
+ * Assumption should implement correct equals() and hashCode() methods.
+ *
+ * @param <Self> self type.
+ */
+public interface Assumption<Self extends Assumption<?>> {
+ /**
+ * Compute least upper bound. Should accept <code>null</null>.
+ * It's allowed to return value, equal either to <code>this</code> or to
+ * <code>other</code>. If you're going to modify result of join operation,
+ * you have to copy it.
+ */
+ Self join(Self other);
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/AssumptionMap.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/AssumptionMap.java
new file mode 100644
index 0000000..5ce0a34
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/AssumptionMap.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2009 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.gflow;
+
+/**
+ *
+ * @param <E>
+ * @param <A>
+ */
+public interface AssumptionMap<E, A extends Assumption<A>> {
+ A getAssumption(E edge);
+ void setAssumption(E edge, A assumption);
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/AssumptionUtil.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/AssumptionUtil.java
new file mode 100644
index 0000000..d4b1577
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/AssumptionUtil.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2009 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.gflow;
+
+import com.google.gwt.dev.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * Utilities for working with assumption values.
+ */
+public class AssumptionUtil {
+ /**
+ * Check assumptions for equality.
+ */
+ public static <A extends Assumption<A>> boolean equals(A a1, A a2) {
+ if (a1 == null || a2 == null) {
+ return a1 == a2;
+ }
+
+ return (a1 == a2) || (a1.equals(a2));
+ }
+
+ /**
+ * Join assumptions.
+ */
+ public static <A extends Assumption<A>> A join(A a1, A a2) {
+ if (a1 == null) {
+ return a2;
+ }
+
+ if (a2 == null) {
+ return a1;
+ }
+
+ return a1 != a2 ? a1.join(a2) : a1;
+ }
+
+ /**
+ * Join assumptions from the list.
+ */
+ public static <A extends Assumption<A>> A join(List<A> assumptions) {
+ A result = null;
+ for (int i = 0; i < assumptions.size(); ++i) {
+ result = join(result, assumptions.get(i));
+ }
+ return result;
+ }
+
+ public static <E, A extends Assumption<A>> A join(List<E> edges,
+ AssumptionMap<E, A> assumptionMap) {
+ A result = null;
+ for (int i = 0; i < edges.size(); ++i) {
+ result = join(result, assumptionMap.getAssumption(edges.get(i)));
+ }
+ return result;
+ }
+
+ public static <E, A extends Assumption<A>> void setAssumptions(List<E> edges,
+ List<A> assumptions, AssumptionMap<E, A> assumptionMap) {
+ Preconditions.checkArgument(assumptions.size() == edges.size());
+ for (int i = 0; i < edges.size(); ++i) {
+ assumptionMap.setAssumption(edges.get(i), assumptions.get(i));
+ }
+ }
+
+ public static <E, A extends Assumption<A>> void setAssumptions(List<E> edges,
+ A assumption, AssumptionMap<E, A> assumptionMap) {
+ for (int i = 0; i < edges.size(); ++i) {
+ assumptionMap.setAssumption(edges.get(i), assumption);
+ }
+ }
+
+ public static <E, A extends Assumption<A>> String toString(
+ List<E> inEdges, List<E> outEdges,
+ AssumptionMap<E, A> assumptionMap) {
+ StringBuilder result = new StringBuilder();
+ for (E e : inEdges) {
+ if (result.length() != 0) {
+ result.append("; ");
+ }
+ result.append(e);
+ result.append("=");
+ result.append(assumptionMap.getAssumption(e));
+ }
+ for (E e : outEdges) {
+ if (result.length() != 0) {
+ result.append("; ");
+ }
+ result.append(e);
+ result.append("=");
+ result.append(assumptionMap.getAssumption(e));
+ }
+ return result.toString();
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/CombinedIntegratedAnalysis.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/CombinedIntegratedAnalysis.java
new file mode 100644
index 0000000..3512e3e
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/CombinedIntegratedAnalysis.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2009 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.gflow;
+
+import com.google.gwt.dev.jjs.impl.gflow.TransformationFunction.Transformation;
+import com.google.gwt.dev.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Integrated analysis, which combines several other integrated analyses into
+ * one. It does so be defining combined assumption, which is vector of original
+ * assumptions, and applies each analysis to its own component.
+ *
+ * If any analysis decides to rewrite the node, combined analysis returns
+ * produced transformation. If more than one analyses decide to transform, the
+ * first one wins.
+ *
+ * @param <N> graph node type.
+ * @param <E> graph edge type.
+ * @param <T> graph transformer type.
+ * @param <G> graph type.
+ *
+ */
+public class CombinedIntegratedAnalysis<N, E, T, G extends Graph<N, E, T>>
+ implements
+ IntegratedAnalysis<N, E, T, G, CombinedIntegratedAnalysis.CombinedAssumption> {
+
+ /**
+ * Combined assumption which holds vector of original assumptions.
+ */
+ public static class CombinedAssumption implements
+ Assumption<CombinedAssumption> {
+
+ private static class CopyOnWrite {
+ private final int size;
+ private CombinedAssumption assumption;
+ private boolean copied = false;
+
+ private CopyOnWrite(CombinedAssumption assumption, int size) {
+ this.assumption = assumption;
+ this.size = size;
+ }
+
+ public boolean isCopied() {
+ return copied;
+ }
+
+ public void set(int slice, Assumption<?> assumption) {
+ copyIfNeeded();
+ this.assumption.set(slice, assumption);
+ }
+
+ public CombinedAssumption unwrap() {
+ return assumption;
+ }
+
+ private void copyIfNeeded() {
+ if (!copied) {
+ copied = true;
+ if (assumption == null) {
+ assumption = new CombinedAssumption(size);
+ } else {
+ assumption = new CombinedAssumption(assumption);
+ }
+ }
+ }
+ }
+
+ /**
+ * Individual assumptions vector.
+ */
+ private final List<Assumption<?>> assumptions;
+
+ public CombinedAssumption(CombinedAssumption assumption) {
+ this.assumptions = new ArrayList<Assumption<?>>(assumption.assumptions);
+ }
+
+ public CombinedAssumption(int size) {
+ this.assumptions = new ArrayList<Assumption<?>>(size);
+ for (int i = 0; i < size; ++i) {
+ this.assumptions.add(null);
+ }
+ }
+
+ public CombinedAssumption(List<Assumption<?>> assumptions) {
+ this.assumptions = assumptions;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+
+ CombinedAssumption other = (CombinedAssumption) obj;
+
+ // We do not implement equals in our zipped lists. Do it here.
+ if (other.assumptions.size() != assumptions.size()) {
+ return false;
+ }
+
+ for (int i = 0; i < assumptions.size(); ++i) {
+ Assumption<?> a1 = assumptions.get(i);
+ Assumption<?> a2 = other.assumptions.get(i);
+ if (a1 == null) {
+ if (a1 != a2) {
+ return false;
+ }
+ } else {
+ if (a2 == null) {
+ return false;
+ }
+ if (!a1.equals(a2)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((assumptions == null) ? 0 : assumptions.hashCode());
+ return result;
+ }
+
+ /**
+ * Joins combined assumption by joining all individual components.
+ */
+ @SuppressWarnings("unchecked")
+ public CombinedAssumption join(CombinedAssumption value) {
+ if (value == null) {
+ return this;
+ }
+ Preconditions.checkArgument(value.assumptions.size() ==
+ assumptions.size());
+
+ List<Assumption<?>> newAssumptions = new ArrayList<Assumption<?>>();
+ for (int i = 0; i < assumptions.size(); ++i) {
+ Assumption a1 = assumptions.get(i);
+ Assumption a2 = value.assumptions.get(i);
+ newAssumptions.add(AssumptionUtil.join(a1, a2));
+ }
+
+ return new CombinedAssumption(newAssumptions);
+ }
+
+ @Override
+ public String toString() {
+ return assumptions.toString();
+ }
+
+ /**
+ * Gets nth assumption component.
+ */
+ private Assumption<?> get(int n) {
+ return assumptions.get(n);
+ }
+
+ private void set(int slice, Assumption<?> assumption) {
+ assumptions.set(slice, assumption);
+ }
+ }
+
+ /**
+ * Combined integrated flow function.
+ */
+ private final class CombinedIntegratedFlowFunction implements
+ IntegratedFlowFunction<N, E, T, G, CombinedAssumption> {
+ @SuppressWarnings("unchecked")
+ public Transformation interpretOrReplace(final N node, final G graph,
+ final AssumptionMap<E, CombinedAssumption> assumptionMap) {
+
+ final Map<E, CombinedAssumption.CopyOnWrite> newAssumptions = new IdentityHashMap<E, CombinedAssumption.CopyOnWrite>();
+
+ final int size = functions.size();
+ for (int i = 0; i < size; ++i) {
+ final int slice = i;
+ IntegratedFlowFunction function = functions.get(i);
+
+ Transformation transformation =
+ function.interpretOrReplace(node, graph,
+ new AssumptionMap() {
+ public Assumption getAssumption(Object edge) {
+ CombinedAssumption combinedAssumption = assumptionMap.getAssumption((E) edge);
+ if (combinedAssumption == null) {
+ return null;
+ }
+ return combinedAssumption.get(slice);
+ }
+
+ public void setAssumption(Object edge, Assumption assumption) {
+ CombinedAssumption.CopyOnWrite newAssumption = newAssumptions.get(edge);
+ if (newAssumption == null) {
+ newAssumption = new CombinedAssumption.CopyOnWrite(assumptionMap.getAssumption((E) edge), size);
+ newAssumptions.put((E) edge, newAssumption);
+ }
+ newAssumption.set(slice, assumption);
+ }
+
+ @Override
+ public String toString() {
+ return AssumptionUtil.toString(graph.getInEdges(node),
+ graph.getOutEdges(node), this);
+ }
+ });
+
+ if (transformation != null) {
+ return transformation;
+ }
+ }
+
+ for (E e : newAssumptions.keySet()) {
+ CombinedAssumption.CopyOnWrite newAssumption = newAssumptions.get(e);
+ if (newAssumption.isCopied()) {
+ assumptionMap.setAssumption(e, newAssumption.unwrap());
+ }
+ }
+
+ return null;
+ }
+ }
+
+ /**
+ * Factory method.
+ */
+ public static <N, E, T, G extends Graph<N, E, T>>
+ CombinedIntegratedAnalysis<N, E, T, G> createAnalysis() {
+ return new CombinedIntegratedAnalysis<N, E, T, G>();
+ }
+ /**
+ * Individual analyses.
+ */
+ List<IntegratedAnalysis<N, E, T, G, ?>> analyses =
+ new ArrayList<IntegratedAnalysis<N, E, T, G, ?>>();
+
+ /**
+ * Their flow functions.
+ */
+ List<IntegratedFlowFunction<N, E, T, G, ?>> functions =
+ new ArrayList<IntegratedFlowFunction<N, E, T, G, ?>>();
+
+ /**
+ * Adds analysis to the combined one.
+ */
+ public void addAnalysis(IntegratedAnalysis<N, E, T, G, ?> analysis) {
+ analyses.add(analysis);
+ functions.add(analysis.getIntegratedFlowFunction());
+ }
+
+ public IntegratedFlowFunction<N, E, T, G, CombinedAssumption>
+ getIntegratedFlowFunction() {
+ return new CombinedIntegratedFlowFunction();
+ }
+
+ @SuppressWarnings("unchecked")
+ public void setInitialGraphAssumptions(G graph,
+ final AssumptionMap<E, CombinedAssumption> assumptionMap) {
+ for (int i = 0; i < functions.size(); ++i) {
+ final int slice = i;
+ IntegratedAnalysis<N, E, T, G, ?> analysis = analyses.get(slice);
+ analysis.setInitialGraphAssumptions(graph, new AssumptionMap() {
+ public Assumption getAssumption(Object edge) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void setAssumption(Object edge,Assumption assumption) {
+ CombinedAssumption combinedAssumption = assumptionMap.getAssumption((E) edge);
+ if (combinedAssumption == null) {
+ combinedAssumption = new CombinedAssumption(functions.size());
+ combinedAssumption.set(slice, assumption);
+ assumptionMap.setAssumption((E) edge, combinedAssumption);
+ } else {
+ combinedAssumption.set(slice, assumption);
+ }
+ }
+ });
+ }
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/DataflowOptimizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/DataflowOptimizer.java
new file mode 100644
index 0000000..9a70c9b
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/DataflowOptimizer.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2009 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.gflow;
+
+import com.google.gwt.dev.jjs.ast.Context;
+import com.google.gwt.dev.jjs.ast.JDeclaredType;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JMethodBody;
+import com.google.gwt.dev.jjs.ast.JModVisitor;
+import com.google.gwt.dev.jjs.ast.JNode;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.impl.DeadCodeElimination;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgBuilder;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgTransformer;
+import com.google.gwt.dev.jjs.impl.gflow.constants.ConstantsAnalysis;
+import com.google.gwt.dev.jjs.impl.gflow.copy.CopyAnalysis;
+import com.google.gwt.dev.jjs.impl.gflow.liveness.LivenessAnalysis;
+import com.google.gwt.dev.jjs.impl.gflow.unreachable.UnreachableAnalysis;
+import com.google.gwt.dev.util.PerfLogger;
+import com.google.gwt.dev.util.Preconditions;
+
+/**
+ */
+public class DataflowOptimizer {
+ public static boolean exec(JProgram jprogram, JNode node) {
+ PerfLogger.start("DataflowOptimizer");
+ boolean didChange = new DataflowOptimizer(jprogram).execImpl(node);
+ PerfLogger.end();
+ return didChange;
+ }
+
+ public static boolean exec(JProgram jprogram) {
+ return exec(jprogram, jprogram);
+ }
+
+ private final JProgram program;
+
+ public DataflowOptimizer(JProgram program) {
+ this.program = program;
+ }
+
+ private class DataflowOptimizerVisitor extends JModVisitor {
+ @Override
+ public boolean visit(JMethodBody methodBody, Context ctx) {
+ Cfg cfg = CfgBuilder.build(program, methodBody.getBlock());
+
+ JMethod method = methodBody.getMethod();
+ JDeclaredType enclosingType = method.getEnclosingType();
+ String methodName = enclosingType.getName() + "." + method.getName();
+
+ // AnalysisSolver.debug = methodName.equals("<some method>");
+
+ Preconditions.checkNotNull(cfg, "Can't build flow for %s", methodName);
+
+ try {
+ CombinedIntegratedAnalysis<CfgNode<?>, CfgEdge, CfgTransformer, Cfg>
+ fwdAnalysis = CombinedIntegratedAnalysis.createAnalysis();
+
+ fwdAnalysis.addAnalysis(new UnreachableAnalysis());
+ fwdAnalysis.addAnalysis(new ConstantsAnalysis(program));
+ fwdAnalysis.addAnalysis(new CopyAnalysis());
+ // fwdAnalysis.addAnalysis(new InlineVarAnalysis(program));
+
+ boolean madeChanges = false;
+
+ madeChanges = AnalysisSolver.solveIntegrated(cfg, fwdAnalysis, true)
+ || madeChanges;
+
+ cfg = CfgBuilder.build(program, methodBody.getBlock());
+ Preconditions.checkNotNull(cfg);
+
+ CombinedIntegratedAnalysis<CfgNode<?>, CfgEdge, CfgTransformer, Cfg>
+ bkwAnalysis = CombinedIntegratedAnalysis.createAnalysis();
+
+ bkwAnalysis.addAnalysis(new LivenessAnalysis());
+
+ madeChanges = AnalysisSolver.solveIntegrated(cfg, bkwAnalysis, false)
+ || madeChanges;
+
+ if (madeChanges) {
+ didChange = true;
+
+ DeadCodeElimination.exec(program, methodBody);
+ }
+ } catch (Throwable t) {
+ throw new RuntimeException("Error optimizing: " + methodName, t);
+ }
+
+ return true;
+ }
+ }
+
+ private boolean execImpl(JNode node) {
+ DataflowOptimizerVisitor visitor = new DataflowOptimizerVisitor();
+ visitor.accept(node);
+ return visitor.didChange();
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/FlowFunction.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/FlowFunction.java
new file mode 100644
index 0000000..9ec28f3
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/FlowFunction.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2009 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.gflow;
+
+/**
+ * A flow function receives node assumptions and transforms them according to
+ * node semantics. Typical flow functions update either outgoing assumptions
+ * (forward flow) or incoming assumptions (backward flow) but not both.
+ *
+ * @param <N> graph node type.
+ * @param <E> edge type.
+ * @param <G> graph type.
+ * @param <A> analysis assumption type.
+ */
+public interface FlowFunction<N, E, G extends Graph<N, E, ?>,
+ A extends Assumption<A>> {
+ /**
+ * Interpret node by computing new node assumptions from current ones.
+ */
+ void interpret(N node, G g,
+ AssumptionMap<E, A> assumptionMAp);
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/Graph.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/Graph.java
new file mode 100644
index 0000000..c6d64cb
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/Graph.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2009 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.gflow;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Directed graph abstraction for flow analysis. We specifically define all
+ * navigation methods in graph interface and do not ask nodes and edges to
+ * conform to any interface. This way graphs can be memory-efficient.
+ *
+ * The graph can have one or more incoming edges (i.e. edges, which end on
+ * some nodes in the graph, but originate somewhere outside the graph), and one
+ * or more outgoing edges.
+ *
+ * @param <NodeType> graph node type.
+ * @param <EdgeType> graph edge type.
+ * @param <TransformerType> transformer type. Transformer instances can be used
+ * to change the graph and its underlying representation to apply
+ * optimizations.
+ */
+public interface Graph<NodeType, EdgeType, TransformerType> {
+
+ Object getEdgeData(EdgeType edge);
+
+ /**
+ * Returns edge end node.
+ */
+ NodeType getEnd(EdgeType edge);
+
+ /**
+ * Returns graph incoming edges.
+ */
+ ArrayList<EdgeType> getGraphInEdges();
+
+ /**
+ * Returns graph outgoing edges.
+ */
+ ArrayList<EdgeType> getGraphOutEdges();
+
+ /**
+ * Returns edges coming into node.
+ */
+ List<EdgeType> getInEdges(NodeType n);
+
+ /**
+ * Returns all nodes in the graph.
+ */
+ ArrayList<NodeType> getNodes();
+
+ /**
+ * Returns edges originating from the node.
+ */
+ List<EdgeType> getOutEdges(NodeType node);
+
+ /**
+ * Returns edge start node.
+ */
+ NodeType getStart(EdgeType edge);
+
+ /**
+ * Returns string representation of the graph.
+ */
+ String print();
+
+ /**
+ * Returns string representation of the graph with all assumptions along its
+ * edges.
+ */
+ <A extends Assumption<A>> String printWithAssumptions(
+ Map<EdgeType, A> assumptions);
+
+ void setEdgeData(EdgeType edge, Object data);
+
+ /**
+ * Transforms the node with transformer. This will be called by solver to
+ * apply optimizations.
+ *
+ * @return <code>true</code> if there were changes made by transformer. While
+ * transformation should be always sound, it might be impossible to apply
+ * it in current context due to complexities of underlying structures. E.g.
+ * it is impossible to delete if statement test expression, while it is not
+ * evaluated if statement is not reachable. In this case transformer can
+ * return <code>false</code> and do no changes.
+ */
+ boolean transform(NodeType node, TransformerType transformer);
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/IntegratedAnalysis.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/IntegratedAnalysis.java
new file mode 100644
index 0000000..49d44c2
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/IntegratedAnalysis.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2009 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.gflow;
+
+/**
+ * Integrated analysis combines analysis with transformation as described in
+ * Lerner et al. paper. See package documentation for references.
+ *
+ * @param <N> graph node type.
+ * @param <E> graph edge type.
+ * @param <T> graph transformer type.
+ * @param <G> graph type.
+ * @param <A> assumption type.
+ */
+public interface IntegratedAnalysis<N, E, T, G extends Graph<N, E, T>,
+ A extends Assumption<A>> {
+ /**
+ * Gets assumptions for graph incoming & outgoing edges to start approximation
+ * from.
+ */
+ void setInitialGraphAssumptions(G graph, AssumptionMap<E, A> assumptionMap);
+
+ /**
+ * Gets analysis integrated flow function.
+ */
+ IntegratedFlowFunction<N, E, T, G, A> getIntegratedFlowFunction();
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/IntegratedFlowFunction.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/IntegratedFlowFunction.java
new file mode 100644
index 0000000..ec60788
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/IntegratedFlowFunction.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2009 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.gflow;
+
+/**
+ * Integrated flow function should either interpret the node, or produce
+ * node transformation based on already computed assumptions.
+ *
+ * @param <N> graph node type.
+ * @param <E> edge type.
+ * @param <T> graph transformation type.
+ * @param <G> graph type.
+ * @param <A> assumption type.
+ *
+ */
+public interface IntegratedFlowFunction<N, E, T, G extends Graph<N, E, T>,
+ A extends Assumption<A>> {
+ /**
+ * Either interpret a node by computing new assumptions, or produce
+ * node transformation.
+ */
+ TransformationFunction.Transformation<T, G>
+ interpretOrReplace(N node, G graph, AssumptionMap<E, A> assumptionMap);
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/SubgraphAssumptions.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/SubgraphAssumptions.java
new file mode 100644
index 0000000..b1a8363
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/SubgraphAssumptions.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2009 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.gflow;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Set of all assumptions for all edges coming from/to a subgraph.
+ *
+ * @param <A> assumption type.
+ */
+public class SubgraphAssumptions<A extends Assumption<?>> {
+ public static <A extends Assumption<?>> SubgraphAssumptions<A>
+ replaceInValues(SubgraphAssumptions<A> assumptions, A inValue) {
+ ArrayList<A> inValues = new ArrayList<A>();
+ for (int i = 0; i < assumptions.getInValues().size(); ++i) {
+ inValues.add(inValue);
+ }
+
+ return replaceInValues(assumptions, inValues);
+ }
+
+ public static <A extends Assumption<?>> SubgraphAssumptions<A>
+ replaceInValues(SubgraphAssumptions<A> assumptions, ArrayList<A> inValues) {
+ return new SubgraphAssumptions<A>(inValues, assumptions.getOutValues());
+ }
+
+ public static <A extends Assumption<?>> SubgraphAssumptions<A>
+ replaceOutValues(SubgraphAssumptions<A> assumptions, A outValue) {
+ ArrayList<A> outValues = new ArrayList<A>();
+ for (int i = 0; i < assumptions.getOutValues().size(); ++i) {
+ outValues.add(outValue);
+ }
+
+ return replaceOutValues(assumptions, outValues);
+ }
+
+ public static <A extends Assumption<?>> SubgraphAssumptions<A>
+ replaceOutValues(SubgraphAssumptions<A> assumptions, ArrayList<A> outValues) {
+ return new SubgraphAssumptions<A>(assumptions.getInValues(), outValues);
+ }
+
+ private List<A> inValues;
+ private List<A> outValues;
+
+ public SubgraphAssumptions(List<A> inValues, List<A> outValues) {
+ if (inValues == null) {
+ this.inValues = new ArrayList<A>(0);
+ } else {
+ this.inValues = inValues;
+ }
+
+ if (outValues == null) {
+ this.outValues = new ArrayList<A>(0);
+ } else {
+ this.outValues = outValues;
+ }
+ }
+
+ /**
+ * Gets assumptions along incoming edges.
+ */
+ public List<A> getInValues() {
+ return inValues;
+ }
+
+ /**
+ * Gets assumptions along outgoing edges.
+ */
+ public List<A> getOutValues() {
+ return outValues;
+ }
+
+ @Override
+ public String toString() {
+ return "[" + inValues + " => " + outValues + "]";
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/TransformationFunction.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/TransformationFunction.java
new file mode 100644
index 0000000..48af29e
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/TransformationFunction.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2009 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.gflow;
+
+/**
+ * Transformation function defines an optional transformation of a graph node
+ * based on node assumptions.
+ *
+ * @param <N> graph node type.
+ * @param <E> graph edge type.
+ * @param <T> graph transformer type.
+ * @param <G> graph type.
+ * @param <A> assumption type.
+ *
+ */
+public interface TransformationFunction<N, E, T, G extends Graph<N, E, T>,
+ A extends Assumption<A>> {
+ /**
+ * Gets node transformation for a given node.
+ * @return node transformation or <code>null</code> if no transformation is
+ * necessary.
+ */
+ Transformation<T, G> transform(N node, G graph,
+ AssumptionMap<E, A> assumptionMap);
+
+ /**
+ * Transformation defines new subgraph replacement for a node, and
+ * transformation which will be applied during the last (actualizing) step
+ * of analysis.
+ *
+ * @param <T> graph transformer type
+ * @param <G> graph type
+ */
+ interface Transformation<T, G extends Graph<?, ?, T>> {
+ G getNewSubgraph();
+ T getGraphTransformer();
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/AssumptionsPrinter.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/AssumptionsPrinter.java
new file mode 100644
index 0000000..bc6e54b
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/AssumptionsPrinter.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.impl.gflow.Assumption;
+import com.google.gwt.dev.jjs.impl.gflow.AssumptionMap;
+
+import java.util.Map;
+
+/**
+ * Special CfgPrinter implementation which prints assumptions along every edge.
+ *
+ * @param <A> assumptions type.
+ */
+public class AssumptionsPrinter<A extends Assumption<A>> extends CfgPrinter {
+ private final Map<CfgEdge, A> assumptions;
+ private final AssumptionMap<CfgEdge, A> assumptionMap;
+
+ public AssumptionsPrinter(Cfg graph, Map<CfgEdge, A> assumptions) {
+ super(graph);
+ this.assumptions = assumptions;
+ this.assumptionMap = null;
+ }
+
+ public AssumptionsPrinter(Cfg graph,
+ AssumptionMap<CfgEdge, A> assumptionMap) {
+ super(graph);
+ this.assumptions = null;
+ this.assumptionMap = assumptionMap;
+ }
+
+ @Override
+ protected void appendEdgeInfo(StringBuffer result, CfgEdge edge) {
+ A a;
+ if (assumptions != null) {
+ a = assumptions.get(edge);
+ } else {
+ a = assumptionMap.getAssumption(edge);
+ }
+ if (a != null) {
+ result.append(" ");
+ result.append(a.toString());
+ }
+ }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/Cfg.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/Cfg.java
new file mode 100644
index 0000000..9d779c8
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/Cfg.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.impl.gflow.Assumption;
+import com.google.gwt.dev.jjs.impl.gflow.Graph;
+import com.google.gwt.dev.util.Preconditions;
+import com.google.gwt.dev.util.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Control flow graph representation for gflow framework.
+ */
+public class Cfg implements Graph<CfgNode<?>, CfgEdge, CfgTransformer> {
+ /**
+ * Graph incoming edges.
+ */
+ private final ArrayList<CfgEdge> graphInEdges = new ArrayList<CfgEdge>();
+ /**
+ * Graph outgoing edges.
+ */
+ private final ArrayList<CfgEdge> graphOutEdges = new ArrayList<CfgEdge>();
+ /**
+ * List of all nodes.
+ */
+ private final ArrayList<CfgNode<?>> nodes = new ArrayList<CfgNode<?>>();
+
+ /**
+ * Add graph incoming edge.
+ */
+ public void addGraphInEdge(CfgEdge edge) {
+ graphInEdges.add(edge);
+ }
+
+ /**
+ * Add graph outgoing edge.
+ */
+ public void addGraphOutEdge(CfgEdge edge) {
+ graphOutEdges.add(edge);
+ }
+
+ /**
+ * Add incoming edge to the node.
+ */
+ public void addIn(CfgNode<?> node, CfgEdge edge) {
+ Preconditions.checkNotNull(edge, "Null edge: %s", edge);
+ Preconditions.checkArgument(edge.end == null,
+ "Edge is already bound: %s", edge);
+ node.in = Lists.add(node.in, edge);
+ edge.end = node;
+ }
+
+ /**
+ * Add new node to the graph.
+ */
+ public <N extends CfgNode<?>> N addNode(N node) {
+ nodes.add(node);
+ return node;
+ }
+
+ /**
+ * Add outgoing edge from the node.
+ */
+ public void addOut(CfgNode<?> node, CfgEdge edge) {
+ if (edge.start != null) {
+ throw new IllegalArgumentException();
+ }
+ node.out = Lists.add(node.out, edge);
+ edge.start = node;
+ }
+
+ public Object getEdgeData(CfgEdge edge) {
+ return edge.data;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public CfgNode<?> getEnd(CfgEdge e) {
+ return e.getEnd();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ArrayList<CfgEdge> getGraphInEdges() {
+ return graphInEdges;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ArrayList<CfgEdge> getGraphOutEdges() {
+ return graphOutEdges;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public List<CfgEdge> getInEdges(CfgNode<?> cfgNode) {
+ return cfgNode.in;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ArrayList<CfgNode<?>> getNodes() {
+ return nodes;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public List<CfgEdge> getOutEdges(CfgNode<?> cfgNode) {
+ return cfgNode.out;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public CfgNode<?> getStart(CfgEdge e) {
+ return e.getStart();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String print() {
+ return new CfgPrinter(this).print();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public <A extends Assumption<A>> String printWithAssumptions(
+ Map<CfgEdge, A> map) {
+ return new AssumptionsPrinter<A>(this, map).print();
+ }
+
+ public void setEdgeData(CfgEdge edge, Object data) {
+ edge.data = data;
+ }
+
+ @Override
+ public String toString() {
+ return print();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean transform(CfgNode<?> node, CfgTransformer actualizer) {
+ if (actualizer == null) {
+ throw new IllegalArgumentException();
+ }
+ return actualizer.transform(node, this);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBinaryConditionalOperationNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBinaryConditionalOperationNode.java
new file mode 100644
index 0000000..abe3deb
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBinaryConditionalOperationNode.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JBinaryOperation;
+import com.google.gwt.dev.jjs.ast.JExpression;
+
+/**
+ * Conditional node generated for short-circuiting binary operations (||, &&).
+ */
+public class CfgBinaryConditionalOperationNode extends
+ CfgConditionalNode<JBinaryOperation> {
+ public CfgBinaryConditionalOperationNode(CfgNode<?> parent, JBinaryOperation node) {
+ super(parent, node);
+ }
+
+ @Override
+ public void accept(CfgVisitor visitor) {
+ visitor.visitBinaryConditionalOperationNode(this);
+ }
+
+ @Override
+ public JExpression getCondition() {
+ return getJNode().getLhs();
+ }
+
+ @Override
+ protected CfgNode<?> cloneImpl() {
+ return new CfgBinaryConditionalOperationNode(getParent(), getJNode());
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBlockNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBlockNode.java
new file mode 100644
index 0000000..2acf2c8
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBlockNode.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JBlock;
+
+/**
+ * Node corresponding to code blocks.
+ */
+public class CfgBlockNode extends CfgStatementNode<JBlock> {
+ public CfgBlockNode(CfgNode<?> parent, JBlock node) {
+ super(parent, node);
+ }
+
+ @Override
+ public void accept(CfgVisitor visitor) {
+ visitor.visitBlockNode(this);
+ }
+
+ @Override
+ public String toDebugString() {
+ return "BLOCK";
+ }
+
+ @Override
+ protected CfgNode<?> cloneImpl() {
+ return new CfgBlockNode(getParent(), getJNode());
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBreakNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBreakNode.java
new file mode 100644
index 0000000..2d8cbc2
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBreakNode.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JBreakStatement;
+
+/**
+ * Node corresponding to break statements.
+ */
+public class CfgBreakNode extends CfgGotoNode<JBreakStatement> {
+ public CfgBreakNode(CfgNode<?> parent, JBreakStatement node) {
+ super(parent, node);
+ }
+
+ @Override
+ public void accept(CfgVisitor visitor) {
+ visitor.visitBreakNode(this);
+ }
+
+ @Override
+ protected CfgNode<?> cloneImpl() {
+ return new CfgBreakNode(getParent(), getJNode());
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilder.java
new file mode 100644
index 0000000..bc55958
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilder.java
@@ -0,0 +1,1115 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.Context;
+import com.google.gwt.dev.jjs.ast.JArrayRef;
+import com.google.gwt.dev.jjs.ast.JBinaryOperation;
+import com.google.gwt.dev.jjs.ast.JBinaryOperator;
+import com.google.gwt.dev.jjs.ast.JBlock;
+import com.google.gwt.dev.jjs.ast.JBreakStatement;
+import com.google.gwt.dev.jjs.ast.JCaseStatement;
+import com.google.gwt.dev.jjs.ast.JClassType;
+import com.google.gwt.dev.jjs.ast.JConditional;
+import com.google.gwt.dev.jjs.ast.JContinueStatement;
+import com.google.gwt.dev.jjs.ast.JDeclarationStatement;
+import com.google.gwt.dev.jjs.ast.JDeclaredType;
+import com.google.gwt.dev.jjs.ast.JDoStatement;
+import com.google.gwt.dev.jjs.ast.JExpression;
+import com.google.gwt.dev.jjs.ast.JExpressionStatement;
+import com.google.gwt.dev.jjs.ast.JFieldRef;
+import com.google.gwt.dev.jjs.ast.JForStatement;
+import com.google.gwt.dev.jjs.ast.JIfStatement;
+import com.google.gwt.dev.jjs.ast.JLabeledStatement;
+import com.google.gwt.dev.jjs.ast.JMethodCall;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.ast.JReboundEntryPoint;
+import com.google.gwt.dev.jjs.ast.JReturnStatement;
+import com.google.gwt.dev.jjs.ast.JStatement;
+import com.google.gwt.dev.jjs.ast.JSwitchStatement;
+import com.google.gwt.dev.jjs.ast.JThrowStatement;
+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.JUnaryOperation;
+import com.google.gwt.dev.jjs.ast.JVariableRef;
+import com.google.gwt.dev.jjs.ast.JVisitor;
+import com.google.gwt.dev.jjs.ast.JWhileStatement;
+import com.google.gwt.dev.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Builder for CFG graph.
+ *
+ * Resulting CFG graph contains as much information as needed by current
+ * analysis set. The amount of detail in call graph can be shrink or extend over
+ * time. Current CfgNode inheritance tree gives an accurate understanding of
+ * current detail level.
+ *
+ * To build an accurate representation of control flow graph in the presence of
+ * exceptions, we maintain all possible exit reason from statement. (This is
+ * called completion in JLS. We use exit name in code for brevity).
+ *
+ * CfgBuilder also tries to assign cfg node parents to correspond to AST tree
+ * structure. This is not always correct (yet). But it is always guaranteed that
+ * you will always meet a cfg node corresponding to containing statements when
+ * traversing cfg node parents.
+ *
+ * Every statement always has the corresponding node in cfg graph.
+ *
+ * TODO: such nodes as if, etc, should be parent of their expressions.
+ */
+public class CfgBuilder {
+ /**
+ * Visitor class which does all cfg building.
+ */
+ private static class BuilderVisitor extends JVisitor {
+
+ /**
+ * Representation of possible exits from statement. Emulates algebraic data
+ * type to save memory.
+ */
+ private static class Exit {
+ private enum Reason {
+ /**
+ * Exit with a break statement.
+ */
+ BREAK,
+ /**
+ * Exit with case else.
+ */
+ CASE_ELSE,
+ /**
+ * Exit with case then.
+ */
+ CASE_THEN,
+ /**
+ * Exit with continue statement.
+ */
+ CONTINUE,
+ /**
+ * Normal exit from statement.
+ */
+ NORMAL,
+ /**
+ * Exit with return statement.
+ */
+ RETURN,
+ /**
+ * Exit with exception throwing.
+ */
+ THROW
+ }
+
+ private static Exit createBreak(CfgNode<?> node, String label) {
+ return new Exit(Reason.BREAK, node, null, label, null);
+ }
+
+ private static Exit createCaseElse(CfgNode<?> node) {
+ return new Exit(Reason.CASE_ELSE, node, null, null,
+ CfgConditionalNode.ELSE);
+ }
+
+ private static Exit createCaseThen(CfgNode<?> node) {
+ return new Exit(Reason.CASE_THEN, node, null, null,
+ CfgConditionalNode.THEN);
+ }
+
+ private static Exit createContinue(CfgNode<?> node, String label) {
+ return new Exit(Reason.CONTINUE, node, null, label, null);
+ }
+
+ private static Exit createNormal(CfgNode<?> node, String role) {
+ return new Exit(Reason.NORMAL, node, null, null, role);
+ }
+
+ private static Exit createReturn(CfgNode<?> node) {
+ return new Exit(Reason.RETURN, node, null, null, null);
+ }
+
+ private static Exit createThrow(CfgNode<?> node,
+ JType exceptionType, String role) {
+ return new Exit(Reason.THROW, node, exceptionType, null, role);
+ }
+
+ /**
+ * Exception type for <code>THROW</code> exit.
+ */
+ private final JType exceptionType;
+ /**
+ * Break/continue target label. Null if label wasn't set.
+ */
+ private final String label;
+ /**
+ * Cfg node which generated this exit.
+ */
+ private final CfgNode<?> node;
+ /**
+ * Exit reason.
+ */
+ private final Reason reason;
+ /**
+ * Role for all cfg edges generated from this exit.
+ */
+ private final String role;
+
+ private Exit(Reason reason, CfgNode<?> source,
+ JType exceptionType, String label, String role) {
+ if (source == null) {
+ throw new IllegalArgumentException();
+ }
+ this.reason = reason;
+ this.node = source;
+ this.exceptionType = exceptionType;
+ this.label = label;
+ this.role = role;
+ }
+
+ public JType getExceptionType() {
+ if (!isThrow()) {
+ throw new IllegalArgumentException();
+ }
+
+ return exceptionType;
+ }
+
+ public String getLabel() {
+ if (!isContinue() && !isBreak()) {
+ throw new IllegalArgumentException();
+ }
+ return label;
+ }
+
+ public CfgNode<?> getNode() {
+ return node;
+ }
+
+ public boolean isBreak() {
+ return reason == Reason.BREAK;
+ }
+
+ public boolean isContinue() {
+ return reason == Reason.CONTINUE;
+ }
+
+ public boolean isNormal() {
+ return reason == Reason.NORMAL;
+ }
+
+ public boolean isThrow() {
+ return reason == Reason.THROW;
+ }
+
+ @Override
+ public String toString() {
+ return reason.toString();
+ }
+ }
+
+ /**
+ * All exits at this point of interpretation.
+ */
+ private List<Exit> currentExits = new ArrayList<Exit>();
+
+ /**
+ * Artificial cfg end node.
+ */
+ private final CfgEndNode endNode = new CfgEndNode();
+
+ /**
+ * Resulting graph.
+ */
+ private final Cfg graph = new Cfg();
+
+ /**
+ * Map from statement to its label.
+ */
+ private final Map<JStatement, String> labels =
+ new HashMap<JStatement, String>();
+
+ /**
+ * All nodes in the graph.
+ */
+ private final List<CfgNode<?>> nodes = new ArrayList<CfgNode<?>>();
+
+ /**
+ * Parent for newly created cfg nodes.
+ */
+ private CfgNode<?> parent = null;
+
+ private final JProgram program;
+
+ private JSwitchStatement switchStatement;
+
+ private final JTypeOracle typeOracle;
+
+ public BuilderVisitor(JProgram program) {
+ this.program = program;
+ this.typeOracle = program.typeOracle;
+ }
+
+ /**
+ * Build cfg for codeblock. Resulting graph will have one incoming edge
+ * and no outgoing edges.
+ */
+ public Cfg build(JBlock codeBlock) {
+ CfgEdge methodIn = new CfgEdge();
+ graph.addGraphInEdge(methodIn);
+ accept(codeBlock);
+ graph.addIn(nodes.get(0), methodIn);
+ addNode(endNode);
+
+ // Wire all remaining exits to end node.
+ for (Exit exit : currentExits) {
+ switch (exit.reason) {
+ case CONTINUE:
+ case BREAK:
+ case CASE_ELSE:
+ case CASE_THEN:
+ // This shouldn't happen.
+ throw new IllegalArgumentException("Unhandled exit: " + exit.reason);
+ case NORMAL:
+ case RETURN:
+ case THROW: {
+ addEdge(exit, endNode);
+ break;
+ }
+ }
+ }
+
+ return graph;
+ }
+
+ /**
+ * Build cfg for codeblock. Resulting graph will have one incoming edge
+ * and no outgoing edges.
+ */
+ public Cfg build(JExpression expression) {
+ accept(expression);
+ addNode(endNode);
+
+ Preconditions.checkArgument(currentExits.isEmpty(),
+ "Unhandled exits %s",
+ currentExits);
+
+ return graph;
+ }
+
+ @Override
+ public boolean visit(JBinaryOperation x, Context ctx) {
+ if (x.isAssignment()) {
+ // Generate writes.
+ accept(x.getRhs());
+ acceptExpressionSubreads(x.getLhs());
+ if (x.getOp() == JBinaryOperator.ASG) {
+ addNode(new CfgWriteNode(parent, x, x.getLhs(), x.getRhs()));
+ } else {
+ addNode(new CfgReadWriteNode(parent, x, x.getLhs(), null));
+ }
+ return false;
+ } else if (x.getOp() == JBinaryOperator.AND
+ || x.getOp() == JBinaryOperator.OR) {
+ // generate conditionals.
+ accept(x.getLhs());
+
+ CfgBinaryConditionalOperationNode node =
+ pushNode(new CfgBinaryConditionalOperationNode(parent, x));
+
+ if (x.getOp() == JBinaryOperator.AND) {
+ addNormalExit(node, CfgConditionalNode.THEN);
+ accept(x.getRhs());
+ List<Exit> thenExits = removeNormalExits();
+ addNormalExit(node, CfgConditionalNode.ELSE);
+ addExits(thenExits);
+ } else {
+ addNormalExit(node, CfgConditionalNode.ELSE);
+ accept(x.getRhs());
+ List<Exit> elseExits = removeNormalExits();
+ addNormalExit(node, CfgConditionalNode.THEN);
+ addExits(elseExits);
+ }
+
+ popNode();
+
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean visit(JBlock x, Context ctx) {
+ pushNode(new CfgBlockNode(parent, x));
+ accept(x.getStatements());
+ popNode();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JBreakStatement x, Context ctx) {
+ pushNode(new CfgStatementNode<JStatement>(parent, x));
+ String label = null;
+ if (x.getLabel() != null) {
+ label = x.getLabel().getName();
+ }
+ CfgBreakNode node = addNode(new CfgBreakNode(parent, x));
+ addExit(Exit.createBreak(node, label));
+ popNode();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JCaseStatement x, Context ctx) {
+ pushNode(new CfgStatementNode<JStatement>(parent, x));
+ if (x.getExpr() != null) {
+ // case label
+ JExpression condition = new JBinaryOperation(x.getSourceInfo(),
+ program.getTypePrimitiveBoolean(),
+ JBinaryOperator.EQ, switchStatement.getExpr(), x.getExpr());
+ CfgCaseNode node = addNode(new CfgCaseNode(parent, x, condition));
+ addExit(Exit.createCaseThen(node));
+ addExit(Exit.createCaseElse(node));
+ } else {
+ // default label
+ }
+ popNode();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JConditional x, Context ctx) {
+ accept(x.getIfTest());
+
+ CfgConditionalExpressionNode node =
+ pushNode(new CfgConditionalExpressionNode(parent, x));
+
+ addNormalExit(node, CfgConditionalNode.THEN);
+ accept(x.getThenExpr());
+ List<Exit> thenExits = removeNormalExits();
+
+ addNormalExit(node, CfgConditionalNode.ELSE);
+ accept(x.getElseExpr());
+
+ addExits(thenExits);
+ popNode();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JContinueStatement x, Context ctx) {
+ pushNode(new CfgStatementNode<JStatement>(parent, x));
+ String label = null;
+ if (x.getLabel() != null) {
+ label = x.getLabel().getName();
+ }
+ CfgContinueNode node = addNode(new CfgContinueNode(parent, x));
+ addExit(Exit.createContinue(node, label));
+ popNode();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JDeclarationStatement x, Context ctx) {
+ pushNode(new CfgStatementNode<JStatement>(parent, x));
+ if (x.getInitializer() != null) {
+ accept(x.getInitializer());
+ addNode(new CfgWriteNode(parent, x, x.getVariableRef(),
+ x.getInitializer()));
+ }
+ popNode();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JDoStatement x, Context ctx) {
+ pushNode(new CfgStatementNode<JStatement>(parent, x));
+ int pos = nodes.size();
+
+ if (x.getBody() != null) {
+ accept(x.getBody());
+ }
+
+ if (x.getTestExpr() != null) {
+ accept(x.getTestExpr());
+ }
+
+ CfgDoNode node = addNode(new CfgDoNode(parent, x));
+
+ addEdge(node, nodes.get(pos), new CfgEdge(CfgConditionalNode.THEN));
+
+ String label = labels.get(x);
+ addContinueEdges(nodes.get(pos), label);
+ addBreakExits(label);
+ addNormalExit(node, CfgConditionalNode.ELSE);
+
+ popNode();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JExpressionStatement x, Context ctx) {
+ pushNode(new CfgStatementNode<JStatement>(parent, x));
+ accept(x.getExpr());
+ popNode();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JForStatement x, Context ctx) {
+ pushNode(new CfgStatementNode<JStatement>(parent, x));
+ accept(x.getInitializers());
+
+ CfgForNode cond = null;
+ int testPos = nodes.size();
+
+ if (x.getTestExpr() != null) {
+ accept(x.getTestExpr());
+ cond = addNode(new CfgForNode(parent, x));
+ addNormalExit(cond, CfgConditionalNode.THEN);
+ }
+
+ if (x.getBody() != null) {
+ accept(x.getBody());
+ }
+ int incrementsPos = nodes.size();
+ accept(x.getIncrements());
+
+ List<Exit> thenExits = removeNormalExits();
+ for (Exit e : thenExits) {
+ addEdge(e, nodes.get(testPos));
+ }
+
+ String label = labels.get(x);
+ // If there's no increments, continue goes straight to test.
+ int continuePos = incrementsPos != nodes.size() ? incrementsPos : testPos;
+ addContinueEdges(nodes.get(continuePos), label);
+ addBreakExits(label);
+ if (cond != null) {
+ addNormalExit(cond, CfgConditionalNode.ELSE);
+ }
+
+ popNode();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JIfStatement x, Context ctx) {
+ pushNode(new CfgStatementNode<JStatement>(parent, x));
+ accept(x.getIfExpr());
+
+ CfgIfNode node = addNode(new CfgIfNode(parent, x));
+
+ addNormalExit(node, CfgConditionalNode.THEN);
+ if (x.getThenStmt() != null) {
+ accept(x.getThenStmt());
+ }
+ List<Exit> thenExits = removeNormalExits();
+
+ addNormalExit(node, CfgConditionalNode.ELSE);
+ if (x.getElseStmt() != null) {
+ accept(x.getElseStmt());
+ }
+
+ addExits(thenExits);
+
+ popNode();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JLabeledStatement x, Context ctx) {
+ labels.put(x.getBody(), x.getLabel().getName());
+ return true;
+ }
+
+ /**
+ * Each method call generates optional throw.
+ */
+ @Override
+ public boolean visit(JMethodCall x, Context ctx) {
+ // TODO: join optthrow + call
+ if (x.getInstance() != null) {
+ // TODO: add optional NPE exception
+ accept(x.getInstance());
+ }
+ accept(x.getArgs());
+
+ CfgOptionalThrowNode node = addNode(new CfgOptionalThrowNode(parent, x));
+
+ addNormalExit(node, CfgOptionalThrowNode.NO_THROW);
+ for (JClassType exceptionType : x.getTarget().getThrownExceptions()) {
+ addExit(Exit.createThrow(node, exceptionType, null));
+ }
+ JDeclaredType runtimeExceptionType =
+ program.getFromTypeMap("java.lang.RuntimeException");
+ if (runtimeExceptionType != null) {
+ addExit(Exit.createThrow(node, runtimeExceptionType,
+ CfgOptionalThrowNode.RUNTIME_EXCEPTION));
+ }
+ JDeclaredType errorExceptionType =
+ program.getFromTypeMap("java.lang.Error");
+ if (errorExceptionType != null) {
+ addExit(Exit.createThrow(node, errorExceptionType,
+ CfgOptionalThrowNode.ERROR));
+ }
+
+ addNode(new CfgMethodCallNode(parent, x));
+
+ return false;
+ }
+
+ @Override
+ public boolean visit(JReboundEntryPoint x, Context ctx) {
+ pushNode(new CfgStatementNode<JReboundEntryPoint>(parent, x));
+ accept(x.getEntryCalls());
+ popNode();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JReturnStatement x, Context ctx) {
+ pushNode(new CfgStatementNode<JStatement>(parent, x));
+ if (x.getExpr() != null) {
+ accept(x.getExpr());
+ }
+
+ CfgReturnNode node = addNode(new CfgReturnNode(parent, x));
+ addExit(Exit.createReturn(node));
+ popNode();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JStatement x, Context ctx) {
+ // The statement isn't supported.
+ throw new UnsupportedNodeException(x.getClass().toString());
+ }
+
+ @Override
+ public boolean visit(JSwitchStatement x, Context ctx) {
+ pushNode(new CfgStatementNode<JStatement>(parent, x));
+ // TODO: Add statement node
+ accept(x.getExpr());
+
+ JSwitchStatement oldSwitchStatement = switchStatement;
+ switchStatement = x;
+
+ int defaultPos = -1;
+
+ List<Exit> breakExits = new ArrayList<Exit>();
+
+ for (JStatement s : x.getBody().getStatements()) {
+ if (s instanceof JCaseStatement) {
+ if (((JCaseStatement) s).getExpr() != null) {
+ // case label
+ List<Exit> elseExits = removeExits(Exit.Reason.CASE_ELSE);
+ for (Exit e : elseExits) {
+ addNormalExit(e.getNode(), e.role);
+ }
+ } else {
+ // default label
+ defaultPos = nodes.size();
+ }
+ } else {
+ List<Exit> thenExits = removeExits(Exit.Reason.CASE_THEN);
+ for (Exit e : thenExits) {
+ addNormalExit(e.getNode(), e.role);
+ }
+ }
+ accept(s);
+ breakExits.addAll(removeExits(Exit.Reason.BREAK));
+ }
+
+ List<Exit> thenExits = removeExits(Exit.Reason.CASE_THEN);
+ for (Exit e : thenExits) {
+ addNormalExit(e.getNode(), e.role);
+ }
+
+ List<Exit> elseExits = removeExits(Exit.Reason.CASE_ELSE);
+
+ if (defaultPos >= 0) {
+ for (Exit e : elseExits) {
+ addEdge(e, nodes.get(defaultPos));
+ }
+ } else {
+ for (Exit e : elseExits) {
+ addExit(Exit.createNormal(e.getNode(), e.role));
+ }
+ }
+
+ for (Exit e : breakExits) {
+ addNormalExit(e.getNode(), e.role);
+ }
+
+ switchStatement = oldSwitchStatement;
+ popNode();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JThrowStatement x, Context ctx) {
+ pushNode(new CfgStatementNode<JStatement>(parent, x));
+ accept(x.getExpr());
+ CfgThrowNode node = addNode(new CfgThrowNode(parent, x));
+ addExit(Exit.createThrow(node, x.getExpr().getType(), null));
+ popNode();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JTryStatement x, Context ctx) {
+ pushNode(new CfgTryNode(parent, x));
+ accept(x.getTryBlock());
+
+ // Process all blocks and determine their exits
+
+ List<Exit> tryBlockExits = currentExits;
+ currentExits = new ArrayList<Exit>();
+
+ List<Integer> catchBlockPos = new ArrayList<Integer>();
+ List<List<Exit>> catchExits = new ArrayList<List<Exit>>();
+
+ for (JBlock b : x.getCatchBlocks()) {
+ catchBlockPos.add(nodes.size());
+ accept(b);
+ catchExits.add(currentExits);
+ currentExits = new ArrayList<Exit>();
+ }
+
+ int finallyPos = nodes.size();
+ if (x.getFinallyBlock() != null) {
+ accept(x.getFinallyBlock());
+ }
+ List<Exit> finallyExits = currentExits;
+ currentExits = new ArrayList<Exit>();
+
+ // Actual work goes here. We are citing JLS to make it easier to follow.
+ // We prefer to have duplicated code for finally block handling just
+ // to make it easier to follow.
+
+ // A try statement without a finally block is executed by first executing
+ // the try block. Then there is a choice:
+ if (x.getFinallyBlock() == null) {
+ // If execution of the try block completes normally, then no further
+ // action is taken and the try statement completes normally.
+ addExits(removeNormalExits(tryBlockExits));
+
+ nextExit : for (Exit e : tryBlockExits) {
+ if (e.isThrow()) {
+ // If execution of the try block completes abruptly because of a
+ // throw of a value V, then there is a choice:
+ nextCatchBlock : for (int i = 0; i < x.getCatchArgs().size(); ++i) {
+ // If the run-time type of V is assignable (�5.2) to the
+ // Parameter of any catch clause of the try statement, then
+ // the first (leftmost) such catch clause is selected.
+ JClassType catchType =
+ (JClassType) x.getCatchArgs().get(i).getType();
+ JType exceptionType = e.getExceptionType();
+
+ boolean canCatch = false;
+ boolean fullCatch = false;
+
+ // It's not that simple in static analysis though.
+ if (typeOracle.canTriviallyCast(exceptionType, catchType)) {
+ // Catch clause fully covers exception type. We'll land
+ // here for sure.
+ canCatch = true;
+ fullCatch = true;
+ } else if (typeOracle.canTriviallyCast(catchType, exceptionType)) {
+ // We can land here if we throw some subclass of
+ // exceptionType
+ canCatch = true;
+ fullCatch = false;
+ }
+
+ if (canCatch) {
+ addEdge(e, nodes.get(catchBlockPos.get(i)));
+ if (fullCatch) {
+ continue nextExit;
+ }
+ continue nextCatchBlock;
+ }
+ }
+
+ // If the run-time type of V is not assignable to the parameter of
+ // any catch clause of the try statement, then the try statement
+ // completes abruptly because of a throw of the value V.
+ addExit(e);
+ } else {
+ // If execution of the try block completes abruptly for any other
+ // reason, then the try statement completes abruptly for the same
+ // reason.
+ addExit(e);
+ }
+ }
+
+ // Continuing catch case here:
+ // If that block completes normally, then the try statement
+ // completes normally; if that block completes abruptly for any reason,
+ // then the try statement completes abruptly for the same reason.
+ for (List<Exit> exits : catchExits) {
+ addExits(exits);
+ }
+ } else {
+ // A try statement with a finally block is executed by first
+ // executing the try block. Then there is a choice:
+
+ // If execution of the try block completes normally, then the finally
+ // block is executed,
+ CfgNode<?> finallyNode = nodes.get(finallyPos);
+ for (Exit e : removeNormalExits(tryBlockExits)) {
+ addEdge(e, finallyNode);
+ }
+
+ // and then there is a choice: If the finally block completes normally,
+ // then the try statement completes normally.
+ // If the finally block completes abruptly for reason S, then the
+ // try statement completes abruptly for reason S.
+ addExits(finallyExits);
+
+ nextExit : for (Exit e : tryBlockExits) {
+ if (e.isThrow()) {
+ // If execution of the try block completes abruptly because of a
+ // throw of a value V, then there is a choice:
+
+ nextCatchBlock : for (int i = 0; i < x.getCatchArgs().size(); ++i) {
+ // If the run-time type of V is assignable to the parameter of any
+ // catch clause of the try statement, then the first
+ // (leftmost) such catch clause is selected.
+ JClassType catchType =
+ (JClassType) x.getCatchArgs().get(i).getType();
+ JType exceptionType = e.getExceptionType();
+
+ boolean canCatch = false;
+ boolean fullCatch = false;
+
+ // It's not that simple in static analysis though.
+ if (typeOracle.canTriviallyCast(exceptionType, catchType)) {
+ // Catch clause fully covers exception type. We'll land
+ // here for sure.
+ canCatch = true;
+ fullCatch = true;
+ } else if (typeOracle.canTriviallyCast(catchType, exceptionType)) {
+ // We can land here if we throw some subclass of
+ // exceptionType
+ canCatch = true;
+ fullCatch = false;
+ }
+
+ if (canCatch) {
+ addEdge(e, nodes.get(catchBlockPos.get(i)));
+ if (fullCatch) {
+ continue nextExit;
+ }
+ continue nextCatchBlock;
+ }
+ }
+
+ // If the run-time type of V is not assignable to the parameter of
+ // any catch clause of the try statement, then the finally block is
+ // executed.
+ addEdge(e, finallyNode);
+ // Then there is a choice:
+ for (Exit finallyExit : finallyExits) {
+ if (finallyExit.isNormal()) {
+ // If the finally block completes normally, then the try
+ // statement completes abruptly because of a throw of the
+ // value V.
+ addExit(new Exit(e.reason, finallyExit.node, e.exceptionType,
+ e.label, e.role));
+ } else {
+ // If the finally block completes abruptly for reason S, then
+ // the try statement completes abruptly for reason S
+ // (and reason R is discarded).
+ // This is already covered earlier:
+ // addExits(finallyExits)
+ }
+ }
+
+ } else {
+ // If execution of the try block completes abruptly for any other
+ // reason R, then the finally block is executed.
+ addEdge(e, finallyNode);
+ // Then there is a choice:
+ for (Exit finallyExit : finallyExits) {
+ if (finallyExit.isNormal()) {
+ // If the finally block completes normally, then the try
+ // statement completes abruptly for reason R.
+ addExit(new Exit(e.reason, finallyExit.node, e.exceptionType,
+ e.label, e.role));
+ } else {
+ // If the finally block completes abruptly for reason S, then
+ // the try statement completes abruptly for reason S
+ // (and reason R is discarded).
+ // This is already covered earlier:
+ // addExits(finallyExits)
+ }
+ }
+ }
+ }
+
+ // Continuing catch case here:
+ // If that block completes normally, then the try statement
+ // completes normally; if that block completes abruptly for any reason,
+ // then the try statement completes abruptly for the same reason.
+ for (List<Exit> exits : catchExits) {
+ for (Exit e : exits) {
+ // If the catch block completes normally, then the finally block is
+ // executed.
+ if (e.isNormal()) {
+ addEdge(e, finallyNode);
+ } else {
+ // If the catch block completes abruptly for reason R, then the
+ // finally block is executed. Then there is a choice:
+ for (Exit finallyExit : finallyExits) {
+ if (finallyExit.isNormal()) {
+ // If the finally block completes normally, then the try
+ // statement completes abruptly for reason R.
+ addExit(new Exit(e.reason, finallyExit.node, e.exceptionType,
+ e.label, e.role));
+ } else {
+ // If the finally block completes abruptly for reason S, then
+ // the try statement completes abruptly for reason S
+ // (and reason R is discarded).
+ // This is already covered earlier:
+ // addExits(finallyExits)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ popNode();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JUnaryOperation x, Context ctx) {
+ if (x.getOp().isModifying()) {
+ acceptExpressionSubreads(x.getArg());
+ addNode(new CfgReadWriteNode(parent, x, x.getArg(), null));
+ return false;
+ }
+ // read will be added by normal flow
+ return true;
+ }
+
+ @Override
+ public boolean visit(JVariableRef x, Context ctx) {
+ // TODO: add NPE exceptions for field references.
+ addNode(new CfgReadNode(parent, x));
+ return true;
+ }
+
+ @Override
+ public boolean visit(JWhileStatement x, Context ctx) {
+ pushNode(new CfgStatementNode<JStatement>(parent, x));
+ int pos = nodes.size();
+ accept(x.getTestExpr());
+
+ CfgWhileNode node = addNode(new CfgWhileNode(parent, x));
+
+ addNormalExit(node, CfgConditionalNode.THEN);
+ if (x.getBody() != null) {
+ accept(x.getBody());
+ }
+
+ List<Exit> thenExits = removeNormalExits();
+ for (Exit e : thenExits) {
+ addEdge(e, nodes.get(pos));
+ }
+
+ String label = labels.get(x);
+ addContinueEdges(nodes.get(pos), label);
+ addBreakExits(label);
+ addNormalExit(node, CfgConditionalNode.ELSE);
+
+ popNode();
+ return false;
+ }
+
+ /**
+ * Detect all reads which are performed before evaluating expression.
+ */
+ private void acceptExpressionSubreads(JExpression expression) {
+ if (expression instanceof JFieldRef) {
+ JExpression instance = ((JFieldRef) expression).getInstance();
+ if (instance != null) {
+ accept(instance);
+ }
+ } else if (expression instanceof JArrayRef) {
+ JArrayRef arrayRef = (JArrayRef) expression;
+ accept(arrayRef.getInstance());
+ accept(arrayRef.getIndexExpr());
+ } else if (!(expression instanceof JVariableRef)) {
+ throw new IllegalArgumentException("Unexpeted lhs: " + expression);
+ }
+ }
+
+ /**
+ * Transform all brake exits into normal exits, thus making sure that
+ * next node will get edges from them.
+ */
+ private void addBreakExits(String label) {
+ List<Exit> exits = removeLoopExits(Exit.Reason.BREAK, label);
+ for (Exit e : exits) {
+ addNormalExit(e.getNode());
+ }
+ }
+
+ /**
+ * Transform all continue exits into normal exits, thus making sure that
+ * next node will get edges from them.
+ */
+ private void addContinueEdges(CfgNode<?> node, String label) {
+ List<Exit> continueExits = removeLoopExits(Exit.Reason.CONTINUE, label);
+ for (Exit e : continueExits) {
+ addEdge(e, node);
+ }
+ }
+
+ private CfgEdge addEdge(CfgNode<?> from, CfgNode<?> to, CfgEdge edge) {
+ graph.addOut(from, edge);
+ graph.addIn(to, edge);
+ return edge;
+ }
+
+ private CfgEdge addEdge(Exit e, CfgNode<?> to) {
+ CfgEdge edge = new CfgEdge(e.role);
+ return addEdge(e.node, to, edge);
+ }
+
+ private void addExit(Exit exit) {
+ currentExits.add(exit);
+ }
+
+ private void addExits(List<Exit> exits) {
+ currentExits.addAll(exits);
+ }
+
+ /**
+ * Add new node to cfg. Wire all current normal exits to it.
+ */
+ private <N extends CfgNode<?>> N addNode(N node) {
+ nodes.add(node);
+ graph.addNode(node);
+
+ // Route all normal exits to this node.
+ for (Iterator<Exit> i = currentExits.iterator(); i.hasNext();) {
+ Exit exit = i.next();
+ if (exit.isNormal()) {
+ i.remove();
+ addEdge(exit, node);
+ }
+ }
+
+ // Simplify all the code, add normal exit for CfgSimplNode automatically.
+ if (node instanceof CfgSimpleNode<?>) {
+ addNormalExit(node);
+ }
+
+ return node;
+ }
+
+ private void addNormalExit(CfgNode<?> node) {
+ addNormalExit(node, null);
+ }
+
+ private void addNormalExit(CfgNode<?> node, String role) {
+ addExit(Exit.createNormal(node, role));
+ }
+
+ private void popNode() {
+ parent = parent.getParent();
+ }
+
+ private <N extends CfgNode<?>> N pushNode(N node) {
+ addNode(node);
+ parent = node;
+ return node;
+ }
+
+ private List<Exit> removeExits(Exit.Reason reason) {
+ return removeExits(currentExits, reason);
+ }
+
+ private List<Exit> removeExits(List<Exit> exits, Exit.Reason reason) {
+ List<Exit> result = new ArrayList<Exit>();
+ for (Iterator<Exit> i = exits.iterator(); i.hasNext();) {
+ Exit exit = i.next();
+ if (exit.reason == reason) {
+ i.remove();
+ result.add(exit);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Remove all loop exits to specified label.
+ */
+ private List<Exit> removeLoopExits(Exit.Reason reason, String label) {
+ List<Exit> result = new ArrayList<Exit>();
+ for (Iterator<Exit> i = currentExits.iterator(); i.hasNext();) {
+ Exit exit = i.next();
+ if (exit.reason == reason
+ && (exit.getLabel() == null || exit.getLabel().equals(label))) {
+ i.remove();
+ result.add(exit);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Remove all normal exits from current exit list.
+ */
+ private List<Exit> removeNormalExits() {
+ return removeNormalExits(currentExits);
+ }
+
+ private List<Exit> removeNormalExits(List<Exit> exits) {
+ return removeExits(exits, Exit.Reason.NORMAL);
+ }
+ }
+
+ /**
+ * Special exception which is thrown when we encounter some syntactic
+ * construction which is not yet supported by CfgBuilder.
+ */
+ private static class UnsupportedNodeException extends RuntimeException {
+ public UnsupportedNodeException(String message) {
+ super(message);
+ }
+ }
+
+ /**
+ * Build Cfg for code block.
+ */
+ public static Cfg build(JProgram program, JBlock codeblock) {
+ return new BuilderVisitor(program).build(codeblock);
+ }
+
+ public static Cfg buildExpressionCfg(JProgram program, JExpression value) {
+ return new BuilderVisitor(program).build(value);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgCaseNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgCaseNode.java
new file mode 100644
index 0000000..2b4461e
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgCaseNode.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JCaseStatement;
+import com.google.gwt.dev.jjs.ast.JExpression;
+
+/**
+ * Node corresponding to if statement.
+ */
+public class CfgCaseNode extends CfgConditionalNode<JCaseStatement> {
+ private final JExpression condition;
+
+ public CfgCaseNode(CfgNode<?> parent, JCaseStatement node,
+ JExpression condition) {
+ super(parent, node);
+ this.condition = condition;
+ }
+
+ @Override
+ public void accept(CfgVisitor visitor) {
+ visitor.visitCaseNode(this);
+ }
+
+ @Override
+ public JExpression getCondition() {
+ return condition;
+ }
+
+ @Override
+ protected CfgNode<?> cloneImpl() {
+ return new CfgCaseNode(getParent(), getJNode(), condition);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgConditionalExpressionNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgConditionalExpressionNode.java
new file mode 100644
index 0000000..94772d0
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgConditionalExpressionNode.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JConditional;
+import com.google.gwt.dev.jjs.ast.JExpression;
+
+/**
+ * Node corresponding to conditional expressions.
+ */
+public class CfgConditionalExpressionNode extends
+ CfgConditionalNode<JConditional> {
+ public CfgConditionalExpressionNode(CfgNode<?> parent, JConditional node) {
+ super(parent, node);
+ }
+
+ @Override
+ public void accept(CfgVisitor visitor) {
+ visitor.visitConditionalExpressionNode(this);
+ }
+
+ @Override
+ public JExpression getCondition() {
+ return getJNode().getIfTest();
+ }
+
+ @Override
+ protected CfgNode<?> cloneImpl() {
+ return new CfgConditionalExpressionNode(getParent(), getJNode());
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgConditionalNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgConditionalNode.java
new file mode 100644
index 0000000..042f6d5
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgConditionalNode.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JExpression;
+import com.google.gwt.dev.jjs.ast.JNode;
+
+/**
+ * Base class for all conditional execution nodes.
+ *
+ * @param <JNodeType> corresponding AST type
+ */
+public abstract class CfgConditionalNode<JNodeType extends JNode> extends
+ CfgNode<JNodeType> {
+ /**
+ * Else edge role.
+ */
+ public static final String ELSE = "ELSE";
+ /**
+ * Then edge role.
+ */
+ public static final String THEN = "THEN";
+
+ public CfgConditionalNode(CfgNode<?> parent, JNodeType node) {
+ super(parent, node);
+ }
+
+ @Override
+ public void accept(CfgVisitor visitor) {
+ visitor.visitConditionalNode(this);
+ }
+
+ /**
+ * Condition which is used to determine the branch.
+ */
+ public abstract JExpression getCondition();
+
+ @Override
+ public String toDebugString() {
+ JExpression condition = getCondition();
+ return "COND (" + (condition != null ? condition.toSource() : "") + ")";
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgContinueNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgContinueNode.java
new file mode 100644
index 0000000..46a8bdb
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgContinueNode.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JContinueStatement;
+
+/**
+ * Node corresponding to continue statement.
+ */
+public class CfgContinueNode extends CfgGotoNode<JContinueStatement> {
+ public CfgContinueNode(CfgNode<?> parent, JContinueStatement node) {
+ super(parent, node);
+ }
+
+ @Override
+ public void accept(CfgVisitor visitor) {
+ visitor.visitContinueNode(this);
+ }
+
+ @Override
+ protected CfgNode<?> cloneImpl() {
+ return new CfgContinueNode(getParent(), getJNode());
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgDoNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgDoNode.java
new file mode 100644
index 0000000..0288eae
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgDoNode.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JDoStatement;
+import com.google.gwt.dev.jjs.ast.JExpression;
+
+/**
+ * Node corresponding to while statement.
+ */
+public class CfgDoNode extends CfgConditionalNode<JDoStatement> {
+ public CfgDoNode(CfgNode<?> parent, JDoStatement node) {
+ super(parent, node);
+ }
+
+ @Override
+ public void accept(CfgVisitor visitor) {
+ visitor.visitDoNode(this);
+ }
+
+ @Override
+ public JExpression getCondition() {
+ return getJNode().getTestExpr();
+ }
+
+ @Override
+ protected CfgNode<?> cloneImpl() {
+ return new CfgDoNode(getParent(), getJNode());
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgEdge.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgEdge.java
new file mode 100644
index 0000000..b61aec3
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgEdge.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+/**
+ * Edge in CFG graph. Edge can be annotated by its role when there are several
+ * edges coming from the node, to be able to reason about them separately (e.g.
+ * it's important which edge is then/else branch in conditional node).
+ */
+public class CfgEdge {
+ Object data;
+ // We do not add setStart/setEnd methods because we'd like to be sure
+ // that no one except CfgNode changes these.
+ CfgNode<?> end;
+
+ CfgNode<?> start;
+ private final String role;
+
+ public CfgEdge() {
+ this.role = null;
+ }
+
+ public CfgEdge(String role) {
+ this.role = role;
+ }
+
+ /**
+ * Get edge end node.
+ */
+ public CfgNode<?> getEnd() {
+ return end;
+ }
+
+ /**
+ * Get edge role.
+ */
+ public String getRole() {
+ return role;
+ }
+
+ /**
+ * Get edge start node.
+ */
+ public CfgNode<?> getStart() {
+ return start;
+ }
+
+ @Override
+ public String toString() {
+ return (start != null ? start.toDebugString() : "*") + "->" +
+ (end != null ? end.toDebugString() : "*");
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgEndNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgEndNode.java
new file mode 100644
index 0000000..b1647fa
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgEndNode.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+/**
+ * End node in CFG graph.
+ */
+public class CfgEndNode extends CfgNopNode {
+ public CfgEndNode() {
+ super(null, null);
+ }
+
+ @Override
+ public void accept(CfgVisitor visitor) {
+ visitor.visitEndNode(this);
+ }
+
+ @Override
+ public String toDebugString() {
+ return "END";
+ }
+
+ @Override
+ protected CfgEndNode cloneImpl() {
+ return new CfgEndNode();
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgForNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgForNode.java
new file mode 100644
index 0000000..bd02968
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgForNode.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JExpression;
+import com.google.gwt.dev.jjs.ast.JForStatement;
+
+/**
+ * Node corresponding to for statement.
+ */
+public class CfgForNode extends CfgConditionalNode<JForStatement> {
+ public CfgForNode(CfgNode<?> parent, JForStatement node) {
+ super(parent, node);
+ }
+
+ @Override
+ public void accept(CfgVisitor visitor) {
+ visitor.visitForNode(this);
+ }
+
+ @Override
+ public JExpression getCondition() {
+ return getJNode().getTestExpr();
+ }
+
+ @Override
+ protected CfgNode<?> cloneImpl() {
+ return new CfgForNode(getParent(), getJNode());
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgGotoNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgGotoNode.java
new file mode 100644
index 0000000..e9234ac
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgGotoNode.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JNode;
+
+/**
+ * Unconditional control transfer node.
+ *
+ * @param <JNodeType> corresponding AST node type.
+ */
+public abstract class CfgGotoNode<JNodeType extends JNode>
+ extends CfgNode<JNodeType> {
+ public CfgGotoNode(CfgNode<?> parent, JNodeType node) {
+ super(parent, node);
+ }
+
+ @Override
+ public String toDebugString() {
+ return "GOTO";
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgIfNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgIfNode.java
new file mode 100644
index 0000000..9668d6f
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgIfNode.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JExpression;
+import com.google.gwt.dev.jjs.ast.JIfStatement;
+
+/**
+ * Node corresponding to if statement.
+ */
+public class CfgIfNode extends CfgConditionalNode<JIfStatement> {
+ public CfgIfNode(CfgNode<?> parent, JIfStatement node) {
+ super(parent, node);
+ }
+
+ @Override
+ public void accept(CfgVisitor visitor) {
+ visitor.visitIfNode(this);
+ }
+
+ @Override
+ public JExpression getCondition() {
+ return getJNode().getIfExpr();
+ }
+
+ @Override
+ protected CfgNode<?> cloneImpl() {
+ return new CfgIfNode(getParent(), getJNode());
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgMethodCallNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgMethodCallNode.java
new file mode 100644
index 0000000..6b18d58
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgMethodCallNode.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JMethodCall;
+
+/**
+ * Node corresponding to method calls.
+ */
+public class CfgMethodCallNode extends CfgSimpleNode<JMethodCall> {
+ public CfgMethodCallNode(CfgNode<?> parent, JMethodCall node) {
+ super(parent, node);
+ }
+
+ @Override
+ public void accept(CfgVisitor visitor) {
+ visitor.visitMethodCallNode(this);
+ }
+
+ @Override
+ public String toDebugString() {
+ return "CALL(" + getJNode().getTarget().getName() + ")";
+ }
+
+ @Override
+ protected CfgNode<?> cloneImpl() {
+ return new CfgMethodCallNode(getParent(), getJNode());
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgNode.java
new file mode 100644
index 0000000..4f3a0e5
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgNode.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JNode;
+import com.google.gwt.dev.util.collect.Lists;
+
+import java.util.List;
+
+/**
+ * Base class for nodes in CFG graph.
+ *
+ * @param <JNodeType> node's corresponding JNode type.
+ */
+public abstract class CfgNode<JNodeType extends JNode> {
+ List<CfgEdge> in = Lists.create();
+ List<CfgEdge> out = Lists.create();
+ private final JNodeType node;
+ private final CfgNode<?> parent;
+
+ public CfgNode(CfgNode<?> parent, JNodeType node) {
+ this.parent = parent;
+ this.node = node;
+ }
+
+ public abstract void accept(CfgVisitor visitor);
+
+ @Override
+ public CfgNode<?> clone() {
+ return cloneImpl();
+ }
+
+ public JNodeType getJNode() {
+ return node;
+ }
+
+ public CfgNode<?> getParent() {
+ return parent;
+ }
+
+ /**
+ * @return debug string representation of the node.
+ */
+ public abstract String toDebugString();
+
+ @Override
+ public String toString() {
+ return toDebugString();
+ }
+
+ /**
+ * @return node clone.
+ */
+ protected abstract CfgNode<?> cloneImpl();
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgNopNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgNopNode.java
new file mode 100644
index 0000000..5403eb3
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgNopNode.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JNode;
+
+/**
+ * Artificial no-operatation node. Is used while doing CFG transformations.
+ */
+public class CfgNopNode extends CfgNode<JNode> {
+ public CfgNopNode(CfgNode<?> parent, JNode node) {
+ super(parent, node);
+ }
+
+ @Override
+ public void accept(CfgVisitor visitor) {
+ visitor.visitNopNode(this);
+ }
+
+ @Override
+ public String toDebugString() {
+ return "NOP";
+ }
+
+ @Override
+ protected CfgNode<?> cloneImpl() {
+ return new CfgNopNode(getParent(), getJNode());
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgOptionalThrowNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgOptionalThrowNode.java
new file mode 100644
index 0000000..3c52d0f
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgOptionalThrowNode.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JMethodCall;
+
+/**
+ * Node which might throw exception.
+ */
+public class CfgOptionalThrowNode extends CfgNode<JMethodCall> {
+ /**
+ * Edge role for normal, no-throwing execution.
+ */
+ public static final String NO_THROW = "NOTHROW";
+ /**
+ * Edge role for throwing RuntimeException.
+ */
+ public static final String RUNTIME_EXCEPTION = "RE";
+ /**
+ * Edge role for throwing generic Error.
+ */
+ public static final String ERROR = "E";
+
+ public CfgOptionalThrowNode(CfgNode<?> parent, JMethodCall node) {
+ super(parent, node);
+ }
+
+ @Override
+ public void accept(CfgVisitor visitor) {
+ visitor.visitOptionalThrowNode(this);
+ }
+
+ @Override
+ public String toDebugString() {
+ return "OPTTHROW(" + getJNode().getTarget().getName() + "())";
+ }
+
+ @Override
+ protected CfgNode<?> cloneImpl() {
+ return new CfgOptionalThrowNode(getParent(), getJNode());
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgPrinter.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgPrinter.java
new file mode 100644
index 0000000..8129969
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgPrinter.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Control flow graph printer. Prints all nodes and all their edges.
+ */
+public class CfgPrinter {
+ private final Cfg graph;
+
+ public CfgPrinter(Cfg graph) {
+ this.graph = graph;
+ }
+
+ public String print() {
+ StringBuffer result = new StringBuffer();
+ List<CfgNode<?>> nodes = graph.getNodes();
+
+ // Determine nodes which have edges incoming not from previous node.
+ Set<CfgNode<?>> targetNodes = new HashSet<CfgNode<?>>();
+ for (int i = 1; i < nodes.size(); ++i) {
+ CfgNode<?> node = nodes.get(i);
+ List<CfgEdge> inEdges = graph.getInEdges(node);
+ for (CfgEdge inEdge : inEdges) {
+ if (inEdge.getStart() != null &&
+ inEdge.getStart() != nodes.get(i - 1)) {
+ targetNodes.add(node);
+ }
+ }
+ }
+
+ Map<CfgNode<?>, String> labels = new HashMap<CfgNode<?>, String>();
+ for (int i = 0, j = 1; i < nodes.size(); ++i) {
+ if (targetNodes.contains(nodes.get(i))) {
+ labels.put(nodes.get(i), String.valueOf(j));
+ ++j;
+ }
+ }
+
+ for (int i = 0; i < nodes.size(); ++i) {
+ CfgNode<?> node = nodes.get(i);
+ if (i != 0) {
+ result.append("\n");
+ }
+
+ if (labels.containsKey(node)) {
+ result.append(labels.get(node));
+ result.append(": ");
+ }
+ result.append(node.toDebugString());
+
+ {
+ List<CfgEdge> out = graph.getOutEdges(node);
+ if (!out.isEmpty()) {
+ result.append(" -> [");
+ for (int j = 0; j < out.size(); ++j) {
+ if (j > 0) {
+ result.append(", ");
+ }
+ CfgEdge edge = out.get(j);
+ if (edge.getRole() != null) {
+ result.append(edge.getRole());
+ result.append("=");
+ }
+ if (i + 1 < nodes.size() && edge.getEnd() != nodes.get(i + 1)) {
+ result.append(labels.get(edge.getEnd()));
+ } else {
+ result.append("*");
+ }
+
+ appendEdgeInfo(result, edge);
+ }
+ result.append("]");
+ }
+ }
+ }
+
+ return result.toString();
+ }
+
+ /**
+ * Template method to append arbitrary edge information.
+ */
+ protected void appendEdgeInfo(@SuppressWarnings("unused") StringBuffer result,
+ @SuppressWarnings("unused") CfgEdge edge) {
+ // Overridden by ancestors.
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgReadNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgReadNode.java
new file mode 100644
index 0000000..077bcd8
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgReadNode.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JVariable;
+import com.google.gwt.dev.jjs.ast.JVariableRef;
+
+/**
+ * Node corresponding to any variable read.
+ */
+public class CfgReadNode extends CfgSimpleNode<JVariableRef> {
+ public CfgReadNode(CfgNode<?> parent, JVariableRef node) {
+ super(parent, node);
+ }
+
+ @Override
+ public void accept(CfgVisitor visitor) {
+ visitor.visitReadNode(this);
+ }
+
+ public JVariable getTarget() {
+ return getJNode().getTarget();
+ }
+
+ @Override
+ public String toDebugString() {
+ return "READ(" + getJNode().getTarget().getName() + ")";
+ }
+
+ @Override
+ protected CfgNode<?> cloneImpl() {
+ return new CfgReadNode(getParent(), getJNode());
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgReadWriteNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgReadWriteNode.java
new file mode 100644
index 0000000..ccfc348
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgReadWriteNode.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JExpression;
+import com.google.gwt.dev.jjs.ast.JNode;
+import com.google.gwt.dev.jjs.ast.JVariable;
+import com.google.gwt.dev.jjs.ast.JVariableRef;
+
+/**
+ * A node corresponding to simultaneous read and write operation (e.g. ++).
+ */
+public class CfgReadWriteNode extends CfgSimpleNode<JNode> {
+ private final JExpression target;
+ private final JExpression value;
+
+ public CfgReadWriteNode(CfgNode<?> parent, JNode node, JExpression target,
+ JExpression value) {
+ super(parent, node);
+ this.target = target;
+ this.value = value;
+ }
+
+ @Override
+ public void accept(CfgVisitor visitor) {
+ visitor.visitReadWriteNode(this);
+ }
+
+ /**
+ * Get operation target. I.e. expression, describing what's changed.
+ */
+ public JExpression getTarget() {
+ return target;
+ }
+
+ /**
+ * Get target variable if target is variable reference. Returns
+ * <code>null</code> otherwise (e.g. target is array reference).
+ */
+ public JVariable getTargetVariable() {
+ return target instanceof JVariableRef ? ((JVariableRef) target).getTarget()
+ : null;
+ }
+
+ /**
+ * Get expression which is assigned to value.
+ * <code>null</code> when new value expression can't be statically determined.
+ */
+ public JExpression getValue() {
+ return value;
+ }
+
+ @Override
+ public String toDebugString() {
+ String targets = target.toString();
+ if (getTargetVariable() != null) {
+ targets = getTargetVariable().getName();
+ }
+ return "READWRITE(" + targets + ", " + value + ")";
+ }
+
+ @Override
+ protected CfgNode<?> cloneImpl() {
+ return new CfgReadWriteNode(getParent(), getJNode(), target, value);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgReturnNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgReturnNode.java
new file mode 100644
index 0000000..7275c8f
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgReturnNode.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JReturnStatement;
+
+/**
+ * Node corresponding to return statement.
+ */
+public class CfgReturnNode extends CfgGotoNode<JReturnStatement> {
+ public CfgReturnNode(CfgNode<?> parent, JReturnStatement node) {
+ super(parent, node);
+ }
+
+ @Override
+ public void accept(CfgVisitor visitor) {
+ visitor.visitReturnNode(this);
+ }
+
+ @Override
+ protected CfgNode<?> cloneImpl() {
+ return new CfgReturnNode(getParent(), getJNode());
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgSimpleNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgSimpleNode.java
new file mode 100644
index 0000000..8cc01c2
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgSimpleNode.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JNode;
+
+/**
+ * A node which doesn't transfer control flow after normal completion.
+ * (It might have abnormal completions though).
+ *
+ * @param <JNodeType> corresponding AST node type.
+ */
+public abstract class CfgSimpleNode<JNodeType extends JNode>
+ extends CfgNode<JNodeType> {
+ public CfgSimpleNode(CfgNode<?> parent, JNodeType node) {
+ super(parent, node);
+ }
+
+ @Override
+ public String toDebugString() {
+ return "{" + getJNode().toSource() + "}";
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgStatementNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgStatementNode.java
new file mode 100644
index 0000000..76b0d27
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgStatementNode.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JStatement;
+
+/**
+ * A statement wrapper node.
+ *
+ * @param <JNodeType> underlying jnode type
+ */
+public class CfgStatementNode<JNodeType extends JStatement> extends
+ CfgSimpleNode<JNodeType> {
+
+ public CfgStatementNode(CfgNode<?> parent, JNodeType node) {
+ super(parent, node);
+ }
+
+ @Override
+ public void accept(CfgVisitor visitor) {
+ visitor.visitStatementNode(this);
+ }
+
+ @Override
+ public String toDebugString() {
+ return "STMT";
+ }
+
+ @Override
+ protected CfgNode<?> cloneImpl() {
+ return new CfgStatementNode<JNodeType>(getParent(), getJNode());
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgThrowNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgThrowNode.java
new file mode 100644
index 0000000..c2a8d42
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgThrowNode.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JThrowStatement;
+
+/**
+ * Node corresponding to throw statement.
+ */
+public class CfgThrowNode extends CfgNode<JThrowStatement> {
+ public CfgThrowNode(CfgNode<?> parent, JThrowStatement node) {
+ super(parent, node);
+ }
+
+ @Override
+ public void accept(CfgVisitor visitor) {
+ visitor.visitThrowNode(this);
+ }
+
+ @Override
+ public String toDebugString() {
+ return "THROW";
+ }
+
+ @Override
+ protected CfgNode<?> cloneImpl() {
+ return new CfgThrowNode(getParent(), getJNode());
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgTransformer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgTransformer.java
new file mode 100644
index 0000000..f204c27
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgTransformer.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+/**
+ * Cfg transformer interface.
+ */
+public interface CfgTransformer {
+ /**
+ * Transform specified node.
+ * @return <code>true</code> if any changes to underlying AST were performed.
+ */
+ boolean transform(CfgNode<?> node, Cfg cfgGraph);
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgTryNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgTryNode.java
new file mode 100644
index 0000000..ec9d865
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgTryNode.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JTryStatement;
+
+/**
+ * Node corresponding to try statement.
+ */
+public class CfgTryNode extends CfgStatementNode<JTryStatement> {
+ public CfgTryNode(CfgNode<?> parent, JTryStatement node) {
+ super(parent, node);
+ }
+
+ @Override
+ public void accept(CfgVisitor visitor) {
+ visitor.visitTryNode(this);
+ }
+
+ @Override
+ public String toDebugString() {
+ return "TRY";
+ }
+
+ @Override
+ protected CfgNode<?> cloneImpl() {
+ return new CfgTryNode(getParent(), getJNode());
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgUtil.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgUtil.java
new file mode 100644
index 0000000..2ac5d3b
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgUtil.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JStatement;
+import com.google.gwt.dev.jjs.impl.gflow.Assumption;
+import com.google.gwt.dev.jjs.impl.gflow.SubgraphAssumptions;
+
+import java.util.ArrayList;
+
+/**
+ * Utilities for working with Cfg.
+ */
+public class CfgUtil {
+ public static void addGraphEdges(Cfg originalGraph, CfgNode<?> originalNode,
+ CfgNode<?> newStartNode, CfgNode<?> newEndNode, Cfg newSubgraph) {
+ for (int i = 0; i < originalGraph.getInEdges(originalNode).size(); ++i) {
+ CfgEdge edge = new CfgEdge();
+ newSubgraph.addIn(newStartNode, edge);
+ newSubgraph.addGraphInEdge(edge);
+ }
+
+ for (CfgEdge e : originalGraph.getOutEdges(originalNode)) {
+ CfgEdge edge = new CfgEdge(e.getRole());
+ newSubgraph.addOut(newEndNode, edge);
+ newSubgraph.addGraphOutEdge(edge);
+ }
+ }
+
+ public static <A extends Assumption<?>> SubgraphAssumptions<A>
+ createGraphBottomAssumptions(Cfg graph) {
+ ArrayList<A> in = new ArrayList<A>();
+ for (int i = 0; i < graph.getGraphInEdges().size(); ++i) {
+ in.add(null);
+ }
+
+ ArrayList<A> out = new ArrayList<A>();
+ for (int i = 0; i < graph.getGraphOutEdges().size(); ++i) {
+ out.add(null);
+ }
+
+ return new SubgraphAssumptions<A>(in, out);
+ }
+
+ /**
+ * Create a graph with single node. Resulting graph will have same amount of
+ * input/output edges as the original node.
+ */
+ public static Cfg createSingleNodeReplacementGraph(
+ Cfg originalGraph,
+ CfgNode<?> originalNode,
+ CfgNode<?> newNode) {
+ Cfg newSubgraph = new Cfg();
+ newSubgraph.addNode(newNode);
+ addGraphEdges(originalGraph, originalNode, newNode, newNode, newSubgraph);
+ return newSubgraph;
+ }
+
+ /**
+ * Find CFG node corresponding to the nearest statement, containing the AST
+ * node of the passed node.
+ */
+ public static CfgNode<?> findContainingStatement(CfgNode<?> node) {
+ while (!(node.getJNode() instanceof JStatement)) {
+ node = node.getParent();
+ }
+
+ return node;
+ }
+
+ /**
+ * Find parent of containing statement.
+ */
+ public static CfgNode<?> findParentOfContainingStatement(CfgNode<?> node) {
+ CfgNode<?> stmtNode = findContainingStatement(node);
+ CfgNode<?> result = stmtNode;
+ while (stmtNode.getJNode() == result.getJNode()) {
+ result = result.getParent();
+ if (result == null) {
+ return null;
+ }
+ // Preconditions.checkNotNull(result, "Can't find parent for: %s", node);
+ }
+
+ return result;
+ }
+
+ private CfgUtil() {
+ //
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgVisitor.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgVisitor.java
new file mode 100644
index 0000000..8c6287b
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgVisitor.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+/**
+ * Visitor for all CFG nodes.
+ */
+public abstract class CfgVisitor {
+ public void visitBinaryConditionalOperationNode(
+ CfgBinaryConditionalOperationNode node) {
+ visitConditionalNode(node);
+ }
+
+ public void visitBlockNode(CfgBlockNode node) {
+ visitSimpleNode(node);
+ }
+
+ public void visitBreakNode(CfgBreakNode node) {
+ visitGotoNode(node);
+ }
+
+ public void visitCaseNode(CfgCaseNode node) {
+ visitConditionalNode(node);
+ }
+
+ public void visitConditionalExpressionNode(
+ CfgConditionalExpressionNode node) {
+ visitConditionalNode(node);
+ }
+
+ public void visitConditionalNode(CfgConditionalNode<?> node) {
+ visitNode(node);
+ }
+
+ public void visitContinueNode(CfgContinueNode node) {
+ visitGotoNode(node);
+ }
+
+ public void visitDoNode(CfgDoNode node) {
+ visitConditionalNode(node);
+ }
+
+ public void visitEndNode(CfgEndNode node) {
+ visitNopNode(node);
+ }
+
+ public void visitForNode(CfgForNode node) {
+ visitConditionalNode(node);
+ }
+
+ public void visitGotoNode(CfgGotoNode<?> node) {
+ visitNode(node);
+ }
+
+ public void visitIfNode(CfgIfNode node) {
+ visitConditionalNode(node);
+ }
+
+ public void visitMethodCallNode(CfgMethodCallNode node) {
+ visitSimpleNode(node);
+ }
+
+ public void visitNode(@SuppressWarnings("unused") CfgNode<?> node) {
+ //
+ }
+
+ public void visitNopNode(CfgNopNode node) {
+ visitNode(node);
+ }
+
+ public void visitOptionalThrowNode(CfgOptionalThrowNode node) {
+ visitNode(node);
+ }
+
+ public void visitReadNode(CfgReadNode node) {
+ visitSimpleNode(node);
+ }
+
+ public void visitReadWriteNode(CfgReadWriteNode node) {
+ visitSimpleNode(node);
+ }
+
+ public void visitReturnNode(CfgReturnNode node) {
+ visitGotoNode(node);
+ }
+
+ public void visitSimpleNode(CfgSimpleNode<?> node) {
+ visitNode(node);
+ }
+
+ public void visitStatementNode(CfgStatementNode<?> node) {
+ visitSimpleNode(node);
+ }
+
+ public void visitThrowNode(CfgThrowNode node) {
+ visitNode(node);
+ }
+
+ public void visitTryNode(CfgTryNode node) {
+ visitSimpleNode(node);
+ }
+
+ public void visitWhileNode(CfgWhileNode node) {
+ visitConditionalNode(node);
+ }
+
+ public void visitWriteNode(CfgWriteNode node) {
+ visitSimpleNode(node);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgWhileNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgWhileNode.java
new file mode 100644
index 0000000..0581084
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgWhileNode.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JExpression;
+import com.google.gwt.dev.jjs.ast.JWhileStatement;
+
+/**
+ * Node corresponding to while statement.
+ */
+public class CfgWhileNode extends CfgConditionalNode<JWhileStatement> {
+ public CfgWhileNode(CfgNode<?> parent, JWhileStatement node) {
+ super(parent, node);
+ }
+
+ @Override
+ public void accept(CfgVisitor visitor) {
+ visitor.visitWhileNode(this);
+ }
+
+ @Override
+ public JExpression getCondition() {
+ return getJNode().getTestExpr();
+ }
+
+ @Override
+ protected CfgNode<?> cloneImpl() {
+ return new CfgWhileNode(getParent(), getJNode());
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgWriteNode.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgWriteNode.java
new file mode 100644
index 0000000..12ce97b
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgWriteNode.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2009 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.gflow.cfg;
+
+import com.google.gwt.dev.jjs.ast.JExpression;
+import com.google.gwt.dev.jjs.ast.JNode;
+import com.google.gwt.dev.jjs.ast.JVariable;
+import com.google.gwt.dev.jjs.ast.JVariableRef;
+
+/**
+ * Node corresponding to write operation.
+ */
+public class CfgWriteNode extends CfgSimpleNode<JNode> {
+ private final JExpression target;
+ private final JExpression value;
+
+ public CfgWriteNode(CfgNode<?> parent, JNode node, JExpression target,
+ JExpression value) {
+ super(parent, node);
+ this.target = target;
+ this.value = value;
+ }
+
+ @Override
+ public void accept(CfgVisitor visitor) {
+ visitor.visitWriteNode(this);
+ }
+
+ /**
+ * Get operation target. I.e. expression, describing what's changed.
+ */
+ public JExpression getTarget() {
+ return target;
+ }
+
+ /**
+ * Get target variable if target is variable reference. Returns
+ * <code>null</code> otherwise (e.g. target is array reference).
+ */
+ public JVariable getTargetVariable() {
+ return target instanceof JVariableRef ? ((JVariableRef) target).getTarget()
+ : null;
+ }
+
+ /**
+ * Get expression which is assigned to value.
+ * <code>null</code> when new value expression can't be statically determined.
+ */
+ public JExpression getValue() {
+ return value;
+ }
+
+ @Override
+ public String toDebugString() {
+ String targets = target.toString();
+ if (getTargetVariable() != null) {
+ targets = getTargetVariable().getName();
+ }
+ return "WRITE(" + targets + ", " + value + ")";
+ }
+
+ @Override
+ protected CfgNode<?> cloneImpl() {
+ return new CfgWriteNode(getParent(), getJNode(), target, value);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/AssumptionDeducer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/AssumptionDeducer.java
new file mode 100644
index 0000000..de82f67
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/AssumptionDeducer.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2008 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.gflow.constants;
+
+import com.google.gwt.dev.jjs.ast.Context;
+import com.google.gwt.dev.jjs.ast.JBinaryOperation;
+import com.google.gwt.dev.jjs.ast.JBooleanLiteral;
+import com.google.gwt.dev.jjs.ast.JDoubleLiteral;
+import com.google.gwt.dev.jjs.ast.JExpression;
+import com.google.gwt.dev.jjs.ast.JFloatLiteral;
+import com.google.gwt.dev.jjs.ast.JLocalRef;
+import com.google.gwt.dev.jjs.ast.JParameterRef;
+import com.google.gwt.dev.jjs.ast.JValueLiteral;
+import com.google.gwt.dev.jjs.ast.JVisitor;
+import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
+
+/**
+ * Assumption deducer analyzes the expression, knowing its value, and deduces
+ * variables constant values assumptions from it.
+ */
+final class AssumptionDeducer extends JVisitor {
+ /**
+ * Deduce assumptions, knowing that <code>expression</code> evaluates to
+ * <code>value</code> and stores individual variable assumptions in the
+ * <code>assumption</code> parameter. It will never override any existing
+ * constant assumptions. It will override top and bottom assumptions though.
+ */
+ static void deduceAssumption(
+ JExpression expression, final JValueLiteral value,
+ final ConstantsAssumption.Updater assumption) {
+ new AssumptionDeducer(value, assumption).accept(expression);
+ }
+ private final ConstantsAssumption.Updater assumption;
+
+ /**
+ * Contains the value of evaluating expression we're currently visiting.
+ * Is <code>null</code> if we do not know current expression value.
+ */
+ private JValueLiteral currentValue;
+
+ AssumptionDeducer(JValueLiteral value, ConstantsAssumption.Updater assumption) {
+ this.assumption = assumption;
+ currentValue = value;
+ }
+
+ @SuppressWarnings("incomplete-switch")
+ @Override
+ public boolean visit(JBinaryOperation x, Context ctx) {
+ switch (x.getOp()) {
+ case EQ:
+ if (isTrue(currentValue)) {
+ if (x.getRhs() instanceof JValueLiteral &&
+ isSubstitutableIfEquals(x.getRhs())) {
+ currentValue = (JValueLiteral) x.getRhs();
+ accept(x.getLhs());
+ return false;
+ } else if (x.getLhs() instanceof JValueLiteral &&
+ isSubstitutableIfEquals(x.getLhs())) {
+ currentValue = (JValueLiteral) x.getLhs();
+ accept(x.getRhs());
+ return false;
+ }
+ }
+ break;
+
+ case NEQ:
+ if (isFalse(currentValue)) {
+ if (x.getRhs() instanceof JValueLiteral &&
+ isSubstitutableIfEquals(x.getRhs())) {
+ currentValue = (JValueLiteral) x.getRhs();
+ accept(x.getLhs());
+ return false;
+ } else if (x.getLhs() instanceof JValueLiteral &&
+ isSubstitutableIfEquals(x.getLhs())) {
+ currentValue = (JValueLiteral) x.getLhs();
+ accept(x.getRhs());
+ return false;
+ }
+ }
+ break;
+
+ case AND:
+ if (isTrue(currentValue)) {
+ accept(x.getLhs());
+ currentValue = JBooleanLiteral.get(true);
+ accept(x.getRhs());
+ return false;
+ }
+ break;
+
+ case OR:
+ if (isFalse(currentValue)) {
+ accept(x.getLhs());
+ currentValue = JBooleanLiteral.FALSE;
+ accept(x.getRhs());
+ return false;
+ }
+ break;
+ }
+ currentValue = null;
+ return true;
+ }
+
+ @Override
+ public boolean visit(JExpression x, Context ctx) {
+ // Unknown expression. Do not go inside.
+ return false;
+ }
+
+ @Override
+ public boolean visit(JLocalRef x, Context ctx) {
+ if (assumption.hasAssumption(x.getTarget())) {
+ // Expression evaluation can't change existing assumptions
+ return false;
+ }
+ assumption.set(x.getTarget(), currentValue);
+ return false;
+ }
+
+ @Override
+ public boolean visit(JMultiExpression x, Context ctx) {
+ // Knowing the value multi expression, we know the value of its last
+ // expression only.
+ accept(x.exprs.get(x.exprs.size() - 1));
+ return false;
+ }
+
+ @Override
+ public boolean visit(JParameterRef x, Context ctx) {
+ if (assumption.hasAssumption(x.getTarget())) {
+ // Expression evaluation shouldn't change existing assumptions
+ return false;
+ }
+ assumption.set(x.getTarget(), currentValue);
+ return false;
+ }
+
+ private boolean isFalse(JValueLiteral value) {
+ return value instanceof JBooleanLiteral &&
+ !((JBooleanLiteral) value).getValue();
+ }
+
+ /**
+ * Checks that if some expression equals <code>e</code>, then we can freely
+ * substitute it by e.
+ */
+ private boolean isSubstitutableIfEquals(JExpression e) {
+ if (!(e instanceof JValueLiteral)) {
+ return false;
+ }
+
+ if (e instanceof JFloatLiteral &&
+ ((JFloatLiteral) e).getValue() == 0.0f) {
+ // There are +0.0 and -0.0. And both of them are equal.
+ // We can't substitute 0.0 instead of them.
+ return false;
+ }
+
+ if (e instanceof JDoubleLiteral &&
+ ((JDoubleLiteral) e).getValue() == 0.0d) {
+ // There are +0.0 and -0.0. And both of them are equal.
+ // We can't substitute 0.0 instead of them.
+ return false;
+ }
+
+ return true;
+ }
+
+ private boolean isTrue(JValueLiteral value) {
+ return value instanceof JBooleanLiteral &&
+ ((JBooleanLiteral) value).getValue();
+ }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/ConstantConditionTransformation.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/ConstantConditionTransformation.java
new file mode 100644
index 0000000..a870c67
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/ConstantConditionTransformation.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2008 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.gflow.constants;
+
+import com.google.gwt.dev.jjs.ast.Context;
+import com.google.gwt.dev.jjs.ast.JBooleanLiteral;
+import com.google.gwt.dev.jjs.ast.JExpression;
+import com.google.gwt.dev.jjs.ast.JModVisitor;
+import com.google.gwt.dev.jjs.ast.JNode;
+import com.google.gwt.dev.jjs.impl.gflow.TransformationFunction.Transformation;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgCaseNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgTransformer;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgConditionalNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNopNode;
+import com.google.gwt.dev.util.Preconditions;
+
+/**
+ * Transformation to be applied when CfgConditionalNode's condition is constant
+ * value. Transformation replaces conditional node with CfgNop node depending
+ * on condition value. It leaves the edge, which will be never executed
+ * unconnected at source, thus making sure that it will be unreachable.
+ *
+ * Doesn't try to optimize unreachable branches, since this is subject to other
+ * optimizations.
+ */
+final class ConstantConditionTransformation implements
+ Transformation<CfgTransformer, Cfg> {
+ private final boolean conditionValue;
+ private final CfgConditionalNode<?> node;
+ private final Cfg graph;
+
+ ConstantConditionTransformation(Cfg graph, boolean conditionValue,
+ CfgConditionalNode<?> node) {
+ this.graph = graph;
+ this.conditionValue = conditionValue;
+ this.node = node;
+ }
+
+ public CfgTransformer getGraphTransformer() {
+ return new CfgTransformer() {
+ public boolean transform(CfgNode<?> cfgNode, Cfg cfgGraph) {
+ Preconditions.checkArgument(cfgNode == node);
+ if (cfgNode instanceof CfgCaseNode) {
+ // TODO: support case node optimization
+ return false;
+ }
+
+ final JExpression oldCondition = node.getCondition();
+ final JExpression newCondition = JBooleanLiteral.get(conditionValue);
+ JModVisitor visitor = new JModVisitor() {
+ @Override
+ public boolean visit(JExpression x, Context ctx) {
+ if (x == oldCondition) {
+ ctx.replaceMe(newCondition);
+ return false;
+ }
+ return true;
+ }
+ };
+ JNode startNode = node.getJNode();
+ visitor.accept(startNode);
+ Preconditions.checkState(visitor.didChange(),
+ "Couldn't replace %s with %s in %s",
+ oldCondition, newCondition, startNode);
+
+ return visitor.didChange();
+ }
+ };
+ }
+
+ public Cfg getNewSubgraph() {
+ Cfg newSubgraph = new Cfg();
+ CfgNode<?> newNode = new CfgNopNode(node.getParent(), node.getJNode());
+ newSubgraph.addNode(newNode);
+
+ // Add all incoming edges.
+ for (int i = 0; i < graph.getInEdges(node).size(); ++i) {
+ CfgEdge edge = new CfgEdge();
+ newSubgraph.addIn(newNode, edge);
+ newSubgraph.addGraphInEdge(edge);
+ }
+
+ for (CfgEdge e : graph.getOutEdges(node)) {
+ CfgEdge edge = new CfgEdge(e.getRole());
+ newSubgraph.addGraphOutEdge(edge);
+
+ if (e.getRole() != null
+ && ((e.getRole().equals(CfgConditionalNode.ELSE) && conditionValue) ||
+ (e.getRole().equals(CfgConditionalNode.THEN) && !conditionValue))) {
+ // Do not connect this edge due to constant condition.
+ } else {
+ newSubgraph.addOut(newNode, edge);
+ }
+ }
+
+ return newSubgraph;
+ }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/ConstantsAnalysis.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/ConstantsAnalysis.java
new file mode 100644
index 0000000..d3a882f
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/ConstantsAnalysis.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2008 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.gflow.constants;
+
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.impl.gflow.Analysis;
+import com.google.gwt.dev.jjs.impl.gflow.AssumptionMap;
+import com.google.gwt.dev.jjs.impl.gflow.IntegratedAnalysis;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgTransformer;
+
+/**
+ * Constant propagation optimization.
+ *
+ * Detects a situation when variable value is constant and replaces variable
+ * access with constant value.
+ * As of now supports only locals & parameters.
+ */
+public class ConstantsAnalysis implements
+ Analysis<CfgNode<?>, CfgEdge, Cfg, ConstantsAssumption>,
+ IntegratedAnalysis<CfgNode<?>, CfgEdge, CfgTransformer, Cfg, ConstantsAssumption> {
+
+ private final JProgram program;
+
+ public ConstantsAnalysis(JProgram program) {
+ this.program = program;
+ }
+
+ public ConstantsFlowFunction getFlowFunction() {
+ return new ConstantsFlowFunction();
+ }
+
+ public ConstantsIntegratedFlowFunction getIntegratedFlowFunction() {
+ return new ConstantsIntegratedFlowFunction(program);
+ }
+
+ public void setInitialGraphAssumptions(Cfg graph,
+ AssumptionMap<CfgEdge, ConstantsAssumption> assumptionMap) {
+ // bottom assumptions.
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/ConstantsAssumption.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/ConstantsAssumption.java
new file mode 100644
index 0000000..19533cf
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/ConstantsAssumption.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2008 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.gflow.constants;
+
+import com.google.gwt.dev.jjs.ast.HasName;
+import com.google.gwt.dev.jjs.ast.JDoubleLiteral;
+import com.google.gwt.dev.jjs.ast.JFloatLiteral;
+import com.google.gwt.dev.jjs.ast.JValueLiteral;
+import com.google.gwt.dev.jjs.ast.JVariable;
+import com.google.gwt.dev.jjs.impl.gflow.Assumption;
+
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Assumptions for ConstantsAnalysis.
+ */
+public class ConstantsAssumption implements Assumption<ConstantsAssumption> {
+ /**
+ * Updates the assumption by copying it on first write.
+ */
+ public static class Updater {
+ private ConstantsAssumption assumption;
+ private boolean copied = false;
+
+ public Updater(ConstantsAssumption assumption) {
+ this.assumption = assumption;
+ }
+
+ public Updater copy() {
+ return new Updater(assumption);
+ }
+
+ public boolean hasAssumption(JVariable target) {
+ if (assumption == null) {
+ return false;
+ }
+ return assumption.hasAssumption(target);
+ }
+
+ public void set(JVariable target, JValueLiteral literal) {
+ copyIfNeeded();
+ assumption.set(target, literal);
+ }
+
+ public ConstantsAssumption unwrap() {
+ return assumption;
+ }
+
+ public ConstantsAssumption unwrapToNotNull() {
+ if (assumption == null) {
+ return new ConstantsAssumption();
+ }
+ return assumption;
+ }
+
+ private void copyIfNeeded() {
+ if (!copied) {
+ assumption = new ConstantsAssumption(assumption);
+ copied = true;
+ }
+ }
+ }
+
+ /**
+ * Contains individual assumptions about variables. If variable isn't in the
+ * map, then variable assumption is _|_ (bottom), if variable's value is
+ * null, then variable assumption is T - variable has non-constant value.
+ */
+ private final Map<JVariable, JValueLiteral> values;
+
+ public ConstantsAssumption() {
+ values = new IdentityHashMap<JVariable, JValueLiteral>();
+ }
+
+ public ConstantsAssumption(ConstantsAssumption a) {
+ if (a != null) {
+ values = new IdentityHashMap<JVariable, JValueLiteral>(a.values);
+ } else {
+ values = new IdentityHashMap<JVariable, JValueLiteral>();
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj == null) {
+ return values.isEmpty();
+ }
+ ConstantsAssumption other = (ConstantsAssumption) obj;
+ return values.equals(other.values);
+ }
+
+ /**
+ * Get variable constant assumption. <code>null</code> if there's no constant
+ * assumption for this variable.
+ */
+ public JValueLiteral get(JVariable variable) {
+ return values.get(variable);
+ }
+
+ /**
+ * Check if we have constant (i.e. not top and not bottom) assumption about
+ * the variable.
+ */
+ public boolean hasAssumption(JVariable variable) {
+ return get(variable) != null;
+ }
+
+ @Override
+ public int hashCode() {
+ return values.hashCode();
+ }
+
+ public ConstantsAssumption join(ConstantsAssumption other) {
+ if (other == null || other.values.isEmpty()) {
+ return this;
+ }
+
+ if (values.isEmpty()) {
+ return other;
+ }
+
+ ConstantsAssumption result = new ConstantsAssumption(this);
+
+ for (JVariable var : other.values.keySet()) {
+ if (values.containsKey(var)) {
+ // Var is present in both assumptions. Join their values.
+ result.values.put(var, join(values.get(var), other.values.get(var)));
+ } else {
+ result.values.put(var, other.values.get(var));
+ }
+ }
+
+ return result;
+ }
+
+ public String toDebugString() {
+ StringBuffer result = new StringBuffer();
+
+ result.append("{");
+ List<JVariable> variables = new ArrayList<JVariable>(values.keySet());
+ HasName.Util.sortByName(variables);
+ for (JVariable variable : variables) {
+ if (result.length() > 1) {
+ result.append(", ");
+ }
+ result.append(variable.getName());
+ result.append(" = ");
+ if (values.get(variable) == null) {
+ result.append("T");
+ } else {
+ result.append(values.get(variable));
+ }
+ }
+ result.append("}");
+
+ return result.toString();
+ }
+
+ @Override
+ public String toString() {
+ return toDebugString();
+ }
+
+ private boolean equal(JValueLiteral literal1, JValueLiteral literal2) {
+ if (literal1 == null || literal2 == null) {
+ return literal1 == literal2;
+ }
+
+ if (literal1.getClass() != literal2.getClass()) {
+ // these are different literal types.
+ return false;
+ }
+
+ if (literal1 instanceof JFloatLiteral) {
+ int bits1 = Float.floatToRawIntBits(
+ ((JFloatLiteral) literal1).getValue());
+ int bits2 = Float.floatToRawIntBits(
+ ((JFloatLiteral) literal2).getValue());
+ return bits1 == bits2;
+ }
+
+ if (literal1 instanceof JDoubleLiteral) {
+ long bits1 = Double.doubleToRawLongBits(
+ ((JDoubleLiteral) literal1).getValue());
+ long bits2 = Double.doubleToRawLongBits(
+ ((JDoubleLiteral) literal2).getValue());
+ return bits1 == bits2;
+ }
+
+ Object valueObj1 = literal1.getValueObj();
+ Object valueObj2 = literal2.getValueObj();
+ if (valueObj1 == null || valueObj2 == null) {
+ return valueObj1 == valueObj2;
+ }
+
+ return valueObj1.equals(valueObj2);
+ }
+
+ private JValueLiteral join(JValueLiteral value1, JValueLiteral value2) {
+ if (!equal(value1, value2)) {
+ return null;
+ }
+
+ return value1;
+ }
+
+ private void set(JVariable variable, JValueLiteral literal) {
+ values.put(variable, literal);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/ConstantsFlowFunction.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/ConstantsFlowFunction.java
new file mode 100644
index 0000000..32bcd22
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/ConstantsFlowFunction.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2008 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.gflow.constants;
+
+import com.google.gwt.dev.jjs.ast.JBooleanLiteral;
+import com.google.gwt.dev.jjs.ast.JExpression;
+import com.google.gwt.dev.jjs.ast.JLocal;
+import com.google.gwt.dev.jjs.ast.JParameter;
+import com.google.gwt.dev.jjs.ast.JValueLiteral;
+import com.google.gwt.dev.jjs.ast.JVariable;
+import com.google.gwt.dev.jjs.impl.gflow.AssumptionMap;
+import com.google.gwt.dev.jjs.impl.gflow.AssumptionUtil;
+import com.google.gwt.dev.jjs.impl.gflow.FlowFunction;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgConditionalNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgReadWriteNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgVisitor;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgWriteNode;
+import com.google.gwt.dev.jjs.impl.gflow.constants.ConstantsAssumption.Updater;
+import com.google.gwt.dev.util.Preconditions;
+
+import java.util.ArrayList;
+
+/**
+ * Flow function for ConstantsAnalysis.
+ */
+public class ConstantsFlowFunction implements
+ FlowFunction<CfgNode<?>, CfgEdge, Cfg, ConstantsAssumption> {
+ public void interpret(CfgNode<?> node,
+ final Cfg graph, AssumptionMap<CfgEdge, ConstantsAssumption> assumptionMap) {
+ ConstantsAssumption in = AssumptionUtil.join(graph.getInEdges(node), assumptionMap);
+
+ final int outSize = graph.getOutEdges(node).size();
+ final ArrayList<ConstantsAssumption> result =
+ new ArrayList<ConstantsAssumption>(outSize);
+
+ final Updater assumption = new Updater(in);
+ node.accept(new CfgVisitor() {
+ @Override
+ public void visitConditionalNode(CfgConditionalNode<?> x) {
+ JExpression condition = x.getCondition();
+
+ Updater thenAssumptions = assumption.copy();
+ Updater elseAssumptions = assumption.copy();
+
+ Preconditions.checkNotNull(condition, "Null condition in %s", x);
+ AssumptionDeducer.deduceAssumption(condition, JBooleanLiteral.TRUE,
+ thenAssumptions);
+ AssumptionDeducer.deduceAssumption(condition, JBooleanLiteral.FALSE,
+ elseAssumptions);
+
+ for (CfgEdge e : graph.getOutEdges(x)) {
+ if (CfgConditionalNode.THEN.equals(e.getRole())) {
+ result.add(thenAssumptions.unwrap());
+ } else if (CfgConditionalNode.ELSE.equals(e.getRole())) {
+ result.add(elseAssumptions.unwrap());
+ } else {
+ result.add(assumption.unwrap());
+ }
+ }
+ }
+
+ @Override
+ public void visitNode(CfgNode<?> node) {
+ // We can't deduce any assumptions from the node. Just copy incoming
+ // assumptions further.
+ for (int i = 0; i < graph.getOutEdges(node).size(); ++i) {
+ result.add(assumption.unwrap());
+ }
+ }
+
+ @Override
+ public void visitReadWriteNode(CfgReadWriteNode node) {
+ processWrite(assumption, node.getTargetVariable(), node.getValue());
+ super.visitReadWriteNode(node);
+ }
+
+ @Override
+ public void visitWriteNode(CfgWriteNode node) {
+ processWrite(assumption, node.getTargetVariable(), node.getValue());
+ super.visitWriteNode(node);
+ }
+
+ private void processWrite(final Updater assumption,
+ JVariable var, JExpression expression) {
+ if (var == null) {
+ return;
+ }
+
+ if (var instanceof JParameter || var instanceof JLocal) {
+ if (expression != null) {
+ JValueLiteral valueLiteral =
+ ExpressionEvaluator.evaluate(expression, assumption.unwrap());
+ assumption.set(var, valueLiteral);
+ } else {
+ assumption.set(var, null);
+ }
+ }
+ }
+ });
+
+ AssumptionUtil.setAssumptions(graph.getOutEdges(node), result, assumptionMap);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/ConstantsIntegratedFlowFunction.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/ConstantsIntegratedFlowFunction.java
new file mode 100644
index 0000000..7f896b6
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/ConstantsIntegratedFlowFunction.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2008 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.gflow.constants;
+
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.impl.gflow.AssumptionMap;
+import com.google.gwt.dev.jjs.impl.gflow.IntegratedFlowFunction;
+import com.google.gwt.dev.jjs.impl.gflow.TransformationFunction.Transformation;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgTransformer;
+
+/**
+ * Integrated flow function for ConstantsAnalysis. First try a transformation,
+ * fall back to flow function if no transformation is possible.
+ */
+public class ConstantsIntegratedFlowFunction
+ implements
+ IntegratedFlowFunction<CfgNode<?>, CfgEdge, CfgTransformer, Cfg, ConstantsAssumption> {
+ private static final ConstantsFlowFunction FLOW_FUNCTION = new ConstantsFlowFunction();
+
+ private final ConstantsTransformationFunction transformationFunction;
+
+ public ConstantsIntegratedFlowFunction(JProgram program) {
+ transformationFunction = new ConstantsTransformationFunction(program);
+ }
+
+ public Transformation<CfgTransformer, Cfg> interpretOrReplace(
+ CfgNode<?> node, Cfg graph,
+ AssumptionMap<CfgEdge, ConstantsAssumption> assumptionMap) {
+ Transformation<CfgTransformer, Cfg> transformation = transformationFunction.transform(
+ node, graph, assumptionMap);
+ if (transformation != null) {
+ return transformation;
+ }
+ FLOW_FUNCTION.interpret(node, graph, assumptionMap);
+ return null;
+ }
+
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/ConstantsTransformationFunction.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/ConstantsTransformationFunction.java
new file mode 100644
index 0000000..bdda674
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/ConstantsTransformationFunction.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2008 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.gflow.constants;
+
+import com.google.gwt.dev.jjs.ast.JBooleanLiteral;
+import com.google.gwt.dev.jjs.ast.JExpression;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.ast.JValueLiteral;
+import com.google.gwt.dev.jjs.impl.gflow.AssumptionMap;
+import com.google.gwt.dev.jjs.impl.gflow.AssumptionUtil;
+import com.google.gwt.dev.jjs.impl.gflow.TransformationFunction;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgConditionalNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgReadNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgTransformer;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgVisitor;
+import com.google.gwt.dev.util.Preconditions;
+
+/**
+ * Transformation function for ConstantsAnalysis. Checks if current node can
+ * be simplified using assumptions
+ */
+public class ConstantsTransformationFunction implements
+ TransformationFunction<CfgNode<?>, CfgEdge, CfgTransformer, Cfg,
+ ConstantsAssumption> {
+ private final class MyTransformationVisitor extends CfgVisitor {
+ private final ConstantsAssumption assumption;
+ private final Cfg graph;
+ private Transformation<CfgTransformer, Cfg> result = null;
+
+ private MyTransformationVisitor(Cfg graph, ConstantsAssumption assumption) {
+ this.graph = graph;
+ this.assumption = assumption;
+ }
+
+ @Override
+ public void visitConditionalNode(final CfgConditionalNode<?> node) {
+ JExpression condition = node.getCondition();
+ if (condition instanceof JValueLiteral) {
+ return;
+ }
+
+ Preconditions.checkNotNull(condition,
+ "Null condition in %s: %s", node, node.getJNode());
+ JValueLiteral evaluatedCondition =
+ ExpressionEvaluator.evaluate(condition, assumption);
+
+ if (evaluatedCondition == null ||
+ !(evaluatedCondition instanceof JBooleanLiteral)) {
+ super.visitConditionalNode(node);
+ return;
+ }
+
+ final boolean b = ((JBooleanLiteral) evaluatedCondition).getValue();
+ result = new ConstantConditionTransformation(graph, b, node);
+ }
+
+ @Override
+ public void visitReadNode(CfgReadNode node) {
+ if (assumption.hasAssumption(node.getTarget())) {
+ result = new FoldConstantsTransformation(program, assumption, node,
+ graph);
+ }
+ }
+ }
+
+ private final JProgram program;
+
+ public ConstantsTransformationFunction(JProgram program) {
+ this.program = program;
+ }
+
+ public Transformation<CfgTransformer, Cfg> transform(
+ final CfgNode<?> node, final Cfg graph,
+ final AssumptionMap<CfgEdge, ConstantsAssumption> assumptionMap) {
+ ConstantsAssumption assumption = AssumptionUtil.join(
+ graph.getInEdges(node), assumptionMap);
+ if (assumption == null) {
+ return null;
+ }
+
+ MyTransformationVisitor visitor = new MyTransformationVisitor(graph,
+ assumption);
+ node.accept(visitor);
+ return visitor.result;
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/ExpressionEvaluator.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/ExpressionEvaluator.java
new file mode 100644
index 0000000..6b8f576
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/ExpressionEvaluator.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2008 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.gflow.constants;
+
+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.JBooleanLiteral;
+import com.google.gwt.dev.jjs.ast.JExpression;
+import com.google.gwt.dev.jjs.ast.JIntLiteral;
+import com.google.gwt.dev.jjs.ast.JNullLiteral;
+import com.google.gwt.dev.jjs.ast.JPrimitiveType;
+import com.google.gwt.dev.jjs.ast.JValueLiteral;
+import com.google.gwt.dev.jjs.ast.JVariableRef;
+import com.google.gwt.dev.jjs.ast.JVisitor;
+import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
+import com.google.gwt.dev.util.Preconditions;
+
+/**
+ * Evaluate expression based on current assumptions.
+ */
+public class ExpressionEvaluator {
+ /**
+ * Main evaluation visitor.
+ */
+ private static class EvaluatorVisitor extends JVisitor {
+ private final ConstantsAssumption assumptions;
+ /**
+ * Contains evaluation result for visited node. Is <code>null</code>
+ * if we can't evaluate.
+ */
+ private JValueLiteral result = null;
+
+ public EvaluatorVisitor(ConstantsAssumption assumptions) {
+ this.assumptions = assumptions;
+ }
+
+ public JValueLiteral evaluate(JExpression expression) {
+ Preconditions.checkNotNull(expression);
+ accept(expression);
+ return result;
+ }
+
+ @Override
+ public boolean visit(JBinaryOperation x, Context ctx) {
+ accept(x.getRhs());
+ if (result == null) {
+ return false;
+ }
+ JValueLiteral rhs = result;
+ accept(x.getLhs());
+ if (result == null) {
+ return false;
+ }
+ JValueLiteral lhs = result;
+ result = evalBinOp(x, lhs, rhs);
+ return false;
+ }
+
+ @Override
+ public boolean visit(JExpression x, Context ctx) {
+ // We don't know what's this expression about. Can't evaluate it.
+ result = null;
+ return false;
+ }
+
+ @Override
+ public boolean visit(JMultiExpression x, Context ctx) {
+ accept(x.exprs.get(x.exprs.size() - 1));
+ return false;
+ }
+
+ @Override
+ public boolean visit(JValueLiteral x, Context ctx) {
+ result = x;
+ return false;
+ }
+
+ @Override
+ public boolean visit(JVariableRef x, Context ctx) {
+ result = assumptions != null ? assumptions.get(x.getTarget()) : null;
+ return false;
+ }
+ }
+
+ public static JValueLiteral evalBinOp(JBinaryOperation x, JValueLiteral lhs,
+ JValueLiteral rhs) {
+ if (lhs instanceof JNullLiteral ||
+ rhs instanceof JNullLiteral) {
+ if (x.getOp() == JBinaryOperator.EQ) {
+ return JBooleanLiteral.get(
+ (lhs instanceof JNullLiteral) && (rhs instanceof JNullLiteral));
+ } else if (x.getOp() == JBinaryOperator.NEQ) {
+ return JBooleanLiteral.get(
+ !(lhs instanceof JNullLiteral) || !(rhs instanceof JNullLiteral));
+ }
+ }
+
+ if (!lhs.getType().equals(rhs.getType())) {
+ // do not even try to get type conversions right :)
+ return null;
+ }
+
+ // TODO: support other types.
+
+ if (lhs.getType().equals(JPrimitiveType.INT)) {
+ if (!(lhs instanceof JIntLiteral) ||
+ !(rhs instanceof JIntLiteral)) {
+ return null;
+ }
+
+ int a = ((JIntLiteral) lhs).getValue();
+ int b = ((JIntLiteral) rhs).getValue();
+
+ switch (x.getOp()) {
+ case ADD:
+ return new JIntLiteral(x.getSourceInfo(), a + b);
+ case MUL:
+ return new JIntLiteral(x.getSourceInfo(), a * b);
+ case SUB:
+ return new JIntLiteral(x.getSourceInfo(), a - b);
+ case DIV:
+ return new JIntLiteral(x.getSourceInfo(), a / b);
+ case EQ:
+ return JBooleanLiteral.get(a == b);
+ case NEQ:
+ return JBooleanLiteral.get(a != b);
+ case GT:
+ return JBooleanLiteral.get(a > b);
+ case GTE:
+ return JBooleanLiteral.get(a >= b);
+ case LT:
+ return JBooleanLiteral.get(a < b);
+ case LTE:
+ return JBooleanLiteral.get(a <= b);
+
+ default:
+ return null;
+ }
+ } else if (lhs.getType().equals(JPrimitiveType.BOOLEAN)) {
+ if (!(lhs instanceof JBooleanLiteral) ||
+ !(rhs instanceof JBooleanLiteral)) {
+ return null;
+ }
+
+ boolean a = ((JBooleanLiteral) lhs).getValue();
+ boolean b = ((JBooleanLiteral) rhs).getValue();
+
+ switch (x.getOp()) {
+ case EQ:
+ return JBooleanLiteral.get(a == b);
+ case NEQ:
+ return JBooleanLiteral.get(a != b);
+
+ default:
+ return null;
+ }
+ }
+
+ return null;
+ }
+
+ public static JValueLiteral evaluate(JExpression expression,
+ ConstantsAssumption assumptions) {
+ return new EvaluatorVisitor(assumptions).evaluate(expression);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/FoldConstantTransformer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/FoldConstantTransformer.java
new file mode 100644
index 0000000..f09ba04
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/FoldConstantTransformer.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2008 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.gflow.constants;
+
+import com.google.gwt.dev.jjs.ast.Context;
+import com.google.gwt.dev.jjs.ast.JModVisitor;
+import com.google.gwt.dev.jjs.ast.JNode;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.ast.JValueLiteral;
+import com.google.gwt.dev.jjs.ast.JVariable;
+import com.google.gwt.dev.jjs.ast.JVariableRef;
+import com.google.gwt.dev.jjs.impl.CloneExpressionVisitor;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgTransformer;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgReadNode;
+import com.google.gwt.dev.util.Preconditions;
+
+/**
+ * Replace variable read by its constant value.
+ */
+final class FoldConstantTransformer implements CfgTransformer {
+ private final ConstantsAssumption assumption;
+ private CloneExpressionVisitor cloner;
+ private final CfgReadNode nodeToFold;
+
+ public FoldConstantTransformer(JProgram program,
+ ConstantsAssumption assumptions, CfgReadNode nodeToFold) {
+ this.assumption = assumptions;
+ this.nodeToFold = nodeToFold;
+ cloner = new CloneExpressionVisitor(program);
+ }
+
+ public boolean transform(CfgNode<?> node, Cfg cfgGraph) {
+ Preconditions.checkArgument(nodeToFold == node);
+ JModVisitor visitor = new JModVisitor() {
+ @Override
+ public boolean visit(JVariableRef x, Context ctx) {
+ JNode newNode = transform(x);
+ if (newNode != null) {
+ ctx.replaceMe(newNode);
+ return false;
+ }
+ return true;
+ }
+ };
+
+ CfgNode<?> parentNode = nodeToFold.getParent();
+ JNode jnode = parentNode.getJNode();
+ Preconditions.checkNotNull(jnode);
+ visitor.accept(jnode);
+ Preconditions.checkState(visitor.didChange());
+ return true;
+ }
+
+ private JNode transform(JVariableRef ref) {
+ if (nodeToFold.getJNode() != ref) {
+ return null;
+ }
+ JVariable var = ref.getTarget();
+ JValueLiteral literal = assumption.get(var);
+ Preconditions.checkNotNull(literal);
+ return cloner.cloneExpression(literal);
+ }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/FoldConstantsTransformation.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/FoldConstantsTransformation.java
new file mode 100644
index 0000000..f3e3fbb
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/constants/FoldConstantsTransformation.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2008 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.gflow.constants;
+
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.impl.gflow.TransformationFunction.Transformation;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgTransformer;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgUtil;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNopNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgReadNode;
+
+/**
+ * Transformation that replaces read node with Nop node in graph, and replaces
+ * JNode by constant value.
+ */
+final class FoldConstantsTransformation implements
+ Transformation<CfgTransformer, Cfg> {
+ private final ConstantsAssumption assumption;
+ private final Cfg graph;
+ private final CfgReadNode node;
+ private final JProgram program;
+
+ FoldConstantsTransformation(JProgram program,
+ ConstantsAssumption assumptions,
+ CfgReadNode node, Cfg graph) {
+ this.program = program;
+ this.assumption = assumptions;
+ this.node = node;
+ this.graph = graph;
+ }
+
+ public CfgTransformer getGraphTransformer() {
+ return new FoldConstantTransformer(program, assumption, node);
+ }
+
+ public Cfg getNewSubgraph() {
+ CfgNode<?> newNode = new CfgNopNode(node.getParent(), node.getJNode());
+ return CfgUtil.createSingleNodeReplacementGraph(graph, node, newNode);
+ }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/copy/CopyAnalysis.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/copy/CopyAnalysis.java
new file mode 100644
index 0000000..3250d6f
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/copy/CopyAnalysis.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2008 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.gflow.copy;
+
+import com.google.gwt.dev.jjs.impl.gflow.Analysis;
+import com.google.gwt.dev.jjs.impl.gflow.AssumptionMap;
+import com.google.gwt.dev.jjs.impl.gflow.AssumptionUtil;
+import com.google.gwt.dev.jjs.impl.gflow.FlowFunction;
+import com.google.gwt.dev.jjs.impl.gflow.IntegratedAnalysis;
+import com.google.gwt.dev.jjs.impl.gflow.IntegratedFlowFunction;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgTransformer;
+
+/**
+ * Analysis which detects that one variable is the copy of the other,
+ * and uses older var instead.
+ */
+public class CopyAnalysis implements
+ Analysis<CfgNode<?>, CfgEdge, Cfg, CopyAssumption>,
+ IntegratedAnalysis<CfgNode<?>, CfgEdge, CfgTransformer, Cfg, CopyAssumption> {
+ public FlowFunction<CfgNode<?>, CfgEdge, Cfg, CopyAssumption> getFlowFunction() {
+ return new CopyFlowFunction();
+ }
+
+ public IntegratedFlowFunction<CfgNode<?>, CfgEdge, CfgTransformer, Cfg, CopyAssumption>
+ getIntegratedFlowFunction() {
+ return new CopyIntegratedFlowFunction();
+ }
+
+ public void setInitialGraphAssumptions(Cfg graph,
+ AssumptionMap<CfgEdge, CopyAssumption> assumptionMap) {
+ AssumptionUtil.setAssumptions(graph.getGraphInEdges(),
+ CopyAssumption.TOP, assumptionMap);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/copy/CopyAssumption.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/copy/CopyAssumption.java
new file mode 100644
index 0000000..cc728b6
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/copy/CopyAssumption.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2008 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.gflow.copy;
+
+import com.google.gwt.dev.jjs.ast.HasName;
+import com.google.gwt.dev.jjs.ast.JVariable;
+import com.google.gwt.dev.jjs.impl.gflow.Assumption;
+import com.google.gwt.dev.util.Preconditions;
+import com.google.gwt.dev.util.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Assumption class for CopyAnalysis.
+ */
+public class CopyAssumption implements Assumption<CopyAssumption> {
+ /**
+ * Top value for copy analysis. Means nothing is the copy of anything.
+ */
+ public static final CopyAssumption TOP = new CopyAssumption();
+
+ /**
+ * Updates the assumption by copying it on first write.
+ */
+ public static class Updater {
+ private CopyAssumption assumption;
+ private boolean copied = false;
+
+ public Updater(CopyAssumption assumption) {
+ this.assumption = assumption;
+ }
+
+ public void addCopy(JVariable original, JVariable targetVariable) {
+ Preconditions.checkArgument(original != targetVariable,
+ "Variable is a copy of itself: %s", original);
+ copyIfNeeded();
+ assumption.addCopy(original, targetVariable);
+ }
+
+ public JVariable getMostOriginal(JVariable variable) {
+ for (int i = 0; i < 10000; ++i) {
+ JVariable original = getOriginal(variable);
+ if (original == null) {
+ return variable;
+ }
+
+ variable = original;
+ }
+ // We shouldn't have cycle if we always call getMostOriginal() before
+ // invoking addCopy.
+ // This is a rudimentary cycle detection :)
+ throw new IllegalStateException("Possible cycle detected for: variable");
+ }
+
+ public JVariable getOriginal(JVariable variable) {
+ if (assumption == null || assumption == TOP) {
+ return null;
+ }
+
+ return assumption.getOriginal(variable);
+ }
+
+ public void kill(JVariable targetVariable) {
+ if (assumption == TOP) {
+ return;
+ }
+ copyIfNeeded();
+ assumption.kill(targetVariable);
+ }
+
+ public CopyAssumption unwrap() {
+ if (assumption == TOP) {
+ return assumption;
+ }
+ if (assumption != null && assumption.copyToOriginal.isEmpty()) {
+ return null;
+ }
+ return assumption;
+ }
+
+ private void copyIfNeeded() {
+ if (!copied) {
+ assumption = new CopyAssumption(assumption);
+ copied = true;
+ }
+ }
+ }
+
+ /**
+ * Map from copies to original values.
+ */
+ private final Map<JVariable, JVariable> copyToOriginal;
+
+ public CopyAssumption() {
+ copyToOriginal = new IdentityHashMap<JVariable, JVariable>();
+ }
+
+ public CopyAssumption(CopyAssumption result) {
+ if (result != null) {
+ copyToOriginal = new IdentityHashMap<JVariable, JVariable>(result.copyToOriginal);
+ } else {
+ copyToOriginal = new IdentityHashMap<JVariable, JVariable>();
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ CopyAssumption other = (CopyAssumption) obj;
+ return other.copyToOriginal.equals(copyToOriginal);
+ }
+
+ public JVariable getOriginal(JVariable v) {
+ return copyToOriginal.get(v);
+ }
+
+ @Override
+ public int hashCode() {
+ return copyToOriginal.hashCode();
+ }
+
+ public CopyAssumption join(CopyAssumption value) {
+ if (value == null) {
+ return this;
+ }
+
+ if (this == TOP || value == TOP) {
+ return TOP;
+ }
+
+ if (value.copyToOriginal.isEmpty() || copyToOriginal.isEmpty()) {
+ return null;
+ }
+
+ CopyAssumption result = new CopyAssumption();
+
+ for (JVariable v : copyToOriginal.keySet()) {
+ JVariable original = copyToOriginal.get(v);
+ if (original == value.copyToOriginal.get(v)) {
+ result.copyToOriginal.put(v, original);
+ } else {
+ result.copyToOriginal.put(v, null);
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ if (this == TOP) {
+ return "T";
+ }
+
+ StringBuffer result = new StringBuffer();
+
+ result.append("{");
+ List<JVariable> variables = new ArrayList<JVariable>(
+ copyToOriginal.keySet());
+ HasName.Util.sortByName(variables);
+ for (JVariable variable : variables) {
+ if (result.length() > 1) {
+ result.append(", ");
+ }
+ result.append(variable.getName());
+ result.append(" = ");
+ if (copyToOriginal.get(variable) == null) {
+ result.append("T");
+ } else {
+ result.append(copyToOriginal.get(variable).getName());
+ }
+ }
+ result.append("}");
+
+ return result.toString();
+ }
+
+ private void addCopy(JVariable original, JVariable copy) {
+ Preconditions.checkArgument(this != TOP);
+ copyToOriginal.put(copy, original);
+ }
+
+ private void kill(JVariable variable) {
+ copyToOriginal.put(variable, null);
+
+ for (JVariable v : Lists.create(copyToOriginal.keySet())) {
+ JVariable original = copyToOriginal.get(v);
+ if (original == variable) {
+ copyToOriginal.put(v, null);
+ }
+ }
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/copy/CopyFlowFunction.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/copy/CopyFlowFunction.java
new file mode 100644
index 0000000..4257b00
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/copy/CopyFlowFunction.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2008 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.gflow.copy;
+
+import com.google.gwt.dev.jjs.ast.JLocal;
+import com.google.gwt.dev.jjs.ast.JParameter;
+import com.google.gwt.dev.jjs.ast.JVariable;
+import com.google.gwt.dev.jjs.ast.JVariableRef;
+import com.google.gwt.dev.jjs.impl.gflow.AssumptionMap;
+import com.google.gwt.dev.jjs.impl.gflow.AssumptionUtil;
+import com.google.gwt.dev.jjs.impl.gflow.FlowFunction;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgReadWriteNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgVisitor;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgWriteNode;
+import com.google.gwt.dev.jjs.impl.gflow.copy.CopyAssumption.Updater;
+
+/**
+ * Flow function for CopyAnalysis.
+ */
+public class CopyFlowFunction implements
+ FlowFunction<CfgNode<?>, CfgEdge, Cfg, CopyAssumption> {
+ public void interpret(CfgNode<?> node,
+ Cfg g, AssumptionMap<CfgEdge, CopyAssumption> assumptionMap) {
+ CopyAssumption in = AssumptionUtil.join(g.getInEdges(node), assumptionMap);
+ final Updater result = new Updater(in);
+
+ node.accept(new CfgVisitor() {
+ @Override
+ public void visitReadWriteNode(CfgReadWriteNode node) {
+ JVariable targetVariable = node.getTargetVariable();
+ if (isSupportedVar(targetVariable)) {
+ result.kill(targetVariable);
+ }
+ }
+
+ @Override
+ public void visitWriteNode(CfgWriteNode node) {
+ JVariable targetVariable = node.getTargetVariable();
+ if (!isSupportedVar(targetVariable)) {
+ return;
+ }
+
+ if (!(node.getValue() instanceof JVariableRef)) {
+ result.kill(targetVariable);
+ return;
+ }
+
+ JVariable original = ((JVariableRef) node.getValue()).getTarget();
+ original = result.getMostOriginal(original);
+
+ if (original != targetVariable) {
+ result.kill(targetVariable);
+ if (isSupportedVar(original)) {
+ result.addCopy(original, targetVariable);
+ }
+ } else {
+ // We don't have to kill any assumptions after i = i assignment.
+ }
+ }
+
+ private boolean isSupportedVar(JVariable targetVariable) {
+ return targetVariable instanceof JParameter ||
+ targetVariable instanceof JLocal;
+ }
+ });
+
+ AssumptionUtil.setAssumptions(g.getOutEdges(node), result.unwrap(), assumptionMap);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/copy/CopyIntegratedFlowFunction.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/copy/CopyIntegratedFlowFunction.java
new file mode 100644
index 0000000..dd1e443
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/copy/CopyIntegratedFlowFunction.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2008 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.gflow.copy;
+
+import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.jjs.ast.Context;
+import com.google.gwt.dev.jjs.ast.JLocal;
+import com.google.gwt.dev.jjs.ast.JLocalRef;
+import com.google.gwt.dev.jjs.ast.JModVisitor;
+import com.google.gwt.dev.jjs.ast.JNode;
+import com.google.gwt.dev.jjs.ast.JParameter;
+import com.google.gwt.dev.jjs.ast.JParameterRef;
+import com.google.gwt.dev.jjs.ast.JVariable;
+import com.google.gwt.dev.jjs.ast.JVariableRef;
+import com.google.gwt.dev.jjs.impl.gflow.AssumptionMap;
+import com.google.gwt.dev.jjs.impl.gflow.AssumptionUtil;
+import com.google.gwt.dev.jjs.impl.gflow.IntegratedFlowFunction;
+import com.google.gwt.dev.jjs.impl.gflow.TransformationFunction.Transformation;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgReadNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgTransformer;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgUtil;
+import com.google.gwt.dev.util.Preconditions;
+
+/**
+ * Integrated flow function for CopyAnalysis. Tries to replace copied vars with
+ * original ones.
+ */
+public class CopyIntegratedFlowFunction implements
+ IntegratedFlowFunction<CfgNode<?>, CfgEdge, CfgTransformer, Cfg, CopyAssumption> {
+ private final class CopyTransformation implements
+ Transformation<CfgTransformer, Cfg> {
+ private final CfgNode<?> node;
+ private final JVariable original;
+ private final Cfg graph;
+
+ private CopyTransformation(CfgNode<?> node, JVariable original,
+ Cfg graph) {
+ this.node = node;
+ this.original = original;
+ this.graph = graph;
+ }
+
+ public CfgTransformer getGraphTransformer() {
+ return new CfgTransformer() {
+ public boolean transform(final CfgNode<?> node, Cfg cfgGraph) {
+ JModVisitor visitor = new JModVisitor() {
+ @Override
+ public void endVisit(JNode x, Context ctx) {
+ if (x == node.getJNode()) {
+ ctx.replaceMe(createRef(x.getSourceInfo(), original));
+ }
+ }
+ };
+ CfgNode<?> parentNode = node.getParent();
+ JNode parentJNode = parentNode.getJNode();
+ visitor.accept(parentJNode);
+ Preconditions.checkState(visitor.didChange());
+ return true;
+ }
+ };
+ }
+
+ public Cfg getNewSubgraph() {
+ CfgReadNode newNode = new CfgReadNode(node.getParent(),
+ createRef(node.getJNode().getSourceInfo(), original));
+ return CfgUtil.createSingleNodeReplacementGraph(graph, node, newNode);
+ }
+
+ @Override
+ public String toString() {
+ return "CopyTransformation(" + node + "," + original + ")";
+ }
+
+ private JVariableRef createRef(SourceInfo sourceInfo, JVariable variable) {
+ if (variable instanceof JLocal) {
+ return new JLocalRef(sourceInfo, (JLocal) variable);
+ } else if (variable instanceof JParameter) {
+ return new JParameterRef(sourceInfo, (JParameter) variable);
+ }
+ throw new IllegalArgumentException("Unsupported variable: " +
+ variable.getClass());
+ }
+ }
+
+ private static final CopyFlowFunction FLOW_FUNCTION = new CopyFlowFunction();
+
+ public Transformation<CfgTransformer, Cfg>
+ interpretOrReplace(final CfgNode<?> node, final Cfg graph,
+ AssumptionMap<CfgEdge, CopyAssumption> assumptionMap) {
+ CopyAssumption in = AssumptionUtil.join(
+ graph.getInEdges(node), assumptionMap);
+
+ if (in != null && node instanceof CfgReadNode) {
+ JVariable v = ((CfgReadNode) node).getTarget();
+ final JVariable original = in.getOriginal(v);
+ Preconditions.checkState(v != original,
+ "Variable is a copy of itself: %s", v);
+ if (original != null) {
+ return new CopyTransformation(node, original, graph);
+ }
+ }
+
+ FLOW_FUNCTION.interpret(node, graph, assumptionMap);
+ return null;
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/liveness/LivenessAnalysis.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/liveness/LivenessAnalysis.java
new file mode 100644
index 0000000..35aa7b2
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/liveness/LivenessAnalysis.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2008 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.gflow.liveness;
+
+import com.google.gwt.dev.jjs.impl.gflow.Analysis;
+import com.google.gwt.dev.jjs.impl.gflow.AssumptionMap;
+import com.google.gwt.dev.jjs.impl.gflow.FlowFunction;
+import com.google.gwt.dev.jjs.impl.gflow.IntegratedAnalysis;
+import com.google.gwt.dev.jjs.impl.gflow.IntegratedFlowFunction;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgTransformer;
+
+/**
+ * Analysis which detects when variable is not used after the assignment,
+ * and eliminates assignment.
+ */
+public class LivenessAnalysis implements Analysis<CfgNode<?>, CfgEdge, Cfg,
+ LivenessAssumption>, IntegratedAnalysis<CfgNode<?>, CfgEdge, CfgTransformer,
+ Cfg, LivenessAssumption> {
+ private static final LivenessFlowFunction FLOW_FUNCTION =
+ new LivenessFlowFunction();
+ private static final LivenessIntegratedFlowFunction INTEGRATED_FLOW_FUNCTION =
+ new LivenessIntegratedFlowFunction();
+
+ public FlowFunction<CfgNode<?>, CfgEdge, Cfg, LivenessAssumption> getFlowFunction() {
+ return FLOW_FUNCTION;
+ }
+
+ public IntegratedFlowFunction<CfgNode<?>, CfgEdge, CfgTransformer, Cfg,
+ LivenessAssumption>
+ getIntegratedFlowFunction() {
+ return INTEGRATED_FLOW_FUNCTION;
+ }
+
+ public void setInitialGraphAssumptions(Cfg graph,
+ AssumptionMap<CfgEdge, LivenessAssumption> assumptionMap) {
+ // bottom assumptions.
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/liveness/LivenessAssumption.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/liveness/LivenessAssumption.java
new file mode 100644
index 0000000..17ea867
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/liveness/LivenessAssumption.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2008 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.gflow.liveness;
+
+import com.google.gwt.dev.jjs.ast.JVariable;
+import com.google.gwt.dev.jjs.impl.gflow.Assumption;
+import com.google.gwt.dev.util.collect.IdentityHashSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Assumption for LivenessAnalysis. Contains set of all live (=used after)
+ * variables.
+ */
+public class LivenessAssumption implements Assumption<LivenessAssumption> {
+ /**
+ * Updates the assumption by copying it on first write.
+ */
+ public static class Updater {
+ private LivenessAssumption assumption;
+ private boolean copied = false;
+
+ public Updater(LivenessAssumption assumption) {
+ this.assumption = assumption;
+ }
+
+ public void kill(JVariable target) {
+ if (assumption == null || !assumption.isLive(target)) {
+ return;
+ }
+ copyIfNeeded();
+ assumption.kill(target);
+ }
+
+ public LivenessAssumption unwrap() {
+ if (assumption != null && assumption.liveVariables.isEmpty()) {
+ return null;
+ }
+ return assumption;
+ }
+
+ public void use(JVariable target) {
+ copyIfNeeded();
+ assumption.use(target);
+ }
+
+ private void copyIfNeeded() {
+ if (!copied) {
+ assumption = new LivenessAssumption(assumption);
+ copied = true;
+ }
+ }
+ }
+
+ /**
+ * Set of all live variables.
+ */
+ private final Set<JVariable> liveVariables = new IdentityHashSet<JVariable>();
+
+ public LivenessAssumption() {
+ super();
+ }
+
+ public LivenessAssumption(LivenessAssumption assumptions) {
+ if (assumptions != null) {
+ this.liveVariables.addAll(assumptions.liveVariables);
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ LivenessAssumption other = (LivenessAssumption) obj;
+ return liveVariables.equals(other.liveVariables);
+ }
+
+ @Override
+ public int hashCode() {
+ return liveVariables.hashCode();
+ }
+
+ public boolean isLive(JVariable variable) {
+ return liveVariables.contains(variable);
+ }
+
+ /**
+ * Computes union of all live variables.
+ */
+ public LivenessAssumption join(LivenessAssumption value) {
+ if (value == null || value.liveVariables.isEmpty()) {
+ return this;
+ }
+ if (liveVariables.isEmpty()) {
+ return value;
+ }
+ LivenessAssumption result = new LivenessAssumption(this);
+ result.liveVariables.addAll(value.liveVariables);
+ return result;
+ }
+
+ public String toDebugString() {
+ StringBuffer result = new StringBuffer();
+
+ result.append("{");
+ List<JVariable> vars = new ArrayList<JVariable>(liveVariables);
+ Collections.sort(vars, new Comparator<JVariable>() {
+ public int compare(JVariable o1, JVariable o2) {
+ return o1.getName().compareTo(o2.getName());
+ }
+ });
+ for (JVariable variable : vars) {
+ if (result.length() > 1) {
+ result.append(", ");
+ }
+ result.append(variable.getName());
+ }
+ result.append("}");
+
+ return result.toString();
+ }
+
+ @Override
+ public String toString() {
+ return toDebugString();
+ }
+
+ private void kill(JVariable variable) {
+ liveVariables.remove(variable);
+ }
+
+ private void use(JVariable variable) {
+ liveVariables.add(variable);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/liveness/LivenessFlowFunction.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/liveness/LivenessFlowFunction.java
new file mode 100644
index 0000000..0a68187
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/liveness/LivenessFlowFunction.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2008 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.gflow.liveness;
+
+import com.google.gwt.dev.jjs.ast.JLocal;
+import com.google.gwt.dev.jjs.ast.JParameter;
+import com.google.gwt.dev.jjs.ast.JVariable;
+import com.google.gwt.dev.jjs.impl.gflow.AssumptionMap;
+import com.google.gwt.dev.jjs.impl.gflow.AssumptionUtil;
+import com.google.gwt.dev.jjs.impl.gflow.FlowFunction;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgReadNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgReadWriteNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgVisitor;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgWriteNode;
+import com.google.gwt.dev.jjs.impl.gflow.liveness.LivenessAssumption.Updater;
+
+/**
+ * Flow function for Liveness Analysis.
+ */
+public class LivenessFlowFunction implements FlowFunction<CfgNode<?>, CfgEdge,
+ Cfg, LivenessAssumption> {
+ public void interpret(CfgNode<?> node, Cfg g,
+ AssumptionMap<CfgEdge, LivenessAssumption> assumptionMap) {
+ final Updater result = new Updater(
+ AssumptionUtil.join(g.getOutEdges(node), assumptionMap));
+
+ node.accept(new CfgVisitor() {
+ @Override
+ public void visitReadNode(CfgReadNode node) {
+ JVariable target = node.getTarget();
+ if (target instanceof JLocal || target instanceof JParameter) {
+ result.use(target);
+ }
+ }
+
+ @Override
+ public void visitReadWriteNode(CfgReadWriteNode node) {
+ JVariable target = node.getTargetVariable();
+ if (target instanceof JLocal || target instanceof JParameter) {
+ result.use(target);
+ }
+ }
+
+ @Override
+ public void visitWriteNode(CfgWriteNode node) {
+ JVariable target = node.getTargetVariable();
+ if (target instanceof JLocal || target instanceof JParameter) {
+ result.kill(target);
+ }
+ }
+ });
+
+ AssumptionUtil.setAssumptions(g.getInEdges(node), result.unwrap(), assumptionMap);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/liveness/LivenessIntegratedFlowFunction.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/liveness/LivenessIntegratedFlowFunction.java
new file mode 100644
index 0000000..2e8f322
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/liveness/LivenessIntegratedFlowFunction.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2008 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.gflow.liveness;
+
+import com.google.gwt.dev.jjs.ast.JLocal;
+import com.google.gwt.dev.jjs.ast.JParameter;
+import com.google.gwt.dev.jjs.ast.JVariable;
+import com.google.gwt.dev.jjs.impl.gflow.AssumptionMap;
+import com.google.gwt.dev.jjs.impl.gflow.AssumptionUtil;
+import com.google.gwt.dev.jjs.impl.gflow.IntegratedFlowFunction;
+import com.google.gwt.dev.jjs.impl.gflow.TransformationFunction.Transformation;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgTransformer;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgWriteNode;
+
+/**
+ *
+ */
+public class LivenessIntegratedFlowFunction implements
+ IntegratedFlowFunction<CfgNode<?>, CfgEdge, CfgTransformer, Cfg,
+ LivenessAssumption> {
+ private final LivenessFlowFunction flowFunction = new LivenessFlowFunction();
+
+ public Transformation<CfgTransformer, Cfg>
+ interpretOrReplace(CfgNode<?> node, Cfg graph,
+ AssumptionMap<CfgEdge, LivenessAssumption> assumptionMap) {
+ LivenessAssumption assumptions = AssumptionUtil.join(
+ graph.getOutEdges(node), assumptionMap);
+
+ if (node instanceof CfgWriteNode) {
+ CfgWriteNode write = (CfgWriteNode) node;
+ JVariable variable = write.getTargetVariable();
+ if ((variable instanceof JLocal || variable instanceof JParameter) &&
+ !isLive(assumptions, variable) && write.getValue() != null) {
+ return new LivenessTransformation(graph, write);
+ }
+ }
+
+ flowFunction.interpret(node, graph, assumptionMap);
+ return null;
+ }
+
+ private boolean isLive(LivenessAssumption assumptions, JVariable variable) {
+ return assumptions != null && assumptions.isLive(variable);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/liveness/LivenessTransformation.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/liveness/LivenessTransformation.java
new file mode 100644
index 0000000..b7a7086
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/liveness/LivenessTransformation.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2008 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.gflow.liveness;
+
+import com.google.gwt.dev.jjs.ast.Context;
+import com.google.gwt.dev.jjs.ast.JBinaryOperation;
+import com.google.gwt.dev.jjs.ast.JDeclarationStatement;
+import com.google.gwt.dev.jjs.ast.JExpression;
+import com.google.gwt.dev.jjs.ast.JExpressionStatement;
+import com.google.gwt.dev.jjs.ast.JModVisitor;
+import com.google.gwt.dev.jjs.ast.JNode;
+import com.google.gwt.dev.jjs.impl.gflow.TransformationFunction.Transformation;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNopNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgTransformer;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgUtil;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgWriteNode;
+import com.google.gwt.dev.util.Preconditions;
+
+/**
+ * Kill assignment. Leave rhs expression evaluation if it has side effects.
+ */
+public class LivenessTransformation implements
+ Transformation<CfgTransformer, Cfg> {
+ private final Cfg graph;
+ private final CfgWriteNode writeToKill;
+
+ public LivenessTransformation(Cfg cfg, CfgWriteNode writeToKill) {
+ this.graph = cfg;
+ this.writeToKill = writeToKill;
+ }
+
+ public CfgTransformer getGraphTransformer() {
+ return new CfgTransformer() {
+ public boolean transform(CfgNode<?> node, Cfg cfgGraph) {
+ JModVisitor visitor = new JModVisitor() {
+ @Override
+ public void endVisit(JBinaryOperation x, Context ctx) {
+ if (!shouldKill(x)) {
+ return;
+ }
+
+ ctx.replaceMe(x.getRhs());
+ }
+
+ @Override
+ public void endVisit(JDeclarationStatement x, Context ctx) {
+ if (writeToKill.getValue() != x.getInitializer() ||
+ x != writeToKill.getJNode()) {
+ return;
+ }
+
+ if (x.getInitializer().hasSideEffects()) {
+ ctx.insertBefore(x.getInitializer().makeStatement());
+ }
+
+ x.initializer = null;
+ didChange = true;
+ }
+
+ @Override
+ public boolean visit(JExpressionStatement x, Context ctx) {
+ JExpression expr = x.getExpr();
+ if (expr instanceof JBinaryOperation) {
+ JBinaryOperation binop = (JBinaryOperation) expr;
+ if (shouldKill(binop) &&
+ !binop.getRhs().hasSideEffects()) {
+ ctx.removeMe();
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean shouldKill(JBinaryOperation x) {
+ return writeToKill.getJNode() == x;
+ }
+ };
+
+ CfgNode<?> parentNode = CfgUtil.findParentOfContainingStatement(node);
+ Preconditions.checkNotNull(parentNode,
+ "Can't find parent of stmt of %s", node);
+ JNode parentJNode = parentNode.getJNode();
+ visitor.accept(parentJNode);
+ Preconditions.checkState(visitor.didChange(),
+ "Can't remove write in %s", node.getJNode());
+ return visitor.didChange();
+ }
+ };
+ }
+
+ public Cfg getNewSubgraph() {
+ CfgNode<?> newNode = new CfgNopNode(writeToKill.getParent(),
+ writeToKill.getJNode());
+ return CfgUtil.createSingleNodeReplacementGraph(graph, writeToKill,
+ newNode);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/package.html b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/package.html
new file mode 100644
index 0000000..419555b
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/package.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+ Copyright 2008 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.
+-->
+</head>
+<body bgcolor="white">
+
+<p>
+Abstract framework for flow-based optimizations.
+</p>
+
+This package defines abstractions and solvers for finding fixed point of
+lattice-based flow functions over arbitrary graphs. It also composes analyses
+with transformation as described in "Composing Dataflow Analyses and
+Transformations" paper. This paper is highly recommended to read before
+diving into framework, since we borrow lots of terminology and algorithms from
+there.
+
+The framework can be used for both inter- and intra- procedural analyses such
+as:
+<ul>
+<li>constant propagation</li>
+<li>copy propagation</li>
+<li>unreachable code elimination</li>
+<li>liveness analysis</li>
+<li>null analysis</li>
+<li>side effects analysis</li>
+<li>possible exceptions</li>
+</ul>
+
+and many others.
+
+If you are new to the framework, consider starting in the following order:
+{@link Graph}, {@link Analysis}, {@link AnalysisFollowedByTransformation},
+{@link CombinedIntegratedAnalysis}, {@link AnalysisSolver}.
+
+
+<h2>References</h2>
+
+<ul>
+<li>Flemming Nielson, Hanne Riis Nielson, Chris Hankin. Principles of Program
+Analysis</li>
+<li>Steven Muchnick. Advanced Compiler Design and Implementation.</li>
+<li>Sorin Lerner, David Grove, Craig Chamber. Composing Dataflow Analyses and
+Transformations.</li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/unreachable/DeleteNodeTransformation.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/unreachable/DeleteNodeTransformation.java
new file mode 100644
index 0000000..238ea9a
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/unreachable/DeleteNodeTransformation.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2008 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.gflow.unreachable;
+
+import com.google.gwt.dev.jjs.ast.JBlock;
+import com.google.gwt.dev.jjs.ast.JNode;
+import com.google.gwt.dev.jjs.impl.gflow.TransformationFunction.Transformation;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNopNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgStatementNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgTransformer;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgUtil;
+import com.google.gwt.dev.util.Preconditions;
+
+class DeleteNodeTransformation implements Transformation<CfgTransformer, Cfg> {
+ private final Cfg graph;
+ private final CfgNode<?> node;
+
+ public DeleteNodeTransformation(Cfg graph, CfgNode<?> node) {
+ this.graph = graph;
+ this.node = node;
+ }
+
+ public CfgTransformer getGraphTransformer() {
+ return new CfgTransformer() {
+ public boolean transform(CfgNode<?> node, Cfg cfgGraph) {
+ if (node.getParent() == null) {
+ throw new IllegalArgumentException("Null parent in " + node);
+ }
+
+ JNode jNode = node.getJNode();
+
+ if (node instanceof CfgStatementNode<?> && !(jNode instanceof JBlock)) {
+ // Don't try deleting inner expressions and blocks.
+ // Delete statements only.
+ CfgNode<?> parentNode = CfgUtil.findParentOfContainingStatement(node);
+ JNode parentJNode = parentNode.getJNode();
+ boolean didChange = DeleteNodeVisitor.delete(jNode, parentJNode);
+ Preconditions.checkState(didChange,
+ "Can't delete %s (%s) from under %s (%s)", jNode, node,
+ parentJNode, parentNode);
+ return true;
+ }
+
+ return false;
+ }
+ };
+ }
+
+ public Cfg getNewSubgraph() {
+ CfgNode<?> newNode = new CfgNopNode(node.getParent(), node.getJNode());
+ return CfgUtil.createSingleNodeReplacementGraph(graph, node, newNode);
+ }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/unreachable/DeleteNodeVisitor.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/unreachable/DeleteNodeVisitor.java
new file mode 100644
index 0000000..bf1cdc1
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/unreachable/DeleteNodeVisitor.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2008 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.gflow.unreachable;
+
+import com.google.gwt.dev.jjs.ast.Context;
+import com.google.gwt.dev.jjs.ast.JLabeledStatement;
+import com.google.gwt.dev.jjs.ast.JModVisitor;
+import com.google.gwt.dev.jjs.ast.JNode;
+
+final class DeleteNodeVisitor extends JModVisitor {
+ public static boolean delete(JNode node, JNode parentNode) {
+ DeleteNodeVisitor visitor = new DeleteNodeVisitor(node);
+ visitor.accept(parentNode);
+ return visitor.didChange();
+ }
+
+ private final JNode node;
+
+ public DeleteNodeVisitor(JNode node) {
+ this.node = node;
+ }
+
+ @Override
+ public boolean visit(JLabeledStatement x, Context ctx) {
+ if (!super.visit(x, ctx)) {
+ return false;
+ }
+
+ if (x.getBody() == node) {
+ // Remove node with its label.
+ ctx.removeMe();
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean visit(JNode x, Context ctx) {
+ if (didChange) {
+ return false;
+ }
+
+ if (x == node) {
+ ctx.removeMe();
+ return false;
+ }
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/unreachable/UnreachabeIntegratedTransformationFunction.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/unreachable/UnreachabeIntegratedTransformationFunction.java
new file mode 100644
index 0000000..97a1be9
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/unreachable/UnreachabeIntegratedTransformationFunction.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2008 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.gflow.unreachable;
+
+import com.google.gwt.dev.jjs.impl.gflow.AssumptionMap;
+import com.google.gwt.dev.jjs.impl.gflow.AssumptionUtil;
+import com.google.gwt.dev.jjs.impl.gflow.IntegratedFlowFunction;
+import com.google.gwt.dev.jjs.impl.gflow.TransformationFunction.Transformation;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNopNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgTransformer;
+
+/**
+ *
+ */
+public class UnreachabeIntegratedTransformationFunction implements
+ IntegratedFlowFunction<CfgNode<?>, CfgEdge, CfgTransformer, Cfg,
+ UnreachableAssumptions> {
+ public Transformation<CfgTransformer, Cfg>
+ interpretOrReplace(CfgNode<?> node, Cfg graph,
+ AssumptionMap<CfgEdge, UnreachableAssumptions> assumptionMap) {
+ UnreachableAssumptions in = AssumptionUtil.join(
+ graph.getInEdges(node), assumptionMap);
+
+ if (UnreachableAssumptions.isReachable(in)) {
+ AssumptionUtil.setAssumptions(graph.getOutEdges(node), UnreachableAssumptions.REACHABLE, assumptionMap);
+ return null;
+ }
+
+ if (node instanceof CfgNopNode) {
+ AssumptionUtil.setAssumptions(graph.getOutEdges(node), UnreachableAssumptions.UNREACHABLE, assumptionMap);
+ return null;
+ }
+
+ return new DeleteNodeTransformation(graph, node);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/unreachable/UnreachableAnalysis.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/unreachable/UnreachableAnalysis.java
new file mode 100644
index 0000000..91373a4
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/unreachable/UnreachableAnalysis.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2008 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.gflow.unreachable;
+
+import com.google.gwt.dev.jjs.impl.gflow.AssumptionMap;
+import com.google.gwt.dev.jjs.impl.gflow.AssumptionUtil;
+import com.google.gwt.dev.jjs.impl.gflow.IntegratedAnalysis;
+import com.google.gwt.dev.jjs.impl.gflow.IntegratedFlowFunction;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgTransformer;
+
+/**
+ *
+ */
+public class UnreachableAnalysis implements IntegratedAnalysis<CfgNode<?>,
+ CfgEdge, CfgTransformer, Cfg, UnreachableAssumptions> {
+ private static final UnreachabeIntegratedTransformationFunction
+ INTEGRATED_FLOW_FUNCTION = new UnreachabeIntegratedTransformationFunction();
+
+ public IntegratedFlowFunction<CfgNode<?>, CfgEdge, CfgTransformer, Cfg,
+ UnreachableAssumptions>
+ getIntegratedFlowFunction() {
+ return INTEGRATED_FLOW_FUNCTION;
+ }
+
+ public void setInitialGraphAssumptions(Cfg graph,
+ AssumptionMap<CfgEdge, UnreachableAssumptions> assumptionMap) {
+ AssumptionUtil.setAssumptions(graph.getGraphInEdges(),
+ UnreachableAssumptions.REACHABLE, assumptionMap);
+
+ AssumptionUtil.setAssumptions(graph.getGraphOutEdges(),
+ UnreachableAssumptions.UNREACHABLE, assumptionMap);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/unreachable/UnreachableAssumptions.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/unreachable/UnreachableAssumptions.java
new file mode 100644
index 0000000..aff1dff
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/unreachable/UnreachableAssumptions.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2008 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.gflow.unreachable;
+
+import com.google.gwt.dev.jjs.impl.gflow.Assumption;
+
+/**
+ *
+ */
+public class UnreachableAssumptions implements Assumption<UnreachableAssumptions> {
+ public static final UnreachableAssumptions REACHABLE = new UnreachableAssumptions();
+ public static final UnreachableAssumptions UNREACHABLE = new UnreachableAssumptions();
+
+ public static boolean isReachable(UnreachableAssumptions in) {
+ return in == REACHABLE;
+ }
+
+ public UnreachableAssumptions join(UnreachableAssumptions value) {
+ if (this == REACHABLE || value == REACHABLE) {
+ return REACHABLE;
+ }
+ return UNREACHABLE;
+ }
+
+ @Override
+ public String toString() {
+ return this == REACHABLE ? "T" : "F";
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/Either.java b/dev/core/src/com/google/gwt/dev/util/Either.java
new file mode 100644
index 0000000..2c892de
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/Either.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2008 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;
+
+/**
+ * Functional-language like either type. Holds either left or right value.
+ * Values must be non-null.
+ *
+ * @param <L> left type.
+ * @param <R> right type.
+ */
+public class Either<L, R> {
+ public static <L, R> Either<L, R> left(L l) {
+ if (l == null) {
+ throw new IllegalArgumentException();
+ }
+ return new Either<L, R>(l, null);
+ }
+
+ public static <L, R> Either<L, R> right(R r) {
+ if (r == null) {
+ throw new IllegalArgumentException();
+ }
+ return new Either<L, R>(null, r);
+ }
+
+ private final L left;
+
+ private final R right;
+
+ private Either(L a, R b) {
+ left = a;
+ right = b;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Either other = (Either) obj;
+ if (left != null && other.left != null) {
+ return left.equals(other.left);
+ } else if (right != null && other.right != null) {
+ return right.equals(other.right);
+ }
+ return false;
+ }
+
+ public L getLeft() {
+ Preconditions.checkNotNull(left);
+ return left;
+ }
+
+ public R getRight() {
+ Preconditions.checkNotNull(right);
+ return right;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((left == null) ? 0 : left.hashCode());
+ result = prime * result + ((right == null) ? 0 : right.hashCode());
+ return result;
+ }
+
+ public boolean isLeft() {
+ return left != null;
+ }
+
+ public boolean isRight() {
+ return right != null;
+ }
+
+ @Override
+ public String toString() {
+ if (isLeft()) {
+ return "L{" + left.toString() + "}";
+ }
+ return "R{" + right.toString() + "}";
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/Pair.java b/dev/core/src/com/google/gwt/dev/util/Pair.java
new file mode 100644
index 0000000..5f630ff
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/Pair.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2008 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;
+
+/**
+ * @param <L>
+ * @param <R>
+ */
+public class Pair<L, R> {
+ public static <L, R> Pair<L, R> create(L l, R r) {
+ return new Pair<L, R>(l, r);
+ }
+
+ public final L left;
+ public final R right;
+
+ private Pair(L left, R right) {
+ this.left = left;
+ this.right = right;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Pair other = (Pair) obj;
+ if (left == null) {
+ if (other.left != null) {
+ return false;
+ }
+ } else if (!left.equals(other.left)) {
+ return false;
+ }
+ if (right == null) {
+ if (other.right != null) {
+ return false;
+ }
+ } else if (!right.equals(other.right)) {
+ return false;
+ }
+ return true;
+ }
+
+ public L getLeft() {
+ return left;
+ }
+
+ public R getRight() {
+ return right;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((left == null) ? 0 : left.hashCode());
+ result = prime * result + ((right == null) ? 0 : right.hashCode());
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "(" + left + "," + right + ")";
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/Preconditions.java b/dev/core/src/com/google/gwt/dev/util/Preconditions.java
new file mode 100644
index 0000000..bbe4cf5
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/Preconditions.java
@@ -0,0 +1,398 @@
+/*
+ * Copyright 2009 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;
+
+/**
+ * Simple static methods to be called at the start of your own methods to verify
+ * correct arguments and state. This allows constructs such as
+ * <pre>
+ * if (count <= 0) {
+ * throw new IllegalArgumentException("must be positive: " + count);
+ * }</pre>
+ *
+ * to be replaced with the more compact
+ * <pre>
+ * checkArgument(count > 0, "must be positive: %s", count);</pre>
+ *
+ * Note that the sense of the expression is inverted; with {@code Preconditions}
+ * you declare what you expect to be <i>true</i>, just as you do with an
+ * <a href="http://java.sun.com/j2se/1.5.0/docs/guide/language/assert.html">
+ * {@code assert}</a> or a JUnit {@code assertTrue} call.
+ *
+ * <p><b>Warning:</b> only the {@code "%s"} specifier is recognized as a
+ * placeholder in these messages, not the full range of {@link
+ * String#format(String, Object[])} specifiers.
+ *
+ * <p>Take care not to confuse precondition checking with other similar types
+ * of checks! Precondition exceptions -- including those provided here, but also
+ * {@link IndexOutOfBoundsException}, {@link
+ * UnsupportedOperationException} and others -- are used to signal that the
+ * <i>calling method</i> has made an error. This tells the caller that it should
+ * not have invoked the method when it did, with the arguments it did, or
+ * perhaps ever. Postcondition or other invariant failures should not throw
+ * these types of exceptions.
+ *
+ * <p>This class was adapted from google-collections project.
+ */
+public final class Preconditions {
+ /**
+ * Ensures the truth of an expression involving one or more parameters to the
+ * calling method.
+ *
+ * @param expression a boolean expression
+ * @throws IllegalArgumentException if {@code expression} is false
+ */
+ public static void checkArgument(boolean expression) {
+ if (!expression) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * Ensures the truth of an expression involving one or more parameters to the
+ * calling method.
+ *
+ * @param expression a boolean expression
+ * @param errorMessage the exception message to use if the check fails; will
+ * be converted to a string using {@link String#valueOf(Object)}
+ * @throws IllegalArgumentException if {@code expression} is false
+ */
+ public static void checkArgument(boolean expression, Object errorMessage) {
+ if (!expression) {
+ throw new IllegalArgumentException(String.valueOf(errorMessage));
+ }
+ }
+
+ /**
+ * Ensures the truth of an expression involving one or more parameters to the
+ * calling method.
+ *
+ * @param expression a boolean expression
+ * @param errorMessageTemplate a template for the exception message should the
+ * check fail. The message is formed by replacing each {@code %s}
+ * placeholder in the template with an argument. These are matched by
+ * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc.
+ * Unmatched arguments will be appended to the formatted message in square
+ * braces. Unmatched placeholders will be left as-is.
+ * @param errorMessageArgs the arguments to be substituted into the message
+ * template. Arguments are converted to strings using
+ * {@link String#valueOf(Object)}.
+ * @throws IllegalArgumentException if {@code expression} is false
+ * @throws NullPointerException if the check fails and either {@code
+ * errorMessageTemplate} or {@code errorMessageArgs} is null (don't let
+ * this happen)
+ */
+ public static void checkArgument(boolean expression,
+ String errorMessageTemplate, Object... errorMessageArgs) {
+ if (!expression) {
+ throw new IllegalArgumentException(
+ format(errorMessageTemplate, errorMessageArgs));
+ }
+ }
+
+ /**
+ * Ensures that {@code index} specifies a valid <i>element</i> in an array,
+ * list or string of size {@code size}. An element index may range from zero,
+ * inclusive, to {@code size}, exclusive.
+ *
+ * @param index a user-supplied index identifying an element of an array, list
+ * or string
+ * @param size the size of that array, list or string
+ * @return the value of {@code index}
+ * @throws IndexOutOfBoundsException if {@code index} is negative or is not
+ * less than {@code size}
+ * @throws IllegalArgumentException if {@code size} is negative
+ */
+ public static int checkElementIndex(int index, int size) {
+ return checkElementIndex(index, size, "index");
+ }
+
+ /**
+ * Ensures that {@code index} specifies a valid <i>element</i> in an array,
+ * list or string of size {@code size}. An element index may range from zero,
+ * inclusive, to {@code size}, exclusive.
+ *
+ * @param index a user-supplied index identifying an element of an array, list
+ * or string
+ * @param size the size of that array, list or string
+ * @param desc the text to use to describe this index in an error message
+ * @return the value of {@code index}
+ * @throws IndexOutOfBoundsException if {@code index} is negative or is not
+ * less than {@code size}
+ * @throws IllegalArgumentException if {@code size} is negative
+ */
+ public static int checkElementIndex(int index, int size, String desc) {
+ // Carefully optimized for execution by hotspot (explanatory comment above)
+ if (index < 0 || index >= size) {
+ throw new IndexOutOfBoundsException(badElementIndex(index, size, desc));
+ }
+ return index;
+ }
+
+ /**
+ * Ensures that an object reference passed as a parameter to the calling
+ * method is not null.
+ *
+ * @param reference an object reference
+ * @return the non-null reference that was validated
+ * @throws NullPointerException if {@code reference} is null
+ */
+ public static <T> T checkNotNull(T reference) {
+ if (reference == null) {
+ throw new NullPointerException();
+ }
+ return reference;
+ }
+
+ /**
+ * Ensures that an object reference passed as a parameter to the calling
+ * method is not null.
+ *
+ * @param reference an object reference
+ * @param errorMessage the exception message to use if the check fails; will
+ * be converted to a string using {@link String#valueOf(Object)}
+ * @return the non-null reference that was validated
+ * @throws NullPointerException if {@code reference} is null
+ */
+ public static <T> T checkNotNull(T reference, Object errorMessage) {
+ if (reference == null) {
+ throw new NullPointerException(String.valueOf(errorMessage));
+ }
+ return reference;
+ }
+
+ /**
+ * Ensures that an object reference passed as a parameter to the calling
+ * method is not null.
+ *
+ * @param reference an object reference
+ * @param errorMessageTemplate a template for the exception message should the
+ * check fail. The message is formed by replacing each {@code %s}
+ * placeholder in the template with an argument. These are matched by
+ * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc.
+ * Unmatched arguments will be appended to the formatted message in square
+ * braces. Unmatched placeholders will be left as-is.
+ * @param errorMessageArgs the arguments to be substituted into the message
+ * template. Arguments are converted to strings using
+ * {@link String#valueOf(Object)}.
+ * @return the non-null reference that was validated
+ * @throws NullPointerException if {@code reference} is null
+ */
+ public static <T> T checkNotNull(T reference, String errorMessageTemplate,
+ Object... errorMessageArgs) {
+ if (reference == null) {
+ // If either of these parameters is null, the right thing happens anyway
+ throw new NullPointerException(
+ format(errorMessageTemplate, errorMessageArgs));
+ }
+ return reference;
+ }
+
+ /**
+ * Ensures that {@code index} specifies a valid <i>position</i> in an array,
+ * list or string of size {@code size}. A position index may range from zero
+ * to {@code size}, inclusive.
+ *
+ * @param index a user-supplied index identifying a position in an array, list
+ * or string
+ * @param size the size of that array, list or string
+ * @return the value of {@code index}
+ * @throws IndexOutOfBoundsException if {@code index} is negative or is
+ * greater than {@code size}
+ * @throws IllegalArgumentException if {@code size} is negative
+ */
+ public static int checkPositionIndex(int index, int size) {
+ return checkPositionIndex(index, size, "index");
+ }
+
+ /**
+ * Ensures that {@code index} specifies a valid <i>position</i> in an array,
+ * list or string of size {@code size}. A position index may range from zero
+ * to {@code size}, inclusive.
+ *
+ * @param index a user-supplied index identifying a position in an array, list
+ * or string
+ * @param size the size of that array, list or string
+ * @param desc the text to use to describe this index in an error message
+ * @return the value of {@code index}
+ * @throws IndexOutOfBoundsException if {@code index} is negative or is
+ * greater than {@code size}
+ * @throws IllegalArgumentException if {@code size} is negative
+ */
+ public static int checkPositionIndex(int index, int size, String desc) {
+ // Carefully optimized for execution by hotspot (explanatory comment above)
+ if (index < 0 || index > size) {
+ throw new IndexOutOfBoundsException(badPositionIndex(index, size, desc));
+ }
+ return index;
+ }
+
+ /**
+ * Ensures that {@code start} and {@code end} specify a valid <i>positions</i>
+ * in an array, list or string of size {@code size}, and are in order. A
+ * position index may range from zero to {@code size}, inclusive.
+ *
+ * @param start a user-supplied index identifying a starting position in an
+ * array, list or string
+ * @param end a user-supplied index identifying a ending position in an array,
+ * list or string
+ * @param size the size of that array, list or string
+ * @throws IndexOutOfBoundsException if either index is negative or is
+ * greater than {@code size}, or if {@code end} is less than {@code start}
+ * @throws IllegalArgumentException if {@code size} is negative
+ */
+ public static void checkPositionIndexes(int start, int end, int size) {
+ // Carefully optimized for execution by hotspot (explanatory comment above)
+ if (start < 0 || end < start || end > size) {
+ throw new IndexOutOfBoundsException(badPositionIndexes(start, end, size));
+ }
+ }
+
+ /**
+ * Ensures the truth of an expression involving the state of the calling
+ * instance, but not involving any parameters to the calling method.
+ *
+ * @param expression a boolean expression
+ * @throws IllegalStateException if {@code expression} is false
+ */
+ public static void checkState(boolean expression) {
+ if (!expression) {
+ throw new IllegalStateException();
+ }
+ }
+
+ /**
+ * Ensures the truth of an expression involving the state of the calling
+ * instance, but not involving any parameters to the calling method.
+ *
+ * @param expression a boolean expression
+ * @param errorMessage the exception message to use if the check fails; will
+ * be converted to a string using {@link String#valueOf(Object)}
+ * @throws IllegalStateException if {@code expression} is false
+ */
+ public static void checkState(boolean expression, Object errorMessage) {
+ if (!expression) {
+ throw new IllegalStateException(String.valueOf(errorMessage));
+ }
+ }
+
+ /**
+ * Ensures the truth of an expression involving the state of the calling
+ * instance, but not involving any parameters to the calling method.
+ *
+ * @param expression a boolean expression
+ * @param errorMessageTemplate a template for the exception message should the
+ * check fail. The message is formed by replacing each {@code %s}
+ * placeholder in the template with an argument. These are matched by
+ * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc.
+ * Unmatched arguments will be appended to the formatted message in square
+ * braces. Unmatched placeholders will be left as-is.
+ * @param errorMessageArgs the arguments to be substituted into the message
+ * template. Arguments are converted to strings using
+ * {@link String#valueOf(Object)}.
+ * @throws IllegalStateException if {@code expression} is false
+ * @throws NullPointerException if the check fails and either {@code
+ * errorMessageTemplate} or {@code errorMessageArgs} is null (don't let
+ * this happen)
+ */
+ public static void checkState(boolean expression,
+ String errorMessageTemplate, Object... errorMessageArgs) {
+ if (!expression) {
+ throw new IllegalStateException(
+ format(errorMessageTemplate, errorMessageArgs));
+ }
+ }
+
+ /**
+ * Substitutes each {@code %s} in {@code template} with an argument. These
+ * are matched by position - the first {@code %s} gets {@code args[0]}, etc.
+ * If there are more arguments than placeholders, the unmatched arguments will
+ * be appended to the end of the formatted message in square braces.
+ *
+ * @param template a non-null string containing 0 or more {@code %s}
+ * placeholders.
+ * @param args the arguments to be substituted into the message
+ * template. Arguments are converted to strings using
+ * {@link String#valueOf(Object)}. Arguments can be null.
+ */
+ static String format(String template, Object... args) {
+ // start substituting the arguments into the '%s' placeholders
+ StringBuilder builder = new StringBuilder(
+ template.length() + 16 * args.length);
+ int templateStart = 0;
+ int i = 0;
+ while (i < args.length) {
+ int placeholderStart = template.indexOf("%s", templateStart);
+ if (placeholderStart == -1) {
+ break;
+ }
+ builder.append(template.substring(templateStart, placeholderStart));
+ builder.append(args[i++]);
+ templateStart = placeholderStart + 2;
+ }
+ builder.append(template.substring(templateStart));
+
+ // if we run out of placeholders, append the extra args in square braces
+ if (i < args.length) {
+ builder.append(" [");
+ builder.append(args[i++]);
+ while (i < args.length) {
+ builder.append(", ");
+ builder.append(args[i++]);
+ }
+ builder.append("]");
+ }
+
+ return builder.toString();
+ }
+
+ private static String badElementIndex(int index, int size, String desc) {
+ if (index < 0) {
+ return format("%s (%s) must not be negative", desc, index);
+ } else if (size < 0) {
+ throw new IllegalArgumentException("negative size: " + size);
+ } else { // index >= size
+ return format("%s (%s) must be less than size (%s)", desc, index, size);
+ }
+ }
+
+ private static String badPositionIndex(int index, int size, String desc) {
+ if (index < 0) {
+ return format("%s (%s) must not be negative", desc, index);
+ } else if (size < 0) {
+ throw new IllegalArgumentException("negative size: " + size);
+ } else { // index > size
+ return format("%s (%s) must not be greater than size (%s)",
+ desc, index, size);
+ }
+ }
+
+ private static String badPositionIndexes(int start, int end, int size) {
+ if (start < 0 || start > size) {
+ return badPositionIndex(start, size, "start index");
+ }
+ if (end < 0 || end > size) {
+ return badPositionIndex(end, size, "end index");
+ }
+ // end < start
+ return format("end index (%s) must not be less than start index (%s)",
+ end, start);
+ }
+
+ private Preconditions() {
+ // static methods class only
+ }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/util/Strings.java b/dev/core/src/com/google/gwt/dev/util/Strings.java
new file mode 100644
index 0000000..625102a
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/Strings.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2008 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;
+
+/**
+ * String manipulation utilities.
+ */
+public class Strings {
+ /**
+ * Join strings inserting separator between them.
+ */
+ public static String join(String[] strings, String separator) {
+ StringBuffer result = new StringBuffer();
+
+ for (String s : strings) {
+ if (result.length() != 0) {
+ result.append(separator);
+ }
+ result.append(s);
+ }
+
+ return result.toString();
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/collect/Lists.java b/dev/core/src/com/google/gwt/dev/util/collect/Lists.java
index 5d7cff6..8498265 100644
--- a/dev/core/src/com/google/gwt/dev/util/collect/Lists.java
+++ b/dev/core/src/com/google/gwt/dev/util/collect/Lists.java
@@ -17,6 +17,7 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -176,6 +177,26 @@
return Collections.emptyList();
}
+ public static <T> List<T> create(Collection<T> collection) {
+ switch (collection.size()) {
+ case 0 :
+ return create();
+ default:
+ return new ArrayList<T>(collection);
+ }
+ }
+
+ public static <T> List<T> create(List<T> list) {
+ switch (list.size()) {
+ case 0:
+ return create();
+ case 1:
+ return create(list.get(0));
+ default:
+ return new ArrayList<T>(list);
+ }
+ }
+
public static <T> List<T> create(T item) {
return Collections.singletonList(item);
}
@@ -209,7 +230,7 @@
}
}
- @SuppressWarnings("unchecked")
+ @SuppressWarnings("unchecked")
public static <T> List<T> normalizeUnmodifiable(List<T> list) {
if (list.size() < 2) {
return normalize(list);
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/DeadCodeEliminationTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/DeadCodeEliminationTest.java
index 02b1f2c..6f4e74b 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/DeadCodeEliminationTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/DeadCodeEliminationTest.java
@@ -122,6 +122,14 @@
optimize("void", "if (b) {i = 1;}").intoString(
"EntryPoint.b && (EntryPoint.i = 1);");
}
+
+ public void testDoOptimization() throws Exception {
+ optimize("void", "do {} while (b);").intoString("do { } while (EntryPoint.b);");
+ optimize("void", "do {} while (true);").intoString("do { } while (true);");
+ optimize("void", "do {} while (false);").intoString("");
+ optimize("void", "do { i++; } while (false);").intoString("++EntryPoint.i;");
+ optimize("void", "do { break; } while (false);").intoString("do { break; } while (false);");
+ }
private Result optimize(final String returnType, final String codeSnippet)
throws UnableToCompleteException {
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/OptimizerTestBase.java b/dev/core/test/com/google/gwt/dev/jjs/impl/OptimizerTestBase.java
index e743928..d1bf20d 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/OptimizerTestBase.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/OptimizerTestBase.java
@@ -42,6 +42,8 @@
*/
public abstract class OptimizerTestBase extends TestCase {
+ public static final String MAIN_METHOD_NAME = "onModuleLoad";
+
/**
* Finds a field with a type.
*/
@@ -87,7 +89,7 @@
}
public static JMethod findMainMethod(JProgram program) {
- return findMethod(program, "onModuleLoad");
+ return findMethod(program, MAIN_METHOD_NAME);
}
public static JMethod findMethod(JDeclaredType type, String methodName) {
@@ -205,10 +207,29 @@
return code;
}
});
+ addBuiltinClasses(sourceOracle);
CompilationState state = CompilationStateBuilder.buildFrom(logger,
sourceOracle.getResources());
JProgram program = JavaAstConstructor.construct(logger, state,
- "test.EntryPoint");
+ "test.EntryPoint", "com.google.gwt.lang.Exceptions");
return program;
}
+
+ protected void addBuiltinClasses(MockResourceOracle sourceOracle) {
+ sourceOracle.addOrReplace(new MockJavaResource("java.lang.RuntimeException") {
+ @Override
+ protected CharSequence getContent() {
+ return "package java.lang;" +
+ "public class RuntimeException extends Exception { }";
+ }
+ });
+
+ sourceOracle.addOrReplace(new MockJavaResource("com.google.gwt.lang.Exceptions") {
+ @Override
+ protected CharSequence getContent() {
+ return "package com.google.gwt.lang;" +
+ "public class Exceptions { static boolean throwAssertionError() { throw new RuntimeException(); } }";
+ }
+ });
+}
}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/CfgAnalysisTestBase.java b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/CfgAnalysisTestBase.java
new file mode 100644
index 0000000..7cda7ba
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/CfgAnalysisTestBase.java
@@ -0,0 +1,50 @@
+package com.google.gwt.dev.jjs.impl.gflow;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.jjs.ast.JMethodBody;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.impl.OptimizerTestBase;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.AssumptionsPrinter;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgBuilder;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+import com.google.gwt.dev.util.Strings;
+
+import java.util.Map;
+
+public abstract class CfgAnalysisTestBase<A extends Assumption<A>>
+ extends OptimizerTestBase {
+ protected boolean forward = true;
+
+ protected AnalysisResult analyze(String returnType, String... codeSnippet)
+ throws UnableToCompleteException {
+ JProgram program = compileSnippet(returnType, Strings.join(codeSnippet,
+ "\n"));
+ JMethodBody body = (JMethodBody) findMainMethod(program).getBody();
+ Cfg cfgGraph = CfgBuilder.build(program, body.getBlock());
+
+ assertNotNull(cfgGraph);
+
+ Map<CfgEdge, A> map = AnalysisSolver.solve(cfgGraph, createAnalysis(program), forward);
+ return new AnalysisResult(cfgGraph, map);
+ }
+
+ protected abstract Analysis<CfgNode<?>, CfgEdge, Cfg, A> createAnalysis(JProgram program);
+
+ protected class AnalysisResult {
+ private final Map<CfgEdge, A> assumptions;
+ private final Cfg graph;
+
+ public AnalysisResult(Cfg graph,
+ Map<CfgEdge, A> assumptions) {
+ this.graph = graph;
+ this.assumptions = assumptions;
+ }
+
+ public void into(String... expected) {
+ String actual = new AssumptionsPrinter<A>(graph, assumptions).print();
+ assertEquals(Strings.join(expected, "\n"), actual);
+ }
+ }
+}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/CfgIntegratedAnalysisTestBase.java b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/CfgIntegratedAnalysisTestBase.java
new file mode 100644
index 0000000..9882273
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/CfgIntegratedAnalysisTestBase.java
@@ -0,0 +1,60 @@
+package com.google.gwt.dev.jjs.impl.gflow;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.jjs.ast.JBlock;
+import com.google.gwt.dev.jjs.ast.JMethodBody;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.impl.OptimizerTestBase;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgBuilder;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgTransformer;
+import com.google.gwt.dev.util.Strings;
+
+public abstract class CfgIntegratedAnalysisTestBase<A extends Assumption<A>>
+ extends OptimizerTestBase {
+ protected boolean forward = true;
+
+ protected Result transform(String returnType, String... codeSnippet)
+ throws UnableToCompleteException {
+ JProgram program = compileSnippet(returnType, Strings.join(codeSnippet,
+ "\n"));
+ JMethodBody body = (JMethodBody) findMainMethod(program).getBody();
+ Cfg cfgGraph = CfgBuilder.build(program, body.getBlock());
+
+ assertNotNull(cfgGraph);
+
+ AnalysisSolver.solveIntegrated(cfgGraph, createIntegratedAnalysis(program), forward);
+ return new Result(program);
+ }
+
+ protected static class Result {
+ private JProgram program;
+
+ public Result(JProgram program) {
+ this.program = program;
+ }
+
+ public void into(String... expected) {
+ String joinedE = "";
+ for (int i = 0; i < expected.length; ++i) {
+ if (i > 0) {
+ joinedE += "\n";
+ }
+ joinedE += expected[i];
+ }
+ JBlock block = ((JMethodBody) findMainMethod(program).getBody()).getBlock();
+
+ String actual = block.toSource();
+ assertTrue(actual.startsWith("{\n"));
+ assertTrue(actual.endsWith("\n}"));
+ actual = actual.substring(2, actual.length() - 2).trim();
+ actual = actual.replaceAll("\\n ", "\n");
+ assertEquals(joinedE, actual);
+ }
+ }
+
+ protected abstract IntegratedAnalysis<CfgNode<?>, CfgEdge, CfgTransformer, Cfg, A> createIntegratedAnalysis(
+ JProgram program);
+}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/DataflowOptimizerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/DataflowOptimizerTest.java
new file mode 100644
index 0000000..bb9e0b0
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/DataflowOptimizerTest.java
@@ -0,0 +1,256 @@
+package com.google.gwt.dev.jjs.impl.gflow;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.impl.OptimizerTestBase;
+import com.google.gwt.dev.jjs.impl.gflow.DataflowOptimizer;
+import com.google.gwt.dev.util.Strings;
+
+public class DataflowOptimizerTest extends OptimizerTestBase {
+ private final class Result {
+ private final String optimized;
+ private final String returnType;
+ private final String userCode;
+ private final boolean madeChages;
+
+ public Result(String returnType, String userCode, String optimized,
+ boolean madeChages) {
+ this.returnType = returnType;
+ this.userCode = userCode;
+ this.optimized = optimized;
+ this.madeChages = madeChages;
+ }
+
+ public void into(String...expected) throws UnableToCompleteException {
+ JProgram program = compileSnippet(returnType, Strings.join(expected, "\n"));
+ assertEquals(userCode, getMainMethodSource(program), optimized);
+ }
+
+ public void intoString(String...expected) {
+ String expectedSnippet = Strings.join(expected, "\n");
+ assertEquals(userCode, expectedSnippet, optimized);
+ }
+
+ public void noChange() {
+ assertFalse(madeChages);
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ addSnippetClassDecl("static void foo(int i) { }");
+ addSnippetClassDecl("static boolean bar() { return true; }");
+ addSnippetClassDecl("static void baz(boolean b) { }");
+ addSnippetClassDecl("static int genInt() { return 1; }");
+ addSnippetClassDecl("static int multiply(int i, int j) { return i * j; }");
+
+ addSnippetClassDecl("static class CheckedException extends Exception {}");
+ addSnippetClassDecl("static class UncheckedException1 extends RuntimeException {}");
+ addSnippetClassDecl("static class UncheckedException2 extends RuntimeException {}");
+
+ addSnippetClassDecl("static void throwUncheckedException1() " +
+ "throws UncheckedException1 {}");
+ addSnippetClassDecl("static void throwCheckedException() " +
+ "throws CheckedException {}");
+ addSnippetClassDecl("static void throwSeveralExceptions() " +
+ "throws CheckedException, UncheckedException1 {}");
+
+ addSnippetClassDecl("static class Foo { int i; int j; int k; }");
+ addSnippetClassDecl("static Foo createFoo() {return null;}");
+ addSnippetClassDecl("static Foo staticFooInstance;");
+ }
+
+ public void testLinearStatements1() throws Exception {
+ optimize("int", "int i = 1; int j = i; return i;").into(
+ "int i; int j; return 1;");
+ }
+
+ public void testLinearStatements2() throws Exception {
+ optimize("int", "int i = 1; int j = i; return j;").into(
+ "int i; int j; return 1;");
+ }
+
+ public void testLinearStatements3() throws Exception {
+ optimize("void", "int i = 1; int j = 1; foo(j);").into(
+ "int i; int j; foo(1);");
+ }
+
+ public void testDeadIfBranch1() throws Exception {
+ optimize("void",
+ "int i = 1; if (i == 1) { int j = 2; } else { int j = 3; }").into(
+ "int i; { int j; } ");
+ }
+
+ public void testDeadIfBranch2() throws Exception {
+ optimize("void",
+ "int i = 1; if (i != 1) { int j = 2; } else { int j = 3; }").into(
+ "int i; { int j; } ");
+ }
+
+ public void testDeadIfBranch3() throws Exception {
+ optimize("int",
+ "int i = 1; int j = 0; if (i != 1) { j = 2; } else { j = 3; } return j;").into(
+ "int i; int j; return 3; ");
+ }
+
+ public void testDeadIfBranch4() throws Exception {
+ addSnippetClassDecl("static Object f = null;");
+ optimize("void",
+ "Object e = null;" +
+ "if (e == null && f == null) {" +
+ " return;" +
+ "}" +
+ "boolean b = e == null;").into(
+ "Object e; if (EntryPoint.f == null) { return; } boolean b;"
+ );
+ }
+
+ public void testDeadWhile() throws Exception {
+ optimize("void",
+ "int j = 0; while (j > 0) { }").into(
+ "int j;");
+ }
+
+ // Various complex stuff
+ public void testComplexCode1() throws Exception {
+ optimize("int",
+ "int i = 1; int j = 0; while (j > 0) { if (i != 1) { i++; j++; } } return i;").into(
+ "int i; int j; return 1;");
+ }
+
+ public void testComplexCode2() throws Exception {
+ optimize("void",
+ "boolean b = bar(); if (b) { baz(b); return; }").into(
+ "boolean b = bar(); if (b) { baz(true); return; }");
+ }
+
+ public void testAssert() throws Exception {
+ optimize("void",
+ "boolean b = true;",
+ "assert b;").into(
+ "boolean b;");
+ }
+
+ public void testNoChange() throws Exception {
+ optimize("void",
+ "try {",
+ " foo(1);",
+ "} catch (RuntimeException e) { }").noChange();
+ }
+
+ public void testAssignToField() throws Exception {
+ optimize("void",
+ "Foo foo = createFoo();",
+ "foo.i = 1;"
+ ).noChange();
+ }
+
+ public void testSwapValues() throws Exception {
+ optimize("int",
+ "int i = genInt(); int j = genInt(); int t;",
+ "if (i > j) { t = i; i = j; j = t; }",
+ "return multiply(i, j);"
+ ).into(
+ "int i = genInt(); int j = genInt(); int t;",
+ "if (i > j) { t = i; i = j; j = t; }",
+ "return multiply(i, j);"
+ );
+ }
+
+ public void testSwapValuesMethodParameter() throws Exception {
+ addSnippetClassDecl("int calculate(int i, int j) {" +
+ "int t;" +
+ "if (i > j) { t = i; i = j; j = t; }" +
+ "return multiply(i, j);" +
+ "}");
+
+ optimizeMethod("int", "calculate", "return 1;"
+ ).intoString(
+ "{",
+ " int t;",
+ " if (i > j) {",
+ " t = i;",
+ " i = j;",
+ " j = t;",
+ " }",
+ " return EntryPoint.multiply(i, j);",
+ "}"
+ );
+ }
+
+/* public void testInlineField1() throws Exception {
+ optimize("int",
+ "int i = staticFooInstance.i;",
+ "return i;"
+ ).into("int i; return EntryPoint.staticFooInstance.i;");
+ }
+
+ public void testInlineField2() throws Exception {
+ optimize("int",
+ "int i = staticFooInstance.i;",
+ "createFoo();",
+ "return i;"
+ ).noChange();
+ }
+
+ public void testInlineField3() throws Exception {
+ optimize("int",
+ "int i = staticFooInstance.i;",
+ "i = 2;",
+ "return i;"
+ ).into("int i; return 2;");
+ }
+
+ public void testLoop1() throws Exception {
+ optimize("int",
+ "int i = 0; int j = 0;",
+ "while (bar()) {",
+ " j = i + 2;",
+ " i = j + 1;",
+ "}",
+ "return i;").into(
+ "int i = 0;",
+ "int j;",
+ "while (EntryPoint.bar()) {",
+ " i = i + 2 + 1;",
+ "}",
+ "return i;");
+ }
+
+ public void testLoop2() throws Exception {
+ optimize("int",
+ "int i = 0; int j = 0;",
+ "while (bar()) {",
+ " j = i + 2;",
+ " i = j + 1;",
+ "}",
+ "return j;").into(
+ "int i = 0;",
+ "int j = 0;",
+ "while (EntryPoint.bar()) {",
+ " j = i + 2;",
+ " i = i + 2 + 1;",
+ "}",
+ "return j;");
+ }
+*/
+ private Result optimize(final String returnType, final String...codeSnippet)
+ throws UnableToCompleteException {
+ return optimizeMethod(returnType, MAIN_METHOD_NAME, codeSnippet);
+ }
+
+ private Result optimizeMethod(final String returnType,
+ final String methodName, final String... codeSnippet)
+ throws UnableToCompleteException {
+ String snippet = Strings.join(codeSnippet, "\n");
+ JProgram program = compileSnippet(returnType, snippet);
+ JMethod method = findMethod(program, methodName);
+ boolean madeChanges = DataflowOptimizer.exec(program, method);
+ return new Result(returnType, snippet, method.getBody().toSource(),
+ madeChanges);
+ }
+
+}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/GflowTests.java b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/GflowTests.java
new file mode 100644
index 0000000..d231147
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/GflowTests.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2008 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.gflow;
+
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgBuilderTest;
+import com.google.gwt.dev.jjs.impl.gflow.constants.ExpressionEvaluatorTest;
+import com.google.gwt.dev.jjs.impl.gflow.constants.AssumptionsDeducerTest;
+import com.google.gwt.dev.jjs.impl.gflow.constants.ConstantsAnalysisTest;
+import com.google.gwt.dev.jjs.impl.gflow.constants.ConstantsAnalysisTransformationTest;
+import com.google.gwt.dev.jjs.impl.gflow.copy.CopyAnalysisTest;
+import com.google.gwt.dev.jjs.impl.gflow.copy.CopyAnalysisTransformationTest;
+import com.google.gwt.dev.jjs.impl.gflow.liveness.LivenessAnalysisTest;
+import com.google.gwt.dev.jjs.impl.gflow.liveness.LivenessTransformationTest;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * All tests for gflow framework in bottom-to-top architecture order
+ * (i.e. if lower test fails, then all further tests might fail too).
+ */
+public class GflowTests {
+ public static Test suite() {
+ TestSuite suite = new TestSuite();
+ suite.addTestSuite(CfgBuilderTest.class);
+ suite.addTestSuite(AssumptionsDeducerTest.class);
+ suite.addTestSuite(ExpressionEvaluatorTest.class);
+ suite.addTestSuite(ConstantsAnalysisTest.class);
+ suite.addTestSuite(ConstantsAnalysisTransformationTest.class);
+ suite.addTestSuite(LivenessAnalysisTest.class);
+ suite.addTestSuite(LivenessTransformationTest.class);
+ suite.addTestSuite(CopyAnalysisTest.class);
+ suite.addTestSuite(CopyAnalysisTransformationTest.class);
+ suite.addTestSuite(DataflowOptimizerTest.class);
+ return suite;
+ }
+}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilderTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilderTest.java
new file mode 100644
index 0000000..13e438a
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilderTest.java
@@ -0,0 +1,1221 @@
+package com.google.gwt.dev.jjs.impl.gflow.cfg;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.jjs.ast.JMethodBody;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.impl.OptimizerTestBase;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgBuilder;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+import com.google.gwt.dev.util.Strings;
+
+import java.util.List;
+
+/**
+ * Test class for CfgBuilfer.
+ */
+public class CfgBuilderTest extends OptimizerTestBase {
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ addSnippetClassDecl("static boolean b;");
+ addSnippetClassDecl("static boolean b1;");
+ addSnippetClassDecl("static boolean b2;");
+ addSnippetClassDecl("static boolean b3;");
+ addSnippetClassDecl("static int i;");
+ addSnippetClassDecl("static int j;");
+ addSnippetClassDecl("static int k;");
+ addSnippetClassDecl("static int l;");
+ addSnippetClassDecl("static int m;");
+ addSnippetClassDecl("static class CheckedException extends Exception {}");
+ addSnippetClassDecl(
+ "static class UncheckedException1 extends RuntimeException {}");
+ addSnippetClassDecl(
+ "static class UncheckedException2 extends RuntimeException {}");
+ addSnippetClassDecl("static RuntimeException runtimeException;");
+ addSnippetClassDecl("static CheckedException checkedException;");
+ addSnippetClassDecl("static UncheckedException1 uncheckedException1;");
+ addSnippetClassDecl("static UncheckedException2 uncheckedException2;");
+
+ addSnippetClassDecl("static int[] ii;");
+ addSnippetClassDecl("static int[] jj;");
+
+ addSnippetClassDecl("static void throwCheckedException() " +
+ "throws CheckedException {}");
+ addSnippetClassDecl("static void throwUncheckedException() {}");
+ addSnippetClassDecl("static void throwSeveralExceptions() " +
+ "throws CheckedException, UncheckedException1 {}");
+
+ addSnippetClassDecl("static class Foo { int i; int j; int k; }");
+ addSnippetClassDecl("static Foo createFoo() {return null;}");
+ }
+
+ public void testConstantAssignment() throws Exception {
+ assertCfg("void", "i = 1;").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(i, 1) -> [*]",
+ "END");
+ }
+
+ public void testReferenceAssignment() throws Exception {
+ assertCfg("void", "i = j;").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(j) -> [*]",
+ "WRITE(i, EntryPoint.j) -> [*]",
+ "END");
+ }
+
+ public void testModAssignment() throws Exception {
+ assertCfg("void", "i += 1;").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READWRITE(i, null) -> [*]",
+ "END");
+ }
+
+ public void testDeclarationWithInitializer() throws Exception {
+ assertCfg("void", "int i = 1;").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(i, 1) -> [*]",
+ "END");
+ }
+
+ public void testDeclarationWithInitializerRead() throws Exception {
+ assertCfg("void", "int i = j + k;").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(j) -> [*]",
+ "READ(k) -> [*]",
+ "WRITE(i, EntryPoint.j + EntryPoint.k) -> [*]",
+ "END");
+ }
+
+ public void testDeclarationWithoutInitializer() throws Exception {
+ assertCfg("void", "int i;").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "END");
+ }
+
+ public void testBinopAssignment() throws Exception {
+ assertCfg("void", "i = j + k;").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(j) -> [*]",
+ "READ(k) -> [*]",
+ "WRITE(i, EntryPoint.j + EntryPoint.k) -> [*]",
+ "END");
+ }
+
+ public void testPostIncrement() throws Exception {
+ assertCfg("void", "i++;").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READWRITE(i, null) -> [*]",
+ "END");
+ }
+
+ public void testPreIncrement() throws Exception {
+ assertCfg("void", "++i;").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READWRITE(i, null) -> [*]",
+ "END");
+ }
+
+ public void testConditional() throws Exception {
+ assertCfg("void", "i = b1 ? j : k;").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b1) -> [*]",
+ "COND (EntryPoint.b1) -> [THEN=*, ELSE=1]",
+ "READ(j) -> [2]",
+ "1: READ(k) -> [*]",
+ "2: WRITE(i, EntryPoint.b1 ? EntryPoint.j : EntryPoint.k) -> [*]",
+ "END");
+ }
+
+ public void testAnd() throws Exception {
+ assertCfg("void", "b3 = b1 && b2;").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b1) -> [*]",
+ "COND (EntryPoint.b1) -> [THEN=*, ELSE=1]",
+ "READ(b2) -> [*]",
+ "1: WRITE(b3, EntryPoint.b1 && EntryPoint.b2) -> [*]",
+ "END");
+ }
+
+ public void testOr() throws Exception {
+ assertCfg("void", "b3 = b1 || b2;").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b1) -> [*]",
+ "COND (EntryPoint.b1) -> [ELSE=*, THEN=1]",
+ "READ(b2) -> [*]",
+ "1: WRITE(b3, EntryPoint.b1 || EntryPoint.b2) -> [*]",
+ "END");
+ }
+
+ public void testMultipleExpressionStatements() throws Exception {
+ assertCfg("void",
+ "i = 1;",
+ "j = 2;",
+ "m = k = j;").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(i, 1) -> [*]",
+ "STMT -> [*]",
+ "WRITE(j, 2) -> [*]",
+ "STMT -> [*]",
+ "READ(j) -> [*]",
+ "WRITE(k, EntryPoint.j) -> [*]",
+ "WRITE(m, EntryPoint.k = EntryPoint.j) -> [*]",
+ "END");
+ }
+
+ public void testIfStatement1() throws Exception {
+ assertCfg("void",
+ "if (i == 1) {",
+ " j = 2;",
+ "}",
+ "k = j;").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(i) -> [*]",
+ "COND (EntryPoint.i == 1) -> [THEN=*, ELSE=1]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(j, 2) -> [*]",
+ "1: STMT -> [*]",
+ "READ(j) -> [*]",
+ "WRITE(k, EntryPoint.j) -> [*]",
+ "END");
+ }
+
+ public void testIfStatement2() throws Exception {
+ assertCfg("void",
+ "if ((i = 1) == 2) {",
+ " j = 2;",
+ "} else {",
+ " k = j;",
+ "}").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(i, 1) -> [*]",
+ "COND ((EntryPoint.i = 1) == 2) -> [THEN=*, ELSE=1]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(j, 2) -> [2]",
+ "1: BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(j) -> [*]",
+ "WRITE(k, EntryPoint.j) -> [*]",
+ "2: END");
+ }
+
+ public void testIfStatement3() throws Exception {
+ assertCfg("void", "if (b1 || b2) {j = 2;}").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b1) -> [*]",
+ "COND (EntryPoint.b1) -> [ELSE=*, THEN=1]",
+ "READ(b2) -> [*]",
+ "1: COND (EntryPoint.b1 || EntryPoint.b2) -> [THEN=*, ELSE=2]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(j, 2) -> [*]",
+ "2: END");
+ }
+
+ public void testWhileStatement() throws Exception {
+ assertCfg("void", "while (i == 1) {j = 2;} k = j;").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "1: READ(i) -> [*]",
+ "COND (EntryPoint.i == 1) -> [THEN=*, ELSE=2]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(j, 2) -> [1]",
+ "2: STMT -> [*]",
+ "READ(j) -> [*]",
+ "WRITE(k, EntryPoint.j) -> [*]",
+ "END");
+ }
+
+ public void testDoStatement() throws Exception {
+ assertCfg("void", "do { j = 2; } while (i == 1);").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "1: BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(j, 2) -> [*]",
+ "READ(i) -> [*]",
+ "COND (EntryPoint.i == 1) -> [THEN=1, ELSE=*]",
+ "END");
+ }
+
+ public void testReturn1() throws Exception {
+ assertCfg("void", "return;").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "GOTO -> [*]",
+ "END");
+ }
+
+ public void testReturn2() throws Exception {
+ assertCfg("boolean", "i = 1; return i == 2;").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(i, 1) -> [*]",
+ "STMT -> [*]",
+ "READ(i) -> [*]",
+ "GOTO -> [*]",
+ "END");
+ }
+
+ public void testReturn3() throws Exception {
+ assertCfg("boolean",
+ "i = 1;",
+ "if (i == 1) {",
+ " i = 2;",
+ " return true;",
+ "} else {",
+ " i = 3;",
+ " return false;",
+ "}").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(i, 1) -> [*]",
+ "STMT -> [*]",
+ "READ(i) -> [*]",
+ "COND (EntryPoint.i == 1) -> [THEN=*, ELSE=1]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(i, 2) -> [*]",
+ "STMT -> [*]",
+ "GOTO -> [2]",
+ "1: BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(i, 3) -> [*]",
+ "STMT -> [*]",
+ "GOTO -> [*]",
+ "2: END");
+ }
+
+ public void testForStatement() throws Exception {
+ assertCfg("int",
+ "int j = 0;",
+ "for (int i = 0; i < 10; ++i) { j++; }",
+ "return j;").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(j, 0) -> [*]",
+ "STMT -> [*]",
+ "STMT -> [*]",
+ "WRITE(i, 0) -> [*]",
+ "1: READ(i) -> [*]",
+ "COND (i < 10) -> [THEN=*, ELSE=2]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READWRITE(j, null) -> [*]",
+ "STMT -> [*]",
+ "READWRITE(i, null) -> [1]",
+ "2: STMT -> [*]",
+ "READ(j) -> [*]",
+ "GOTO -> [*]",
+ "END");
+ }
+
+ public void testEmptyForStatement() throws Exception {
+ assertCfg("void",
+ "for (;;) { j++; }").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "1: BLOCK -> [*]",
+ "STMT -> [*]",
+ "READWRITE(j, null) -> [1]",
+ "END");
+ }
+
+ public void testThrowWithoutCatch1() throws Exception {
+ assertCfg("void", "throw runtimeException;").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(runtimeException) -> [*]",
+ "THROW -> [*]",
+ "END");
+ }
+
+ public void testThrowWithoutCatch2() throws Exception {
+ assertCfg("void", "if (b1) { throw runtimeException; } b1 = true;").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b1) -> [*]",
+ "COND (EntryPoint.b1) -> [THEN=*, ELSE=1]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(runtimeException) -> [*]",
+ "THROW -> [2]",
+ "1: STMT -> [*]",
+ "WRITE(b1, true) -> [*]",
+ "2: END"
+ );
+ }
+
+ public void testWhileContinueNoLabel() throws Exception {
+ assertCfg("void", "while (b1) { if (b2) { continue; } i++; }").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "1: READ(b1) -> [*]",
+ "COND (EntryPoint.b1) -> [THEN=*, ELSE=3]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b2) -> [*]",
+ "COND (EntryPoint.b2) -> [THEN=*, ELSE=2]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "GOTO -> [1]",
+ "2: STMT -> [*]",
+ "READWRITE(i, null) -> [1]",
+ "3: END");
+ }
+
+ public void testWhileContinueWithLabel1() throws Exception {
+ assertCfg("void",
+ "nextLoop: while(b3)",
+ " while (b1) {",
+ " if (b2) { continue; }",
+ " i++;" +
+ " }").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "1: READ(b3) -> [*]",
+ "COND (EntryPoint.b3) -> [THEN=*, ELSE=4]",
+ "STMT -> [*]",
+ "2: READ(b1) -> [*]",
+ "COND (EntryPoint.b1) -> [THEN=*, ELSE=1]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b2) -> [*]",
+ "COND (EntryPoint.b2) -> [THEN=*, ELSE=3]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "GOTO -> [2]",
+ "3: STMT -> [*]",
+ "READWRITE(i, null) -> [2]",
+ "4: END");
+ }
+
+
+ public void testWhileContinueWithLabel2() throws Exception {
+ assertCfg("void",
+ "nextLoop: while(b3)",
+ " while (b1) {",
+ " if (b2) { continue nextLoop; }",
+ " i++;",
+ " }").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "1: READ(b3) -> [*]",
+ "COND (EntryPoint.b3) -> [THEN=*, ELSE=4]",
+ "STMT -> [*]",
+ "2: READ(b1) -> [*]",
+ "COND (EntryPoint.b1) -> [THEN=*, ELSE=1]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b2) -> [*]",
+ "COND (EntryPoint.b2) -> [THEN=*, ELSE=3]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "GOTO -> [1]",
+ "3: STMT -> [*]",
+ "READWRITE(i, null) -> [2]",
+ "4: END");
+ }
+
+ public void testWhileContinueWithLabel3() throws Exception {
+ assertCfg("void",
+ "nextLoop: while (b1) {",
+ " if (b2) { continue; }",
+ " i++;",
+ "}").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "1: READ(b1) -> [*]",
+ "COND (EntryPoint.b1) -> [THEN=*, ELSE=3]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b2) -> [*]",
+ "COND (EntryPoint.b2) -> [THEN=*, ELSE=2]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "GOTO -> [1]",
+ "2: STMT -> [*]",
+ "READWRITE(i, null) -> [1]",
+ "3: END");
+ }
+
+ public void testWhileBreakNoLabel() throws Exception {
+ assertCfg("void", "while (b1) { if (b2) { break; } i++; }").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "1: READ(b1) -> [*]",
+ "COND (EntryPoint.b1) -> [THEN=*, ELSE=3]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b2) -> [*]",
+ "COND (EntryPoint.b2) -> [THEN=*, ELSE=2]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "GOTO -> [3]",
+ "2: STMT -> [*]",
+ "READWRITE(i, null) -> [1]",
+ "3: END");
+ }
+
+ public void testWhileBreakWithLabel1() throws Exception {
+ assertCfg("void",
+ "nextLoop: while(b3)",
+ " while (b1) {",
+ " if (b2) { break; }",
+ " i++;",
+ " }").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "1: READ(b3) -> [*]",
+ "COND (EntryPoint.b3) -> [THEN=*, ELSE=4]",
+ "STMT -> [*]",
+ "2: READ(b1) -> [*]",
+ "COND (EntryPoint.b1) -> [THEN=*, ELSE=1]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b2) -> [*]",
+ "COND (EntryPoint.b2) -> [THEN=*, ELSE=3]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "GOTO -> [1]",
+ "3: STMT -> [*]",
+ "READWRITE(i, null) -> [2]",
+ "4: END");
+ }
+
+ public void testWhileBreakWithLabel2() throws Exception {
+ assertCfg("void",
+ "nextLoop: while(b3)",
+ " while (b1) {",
+ " if (b2) { break nextLoop; }",
+ " i++;",
+ " }").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "1: READ(b3) -> [*]",
+ "COND (EntryPoint.b3) -> [THEN=*, ELSE=4]",
+ "STMT -> [*]",
+ "2: READ(b1) -> [*]",
+ "COND (EntryPoint.b1) -> [THEN=*, ELSE=1]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b2) -> [*]",
+ "COND (EntryPoint.b2) -> [THEN=*, ELSE=3]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "GOTO -> [4]",
+ "3: STMT -> [*]",
+ "READWRITE(i, null) -> [2]",
+ "4: END");
+ }
+
+ public void testWhileBreakWithLabel3() throws Exception {
+ assertCfg("void",
+ "nextLoop: while (b1) { if (b2) { break; } i++; }").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "1: READ(b1) -> [*]",
+ "COND (EntryPoint.b1) -> [THEN=*, ELSE=3]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b2) -> [*]",
+ "COND (EntryPoint.b2) -> [THEN=*, ELSE=2]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "GOTO -> [3]",
+ "2: STMT -> [*]",
+ "READWRITE(i, null) -> [1]",
+ "3: END");
+ }
+
+ public void testForContinueNoLabel() throws Exception {
+ assertCfg("void",
+ "for(int i = 0; i < 10; i++) { if (b2) { continue; } i++; }").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "STMT -> [*]",
+ "WRITE(i, 0) -> [*]",
+ "1: READ(i) -> [*]",
+ "COND (i < 10) -> [THEN=*, ELSE=4]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b2) -> [*]",
+ "COND (EntryPoint.b2) -> [THEN=*, ELSE=2]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "GOTO -> [3]",
+ "2: STMT -> [*]",
+ "READWRITE(i, null) -> [*]",
+ "3: STMT -> [*]",
+ "READWRITE(i, null) -> [1]",
+ "4: END");
+ }
+
+ public void testCatchThrowException1() throws Exception {
+ assertCfg("void",
+ "try {",
+ " if (b) throw checkedException;",
+ " k++;",
+ "} catch (CheckedException e) {",
+ " i++;",
+ "}",
+ "j++;").is(
+ "BLOCK -> [*]",
+ "TRY -> [*]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b) -> [*]",
+ "COND (EntryPoint.b) -> [THEN=*, ELSE=1]",
+ "STMT -> [*]",
+ "READ(checkedException) -> [*]",
+ "THROW -> [2]",
+ "1: STMT -> [*]",
+ "READWRITE(k, null) -> [3]",
+ "2: BLOCK -> [*]",
+ "STMT -> [*]",
+ "READWRITE(i, null) -> [*]",
+ "3: STMT -> [*]",
+ "READWRITE(j, null) -> [*]",
+ "END"
+ );
+ }
+
+ public void testCatchThrowUncatchedException() throws Exception {
+ assertCfg("void",
+ "try {",
+ " if (b) throw uncheckedException2;",
+ " k++;",
+ "} catch (UncheckedException1 e) {",
+ " i++;",
+ "}",
+ "j++;").is(
+ "BLOCK -> [*]",
+ "TRY -> [*]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b) -> [*]",
+ "COND (EntryPoint.b) -> [THEN=*, ELSE=1]",
+ "STMT -> [*]",
+ "READ(uncheckedException2) -> [*]",
+ "THROW -> [3]",
+ "1: STMT -> [*]",
+ "READWRITE(k, null) -> [2]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READWRITE(i, null) -> [*]",
+ "2: STMT -> [*]",
+ "READWRITE(j, null) -> [*]",
+ "3: END"
+ );
+ }
+
+ public void testCatchThrowSupertype() throws Exception {
+ assertCfg("void",
+ "try {",
+ " if (b) throw runtimeException;",
+ " k++;",
+ "} catch (UncheckedException1 e) {",
+ " i++;",
+ "}",
+ "j++;").is(
+ "BLOCK -> [*]",
+ "TRY -> [*]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b) -> [*]",
+ "COND (EntryPoint.b) -> [THEN=*, ELSE=1]",
+ "STMT -> [*]",
+ "READ(runtimeException) -> [*]",
+ "THROW -> [2, 4]",
+ "1: STMT -> [*]",
+ "READWRITE(k, null) -> [3]",
+ "2: BLOCK -> [*]",
+ "STMT -> [*]",
+ "READWRITE(i, null) -> [*]",
+ "3: STMT -> [*]",
+ "READWRITE(j, null) -> [*]",
+ "4: END"
+ );
+ }
+
+ public void testCatchSupertype() throws Exception {
+ assertCfg("void",
+ "try {",
+ " if (b) throw uncheckedException1;",
+ " k++;",
+ "} catch (RuntimeException e) {",
+ " i++;",
+ "}",
+ "j++;").is(
+ "BLOCK -> [*]",
+ "TRY -> [*]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b) -> [*]",
+ "COND (EntryPoint.b) -> [THEN=*, ELSE=1]",
+ "STMT -> [*]",
+ "READ(uncheckedException1) -> [*]",
+ "THROW -> [2]",
+ "1: STMT -> [*]",
+ "READWRITE(k, null) -> [3]",
+ "2: BLOCK -> [*]",
+ "STMT -> [*]",
+ "READWRITE(i, null) -> [*]",
+ "3: STMT -> [*]",
+ "READWRITE(j, null) -> [*]",
+ "END"
+ );
+ }
+
+ public void testCatchReturn() throws Exception {
+ assertCfg("void",
+ "try { try {",
+ " if (b) return;",
+ " k++;",
+ "} catch (RuntimeException e) {",
+ " i++;",
+ "} } catch (UncheckedException1 e) { j++; }").is(
+ "BLOCK -> [*]",
+ "TRY -> [*]",
+ "BLOCK -> [*]",
+ "TRY -> [*]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b) -> [*]",
+ "COND (EntryPoint.b) -> [THEN=*, ELSE=1]",
+ "STMT -> [*]",
+ "GOTO -> [2]",
+ "1: STMT -> [*]",
+ "READWRITE(k, null) -> [2]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READWRITE(i, null) -> [2]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READWRITE(j, null) -> [*]",
+ "2: END"
+ );
+ }
+
+ public void testRethrow() throws Exception {
+ assertCfg("void",
+ "try {",
+ " if (b) throw uncheckedException1;",
+ " k++;",
+ "} catch (UncheckedException1 e) {",
+ " i++;",
+ " throw e;",
+ "}",
+ "j++;").is(
+ "BLOCK -> [*]",
+ "TRY -> [*]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b) -> [*]",
+ "COND (EntryPoint.b) -> [THEN=*, ELSE=1]",
+ "STMT -> [*]",
+ "READ(uncheckedException1) -> [*]",
+ "THROW -> [2]",
+ "1: STMT -> [*]",
+ "READWRITE(k, null) -> [3]",
+ "2: BLOCK -> [*]",
+ "STMT -> [*]",
+ "READWRITE(i, null) -> [*]",
+ "STMT -> [*]",
+ "READ(e) -> [*]",
+ "THROW -> [4]",
+ "3: STMT -> [*]",
+ "READWRITE(j, null) -> [*]",
+ "4: END"
+ );
+ }
+
+ public void testCatchMethodCall1() throws Exception {
+ assertCfg("void",
+ "try {",
+ " if (b) throwCheckedException();",
+ " k++;",
+ "} catch (CheckedException e) {",
+ " i++;",
+ "}",
+ "j++;").is(
+ "BLOCK -> [*]",
+ "TRY -> [*]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b) -> [*]",
+ "COND (EntryPoint.b) -> [THEN=*, ELSE=1]",
+ "STMT -> [*]",
+ "OPTTHROW(throwCheckedException()) -> [NOTHROW=*, 2, RE=4]",
+ "CALL(throwCheckedException) -> [*]",
+ "1: STMT -> [*]",
+ "READWRITE(k, null) -> [3]",
+ "2: BLOCK -> [*]",
+ "STMT -> [*]",
+ "READWRITE(i, null) -> [*]",
+ "3: STMT -> [*]",
+ "READWRITE(j, null) -> [*]",
+ "4: END"
+ );
+ }
+
+ public void testCatchMethodCall2() throws Exception {
+ assertCfg("void",
+ "try {",
+ " if (b) throwCheckedException();",
+ " k++;",
+ "} catch (CheckedException e) {",
+ " i++;",
+ "} catch (RuntimeException e) {",
+ " l++;",
+ "}",
+ "j++;").is(
+ "BLOCK -> [*]",
+ "TRY -> [*]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b) -> [*]",
+ "COND (EntryPoint.b) -> [THEN=*, ELSE=1]",
+ "STMT -> [*]",
+ "OPTTHROW(throwCheckedException()) -> [NOTHROW=*, 2, RE=3]",
+ "CALL(throwCheckedException) -> [*]",
+ "1: STMT -> [*]",
+ "READWRITE(k, null) -> [4]",
+ "2: BLOCK -> [*]",
+ "STMT -> [*]",
+ "READWRITE(i, null) -> [4]",
+ "3: BLOCK -> [*]",
+ "STMT -> [*]",
+ "READWRITE(l, null) -> [*]",
+ "4: STMT -> [*]",
+ "READWRITE(j, null) -> [*]",
+ "END"
+ );
+ }
+
+ public void testCatchMethodCallUnchecked() throws Exception {
+ assertCfg("void",
+ "try {",
+ " if (b) throwUncheckedException();",
+ " k++;",
+ "} catch (UncheckedException1 e) {",
+ " i++;",
+ "} catch (RuntimeException e) {",
+ " l++;",
+ "}",
+ "j++;").is(
+ "BLOCK -> [*]",
+ "TRY -> [*]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b) -> [*]",
+ "COND (EntryPoint.b) -> [THEN=*, ELSE=1]",
+ "STMT -> [*]",
+ "OPTTHROW(throwUncheckedException()) -> [NOTHROW=*, RE=2, RE=3]",
+ "CALL(throwUncheckedException) -> [*]",
+ "1: STMT -> [*]",
+ "READWRITE(k, null) -> [4]",
+ "2: BLOCK -> [*]",
+ "STMT -> [*]",
+ "READWRITE(i, null) -> [4]",
+ "3: BLOCK -> [*]",
+ "STMT -> [*]",
+ "READWRITE(l, null) -> [*]",
+ "4: STMT -> [*]",
+ "READWRITE(j, null) -> [*]",
+ "END"
+ );
+ }
+
+ public void testFinallyReturn1() throws Exception {
+ assertCfg("void",
+ "try {",
+ " if (b) return;",
+ " j++;",
+ "} finally {",
+ " i++;",
+ "}").is(
+ "BLOCK -> [*]",
+ "TRY -> [*]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b) -> [*]",
+ "COND (EntryPoint.b) -> [THEN=*, ELSE=1]",
+ "STMT -> [*]",
+ "GOTO -> [2]",
+ "1: STMT -> [*]",
+ "READWRITE(j, null) -> [*]",
+ "2: BLOCK -> [*]",
+ "STMT -> [*]",
+ "READWRITE(i, null) -> [*, *]",
+ "END"
+ );
+ }
+
+ public void testThrowFromFinally() throws Exception {
+ assertCfg("void",
+ "try {",
+ " return;",
+ "} finally {",
+ " throw runtimeException;",
+ "}").is(
+ "BLOCK -> [*]",
+ "TRY -> [*]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "GOTO -> [*]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(runtimeException) -> [*]",
+ "THROW -> [*]",
+ "END"
+ );
+ }
+
+ public void testFinallyReturn2() throws Exception {
+ assertCfg("void",
+ "try {",
+ " return;",
+ "} finally {",
+ " i++;",
+ "}").is(
+ "BLOCK -> [*]",
+ "TRY -> [*]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "GOTO -> [*]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READWRITE(i, null) -> [*, *]",
+ "END"
+ );
+ }
+
+ public void testFinallyReturn3() throws Exception {
+ assertCfg("void",
+ "try {",
+ "try {",
+ " if (b) return;",
+ " k++;",
+ "} finally {",
+ " i++;",
+ "} m++; } finally {",
+ " j++;",
+ "}").is(
+ "BLOCK -> [*]",
+ "TRY -> [*]",
+ "BLOCK -> [*]",
+ "TRY -> [*]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b) -> [*]",
+ "COND (EntryPoint.b) -> [THEN=*, ELSE=1]",
+ "STMT -> [*]",
+ "GOTO -> [2]",
+ "1: STMT -> [*]",
+ "READWRITE(k, null) -> [*]",
+ "2: BLOCK -> [*]",
+ "STMT -> [*]",
+ "READWRITE(i, null) -> [*, 3]",
+ "STMT -> [*]",
+ "READWRITE(m, null) -> [*]",
+ "3: BLOCK -> [*]",
+ "STMT -> [*]",
+ "READWRITE(j, null) -> [*, *]",
+ "END"
+ );
+ }
+
+
+ public void testFinallyContinue() throws Exception {
+ assertCfg("void",
+ "while (b) {",
+ "try {",
+ " if (b) continue;",
+ "} finally {",
+ " i++;",
+ "} j++; }").is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "1: READ(b) -> [*]",
+ "COND (EntryPoint.b) -> [THEN=*, ELSE=3]",
+ "BLOCK -> [*]",
+ "TRY -> [*]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b) -> [*]",
+ "COND (EntryPoint.b) -> [THEN=*, ELSE=2]",
+ "STMT -> [*]",
+ "GOTO -> [*]",
+ "2: BLOCK -> [*]",
+ "STMT -> [*]",
+ "READWRITE(i, null) -> [*, 1]",
+ "STMT -> [*]",
+ "READWRITE(j, null) -> [1]",
+ "3: END"
+ );
+ }
+
+ public void testCatchFinally() throws Exception {
+ assertCfg("void",
+ "try {",
+ " if (b) throw checkedException;",
+ " k++;",
+ "} catch (CheckedException e) {",
+ " i++;",
+ "} finally {",
+ " j++;",
+ "}"
+ ).is(
+ "BLOCK -> [*]",
+ "TRY -> [*]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(b) -> [*]",
+ "COND (EntryPoint.b) -> [THEN=*, ELSE=1]",
+ "STMT -> [*]",
+ "READ(checkedException) -> [*]",
+ "THROW -> [2]",
+ "1: STMT -> [*]",
+ "READWRITE(k, null) -> [3]",
+ "2: BLOCK -> [*]",
+ "STMT -> [*]",
+ "READWRITE(i, null) -> [*]",
+ "3: BLOCK -> [*]",
+ "STMT -> [*]",
+ "READWRITE(j, null) -> [*]",
+ "END"
+ );
+ }
+
+ public void testFieldWrite() throws Exception {
+ assertCfg("void",
+ "Foo foo = createFoo();",
+ "foo.i = 1;"
+ ).is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "OPTTHROW(createFoo()) -> [NOTHROW=*, RE=1]",
+ "CALL(createFoo) -> [*]",
+ "WRITE(foo, EntryPoint.createFoo()) -> [*]",
+ "STMT -> [*]",
+ "READ(foo) -> [*]",
+ "WRITE(i, 1) -> [*]",
+ "1: END"
+ );
+ }
+
+ public void testFieldUnary() throws Exception {
+ assertCfg("void",
+ "Foo foo = createFoo();",
+ "++foo.i;"
+ ).is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "OPTTHROW(createFoo()) -> [NOTHROW=*, RE=1]",
+ "CALL(createFoo) -> [*]",
+ "WRITE(foo, EntryPoint.createFoo()) -> [*]",
+ "STMT -> [*]",
+ "READ(foo) -> [*]",
+ "READWRITE(i, null) -> [*]",
+ "1: END"
+ );
+ }
+
+ public void testArrayRead() throws Exception {
+ assertCfg("void",
+ "i = ii[j];"
+ ).is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(ii) -> [*]",
+ "READ(j) -> [*]",
+ "WRITE(i, EntryPoint.ii[EntryPoint.j]) -> [*]",
+ "END"
+ );
+ }
+
+ public void testArrayWrite() throws Exception {
+ assertCfg("void",
+ "ii[i] = j;"
+ ).is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(j) -> [*]",
+ "READ(ii) -> [*]",
+ "READ(i) -> [*]",
+ "WRITE(EntryPoint.ii[EntryPoint.i], EntryPoint.j) -> [*]",
+ "END"
+ );
+ }
+
+ public void testArrayUnary() throws Exception {
+ assertCfg("void",
+ "++ii[i];"
+ ).is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(ii) -> [*]",
+ "READ(i) -> [*]",
+ "READWRITE(EntryPoint.ii[EntryPoint.i], null) -> [*]",
+ "END"
+ );
+ }
+
+ public void testSwitch() throws Exception {
+ assertCfg("void",
+ "switch(i) {",
+ " case 1: ",
+ " return;",
+ " case 2: ",
+ " case 3: ",
+ " j = 1;",
+ " break;",
+ " case 4: ",
+ " j = 2;",
+ " default:",
+ " j = 4;",
+ " case 5: ",
+ " j = 3;",
+ " break;",
+ "}"
+ ).is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(i) -> [*]",
+ "STMT -> [*]",
+ "COND (EntryPoint.i == 1) -> [THEN=*, ELSE=1]",
+ "STMT -> [*]",
+ "GOTO -> [6]",
+ "1: STMT -> [*]",
+ "COND (EntryPoint.i == 2) -> [ELSE=*, THEN=2]",
+ "STMT -> [*]",
+ "COND (EntryPoint.i == 3) -> [THEN=*, ELSE=3]",
+ "2: STMT -> [*]",
+ "WRITE(j, 1) -> [*]",
+ "STMT -> [*]",
+ "GOTO -> [6]",
+ "3: STMT -> [*]",
+ "COND (EntryPoint.i == 4) -> [THEN=*, ELSE=5]",
+ "STMT -> [*]",
+ "WRITE(j, 2) -> [*]",
+ "4: STMT -> [*]",
+ "STMT -> [*]",
+ "WRITE(j, 4) -> [*]",
+ "5: STMT -> [*]",
+ "COND (EntryPoint.i == 5) -> [THEN=*, ELSE=4]",
+ "STMT -> [*]",
+ "WRITE(j, 3) -> [*]",
+ "STMT -> [*]",
+ "GOTO -> [*]",
+ "6: END"
+ );
+ }
+
+ public void testSwitchWithLoopAndBreak() throws Exception {
+ assertCfg("void",
+ "switch(i) {",
+ " case 1: ",
+ " i = 1;" +
+ " break;",
+ " case 2: ",
+ " while (b) { i = 2; break; }",
+ " j = 3;",
+ "}"
+ ).is(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(i) -> [*]",
+ "STMT -> [*]",
+ "COND (EntryPoint.i == 1) -> [THEN=*, ELSE=1]",
+ "STMT -> [*]",
+ "WRITE(i, 1) -> [*]",
+ "STMT -> [*]",
+ "GOTO -> [3]",
+ "1: STMT -> [*]",
+ "COND (EntryPoint.i == 2) -> [THEN=*, ELSE=3]",
+ "STMT -> [*]",
+ "READ(b) -> [*]",
+ "COND (EntryPoint.b) -> [THEN=*, ELSE=2]",
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(i, 2) -> [*]",
+ "STMT -> [*]",
+ "GOTO -> [*]",
+ "2: STMT -> [*]",
+ "WRITE(j, 3) -> [*]",
+ "3: END"
+ );
+ }
+
+ private CfgBuilderResult assertCfg(String returnType, String ...codeSnippet)
+ throws UnableToCompleteException {
+ JProgram program = compileSnippet(returnType,
+ Strings.join(codeSnippet, "\n"));
+ JMethodBody body = (JMethodBody) findMainMethod(program).getBody();
+ Cfg cfgGraph = CfgBuilder.build(program, body.getBlock());
+ return new CfgBuilderResult(cfgGraph);
+ }
+
+ static class CfgBuilderResult {
+ private final Cfg cfg;
+
+ public CfgBuilderResult(Cfg cfgGraph) {
+ assertNotNull("Can't build cfg", cfgGraph);
+ this.cfg = cfgGraph;
+
+ validateGraph();
+ }
+
+ private void validateGraph() {
+ for (CfgNode<?> node : cfg.getNodes()) {
+ List<CfgEdge> incomingEdges = cfg.getInEdges(node);
+ for (CfgEdge e : incomingEdges) {
+ CfgNode<?> start = e.getStart();
+ if (cfg.getGraphInEdges().contains(e)) {
+ assertNull(start);
+ continue;
+ }
+ assertNotNull("No start in edge " + e.getRole() + " to " + node,
+ start);
+ assertTrue(start + " doesn't have outgoing edge to " + node,
+ cfg.getOutEdges(start).contains(e));
+ }
+
+ List<CfgEdge> outcomingEdges = cfg.getOutEdges(node);
+ for (CfgEdge e : outcomingEdges) {
+ CfgNode<?> end = e.getEnd();
+ assertNotNull("No end in edge " + e.getRole() + " from " + node, end);
+ assertTrue(end + " doesn't have incoming edge from " + node,
+ cfg.getInEdges(end).contains(e));
+ }
+ }
+ }
+
+ public void is(String... expected) {
+ assertEquals(Strings.join(expected, "\n"), cfg.print());
+ }
+ }
+}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/constants/AssumptionsDeducerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/constants/AssumptionsDeducerTest.java
new file mode 100644
index 0000000..69ccd27
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/constants/AssumptionsDeducerTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2008 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.gflow.constants;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.jjs.ast.JBlock;
+import com.google.gwt.dev.jjs.ast.JBooleanLiteral;
+import com.google.gwt.dev.jjs.ast.JIfStatement;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JMethodBody;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.ast.JStatement;
+import com.google.gwt.dev.jjs.impl.OptimizerTestBase;
+import com.google.gwt.dev.jjs.impl.gflow.constants.AssumptionDeducer;
+import com.google.gwt.dev.jjs.impl.gflow.constants.ConstantsAssumption;
+import com.google.gwt.dev.jjs.impl.gflow.constants.ConstantsAssumption.Updater;
+
+import java.util.List;
+
+public class AssumptionsDeducerTest extends OptimizerTestBase {
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ addSnippetClassDecl("static class Foo { " +
+ "public int i;" +
+ "public Object o;" +
+ "}");
+ }
+
+ public void testBooleanVar() throws Exception {
+ from("boolean b = false;", "b", true).deduce("{b = true}");
+ from("boolean b = false;", "b", false).deduce("{b = false}");
+ }
+
+ public void testEq() throws Exception {
+ from("int i = 0;", "i == 10", true).deduce("{i = 10}");
+ from("int i = 0;", "i == 10", false).deduce("{i = T}");
+ }
+
+ public void testNeq() throws Exception {
+ from("int i = 0;", "i != 10", true).deduce("{i = T}");
+ from("int i = 0;", "i != 10", false).deduce("{i = 10}");
+ }
+
+ public void testInstanceof() throws Exception {
+ from("Object o = null;", "o instanceof String", true).deduce("{}");
+ from("Object o = null;", "o instanceof String", false).deduce("{}");
+ }
+
+ public void testReference() throws Exception {
+ from("String s = null;", "s.length() == 0", true).deduce("{}");
+ from("String s = null;", "s.length() == 0", false).deduce("{}");
+ from("Foo f = null;", "f.o == null", true).deduce("{}");
+ from("Foo f = null;", "f.o == null", false).deduce("{}");
+ }
+
+ public void testAnd() throws Exception {
+ from("int i = 0; int j = 0;", "i == 10 && j == 11", true).deduce("{i = 10, j = 11}");
+ from("int i = 0; int j = 0;", "i == 10 && j == 11", false).deduce("{i = T, j = T}");
+ }
+
+ public void testOr() throws Exception {
+ from("int i = 0; int j = 0;", "i != 10 || j != 11", false).deduce("{i = 10, j = 11}");
+ from("int i = 0; int j = 0;", "i != 10 || j != 11", true).deduce("{i = T, j = T}");
+ }
+
+ public void testFloatEq() throws Exception {
+ from("float f = 0;", "f == 1.0", true).deduce("{f = 1.0}");
+ // There are positive and negative zeros. Do not deduce anything in here
+ from("float f = 0;", "f == 0.0", true).deduce("{f = T}");
+ }
+
+ public void testDoubleEq() throws Exception {
+ from("double f = 0;", "f == 1.0", true).deduce("{f = 1.0}");
+ // There are positive and negative zeros. Do not deduce anything in here
+ from("double f = 0;", "f == 0.0", true).deduce("{f = T}");
+ }
+
+ public void testNullNotNull() throws Exception {
+ from("String s = null;", "s == null", true).deduce("{s = null}");
+ from("String s = null;", "s == null", false).deduce("{s = T}");
+ from("String s = null;", "s != null", true).deduce("{s = T}");
+ from("String s = null;", "s != null", false).deduce("{s = null}");
+ from("String s = null;", "null == s", true).deduce("{s = null}");
+ from("String s = null;", "null == s", false).deduce("{s = T}");
+ from("String s = null;", "null != s", true).deduce("{s = T}");
+ from("String s = null;", "null != s", false).deduce("{s = null}");
+ }
+
+ private Result from(String decls, String expr, boolean b) throws UnableToCompleteException {
+ JProgram program = compileSnippet("void", decls + "\n if(" + expr + ") return;");
+ JMethod mainMethod = findMainMethod(program);
+ JBlock block = ((JMethodBody) mainMethod.getBody()).getBlock();
+ List<JStatement> statements = block.getStatements();
+ JIfStatement ifStatement = (JIfStatement) statements.get(statements.size() - 1);
+
+ Updater assumptions = new Updater(null);
+ AssumptionDeducer.deduceAssumption(ifStatement.getIfExpr(),
+ JBooleanLiteral.get(b), assumptions);
+ return new Result(assumptions.unwrapToNotNull());
+ }
+
+ private class Result {
+ private final ConstantsAssumption assumptions;
+
+ public Result(ConstantsAssumption assumptions) {
+ this.assumptions = assumptions;
+ }
+
+ public void deduce(String expected) {
+ assertEquals(expected, assumptions.toDebugString());
+ }
+
+ }
+}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/constants/ConstantsAnalysisTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/constants/ConstantsAnalysisTest.java
new file mode 100644
index 0000000..3aa10e0
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/constants/ConstantsAnalysisTest.java
@@ -0,0 +1,220 @@
+package com.google.gwt.dev.jjs.impl.gflow.constants;
+
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.impl.gflow.Analysis;
+import com.google.gwt.dev.jjs.impl.gflow.CfgAnalysisTestBase;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+
+public class ConstantsAnalysisTest extends CfgAnalysisTestBase<ConstantsAssumption> {
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ addSnippetClassDecl("static int i;");
+ addSnippetClassDecl("static int j;");
+ addSnippetClassDecl("static int k;");
+ addSnippetClassDecl("static int l;");
+ addSnippetClassDecl("static int m;");
+
+ addSnippetClassDecl("static int foo() { return 0; };");
+ addSnippetClassDecl("static void bar(Object o) { };");
+ addSnippetClassDecl("static int baz(Object o) { return 0; };");
+ }
+
+ public void testDeclWithConstInit() throws Exception {
+ analyze("void", "int i = 1;").into(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(i, 1) -> [* {i = 1}]",
+ "END");
+ }
+
+ public void testDeclWithConstOps() throws Exception {
+ analyze("void", "int i = 1 + 1;").into(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(i, 2) -> [* {i = 2}]",
+ "END");
+ }
+
+ public void testDeclWithNonconstInit() throws Exception {
+ analyze("void", "int i = foo();").into(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "OPTTHROW(foo()) -> [NOTHROW=*, RE=1]",
+ "CALL(foo) -> [*]",
+ "WRITE(i, EntryPoint.foo()) -> [* {i = T}]",
+ "1: END");
+ }
+
+ public void testReassign() throws Exception {
+ analyze("void", "int i = 1; i = 2;").into(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(i, 1) -> [* {i = 1}]",
+ "STMT -> [* {i = 1}]",
+ "WRITE(i, 2) -> [* {i = 2}]",
+ "END");
+ analyze("void", "int i; i = 3;").into(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "STMT -> [*]",
+ "WRITE(i, 3) -> [* {i = 3}]",
+ "END");
+ }
+
+ public void test2Vars() throws Exception {
+ analyze("void", "int i = 1; int j = 2;").into(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(i, 1) -> [* {i = 1}]",
+ "STMT -> [* {i = 1}]",
+ "WRITE(j, 2) -> [* {i = 1, j = 2}]",
+ "END");
+ }
+
+ public void testSequence() throws Exception {
+ analyze("void", "int i = 1; int j = i; int k = j; int l = k;").into(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(i, 1) -> [* {i = 1}]",
+ "STMT -> [* {i = 1}]",
+ "READ(i) -> [* {i = 1}]",
+ "WRITE(j, i) -> [* {i = 1, j = 1}]",
+ "STMT -> [* {i = 1, j = 1}]",
+ "READ(j) -> [* {i = 1, j = 1}]",
+ "WRITE(k, j) -> [* {i = 1, j = 1, k = 1}]",
+ "STMT -> [* {i = 1, j = 1, k = 1}]",
+ "READ(k) -> [* {i = 1, j = 1, k = 1}]",
+ "WRITE(l, k) -> [* {i = 1, j = 1, k = 1, l = 1}]",
+ "END");
+ }
+
+ public void testIfStatement() throws Exception {
+ analyze("void", "int i = k; if (i == 1) { int j = i; } else { int j = i; } ").into(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "READ(k) -> [*]",
+ "WRITE(i, EntryPoint.k) -> [* {i = T}]",
+ "STMT -> [* {i = T}]",
+ "READ(i) -> [* {i = T}]",
+ "COND (i == 1) -> [THEN=* {i = 1}, ELSE=1 {i = T}]",
+ "BLOCK -> [* {i = 1}]",
+ "STMT -> [* {i = 1}]",
+ "READ(i) -> [* {i = 1}]",
+ "WRITE(j, i) -> [2 {i = 1, j = 1}]",
+ "1: BLOCK -> [* {i = T}]",
+ "STMT -> [* {i = T}]",
+ "READ(i) -> [* {i = T}]",
+ "WRITE(j, i) -> [* {i = T, j = T}]",
+ "2: END");
+
+ analyze("int", "int j = 0; if (foo() == 1) j = 1; return j;").into(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(j, 0) -> [* {j = 0}]",
+ "STMT -> [* {j = 0}]",
+ "OPTTHROW(foo()) -> [NOTHROW=* {j = 0}, RE=2 {j = 0}]",
+ "CALL(foo) -> [* {j = 0}]",
+ "COND (EntryPoint.foo() == 1) -> [THEN=* {j = 0}, ELSE=1 {j = 0}]",
+ "STMT -> [* {j = 0}]",
+ "WRITE(j, 1) -> [* {j = 1}]",
+ "1: STMT -> [* {j = T}]",
+ "READ(j) -> [* {j = T}]",
+ "GOTO -> [* {j = T}]",
+ "2: END");
+
+ analyze("int", "int j = 0; if (foo() == 1) j = foo(); return j;").into(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(j, 0) -> [* {j = 0}]",
+ "STMT -> [* {j = 0}]",
+ "OPTTHROW(foo()) -> [NOTHROW=* {j = 0}, RE=2 {j = 0}]",
+ "CALL(foo) -> [* {j = 0}]",
+ "COND (EntryPoint.foo() == 1) -> [THEN=* {j = 0}, ELSE=1 {j = 0}]",
+ "STMT -> [* {j = 0}]",
+ "OPTTHROW(foo()) -> [NOTHROW=* {j = 0}, RE=2 {j = 0}]",
+ "CALL(foo) -> [* {j = 0}]",
+ "WRITE(j, EntryPoint.foo()) -> [* {j = T}]",
+ "1: STMT -> [* {j = T}]",
+ "READ(j) -> [* {j = T}]",
+ "GOTO -> [* {j = T}]",
+ "2: END");
+ }
+
+ public void testWhileLoop1() throws Exception {
+ analyze("void", "int j = 1; while (j > 0) ++j;").into(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(j, 1) -> [* {j = 1}]",
+ "STMT -> [* {j = 1}]",
+ "1: READ(j) -> [* {j = T}]",
+ "COND (j > 0) -> [THEN=* {j = T}, ELSE=2 {j = T}]",
+ "STMT -> [* {j = T}]",
+ "READWRITE(j, null) -> [1 {j = T}]",
+ "2: END");
+ }
+
+ public void testWhileLoop2() throws Exception {
+ analyze("void", "int j = 0; while (j > 0) {};").into(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(j, 0) -> [* {j = 0}]",
+ "STMT -> [* {j = 0}]",
+ "1: READ(j) -> [* {j = 0}]",
+ "COND (j > 0) -> [THEN=* {j = 0}, ELSE=2 {j = 0}]",
+ "BLOCK -> [1 {j = 0}]",
+ "2: END");
+ }
+
+ public void testConditionalExpressions() throws Exception {
+ analyze("void", "boolean b1 = false; boolean b2 = false; if (b1 && (b2 = true)) b1 = true;").into(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(b1, false) -> [* {b1 = false}]",
+ "STMT -> [* {b1 = false}]",
+ "WRITE(b2, false) -> [* {b1 = false, b2 = false}]",
+ "STMT -> [* {b1 = false, b2 = false}]",
+ "READ(b1) -> [* {b1 = false, b2 = false}]",
+ "COND (b1) -> [THEN=* {b1 = false, b2 = false}, ELSE=1 {b1 = false, b2 = false}]",
+ "WRITE(b2, true) -> [* {b1 = false, b2 = true}]",
+ "1: COND (b1 && (b2 = true)) -> [THEN=* {b1 = false, b2 = T}, ELSE=2 {b1 = false, b2 = T}]",
+ "STMT -> [* {b1 = false, b2 = T}]",
+ "WRITE(b1, true) -> [* {b1 = true, b2 = T}]",
+ "2: END");
+ }
+
+ // Various real-world stuff
+ public void testVariousStuff() throws Exception {
+ addSnippetClassDecl("static Object f = null;");
+
+ analyze("void",
+ "Object e = null;" +
+ "if (f != null) if (e == null)" +
+ " return;" +
+ "boolean b = e == null;").into(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(e, null) -> [* {e = null}]",
+ "STMT -> [* {e = null}]",
+ "READ(f) -> [* {e = null}]",
+ "COND (EntryPoint.f != null) -> [THEN=* {e = null}, ELSE=1 {e = null}]",
+ "STMT -> [* {e = null}]",
+ "READ(e) -> [* {e = null}]",
+ "COND (e == null) -> [THEN=* {e = null}, ELSE=1 {e = null}]",
+ "STMT -> [* {e = null}]",
+ "GOTO -> [2 {e = null}]",
+ "1: STMT -> [* {e = null}]",
+ "READ(e) -> [* {e = null}]",
+ "WRITE(b, e == null) -> [* {b = true, e = null}]",
+ "2: END"
+ );
+ }
+
+ @Override
+ protected Analysis<CfgNode<?>, CfgEdge, Cfg, ConstantsAssumption> createAnalysis(
+ JProgram program) {
+ return new ConstantsAnalysis(program);
+ }
+}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/constants/ConstantsAnalysisTransformationTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/constants/ConstantsAnalysisTransformationTest.java
new file mode 100644
index 0000000..50b443f
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/constants/ConstantsAnalysisTransformationTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2008 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.gflow.constants;
+
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.impl.gflow.CfgIntegratedAnalysisTestBase;
+import com.google.gwt.dev.jjs.impl.gflow.IntegratedAnalysis;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgTransformer;
+
+/**
+ *
+ */
+public class ConstantsAnalysisTransformationTest extends CfgIntegratedAnalysisTestBase<ConstantsAssumption> {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ addSnippetClassDecl("static int i_;");
+ addSnippetClassDecl("static int foo() { return 0; }");
+ addSnippetClassDecl("static int bar(int i) {return 0;}");
+ addSnippetClassDecl("static String baz() {return null;}");
+ }
+
+ public void testLinearStatements() throws Exception {
+ transform("void", "int i = 1; int j = i;").into(
+ "int i = 1;",
+ "int j = 1;");
+ transform("void", "int i = 1; int j = i; i = 2; j = i;").into(
+ "int i = 1;",
+ "int j = 1;",
+ "i = 2;",
+ "j = 2;");
+ transform("void", "int i = 1; i = i + 1; int j = i;").into(
+ "int i = 1;",
+ "i = 1 + 1;",
+ "int j = 2;");
+ }
+
+ public void testSequence() throws Exception {
+ transform("void", "int i = 1; int j = i; int k = j;").into(
+ "int i = 1;",
+ "int j = 1;",
+ "int k = 1;");
+ }
+
+ public void testIfStatement() throws Exception {
+ transform("void", "int i = 1; if (i_ == i) { i = 2; int j = i;} ").into(
+ "int i = 1;",
+ "if (EntryPoint.i_ == 1) {",
+ " i = 2;",
+ " int j = 2;",
+ "}");
+ transform("void", "int i = foo(); if (i == 1) { int j = i; } else { int j = i; } ").into(
+ "int i = EntryPoint.foo();",
+ "if (i == 1) {",
+ " int j = 1;",
+ "} else {",
+ " int j = i;",
+ "}");
+ }
+
+ public void testReplaceInMethodCall() throws Exception {
+ transform("void", "int i = 1; bar(i);").into(
+ "int i = 1;",
+ "EntryPoint.bar(1);");
+ }
+
+ public void testExpressionEvaluation() throws Exception {
+ transform("void", "int i = 1; int j = i + 1;").into(
+ "int i = 1;",
+ "int j = 1 + 1;");
+ transform("void", "int i = 1; int j = i - 1;").into(
+ "int i = 1;",
+ "int j = 1 - 1;");
+ transform("void", "int i = 1; boolean b = i == 1;").into(
+ "int i = 1;",
+ "boolean b = 1 == 1;");
+ }
+
+ public void testWhile() throws Exception {
+ transform("void", "int j = 0; while (j > 0) { }").into(
+ "int j = 0;",
+ "while (false) {",
+ "}" );
+
+ }
+
+ public void testConstantCondition() throws Exception {
+ transform("void", "while (true) { }").into(
+ "while (true) {",
+ "}" );
+
+ }
+
+ public void testNullValue() throws Exception {
+ transform("void", "Object e = null; boolean b = e == null;").into(
+ "Object e = null;",
+ "boolean b = null == null;" );
+
+ transform("void", "Object e = null; boolean b = e != null;").into(
+ "Object e = null;",
+ "boolean b = null != null;" );
+ }
+
+ public void testIncDec() throws Exception {
+ transform("void",
+ "int i = 0;",
+ "i++;",
+ "i++;",
+ "++i;"
+ ).into(
+ "int i = 0;",
+ "i++;",
+ "i++;",
+ "++i;"
+ );
+ }
+
+ public void testNotNull() throws Exception {
+ transform("boolean", "String s = baz(); if (s == null) return false; return s != null;").into(
+ "String s = EntryPoint.baz();", "if (s == null)", " return false;", "return s != null;");
+ }
+
+ @Override
+ protected IntegratedAnalysis<CfgNode<?>, CfgEdge, CfgTransformer, Cfg, ConstantsAssumption> createIntegratedAnalysis(
+ JProgram program) {
+ return new ConstantsAnalysis(program);
+ }
+}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/constants/ExpressionEvaluatorTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/constants/ExpressionEvaluatorTest.java
new file mode 100644
index 0000000..14b3444
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/constants/ExpressionEvaluatorTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2008 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.gflow.constants;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.jjs.ast.JBlock;
+import com.google.gwt.dev.jjs.ast.JDeclarationStatement;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JMethodBody;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.ast.JReturnStatement;
+import com.google.gwt.dev.jjs.ast.JStatement;
+import com.google.gwt.dev.jjs.ast.JValueLiteral;
+import com.google.gwt.dev.jjs.impl.OptimizerTestBase;
+import com.google.gwt.dev.jjs.impl.gflow.constants.ConstantsAssumption;
+import com.google.gwt.dev.jjs.impl.gflow.constants.ExpressionEvaluator;
+
+import java.util.List;
+
+/**
+ * Tests for ExpressionEvaluator - testing evaluation expressions based on
+ * assumptions.
+ */
+public class ExpressionEvaluatorTest extends OptimizerTestBase {
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ addSnippetClassDecl("static String foo() { return null; };");
+ }
+
+ public void testVariableRef() throws Exception {
+ assertThat("i", "int",
+ "int i = 1;").evaluatesInto("1");
+ }
+
+ public void testEq() throws Exception {
+ assertThat("i == 1", "boolean",
+ "int i = 1;").evaluatesInto("true");
+ assertThat("i != 1", "boolean",
+ "int i = 1;").evaluatesInto("false");
+ }
+
+ public void testNullNotNull() throws Exception {
+ assertThat("s == null", "boolean",
+ "String s = null;").evaluatesInto("true");
+ assertThat("s != null", "boolean",
+ "String s = null;").evaluatesInto("false");
+ assertThat("null == s", "boolean",
+ "String s = null;").evaluatesInto("true");
+ assertThat("null != s", "boolean",
+ "String s = null;").evaluatesInto("false");
+ }
+
+ public void testBinaryExpr() throws Exception {
+ assertThat("i + 1", "int", "int i; i = 1;").evaluatesInto("<null>");
+ assertThat("1 + i", "int", "int i; i = 1;").evaluatesInto("<null>");
+ assertThat("2 + i", "int", "int i = 1;").evaluatesInto("3");
+ assertThat("i + 3", "int", "int i = 1;").evaluatesInto("4");
+ }
+
+ private static class Result {
+ private final JValueLiteral literal;
+
+ public Result(JValueLiteral literal) {
+ this.literal = literal;
+ }
+
+ public void evaluatesInto(String string) {
+ String actual = literal == null ? "<null>" : literal.toSource();
+ assertEquals(string, actual);
+ }
+ }
+
+ private Result assertThat(String expr, String type,
+ String decls) throws UnableToCompleteException {
+ ConstantsAssumption.Updater updater =
+ new ConstantsAssumption.Updater(new ConstantsAssumption());
+
+ String codeSnippet = decls;
+ codeSnippet += "return " + expr + ";";
+ JProgram program = compileSnippet(type, codeSnippet);
+ JMethod mainMethod = findMainMethod(program);
+ JBlock block = ((JMethodBody) mainMethod.getBody()).getBlock();
+
+ List<JStatement> statements = block.getStatements();
+ // TODO: not a pretty assumption detection.
+ for (JStatement stmt : statements) {
+ if (!(stmt instanceof JDeclarationStatement)) {
+ continue;
+ }
+ JDeclarationStatement decl = (JDeclarationStatement) stmt;
+ if (decl.getInitializer() != null) {
+ updater.set(decl.getVariableRef().getTarget(),
+ (JValueLiteral) decl.getInitializer());
+ }
+ }
+
+ JReturnStatement returnStatement =
+ (JReturnStatement) statements.get(statements.size() - 1);
+ return new Result(ExpressionEvaluator.evaluate(returnStatement.getExpr(),
+ updater.unwrap()));
+ }
+}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/copy/CopyAnalysisTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/copy/CopyAnalysisTest.java
new file mode 100644
index 0000000..91d8107
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/copy/CopyAnalysisTest.java
@@ -0,0 +1,95 @@
+package com.google.gwt.dev.jjs.impl.gflow.copy;
+
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.impl.gflow.Analysis;
+import com.google.gwt.dev.jjs.impl.gflow.CfgAnalysisTestBase;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+
+public class CopyAnalysisTest extends CfgAnalysisTestBase<CopyAssumption> {
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ addSnippetClassDecl("static boolean b;");
+ }
+
+ public void testCopyCreation() throws Exception {
+ analyze("void", "int i = 1; int j = i;").into(
+ "BLOCK -> [* T]",
+ "STMT -> [* T]",
+ "WRITE(i, 1) -> [* T]",
+ "STMT -> [* T]",
+ "READ(i) -> [* T]",
+ "WRITE(j, i) -> [* {j = i}]",
+ "END");
+ }
+
+ public void testCopyKill1() throws Exception {
+ analyze("void", "int i = 1; int j = i; j = 1;").into(
+ "BLOCK -> [* T]",
+ "STMT -> [* T]",
+ "WRITE(i, 1) -> [* T]",
+ "STMT -> [* T]",
+ "READ(i) -> [* T]",
+ "WRITE(j, i) -> [* {j = i}]",
+ "STMT -> [* {j = i}]",
+ "WRITE(j, 1) -> [* {j = T}]",
+ "END");
+ }
+
+ public void testCopyKill2() throws Exception {
+ analyze("void", "int i = 1; int j = i; i = 2;").into(
+ "BLOCK -> [* T]",
+ "STMT -> [* T]",
+ "WRITE(i, 1) -> [* T]",
+ "STMT -> [* T]",
+ "READ(i) -> [* T]",
+ "WRITE(j, i) -> [* {j = i}]",
+ "STMT -> [* {j = i}]",
+ "WRITE(i, 2) -> [* {i = T, j = T}]",
+ "END");
+ }
+
+ public void testConditionalKill() throws Exception {
+ analyze("void", "int i = 1; int j = i; if (b) { j = 1; } int k = j;").into(
+ "BLOCK -> [* T]",
+ "STMT -> [* T]",
+ "WRITE(i, 1) -> [* T]",
+ "STMT -> [* T]",
+ "READ(i) -> [* T]",
+ "WRITE(j, i) -> [* {j = i}]",
+ "STMT -> [* {j = i}]",
+ "READ(b) -> [* {j = i}]",
+ "COND (EntryPoint.b) -> [THEN=* {j = i}, ELSE=1 {j = i}]",
+ "BLOCK -> [* {j = i}]",
+ "STMT -> [* {j = i}]",
+ "WRITE(j, 1) -> [* {j = T}]",
+ "1: STMT -> [* {j = T}]",
+ "READ(j) -> [* {j = T}]",
+ "WRITE(k, j) -> [* {j = T, k = j}]",
+ "END");
+ }
+
+ public void testRecursion() throws Exception {
+ analyze("void", "int i = 1; int j = 1; j = i; i = j;").into(
+ "BLOCK -> [* T]",
+ "STMT -> [* T]",
+ "WRITE(i, 1) -> [* T]",
+ "STMT -> [* T]",
+ "WRITE(j, 1) -> [* T]",
+ "STMT -> [* T]",
+ "READ(i) -> [* T]",
+ "WRITE(j, i) -> [* {j = i}]",
+ "STMT -> [* {j = i}]",
+ "READ(j) -> [* {j = i}]",
+ "WRITE(i, j) -> [* {j = i}]",
+ "END");
+ }
+
+ @Override
+ protected Analysis<CfgNode<?>, CfgEdge, Cfg, CopyAssumption> createAnalysis(
+ JProgram program) {
+ return new CopyAnalysis();
+ }
+}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/copy/CopyAnalysisTransformationTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/copy/CopyAnalysisTransformationTest.java
new file mode 100644
index 0000000..2f504a4
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/copy/CopyAnalysisTransformationTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2008 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.gflow.copy;
+
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.impl.gflow.CfgIntegratedAnalysisTestBase;
+import com.google.gwt.dev.jjs.impl.gflow.IntegratedAnalysis;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgTransformer;
+
+/**
+ *
+ */
+public class CopyAnalysisTransformationTest extends CfgIntegratedAnalysisTestBase<CopyAssumption> {
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ addSnippetClassDecl("static boolean b;");
+ }
+
+ public void testLinearStatements() throws Exception {
+ transform("void", "int i = 1; int j = i; int k = j;").into(
+ "int i = 1;",
+ "int j = i;",
+ "int k = i;");
+ }
+
+ public void testIf() throws Exception {
+ transform("void", "int i = 1; int j = i; if (b) { j = 1; } int k = j;").into(
+ "int i = 1;",
+ "int j = i;",
+ "if (EntryPoint.b) {",
+ " j = 1;",
+ "}",
+ "int k = j;");
+ }
+
+ public void testRecursion() throws Exception {
+ transform("int", "int i = 1; i = i; return i;").into(
+ "int i = 1;",
+ "i = i;",
+ "return i;");
+ transform("int", "int i = 1; int j = 1; j = i; i = j; return i;").into(
+ "int i = 1;",
+ "int j = 1;",
+ "j = i;",
+ "i = i;",
+ "return i;");
+ }
+
+ @Override
+ protected IntegratedAnalysis<CfgNode<?>, CfgEdge, CfgTransformer, Cfg, CopyAssumption> createIntegratedAnalysis(
+ JProgram program) {
+ return new CopyAnalysis();
+ }
+}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/liveness/LivenessAnalysisTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/liveness/LivenessAnalysisTest.java
new file mode 100644
index 0000000..6834eac
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/liveness/LivenessAnalysisTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2008 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.gflow.liveness;
+
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.impl.gflow.Analysis;
+import com.google.gwt.dev.jjs.impl.gflow.CfgAnalysisTestBase;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+
+/**
+ *
+ */
+public class LivenessAnalysisTest extends CfgAnalysisTestBase<LivenessAssumption> {
+ @Override
+ protected void setUp() throws Exception {
+ forward = false;
+ super.setUp();
+ }
+
+ public void testSingleStatement() throws Exception {
+ analyze("void", "int i = 1;").into(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(i, 1) -> [*]",
+ "END");
+ }
+
+ public void testMultipleLinearStatements() throws Exception {
+ analyze("int", "int i = 1; int j = 2; int k = i * j; i = 3; j = 4; return i;").into(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(i, 1) -> [* {i}]",
+ "STMT -> [* {i}]",
+ "WRITE(j, 2) -> [* {i, j}]",
+ "STMT -> [* {i, j}]",
+ "READ(i) -> [* {j}]",
+ "READ(j) -> [*]",
+ "WRITE(k, i * j) -> [*]",
+ "STMT -> [*]",
+ "WRITE(i, 3) -> [* {i}]",
+ "STMT -> [* {i}]",
+ "WRITE(j, 4) -> [* {i}]",
+ "STMT -> [* {i}]",
+ "READ(i) -> [*]",
+ "GOTO -> [*]",
+ "END");
+ }
+
+ public void testNonAsignmentWriteTest() throws Exception {
+ analyze("int", "int i = 1; i += 2; return i;").into(
+ "BLOCK -> [*]",
+ "STMT -> [*]",
+ "WRITE(i, 1) -> [* {i}]",
+ "STMT -> [* {i}]",
+ "READWRITE(i, null) -> [* {i}]",
+ "STMT -> [* {i}]",
+ "READ(i) -> [*]",
+ "GOTO -> [*]",
+ "END");
+ }
+
+ @Override
+ protected Analysis<CfgNode<?>, CfgEdge, Cfg, LivenessAssumption>
+ createAnalysis(JProgram program) {
+ return new LivenessAnalysis();
+ }
+}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/liveness/LivenessTransformationTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/liveness/LivenessTransformationTest.java
new file mode 100644
index 0000000..3c02c5c
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/liveness/LivenessTransformationTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2008 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.gflow.liveness;
+
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.impl.gflow.CfgIntegratedAnalysisTestBase;
+import com.google.gwt.dev.jjs.impl.gflow.IntegratedAnalysis;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
+import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgTransformer;
+
+/**
+ *
+ */
+public class LivenessTransformationTest extends CfgIntegratedAnalysisTestBase<LivenessAssumption> {
+ @Override
+ protected void setUp() throws Exception {
+ forward = false;
+ super.setUp();
+ }
+
+ public void testLinearStatements() throws Exception {
+ transform("void", "int i = 1; int j = 2;").into(
+ "int i;",
+ "int j;");
+ }
+
+ public void testUsage() throws Exception {
+ transform("void", "int i = 1; int j = i;").into(
+ "int i = 1;",
+ "int j;");
+
+ transform("int", "int i = 1; int j = 2; return i;").into(
+ "int i = 1;",
+ "int j;",
+ "return i;");
+ }
+
+ public void testDeadAssignments() throws Exception {
+ transform("void", "int i = 1; i = 2; i = 3; int j = i;").into(
+ "int i;",
+ "i = 3;",
+ "int j;");
+ }
+
+ public void testSomeDeadAssignment() throws Exception {
+ transform("int", "int i = 1; i = 2; i = 3; int j = i; return j;").into(
+ "int i;",
+ "i = 3;",
+ "int j = i;",
+ "return j;");
+ }
+
+ public void testFieldAssignment() throws Exception {
+ addSnippetClassDecl("static int i;");
+
+ transform("void", "i = 1;").into("EntryPoint.i = 1;");
+ }
+
+ public void testSideEffects() throws Exception {
+ addSnippetClassDecl("static int foo() { return 0; };");
+
+ transform("void", "int i = foo();").into(
+ "EntryPoint.foo();",
+ "int i;");
+ }
+
+ public void testMultiAssignments() throws Exception {
+ transform("void", "int i = 1, j = 1; i = j = 2;").into(
+ "int i;",
+ "int j;");
+ }
+
+ @Override
+ protected IntegratedAnalysis<CfgNode<?>, CfgEdge, CfgTransformer, Cfg, LivenessAssumption> createIntegratedAnalysis(
+ JProgram program) {
+ return new LivenessAnalysis();
+ }
+}