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();
+  }
+}