Merge remote-tracking branch 'aosp/ub-jack' into optim-dev
diff --git a/.gitignore b/.gitignore
index d50522a..e480c67 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,3 +27,8 @@
 # Code coverage
 jack-coverage/testResults/
 
+# IDEA
+.idea
+JackCheckStyleSuppressions.xml
+JackStyleRules.xml
+**/*.iml
diff --git a/jack-tests/src/com/android/jack/test/dex/DexCodeItemPrinter.java b/jack-tests/src/com/android/jack/test/dex/DexCodeItemPrinter.java
index 07b61f8..93e3d46 100644
--- a/jack-tests/src/com/android/jack/test/dex/DexCodeItemPrinter.java
+++ b/jack-tests/src/com/android/jack/test/dex/DexCodeItemPrinter.java
@@ -18,6 +18,7 @@
 
 import org.jf.dexlib.Code.FiveRegisterInstruction;
 import org.jf.dexlib.Code.Format.Format;
+import org.jf.dexlib.Code.Format.PackedSwitchDataPseudoInstruction;
 import org.jf.dexlib.Code.Instruction;
 import org.jf.dexlib.Code.InstructionWithReference;
 import org.jf.dexlib.Code.LiteralInstruction;
@@ -116,10 +117,13 @@
 
   private void dump(
       @Nonnull Instruction instr, int address,
-      @Nonnull TreeMap<Integer, String> references) {
-    write(instr.opcode.name);
-
+      @Nonnull TreeMap<Integer, String> references,
+      @Nonnull Map<Integer, Integer> crossAddressTable) {
     Format format = instr.getFormat();
+    if (format != Format.PackedSwitchData) {
+      write(instr.opcode.name);
+    }
+
     switch (format) {
       case Format10x:
         break;
@@ -131,6 +135,7 @@
         break;
 
       case Format21t:
+      case Format22t:
       case Format31t:
         write(" ").registers(instr).write(", ").address(instr, address, references);
         break;
@@ -157,6 +162,19 @@
         write(" ").registers(instr).write(", ").reference(instr);
         break;
 
+      case PackedSwitchData:
+        PackedSwitchDataPseudoInstruction packed = (PackedSwitchDataPseudoInstruction) instr;
+        write("packed-switch: [").write(packed.getFirstKey())
+            .write("..").write(packed.getTargetCount() + packed.getFirstKey() - 1).write("] -> ");
+        String sep = "[";
+        int[] targets = packed.getTargets();
+        for (int i = 0; i < targets.length; i++) {
+          write(sep).write(references.get(targets[i] + crossAddressTable.get(address)));
+          sep = ", ";
+        }
+        write("]");
+        break;
+
       default:
         write(" // Details are not implemented yet for this format: " + format);
         break;
@@ -168,20 +186,35 @@
   private void dump(@Nonnull Instruction[] instructions) {
     String indent = "    | ";
 
-    TreeMap<Integer, String> references = getReferencedAddresses(instructions);
+    TreeMap<Integer, Integer> crossAddressTable = getCrossAddressTable(instructions);
+    TreeMap<Integer, String> references = getReferencedAddresses(instructions, crossAddressTable);
 
     int address = 0;
     for (Instruction instr : instructions) {
       // Indentation with optional label
       String label = references.get(address);
       write(label == null ? indent : (label + "-> "));
-      dump(instr, address, references);
+      dump(instr, address, references, crossAddressTable);
       address += instr.getSize(address);
     }
   }
 
   @Nonnull
-  private TreeMap<Integer, String> getReferencedAddresses(@Nonnull Instruction[] instructions) {
+  private TreeMap<Integer, Integer> getCrossAddressTable(@Nonnull Instruction[] instructions) {
+    int address = 0;
+    TreeMap<Integer, Integer> references = new TreeMap<>();
+    for (Instruction instr : instructions) {
+      if (instr instanceof OffsetInstruction) {
+        references.put(address + ((OffsetInstruction) instr).getTargetAddressOffset(), address);
+      }
+      address += instr.getSize(address);
+    }
+    return references;
+  }
+
+  @Nonnull
+  private TreeMap<Integer, String> getReferencedAddresses(
+      @Nonnull Instruction[] instructions, @Nonnull Map<Integer, Integer> crossAddressTable) {
     int address = 0;
     TreeMap<Integer, String> references = new TreeMap<Integer, String>();
     for (Instruction instr : instructions) {
@@ -189,7 +222,7 @@
         references.put(address + ((OffsetInstruction) instr).getTargetAddressOffset(), null);
       } else if (instr instanceof MultiOffsetInstruction) {
         for (int target : ((MultiOffsetInstruction) instr).getTargets()) {
-          references.put(target, null);
+          references.put(target + crossAddressTable.get(address), null);
         }
       }
       address += instr.getSize(address);
diff --git a/jack-tests/src/com/android/jack/test/dex/DexNoOpValidator.java b/jack-tests/src/com/android/jack/test/dex/DexNoOpValidator.java
new file mode 100644
index 0000000..449289c
--- /dev/null
+++ b/jack-tests/src/com/android/jack/test/dex/DexNoOpValidator.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.test.dex;
+
+import javax.annotation.Nonnull;
+
+/** DEX validator performing no validation */
+public final class DexNoOpValidator<T> extends DexValidator<T> {
+  protected void validateImpl(@Nonnull T element) {
+  }
+}
diff --git a/jack-tests/tests/com/android/jack/AllTests.java b/jack-tests/tests/com/android/jack/AllTests.java
index fa07f24..1e5ae93 100644
--- a/jack-tests/tests/com/android/jack/AllTests.java
+++ b/jack-tests/tests/com/android/jack/AllTests.java
@@ -50,6 +50,7 @@
 import com.android.jack.newarray.NewarrayTests;
 import com.android.jack.nopackage.NoPackageTests;
 import com.android.jack.opcodes.OpcodesTests;
+import com.android.jack.optimizations.blockmerger.BlockMergerTests;
 import com.android.jack.optimizations.defuse.DefUseTests;
 import com.android.jack.optimizations.exprsimplifier.ExprsimplifierTests;
 import com.android.jack.optimizations.ifwithconstantsimplifier.IfWithConstantSimplifierTests;
@@ -95,6 +96,7 @@
     ArrayTests.class,
     AssertionTests.class,
     AssignTests.class,
+    BlockMergerTests.class,
     BoostLockedRegionPriorityTests.class,
     BoxTests.class,
     BridgeTests.class,
diff --git a/jack-tests/tests/com/android/jack/optimizations/blockmerger/BlockMergerTests.java b/jack-tests/tests/com/android/jack/optimizations/blockmerger/BlockMergerTests.java
new file mode 100644
index 0000000..b5c7935
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/optimizations/blockmerger/BlockMergerTests.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.optimizations.blockmerger;
+
+import com.android.jack.optimizations.Optimizations;
+import com.android.jack.optimizations.cfg.VariablesScope;
+import com.android.jack.test.dex.DexFileTypesValidator;
+import com.android.jack.test.dex.DexMethod;
+import com.android.jack.test.dex.DexMethodDalvikCodeValidator;
+import com.android.jack.test.dex.DexNoOpValidator;
+import com.android.jack.test.dex.DexOutputBasedTest;
+import com.android.jack.test.dex.DexTypeMethodsValidator;
+import com.android.jack.test.dex.DexValidator;
+import com.android.jack.test.junit.Runtime;
+
+import org.junit.Test;
+
+import javax.annotation.Nonnull;
+
+/** Set of unused local variables removal */
+public class BlockMergerTests extends DexOutputBasedTest {
+  @Nonnull
+  private CompilationProperties properties() {
+    return CompilationProperties.EMPTY
+        .with(Optimizations.SimpleBasicBlockMerging.ENABLE.getName(), Boolean.FALSE);
+  }
+
+  @Nonnull
+  private CompilationProperties properties(
+      boolean preserveSourceInfo, @Nonnull VariablesScope scope) {
+    return CompilationProperties.EMPTY
+        .with(Optimizations.SimpleBasicBlockMerging.ENABLE.getName(), Boolean.TRUE)
+        .with(Optimizations.SimpleBasicBlockMerging.PRESERVE_SOURCE_INFO.getName(),
+            Boolean.valueOf(preserveSourceInfo))
+        .with(Optimizations.SimpleBasicBlockMerging.MERGE_VARIABLES.getName(),
+            scope.toString());
+  }
+
+  @Nonnull
+  private DexValidator<DexMethod> dalvik(@Nonnull String test, @Nonnull String expected) {
+    return usingLegacyCompiler() ? new DexNoOpValidator<DexMethod>() :
+        new DexMethodDalvikCodeValidator(resource(test, expected));
+  }
+
+  @Test
+  @Runtime
+  public void test001() throws Exception {
+    String test = "com.android.jack.optimizations.blockmerger.test001";
+    String aType = "Lcom/android/jack/optimizations/blockmerger/test001/jack/A;";
+
+    compileAndValidate(test, properties(),
+        new DexFileTypesValidator()
+            .insert(aType,
+                new DexTypeMethodsValidator()
+                    .insert("testA(IIIII)I", dalvik(test, "A.testA.no-opt.dalvik"))
+                    .insert("testB(IIIII)I", dalvik(test, "A.testB.no-opt.dalvik"))
+                    .insert("testC(IIIII)I", dalvik(test, "A.testC.no-opt.dalvik"))));
+
+    compileAndValidate(test, properties(false, VariablesScope.NONE),
+        new DexFileTypesValidator()
+            .insert(aType,
+                new DexTypeMethodsValidator()
+                    .insert("testA(IIIII)I", dalvik(test, "A.testA.no-opt.dalvik" /* SAME */))
+                    .insert("testB(IIIII)I", dalvik(test, "A.testB.no-opt.dalvik" /* SAME */))
+                    .insert("testC(IIIII)I", dalvik(test, "A.testC.opt-none.dalvik"))));
+
+    compileAndValidate(test, properties(false, VariablesScope.SYNTHETIC),
+        new DexFileTypesValidator()
+            .insert(aType,
+                new DexTypeMethodsValidator()
+                    .insert("testA(IIIII)I", dalvik(test, "A.testA.opt-syn.dalvik"))
+                    .insert("testB(IIIII)I", dalvik(test, "A.testB.no-opt.dalvik" /* SAME */))
+                    .insert("testC(IIIII)I", dalvik(test, "A.testC.opt-none.dalvik" /* SAME */))));
+
+    compileAndValidate(test, properties(false, VariablesScope.ALL),
+        new DexFileTypesValidator()
+            .insert(aType,
+                new DexTypeMethodsValidator()
+                    .insert("testA(IIIII)I", dalvik(test, "A.testA.opt-syn.dalvik") /* SAME */)
+                    .insert("testB(IIIII)I", dalvik(test, "A.testB.opt-all.dalvik"))
+                    .insert("testC(IIIII)I", dalvik(test, "A.testC.opt-none.dalvik") /* SAME */)));
+  }
+
+  @Test
+  @Runtime
+  public void test002() throws Exception {
+    String test = "com.android.jack.optimizations.blockmerger.test002";
+    String aType = "Lcom/android/jack/optimizations/blockmerger/test002/jack/A;";
+
+    compileAndValidate(test, properties(),
+        new DexFileTypesValidator()
+            .insert(aType,
+                new DexTypeMethodsValidator()
+                    .insert("testA(I)I", dalvik(test, "A.testA.no-opt.dalvik"))
+                    .insert("testB(III)I", dalvik(test, "A.testB.no-opt.dalvik"))
+                    .insert("testC(I)I", dalvik(test, "A.testC.no-opt.dalvik"))));
+
+    compileAndValidate(test, properties(true, VariablesScope.ALL),
+        new DexFileTypesValidator()
+            .insert(aType,
+                new DexTypeMethodsValidator()
+                    .insert("testA(I)I", dalvik(test, "A.testA.no-opt.dalvik"))
+                    .insert("testB(III)I", dalvik(test, "A.testB.no-opt.dalvik"))));
+
+    compileAndValidate(test, properties(false, VariablesScope.SYNTHETIC),
+        new DexFileTypesValidator()
+            .insert(aType,
+                new DexTypeMethodsValidator()
+                    .insert("testC(I)I", dalvik(test, "A.testC.opt-syn.dalvik"))));
+
+    compileAndValidate(test, properties(false, VariablesScope.ALL),
+        new DexFileTypesValidator()
+            .insert(aType,
+                new DexTypeMethodsValidator()
+                    .insert("testA(I)I", dalvik(test, "A.testA.opt-all.dalvik"))
+                    .insert("testB(III)I", dalvik(test, "A.testB.opt-all.dalvik"))));
+  }
+}
diff --git a/jack-tests/tests/com/android/jack/optimizations/blockmerger/test001/A.testA.no-opt.dalvik b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test001/A.testA.no-opt.dalvik
new file mode 100644
index 0000000..f6c515c
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test001/A.testA.no-opt.dalvik
@@ -0,0 +1,31 @@
+method: Lcom/android/jack/optimizations/blockmerger/test001/jack/A;->testA(IIIII)I
+registers: 8, in/out: 6/0
+instructions: 28
+    | add-int/lit8 v0, v3, 0
+    | packed-switch v3, #04
+    | mul-int/lit16 v1, v7, 10000
+    | add-int/2addr v0, v1
+#00-> return v0
+#01-> mul-int/lit8 v1, v4, 10
+    | add-int/2addr v0, v1
+    | mul-int/lit8 v1, v5, 100
+    | add-int/2addr v0, v1
+    | mul-int/lit16 v1, v6, 1000
+    | add-int/2addr v0, v1
+    | mul-int/lit16 v1, v7, 10000
+    | add-int/2addr v0, v1
+    | goto #00
+#02-> mul-int/lit8 v1, v5, 100
+    | add-int/2addr v0, v1
+    | mul-int/lit16 v1, v6, 1000
+    | add-int/2addr v0, v1
+    | mul-int/lit16 v1, v7, 10000
+    | add-int/2addr v0, v1
+    | goto #00
+#03-> mul-int/lit16 v1, v6, 1000
+    | add-int/2addr v0, v1
+    | mul-int/lit16 v1, v7, 10000
+    | add-int/2addr v0, v1
+    | goto #00
+    | nop
+#04-> packed-switch: [0..2] -> [#01, #02, #03]
diff --git a/jack-tests/tests/com/android/jack/optimizations/blockmerger/test001/A.testA.opt-syn.dalvik b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test001/A.testA.opt-syn.dalvik
new file mode 100644
index 0000000..9e71592
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test001/A.testA.opt-syn.dalvik
@@ -0,0 +1,17 @@
+method: Lcom/android/jack/optimizations/blockmerger/test001/jack/A;->testA(IIIII)I
+registers: 8, in/out: 6/0
+instructions: 14
+    | add-int/lit8 v0, v3, 0
+    | packed-switch v3, #04
+#00-> mul-int/lit16 v1, v7, 10000
+    | add-int/2addr v0, v1
+    | return v0
+#01-> mul-int/lit8 v1, v4, 10
+    | add-int/2addr v0, v1
+#02-> mul-int/lit8 v1, v5, 100
+    | add-int/2addr v0, v1
+#03-> mul-int/lit16 v1, v6, 1000
+    | add-int/2addr v0, v1
+    | goto #00
+    | nop
+#04-> packed-switch: [0..2] -> [#01, #02, #03]
diff --git a/jack-tests/tests/com/android/jack/optimizations/blockmerger/test001/A.testB.no-opt.dalvik b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test001/A.testB.no-opt.dalvik
new file mode 100644
index 0000000..80b41c4
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test001/A.testB.no-opt.dalvik
@@ -0,0 +1,53 @@
+method: Lcom/android/jack/optimizations/blockmerger/test001/jack/A;->testB(IIIII)I
+registers: 8, in/out: 6/0
+instructions: 50
+    | add-int/lit8 v0, v3, 0
+    | if-nez v3, #00
+    | mul-int/lit8 v1, v4, 2
+    | sub-int/2addr v1, v4
+    | mul-int/lit8 v1, v1, 10
+    | add-int/2addr v0, v1
+    | mul-int/lit8 v1, v5, 2
+    | sub-int/2addr v1, v5
+    | mul-int/lit8 v1, v1, 100
+    | add-int/2addr v0, v1
+    | mul-int/lit8 v1, v6, 2
+    | sub-int/2addr v1, v6
+    | mul-int/lit16 v1, v1, 1000
+    | add-int/2addr v0, v1
+    | mul-int/lit8 v1, v7, 2
+    | sub-int/2addr v1, v7
+    | mul-int/lit16 v1, v1, 10000
+    | add-int/2addr v0, v1
+    | return v0
+#00-> const/4 v1, 1
+    | if-ne v3, v1, #01
+    | mul-int/lit8 v1, v5, 2
+    | sub-int/2addr v1, v5
+    | mul-int/lit8 v1, v1, 100
+    | add-int/2addr v0, v1
+    | mul-int/lit8 v1, v6, 2
+    | sub-int/2addr v1, v6
+    | mul-int/lit16 v1, v1, 1000
+    | add-int/2addr v0, v1
+    | mul-int/lit8 v1, v7, 2
+    | sub-int/2addr v1, v7
+    | mul-int/lit16 v1, v1, 10000
+    | add-int/2addr v0, v1
+    | return v0
+#01-> const/4 v1, 2
+    | if-ne v3, v1, #02
+    | mul-int/lit8 v1, v6, 2
+    | sub-int/2addr v1, v6
+    | mul-int/lit16 v1, v1, 1000
+    | add-int/2addr v0, v1
+    | mul-int/lit8 v1, v7, 2
+    | sub-int/2addr v1, v7
+    | mul-int/lit16 v1, v1, 10000
+    | add-int/2addr v0, v1
+    | return v0
+#02-> mul-int/lit8 v1, v7, 2
+    | sub-int/2addr v1, v7
+    | mul-int/lit16 v1, v1, 10000
+    | add-int/2addr v0, v1
+    | return v0
diff --git a/jack-tests/tests/com/android/jack/optimizations/blockmerger/test001/A.testB.opt-all.dalvik b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test001/A.testB.opt-all.dalvik
new file mode 100644
index 0000000..864da92
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test001/A.testB.opt-all.dalvik
@@ -0,0 +1,27 @@
+method: Lcom/android/jack/optimizations/blockmerger/test001/jack/A;->testB(IIIII)I
+registers: 8, in/out: 6/0
+instructions: 24
+    | add-int/lit8 v0, v3, 0
+    | if-nez v3, #03
+    | mul-int/lit8 v1, v4, 2
+    | sub-int/2addr v1, v4
+    | mul-int/lit8 v1, v1, 10
+    | add-int/2addr v0, v1
+#00-> mul-int/lit8 v1, v5, 2
+    | sub-int/2addr v1, v5
+    | mul-int/lit8 v1, v1, 100
+    | add-int/2addr v0, v1
+#01-> mul-int/lit8 v1, v6, 2
+    | sub-int/2addr v1, v6
+    | mul-int/lit16 v1, v1, 1000
+    | add-int/2addr v0, v1
+#02-> mul-int/lit8 v1, v7, 2
+    | sub-int/2addr v1, v7
+    | mul-int/lit16 v1, v1, 10000
+    | add-int/2addr v0, v1
+    | return v0
+#03-> const/4 v1, 1
+    | if-eq v3, v1, #00
+    | const/4 v1, 2
+    | if-ne v3, v1, #02
+    | goto #01
diff --git a/jack-tests/tests/com/android/jack/optimizations/blockmerger/test001/A.testC.no-opt.dalvik b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test001/A.testC.no-opt.dalvik
new file mode 100644
index 0000000..122ee87
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test001/A.testC.no-opt.dalvik
@@ -0,0 +1,22 @@
+method: Lcom/android/jack/optimizations/blockmerger/test001/jack/A;->testC(IIIII)I
+registers: 7, in/out: 6/0
+instructions: 19
+    | if-nez v2, #00
+    | add-int v0, v2, v3
+    | add-int/2addr v0, v4
+    | add-int/2addr v0, v5
+    | add-int/2addr v0, v6
+    | return v0
+#00-> const/4 v0, 1
+    | if-ne v2, v0, #01
+    | add-int v0, v2, v4
+    | add-int/2addr v0, v5
+    | add-int/2addr v0, v6
+    | return v0
+#01-> const/4 v0, 2
+    | if-ne v2, v0, #02
+    | add-int v0, v2, v5
+    | add-int/2addr v0, v6
+    | return v0
+#02-> add-int v0, v2, v6
+    | return v0
diff --git a/jack-tests/tests/com/android/jack/optimizations/blockmerger/test001/A.testC.opt-none.dalvik b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test001/A.testC.opt-none.dalvik
new file mode 100644
index 0000000..a1808fd
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test001/A.testC.opt-none.dalvik
@@ -0,0 +1,19 @@
+method: Lcom/android/jack/optimizations/blockmerger/test001/jack/A;->testC(IIIII)I
+registers: 7, in/out: 6/0
+instructions: 16
+    | if-nez v2, #02
+    | add-int v0, v2, v3
+    | add-int/2addr v0, v4
+#00-> add-int/2addr v0, v5
+#01-> add-int/2addr v0, v6
+    | return v0
+#02-> const/4 v0, 1
+    | if-ne v2, v0, #03
+    | add-int v0, v2, v4
+    | goto #00
+#03-> const/4 v0, 2
+    | if-ne v2, v0, #04
+    | add-int v0, v2, v5
+    | goto #01
+#04-> add-int v0, v2, v6
+    | return v0
diff --git a/jack-tests/tests/com/android/jack/optimizations/blockmerger/test001/dx/Tests.java b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test001/dx/Tests.java
new file mode 100644
index 0000000..deadbd5
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test001/dx/Tests.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.optimizations.blockmerger.test001.dx;
+
+import com.android.jack.optimizations.blockmerger.test001.jack.*;
+
+import junit.framework.Assert;
+import org.junit.Test;
+
+/** Just touch all the classes */
+public class Tests {
+  @Test
+  public void test001() {
+    A a = new A();
+    Assert.assertEquals("|43210|43201|43002|40003", a.testA());
+    Assert.assertEquals("|43210|43201|43002|40003", a.testB());
+    Assert.assertEquals("|43210|43201|43002|40003", a.testC());
+  }
+}
diff --git a/jack-tests/tests/com/android/jack/optimizations/blockmerger/test001/jack/A.java b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test001/jack/A.java
new file mode 100644
index 0000000..13e9f5c
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test001/jack/A.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.optimizations.blockmerger.test001.jack;
+
+public class A {
+  private int testA(int s, int a, int b, int c, int d) {
+    int result = 0;
+    result += s;
+    switch (s) {
+      case 0: {
+        int A = a, B = b, C = c, D = d;
+        result += A * 10;
+        result += B * 100;
+        result += C * 1000;
+        result += D * 10000;
+        break;
+      }
+      case 1: {
+        int A = a, B = b, C = c, D = d;
+        result += B * 100;
+        result += C * 1000;
+        result += D * 10000;
+        break;
+      }
+      case 2: {
+        int A = a, B = b, C = c, D = d;
+        result += C * 1000;
+        result += D * 10000;
+        break;
+      }
+      default: {
+        int A = a, B = b, C = c, D = d;
+        result += D * 10000;
+        break;
+      }
+    }
+    return result;
+  }
+
+  private int testB(int s, int a, int b, int c, int d) {
+    int result = 0;
+    result += s;
+    if (s == 0) {
+      int A = a * 2 - a;
+      result += A * 10;
+      int B = b * 2 - b;
+      result += B * 100;
+      int C = c * 2 - c;
+      result += C * 1000;
+      int D = d * 2 - d;
+      result += D * 10000;
+    } else if (s == 1) {
+      int B = b * 2 - b;
+      result += B * 100;
+      int C = c * 2 - c;
+      result += C * 1000;
+      int D = d * 2 - d;
+      result += D * 10000;
+    } else if (s == 2) {
+      int C = c * 2 - c;
+      result += C * 1000;
+      int D = d * 2 - d;
+      result += D * 10000;
+    } else {
+      int D = d * 2 - d;
+      result += D * 10000;
+    }
+    return result;
+  }
+
+  private int testC(int s, int a, int b, int c, int d) {
+    int result = s;
+    if (s == 0) {
+      result += a;
+      result += b;
+      result += c;
+      result += d;
+    } else if (s == 1) {
+      result += b;
+      result += c;
+      result += d;
+    } else if (s == 2) {
+      result += c;
+      result += d;
+    } else {
+      result += d;
+    }
+    return result;
+  }
+
+  public String testA() {
+    return "" +
+        "|" + testA(0, 1, 2, 3, 4) +
+        "|" + testA(1, 9, 2, 3, 4) +
+        "|" + testA(2, 9, 9, 3, 4) +
+        "|" + testA(3, 9, 9, 9, 4);
+  }
+
+  public String testB() {
+    return "" +
+        "|" + testB(0, 1, 2, 3, 4) +
+        "|" + testB(1, 9, 2, 3, 4) +
+        "|" + testB(2, 9, 9, 3, 4) +
+        "|" + testB(3, 9, 9, 9, 4);
+  }
+
+  public String testC() {
+    return "" +
+        "|" + testC(0, 10, 200, 3000, 40000) +
+        "|" + testC(1, 99, 200, 3000, 40000) +
+        "|" + testC(2, 99, 999, 3000, 40000) +
+        "|" + testC(3, 99, 999, 9999, 40000);
+  }
+}
diff --git a/jack-tests/tests/com/android/jack/optimizations/blockmerger/test002/A.testA.no-opt.dalvik b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test002/A.testA.no-opt.dalvik
new file mode 100644
index 0000000..a2331cd
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test002/A.testA.no-opt.dalvik
@@ -0,0 +1,13 @@
+method: Lcom/android/jack/optimizations/blockmerger/test002/jack/A;->testA(I)I
+registers: 2, in/out: 2/0
+instructions: 10
+    | if-lez v1, #03
+    | add-int/lit8 v1, v1, -1
+#00-> if-lez v1, #01
+    | return v1
+#01-> if-gez v1, #02
+    | return v1
+#02-> return v1
+#03-> if-gez v1, #00
+    | add-int/lit8 v1, v1, 1
+    | goto #00
diff --git a/jack-tests/tests/com/android/jack/optimizations/blockmerger/test002/A.testA.opt-all.dalvik b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test002/A.testA.opt-all.dalvik
new file mode 100644
index 0000000..bd32da0
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test002/A.testA.opt-all.dalvik
@@ -0,0 +1,9 @@
+method: Lcom/android/jack/optimizations/blockmerger/test002/jack/A;->testA(I)I
+registers: 2, in/out: 2/0
+instructions: 6
+    | if-lez v1, #01
+    | add-int/lit8 v1, v1, -1
+#00-> return v1
+#01-> if-gez v1, #00
+    | add-int/lit8 v1, v1, 1
+    | goto #00
diff --git a/jack-tests/tests/com/android/jack/optimizations/blockmerger/test002/A.testB.no-opt.dalvik b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test002/A.testB.no-opt.dalvik
new file mode 100644
index 0000000..4d6abda
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test002/A.testB.no-opt.dalvik
@@ -0,0 +1,22 @@
+method: Lcom/android/jack/optimizations/blockmerger/test002/jack/A;->testB(III)I
+registers: 5, in/out: 4/0
+instructions: 19
+    | if-lez v2, #03
+    | add-int/lit8 v2, v2, -1
+#00-> if-lez v2, #01
+    | mul-int v0, v3, v4
+    | mul-int/lit8 v0, v0, 10
+    | add-int/2addr v0, v2
+    | return v0
+#01-> if-gez v2, #02
+    | mul-int v0, v3, v4
+    | mul-int/lit8 v0, v0, 10
+    | add-int/2addr v0, v2
+    | return v0
+#02-> mul-int v0, v3, v4
+    | mul-int/lit8 v0, v0, 10
+    | add-int/2addr v0, v2
+    | return v0
+#03-> if-gez v2, #00
+    | add-int/lit8 v2, v2, 1
+    | goto #00
diff --git a/jack-tests/tests/com/android/jack/optimizations/blockmerger/test002/A.testB.opt-all.dalvik b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test002/A.testB.opt-all.dalvik
new file mode 100644
index 0000000..7073afe
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test002/A.testB.opt-all.dalvik
@@ -0,0 +1,12 @@
+method: Lcom/android/jack/optimizations/blockmerger/test002/jack/A;->testB(III)I
+registers: 5, in/out: 4/0
+instructions: 9
+    | if-lez v2, #01
+    | add-int/lit8 v2, v2, -1
+#00-> mul-int v0, v3, v4
+    | mul-int/lit8 v0, v0, 10
+    | add-int/2addr v0, v2
+    | return v0
+#01-> if-gez v2, #00
+    | add-int/lit8 v2, v2, 1
+    | goto #00
diff --git a/jack-tests/tests/com/android/jack/optimizations/blockmerger/test002/A.testC.no-opt.dalvik b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test002/A.testC.no-opt.dalvik
new file mode 100644
index 0000000..4bb87e0
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test002/A.testC.no-opt.dalvik
@@ -0,0 +1,11 @@
+method: Lcom/android/jack/optimizations/blockmerger/test002/jack/A;->testC(I)I
+registers: 3, in/out: 2/0
+instructions: 8
+    | if-lez v2, #00
+    | const-string/jumbo v0, string_id_item: a > 0
+    | return v2
+#00-> if-gez v2, #01
+    | const-string/jumbo v0, string_id_item: a < 0
+    | return v2
+#01-> const-string/jumbo v0, string_id_item: a == 0
+    | return v2
diff --git a/jack-tests/tests/com/android/jack/optimizations/blockmerger/test002/A.testC.opt-syn.dalvik b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test002/A.testC.opt-syn.dalvik
new file mode 100644
index 0000000..7d690ac
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test002/A.testC.opt-syn.dalvik
@@ -0,0 +1,11 @@
+method: Lcom/android/jack/optimizations/blockmerger/test002/jack/A;->testC(I)I
+registers: 3, in/out: 2/0
+instructions: 8
+    | if-lez v2, #01
+    | const-string/jumbo v0, string_id_item: a > 0
+#00-> return v2
+#01-> if-gez v2, #02
+    | const-string/jumbo v0, string_id_item: a < 0
+    | goto #00
+#02-> const-string/jumbo v0, string_id_item: a == 0
+    | goto #00
diff --git a/jack-tests/tests/com/android/jack/optimizations/blockmerger/test002/dx/Tests.java b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test002/dx/Tests.java
new file mode 100644
index 0000000..8cb874f
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test002/dx/Tests.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.optimizations.blockmerger.test002.dx;
+
+import com.android.jack.optimizations.blockmerger.test002.jack.*;
+
+import junit.framework.Assert;
+import org.junit.Test;
+
+/** Just touch all the classes */
+public class Tests {
+  @Test
+  public void test001() {
+    A a = new A();
+    Assert.assertEquals("1|0|0|0|-1", a.testA());
+    Assert.assertEquals("11|20|30|-40|-51", a.testB());
+    Assert.assertEquals("1|0|-1", a.testC());
+  }
+}
diff --git a/jack-tests/tests/com/android/jack/optimizations/blockmerger/test002/jack/A.java b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test002/jack/A.java
new file mode 100644
index 0000000..7962626
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/optimizations/blockmerger/test002/jack/A.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.optimizations.blockmerger.test002.jack;
+
+public class A {
+  private int testA(int a) {
+    if (a > 0) {
+      a--;
+    } else if (a < 0) {
+      a++;
+    }
+    if (a > 0) {
+      return a;
+    } else if (a < 0) {
+      return a;
+    } else {
+      return a;
+    }
+  }
+
+  private int testB(int a, int b, int c) {
+    if (a > 0) {
+      a--;
+    } else if (a < 0) {
+      a++;
+    }
+    if (a > 0) {
+      return a + b * c * 10;
+    } else if (a < 0) {
+      return a + b * c * 10;
+    } else {
+      return a + b * c * 10;
+    }
+  }
+
+  private int testC(int a) {
+    if (a > 0) {
+      String x = "a > 0";
+      return a;
+    } else if (a < 0) {
+      String x = "a < 0";
+      return a;
+    } else {
+      String x = "a == 0";
+      return a;
+    }
+  }
+
+  public String testA() {
+    return testA(2) + "|" + testA(1) + "|" +
+        testA(0) + "|" + testA(-1) + "|" + testA(-2);
+  }
+
+  public String testB() {
+    return testB(2, 1, 1) + "|" + testB(1, 1, 2) + "|" +
+        testB(0, 1, 3) + "|" + testB(-1, -1, 4) + "|" + testB(-2, -1, 5);
+  }
+
+  public String testC() {
+    return testC(1) + "|" + testC(0) + "|" + testC(-1);
+  }
+}
diff --git a/jack-tests/tests/com/android/jack/optimizations/ssa/copypropagation/CopyPropagationTests.java b/jack-tests/tests/com/android/jack/optimizations/ssa/copypropagation/CopyPropagationTests.java
new file mode 100644
index 0000000..a5f49a5
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/optimizations/ssa/copypropagation/CopyPropagationTests.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.android.jack.optimizations.ssa.copypropagation;
+
+import com.android.jack.Options.UseJackSsaIR;
+import com.android.jack.test.helper.RuntimeTestHelper;
+import com.android.jack.test.junit.Runtime;
+import com.android.jack.test.runtime.RuntimeTest;
+import com.android.jack.test.runtime.RuntimeTestInfo;
+import com.android.jack.test.toolchain.AbstractTestTools;
+
+import org.junit.Test;
+
+public class CopyPropagationTests extends RuntimeTest {
+
+  private RuntimeTestInfo TEST001 = new RuntimeTestInfo(
+      AbstractTestTools
+          .getTestRootDir("com.android.jack.optimizations.ssa.copypropagation.test001"),
+      "com.android.jack.optimizations.ssa.copypropagation.test001.dx.Tests");
+
+  @Test
+  @Runtime
+  public void test001() throws Exception {
+    new RuntimeTestHelper(TEST001).addProperty(UseJackSsaIR.ENABLE.getName(), "true")
+        .compileAndRunTest();
+  }
+
+  @Override
+  protected void fillRtTestInfos() {
+    rtTestInfos.add(TEST001);
+  }
+}
diff --git a/jack-tests/tests/com/android/jack/optimizations/ssa/copypropagation/test001/dx/Tests.java b/jack-tests/tests/com/android/jack/optimizations/ssa/copypropagation/test001/dx/Tests.java
new file mode 100644
index 0000000..645aca7
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/optimizations/ssa/copypropagation/test001/dx/Tests.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.android.jack.optimizations.ssa.copypropagation.test001.dx;
+
+import com.android.jack.optimizations.ssa.copypropagation.test001.jack.CopyPropagation;
+import com.android.jack.optimizations.ssa.copypropagation.test001.jack.CopyPropagation.Testing;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+
+public class Tests {
+
+  @Test
+  public void test001() {
+    Testing a = new Testing(99);
+    Testing b = new Testing(3);
+    Testing c = new Testing(4);
+    Testing d = new Testing(5);
+
+    a.next = b;
+    b.next = c;
+    c.next = d;
+    d.next = a;
+
+    CopyPropagation cp = new CopyPropagation();
+    Assert.assertEquals(3 + 4 + 5, cp.sum(a));
+    Assert.assertEquals(4 + 5 + 99, cp.sum(b));
+    Assert.assertEquals(5 + 99 + 3, cp.sum(c));
+    Assert.assertEquals(99 + 3 + 4, cp.sum(d));
+  }
+}
diff --git a/jack-tests/tests/com/android/jack/optimizations/ssa/copypropagation/test001/jack/CopyPropagation.java b/jack-tests/tests/com/android/jack/optimizations/ssa/copypropagation/test001/jack/CopyPropagation.java
new file mode 100644
index 0000000..30fd4e8
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/optimizations/ssa/copypropagation/test001/jack/CopyPropagation.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.android.jack.optimizations.ssa.copypropagation.test001.jack;
+
+public class CopyPropagation {
+
+  /**
+   * Linked List like object
+   */
+  public static class Testing {
+    public final int value;
+    public Testing next = null;
+
+    public Testing(int value) {
+      this.value = value;
+    }
+
+    public Testing getNext() {
+      return next;
+    }
+  }
+
+  /**
+   * Sum the list, ignoring the head value.
+   */
+  public int sum(Testing head) {
+    Testing e = head.getNext();
+    int sum = 0;
+    while (e != head) {
+      sum += e.value;
+      Testing next = e.getNext();
+      e = next;
+    }
+    return sum;
+  }
+}
diff --git a/jack-tests/tests/com/android/jack/ssa/SsaTests.java b/jack-tests/tests/com/android/jack/ssa/SsaTests.java
new file mode 100644
index 0000000..541dffc
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/ssa/SsaTests.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.android.jack.ssa;
+
+import com.android.jack.Options;
+import com.android.jack.test.helper.RuntimeTestHelper;
+import com.android.jack.test.junit.Runtime;
+import com.android.jack.test.runtime.RuntimeTest;
+import com.android.jack.test.runtime.RuntimeTestInfo;
+import com.android.jack.test.toolchain.AbstractTestTools;
+
+import org.junit.Test;
+
+import java.security.cert.PKIXRevocationChecker.Option;
+
+/**
+ * A list of tests to prevent regression in certain corner cases when using Jack's SSA.
+ *
+ * Note that these tests are not meant to be comprehensive verification of the SSA pipeline. It is
+ * used mostly to cover certain cases that requires extra attention. It should be used with
+ * conjunction with the rest of the jack-tests.
+ */
+public class SsaTests extends RuntimeTest {
+
+  private RuntimeTestInfo TEST001 =
+      new RuntimeTestInfo(AbstractTestTools.getTestRootDir("com.android.jack.ssa.test001"),
+          "com.android.jack.ssa.test001.dx.Tests");
+
+  @Test
+  @Runtime
+  public void test001() throws Exception {
+    new RuntimeTestHelper(TEST001).addProperty(Options.UseJackSsaIR.ENABLE.getName(), "true")
+        .compileAndRunTest();
+  }
+
+  @Override
+  protected void fillRtTestInfos() {
+    rtTestInfos.add(TEST001);
+  }
+}
diff --git a/jack-tests/tests/com/android/jack/ssa/test001/dx/Tests.java b/jack-tests/tests/com/android/jack/ssa/test001/dx/Tests.java
new file mode 100644
index 0000000..a99a5d5
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/ssa/test001/dx/Tests.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.android.jack.ssa.test001.dx;
+
+import com.android.jack.ssa.test001.jack.Ssa;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class Tests {
+  @Test
+  public void testNestedCatch() {
+    Assert.assertEquals("message".length(), Ssa.doubleNestedCatch());
+  }
+
+  @Test
+  public void testMultipleUses() {
+    int x = 11;
+    int y = 51;
+    int z = 91;
+
+    int a = x + y;
+    int b = y + z;
+    int c = x + z;
+
+    int x_1 = x + 1;
+    int y_1 = y + 1;
+    int z_1 = z + 1;
+
+    int result = a + b + c + x_1 + y_1 + z_1;
+
+    Assert.assertEquals(result, Ssa.multipleUses(x, y, z));
+  }
+
+  @Test
+  public void testFallThroughCaseWithPhi() {
+    Assert.assertEquals(2, Ssa.fallThroughCaseWithPhi(2, -1));
+  }
+}
diff --git a/jack-tests/tests/com/android/jack/ssa/test001/jack/Ssa.java b/jack-tests/tests/com/android/jack/ssa/test001/jack/Ssa.java
new file mode 100644
index 0000000..27bdf39
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/ssa/test001/jack/Ssa.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.android.jack.ssa.test001.jack;
+
+public class Ssa {
+
+  private static void sometimesThrow(boolean shouldThrow) {
+    if (shouldThrow) {
+      throw new RuntimeException("message");
+    }
+  }
+
+  /**
+   * A doublely nested catch case.
+   */
+  public static int doubleNestedCatch() {
+    try {
+      sometimesThrow(true);
+      return -1;
+    } catch (Throwable e0) {
+      try {
+        sometimesThrow("message".equals(e0.getMessage()));
+      } catch (Exception e1) {
+        return e0.getMessage().length();
+      }
+    }
+    return -1;
+  }
+
+  public static int multipleUses(int x, int y, int z) {
+    int a = x + y;
+    int b = y + z;
+    int c = x + z;
+
+    x = x + 1;
+    y = y + 1;
+    z = z + 1;
+
+    int result = a + b + c + x + y + z;
+    return result;
+  }
+
+  public static int fallThroughCaseWithPhi(int x, int y) {
+    switch(x) {
+      case 2:
+        y = 2;
+      case 1: {
+        return y;
+      }
+    }
+    return -1;
+  }
+}
diff --git a/jack/src/com/android/jack/Jack.java b/jack/src/com/android/jack/Jack.java
index 3ee7a0e..4015559 100644
--- a/jack/src/com/android/jack/Jack.java
+++ b/jack/src/com/android/jack/Jack.java
@@ -78,6 +78,7 @@
 import com.android.jack.backend.dex.multidex.legacy.RuntimeAnnotationFinder;
 import com.android.jack.backend.dex.rop.CodeItemBuilder;
 import com.android.jack.backend.dex.rop.DexCodeMarkerRemover;
+import com.android.jack.backend.dex.rop.SsaCodeItemBuilder;
 import com.android.jack.backend.jayce.JayceFileImporter;
 import com.android.jack.backend.jayce.JayceInLibraryProduct;
 import com.android.jack.backend.jayce.JayceInLibraryWriterAll;
@@ -105,10 +106,13 @@
 import com.android.jack.ir.ast.JDefinedClassOrInterface;
 import com.android.jack.ir.ast.JField;
 import com.android.jack.ir.ast.JMethod;
+import com.android.jack.ir.ast.JMethodBodyCfg;
 import com.android.jack.ir.ast.JPackage;
 import com.android.jack.ir.ast.JSession;
 import com.android.jack.ir.ast.JSwitchStatement;
 import com.android.jack.ir.ast.Resource;
+import com.android.jack.ir.ast.cfg.CfgChecker;
+import com.android.jack.ir.ast.cfg.MethodBodyCfgBuilder;
 import com.android.jack.ir.formatter.InternalFormatter;
 import com.android.jack.ir.formatter.TypePackageAndMethodFormatter;
 import com.android.jack.ir.formatter.UserFriendlyFormatter;
@@ -132,6 +136,14 @@
 import com.android.jack.optimizations.Optimizations;
 import com.android.jack.optimizations.UnusedDefinitionRemover;
 import com.android.jack.optimizations.UseDefsChainsSimplifier;
+import com.android.jack.optimizations.blockmerger.CfgSimpleBasicBlockMerger;
+import com.android.jack.optimizations.cfg.OptimizeConditionalPrimarySuccessor;
+import com.android.jack.optimizations.cfg.RemoveEmptyBasicBlocks;
+import com.android.jack.optimizations.cfg.RemoveRedundantConditionalBlocks;
+import com.android.jack.optimizations.cfg.RemoveRedundantGotoReturnEdges;
+import com.android.jack.optimizations.cfg.RemoveUnreachableBasicBlocks;
+import com.android.jack.optimizations.cfg.SimplifyConditionalExpressions;
+import com.android.jack.optimizations.cfg.ToggleCfgSsaFlagProcessor;
 import com.android.jack.optimizations.common.DirectlyDerivedClassesProvider;
 import com.android.jack.optimizations.inlining.InlineAnnotatedMethods;
 import com.android.jack.optimizations.inlining.InlineAnnotationSanityCheck;
@@ -139,6 +151,7 @@
 import com.android.jack.optimizations.modifiers.ClassFinalizer;
 import com.android.jack.optimizations.modifiers.FieldFinalizer;
 import com.android.jack.optimizations.modifiers.MethodFinalizer;
+import com.android.jack.optimizations.ssa.CopyPropagation;
 import com.android.jack.optimizations.tailrecursion.TailRecursionOptimization;
 import com.android.jack.optimizations.tailrecursion.TailRecursionOptimizer;
 import com.android.jack.optimizations.valuepropagation.argument.AvpCalculateTaintedMethods;
@@ -161,6 +174,7 @@
 import com.android.jack.scheduling.adapter.JDefinedClassOrInterfaceAdapter;
 import com.android.jack.scheduling.adapter.JFieldAdapter;
 import com.android.jack.scheduling.adapter.JMethodAdapter;
+import com.android.jack.scheduling.adapter.JMethodBodyCfgAdapter;
 import com.android.jack.scheduling.adapter.JPackageAdapter;
 import com.android.jack.scheduling.feature.CompiledTypeStats;
 import com.android.jack.scheduling.feature.DropMethodBody;
@@ -305,11 +319,19 @@
 import com.android.jack.transformations.parent.TypeAstChecker;
 import com.android.jack.transformations.renamepackage.PackageRenamer;
 import com.android.jack.transformations.rop.cast.RopCastLegalizer;
+import com.android.jack.transformations.ssa.CfgNodeIdAssignment;
+import com.android.jack.transformations.ssa.CfgNodeIdRemoval;
+import com.android.jack.transformations.ssa.CfgNodeListAssignment;
+import com.android.jack.transformations.ssa.CfgNodeListRemoval;
+import com.android.jack.transformations.ssa.DominanceFrontierAssignment;
+import com.android.jack.transformations.ssa.DominanceFrontierRemoval;
+import com.android.jack.transformations.ssa.JPhiElementInsertion;
+import com.android.jack.transformations.ssa.SsaBasicBlockSplitter;
+import com.android.jack.transformations.ssa.SsaRenamer;
 import com.android.jack.transformations.threeaddresscode.ThreeAddressCodeBuilder;
 import com.android.jack.transformations.typedef.TypeDefRemover;
 import com.android.jack.transformations.typedef.TypeDefRemover.RemoveTypeDef;
 import com.android.jack.transformations.uselessif.UselessIfChecker;
-import com.android.jack.transformations.uselessif.UselessIfRemover;
 import com.android.jack.util.collect.UnmodifiableCollections;
 import com.android.sched.item.Component;
 import com.android.sched.reflections.ReflectionFactory;
@@ -735,6 +757,13 @@
           if (config.get(Optimizations.WriteOnlyFieldRemoval.ENABLE).booleanValue()) {
             request.addFeature(Optimizations.WriteOnlyFieldRemoval.class);
           }
+          if (config.get(Options.UseJackSsaIR.ENABLE).booleanValue()) {
+            request.addFeature(Options.UseJackSsaIR.class);
+          }
+          if (config.get(Optimizations.SimpleBasicBlockMerging.ENABLE).booleanValue()) {
+            request.addFeature(Optimizations.SimpleBasicBlockMerging.class);
+          }
+
           if (config.get(Optimizations.InlineAnnotatedMethods.ENABLE).booleanValue()) {
             request.addFeature(Optimizations.InlineAnnotatedMethods.class);
           }
@@ -1660,82 +1689,112 @@
           features.contains(Optimizations.FieldValuePropagation.class);
       boolean enableWriteOnlyFieldRemoval =
           features.contains(Optimizations.WriteOnlyFieldRemoval.class);
-
+    {
+      SubPlanBuilder<JDefinedClassOrInterface> typePlan5 =
+          planBuilder.appendSubPlan(JDefinedClassOrInterfaceAdapter.class);
       {
-        SubPlanBuilder<JDefinedClassOrInterface> typePlan5 =
-            planBuilder.appendSubPlan(JDefinedClassOrInterfaceAdapter.class);
         {
-          {
-            SubPlanBuilder<JMethod> methodPlan4 = typePlan5.appendSubPlan(JMethodAdapter.class);
-            methodPlan4.append(RefAsStatementRemover.class);
-            methodPlan4.append(CfgBuilder.class);
-            methodPlan4.append(DefinitionMarkerAdder.class);
-            methodPlan4.append(ReachingDefinitions.class);
-            methodPlan4.append(UsedVariableAdder.class);
-            methodPlan4.append(DefUsesAndUseDefsChainComputation.class);
-            if (hasSanityChecks) {
-              methodPlan4.append(UseDefsChecker.class);
-            }
-            methodPlan4.append(ConstantRefiner.class);
-            if (features.contains(Optimizations.UseDefSimplifier.class)) {
-              methodPlan4.append(UseDefsChainsSimplifier.class);
-            }
-            if (features.contains(Optimizations.DefUseSimplifier.class)) {
-              methodPlan4.append(DefUsesChainsSimplifier.class);
-            }
-            methodPlan4.append(UnusedDefinitionRemover.class);
-            methodPlan4.append(RefAsStatementRemover.class);
-            methodPlan4.append(CfgMarkerRemover.class);
-            methodPlan4.append(CfgBuilder.class);
-            if (features.contains(Optimizations.IfSimplifier.class)) {
-              methodPlan4.append(IfWithConstantSimplifier.class);
-            }
-            methodPlan4.append(UnusedLocalRemover.class);
-            if (enableFieldValuePropagation) {
-              methodPlan4.append(FvpCollectFieldAssignments.class);
-            }
-            if (enableArgumentValuePropagation) {
-              methodPlan4.append(AvpCollectMethodCallArguments.class);
-            }
-            if (enableWriteOnlyFieldRemoval) {
-              methodPlan4.append(WofrCollectFieldAccesses.class);
-            }
-            methodPlan4.append(DefUsesAndUseDefsChainRemover.class);
-            methodPlan4.append(DefinitionMarkerRemover.class);
-            methodPlan4.append(UsedVariableRemover.class);
-            if (features.contains(Optimizations.ExpressionSimplifier.class)) {
-              methodPlan4.append(ExpressionSimplifier.class);
-            }
-            methodPlan4.append(UselessIfRemover.class);
-            methodPlan4.append(CfgMarkerRemover.class);
-            methodPlan4.append(CfgBuilder.class);
-            methodPlan4.append(ContainerAnnotationAdder.MethodContainerAnnotationAdder.class);
+          SubPlanBuilder<JMethod> methodPlan4 =
+              typePlan5.appendSubPlan(JMethodAdapter.class);
+          methodPlan4.append(RefAsStatementRemover.class);
+          methodPlan4.append(CfgBuilder.class);
+          methodPlan4.append(DefinitionMarkerAdder.class);
+          methodPlan4.append(ReachingDefinitions.class);
+          methodPlan4.append(UsedVariableAdder.class);
+          methodPlan4.append(DefUsesAndUseDefsChainComputation.class);
+          if (hasSanityChecks) {
+            methodPlan4.append(UseDefsChecker.class);
           }
+          methodPlan4.append(ConstantRefiner.class);
+          if (features.contains(Optimizations.UseDefSimplifier.class)) {
+            methodPlan4.append(UseDefsChainsSimplifier.class);
+          }
+          if (features.contains(Optimizations.DefUseSimplifier.class)) {
+            methodPlan4.append(DefUsesChainsSimplifier.class);
+          }
+          methodPlan4.append(UnusedDefinitionRemover.class);
+          methodPlan4.append(RefAsStatementRemover.class);
+          methodPlan4.append(CfgMarkerRemover.class);
+          methodPlan4.append(CfgBuilder.class);
+          if (features.contains(Optimizations.IfSimplifier.class)) {
+            methodPlan4.append(IfWithConstantSimplifier.class);
+          }
+          methodPlan4.append(UnusedLocalRemover.class);
+          if (enableFieldValuePropagation) {
+            methodPlan4.append(FvpCollectFieldAssignments.class);
+          }
+          if (enableArgumentValuePropagation) {
+            methodPlan4.append(AvpCollectMethodCallArguments.class);
+          }
+          if (enableWriteOnlyFieldRemoval) {
+            methodPlan4.append(WofrCollectFieldAccesses.class);
+          }
+          methodPlan4.append(DefUsesAndUseDefsChainRemover.class);
+          methodPlan4.append(DefinitionMarkerRemover.class);
+          methodPlan4.append(UsedVariableRemover.class);
+          if (features.contains(Optimizations.ExpressionSimplifier.class)) {
+            methodPlan4.append(ExpressionSimplifier.class);
+          }
+          methodPlan4.append(CfgMarkerRemover.class);
+          methodPlan4.append(CfgBuilder.class);
+          methodPlan4.append(ContainerAnnotationAdder.MethodContainerAnnotationAdder.class);
         }
       }
+    }
+    if (enableArgumentValuePropagation) {
+      planBuilder.append(
+          AvpComputeMethodArgumentsValues.class);
+    }
 
+    {
+      // Build cfg-IR representation of body methods
+      SubPlanBuilder<JMethod> methodPlan = planBuilder
+          .appendSubPlan(JDefinedClassOrInterfaceAdapter.class)
+          .appendSubPlan(JMethodAdapter.class);
+      methodPlan.append(MethodBodyCfgBuilder.class);
+      methodPlan.append(CfgMarkerRemover.class);
+
+      // Cfg-IR base transformations
+      SubPlanBuilder<JMethodBodyCfg> cfgPlan = planBuilder
+          .appendSubPlan(JDefinedClassOrInterfaceAdapter.class)
+          .appendSubPlan(JMethodBodyCfgAdapter.class);
+
+      if (hasSanityChecks) {
+        cfgPlan.append(CfgChecker.class);
+      }
+      if (enableFieldValuePropagation) {
+        cfgPlan.append(FvpPropagateFieldValues.class);
+      }
       if (enableArgumentValuePropagation) {
-        planBuilder.append(AvpComputeMethodArgumentsValues.class);
+        cfgPlan.append(AvpPropagateArgumentValues.class);
+      }
+      if (enableWriteOnlyFieldRemoval) {
+        cfgPlan.append(WofrRemoveFieldWrites.class);
+      }
+      if (hasSanityChecks) {
+        cfgPlan.append(CfgChecker.class);
       }
 
-      if (enableFieldValuePropagation || enableArgumentValuePropagation
-          || enableWriteOnlyFieldRemoval) {
-        SubPlanBuilder<JDefinedClassOrInterface> typePlan =
-            planBuilder.appendSubPlan(JDefinedClassOrInterfaceAdapter.class);
-        SubPlanBuilder<JMethod> methodPlan = typePlan.appendSubPlan(JMethodAdapter.class);
-        if (enableFieldValuePropagation) {
-          methodPlan.append(FvpPropagateFieldValues.class);
-        }
-        if (enableArgumentValuePropagation) {
-          methodPlan.append(AvpPropagateArgumentValues.class);
-        }
-        if (enableWriteOnlyFieldRemoval) {
-          methodPlan.append(WofrRemoveFieldWrites.class);
-        }
-        methodPlan.append(CfgMarkerRemover.class);
-        methodPlan.append(CfgBuilder.class);
+      if (features.contains(Optimizations.SimpleBasicBlockMerging.class)) {
+        cfgPlan.append(CfgSimpleBasicBlockMerger.class);
+      }
+      if (hasSanityChecks) {
+        cfgPlan.append(CfgChecker.class);
       }
 
+      cfgPlan.append(RemoveRedundantConditionalBlocks.class);
+      cfgPlan.append(RemoveEmptyBasicBlocks.class);
+      cfgPlan.append(RemoveRedundantGotoReturnEdges.class);
+      cfgPlan.append(RemoveUnreachableBasicBlocks.class);
+      cfgPlan.append(OptimizeConditionalPrimarySuccessor.class);
+      cfgPlan.append(SimplifyConditionalExpressions.class);
+
+      if (hasSanityChecks) {
+        cfgPlan.append(CfgChecker.class);
+      }
+    }
+
+    {
       SubPlanBuilder<JDefinedClassOrInterface> typePlan1 =
           planBuilder.appendSubPlan(JDefinedClassOrInterfaceAdapter.class);
 
@@ -1744,49 +1803,74 @@
       if (enableWriteOnlyFieldRemoval) {
         fieldPlan.append(WofrRemoveFields.class);
       }
+    }
 
-      // The sub plan that write dex files representing type must not be split in order to remove dx
-      // IR from memory when dex file is written.
+    // SSA Construction.
+    if (features.contains(Options.UseJackSsaIR.class)) {
+      SubPlanBuilder<JMethodBodyCfg> cfgPlan = planBuilder
+          .appendSubPlan(JDefinedClassOrInterfaceAdapter.class)
+          .appendSubPlan(JMethodBodyCfgAdapter.class);
+      cfgPlan.append(ToggleCfgSsaFlagProcessor.class);
+      cfgPlan.append(SsaBasicBlockSplitter.class);
+      cfgPlan.append(CfgNodeIdAssignment.class);
+      cfgPlan.append(CfgNodeListAssignment.class);
+      cfgPlan.append(DominanceFrontierAssignment.class);
+      cfgPlan.append(JPhiElementInsertion.class);
+      cfgPlan.append(SsaRenamer.class);
+      cfgPlan.append(CopyPropagation.class);
+      cfgPlan.append(CfgNodeIdRemoval.class);
+      cfgPlan.append(CfgNodeListRemoval.class);
+      cfgPlan.append(DominanceFrontierRemoval.class);
+
+      if (hasSanityChecks) {
+        cfgPlan.append(CfgChecker.class);
+      }
+    }
+
+    // The sub plan that write dex files representing type must not be split in order to remove dx
+    // IR from memory when dex file is written.
+    {
+      SubPlanBuilder<JDefinedClassOrInterface> typePlan6 =
+          planBuilder.appendSubPlan(JDefinedClassOrInterfaceAdapter.class);
+
       {
-        SubPlanBuilder<JDefinedClassOrInterface> typePlan6 =
-            planBuilder.appendSubPlan(JDefinedClassOrInterfaceAdapter.class);
-
-        {
-          SubPlanBuilder<JMethod> methodPlan5 = typePlan6.appendSubPlan(JMethodAdapter.class);
-          methodPlan5.append(CodeItemBuilder.class);
-          methodPlan5.append(CfgMarkerRemover.class);
-          methodPlan5.append(EncodedMethodBuilder.class);
-          methodPlan5.append(DexCodeMarkerRemover.class);
-          if (features.contains(ParameterMetadataFeature.class)) {
-            methodPlan5.append(ParameterMetadataAnnotationsAdder.class);
-          }
-          methodPlan5.append(MethodAnnotationBuilder.class);
-          if (features.contains(DropMethodBody.class)) {
-            methodPlan5.append(MethodBodyRemover.class);
-          }
-        }
-
-        {
-          SubPlanBuilder<JField> fieldPlan2 = typePlan6.appendSubPlan(JFieldAdapter.class);
-          fieldPlan2.append(EncodedFieldBuilder.class);
-          fieldPlan2.append(FieldAnnotationBuilder.class);
-        }
-
-        if (hasSanityChecks) {
-          typePlan6.append(TypeAstChecker.class);
-        }
-
-        // Jayce files must be copied into output library in incremental library mode or in non
-        // incremental mode
-        if (features.contains(GenerateLibraryFromIncrementalFolder.class)
-            || !features.contains(Incremental.class)) {
-          typePlan6.append(DexInLibraryWriterAll.class);
+        SubPlanBuilder<JMethod> methodPlan5 = typePlan6.appendSubPlan(JMethodAdapter.class);
+        if (features.contains(Options.UseJackSsaIR.class)) {
+          methodPlan5.append(SsaCodeItemBuilder.class);
         } else {
-          typePlan6.append(DexInLibraryWriterNoPrebuilt.class);
+          methodPlan5.append(CodeItemBuilder.class);
         }
-        typePlan6.append(ClassDefItemMarkerRemover.class);
+        methodPlan5.append(EncodedMethodBuilder.class);
+        methodPlan5.append(DexCodeMarkerRemover.class);
+        if (features.contains(ParameterMetadataFeature.class)) {
+          methodPlan5.append(ParameterMetadataAnnotationsAdder.class);
+        }
+        methodPlan5.append(MethodAnnotationBuilder.class);
+        if (features.contains(DropMethodBody.class)) {
+          methodPlan5.append(MethodBodyRemover.class);
+        }
       }
 
+      {
+        SubPlanBuilder<JField> fieldPlan2 = typePlan6.appendSubPlan(JFieldAdapter.class);
+        fieldPlan2.append(EncodedFieldBuilder.class);
+        fieldPlan2.append(FieldAnnotationBuilder.class);
+      }
+
+      if (hasSanityChecks) {
+        typePlan6.append(TypeAstChecker.class);
+      }
+
+      // Jayce files must be copied into output library in incremental library mode or in non
+      // incremental mode
+      if (features.contains(GenerateLibraryFromIncrementalFolder.class)
+          || !features.contains(Incremental.class)) {
+        typePlan6.append(DexInLibraryWriterAll.class);
+      } else {
+        typePlan6.append(DexInLibraryWriterNoPrebuilt.class);
+      }
+      typePlan6.append(ClassDefItemMarkerRemover.class);
+      }
     }
 
     if (hasSanityChecks) {
diff --git a/jack/src/com/android/jack/Options.java b/jack/src/com/android/jack/Options.java
index b13ead8..1f29c83 100644
--- a/jack/src/com/android/jack/Options.java
+++ b/jack/src/com/android/jack/Options.java
@@ -56,6 +56,8 @@
 import com.android.jack.util.ClassNameCodec;
 import com.android.jack.util.args4j.JackEnumOptionHandler;
 import com.android.jack.util.filter.Filter;
+import com.android.sched.item.Description;
+import com.android.sched.item.Feature;
 import com.android.sched.reflections.ReflectionFactory;
 import com.android.sched.util.RunnableHooks;
 import com.android.sched.util.SubReleaseKind;
@@ -345,6 +347,21 @@
         "jack.annotation-processor", "Enable annotation processors")
         .addDefaultValue(true).addCategory(DumpInLibrary.class);
 
+  /**
+   * Enables Jack SSA IR.
+   */
+  @HasKeyId
+  @Description("Uses Jack SSA IR.")
+  public static class UseJackSsaIR implements Feature {
+    @Nonnull
+    public static final BooleanPropertyId ENABLE = BooleanPropertyId
+        .create("jack.optimization.use-jack-ssa-ir",
+            "Apply method argument value propagation optimization")
+        .addDefaultValue(Boolean.FALSE)
+        .addCategory(DumpInLibrary.class)
+        .addCategory(PrebuiltCompatibility.class)
+        .addCategory(Private.class);
+  }
   @Option(name = "--version", usage = "display version")
   private boolean version;
 
diff --git a/jack/src/com/android/jack/backend/dex/rop/CodeItemBuilder.java b/jack/src/com/android/jack/backend/dex/rop/CodeItemBuilder.java
index 7baa30a..6a128f3 100644
--- a/jack/src/com/android/jack/backend/dex/rop/CodeItemBuilder.java
+++ b/jack/src/com/android/jack/backend/dex/rop/CodeItemBuilder.java
@@ -17,15 +17,6 @@
 
 import com.android.jack.JackEventType;
 import com.android.jack.Options;
-import com.android.jack.cfg.BasicBlock;
-import com.android.jack.cfg.CatchBasicBlock;
-import com.android.jack.cfg.ConditionalBasicBlock;
-import com.android.jack.cfg.ControlFlowGraph;
-import com.android.jack.cfg.NormalBasicBlock;
-import com.android.jack.cfg.PeiBasicBlock;
-import com.android.jack.cfg.ReturnBasicBlock;
-import com.android.jack.cfg.SwitchBasicBlock;
-import com.android.jack.cfg.ThrowBasicBlock;
 import com.android.jack.dx.dex.DexOptions;
 import com.android.jack.dx.dex.code.DalvCode;
 import com.android.jack.dx.dex.code.PositionList;
@@ -62,16 +53,34 @@
 import com.android.jack.ir.ast.JFieldInitializer;
 import com.android.jack.ir.ast.JLoop;
 import com.android.jack.ir.ast.JMethod;
-import com.android.jack.ir.ast.JMethodBody;
+import com.android.jack.ir.ast.JMethodBodyCfg;
 import com.android.jack.ir.ast.JMultiExpression;
 import com.android.jack.ir.ast.JParameter;
 import com.android.jack.ir.ast.JPrimitiveType.JPrimitiveTypeEnum;
-import com.android.jack.ir.ast.JStatement;
+import com.android.jack.ir.ast.JSsaVariableRef;
 import com.android.jack.ir.ast.JSwitchStatement;
 import com.android.jack.ir.ast.JThis;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.ast.cfg.ExceptionHandlingContext;
+import com.android.jack.ir.ast.cfg.JBasicBlock;
+import com.android.jack.ir.ast.cfg.JBasicBlockElement;
+import com.android.jack.ir.ast.cfg.JCaseBasicBlock;
+import com.android.jack.ir.ast.cfg.JCatchBasicBlock;
+import com.android.jack.ir.ast.cfg.JConditionalBasicBlock;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.jack.ir.ast.cfg.JEntryBasicBlock;
+import com.android.jack.ir.ast.cfg.JExitBasicBlock;
+import com.android.jack.ir.ast.cfg.JRegularBasicBlock;
+import com.android.jack.ir.ast.cfg.JReturnBasicBlock;
+import com.android.jack.ir.ast.cfg.JSimpleBasicBlock;
+import com.android.jack.ir.ast.cfg.JSwitchBasicBlock;
+import com.android.jack.ir.ast.cfg.JThrowBasicBlock;
+import com.android.jack.ir.ast.cfg.JThrowingExpressionBasicBlock;
 import com.android.jack.ir.ast.marker.ThrownExceptionMarker;
+import com.android.jack.ir.sourceinfo.SourceInfo;
 import com.android.jack.library.DumpInLibrary;
 import com.android.jack.library.PrebuiltCompatibility;
+import com.android.jack.optimizations.cfg.CfgBasicBlockUtils;
 import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
 import com.android.jack.scheduling.marker.DexCodeMarker;
 import com.android.jack.transformations.EmptyClinit;
@@ -87,7 +96,6 @@
 import com.android.jack.transformations.ast.UnassignedValues;
 import com.android.jack.transformations.ast.inner.InnerAccessor;
 import com.android.jack.transformations.ast.switches.UselessSwitches;
-import com.android.jack.transformations.booleanoperators.FallThroughMarker;
 import com.android.jack.transformations.cast.SourceCast;
 import com.android.jack.transformations.rop.cast.RopLegalCast;
 import com.android.jack.transformations.threeaddresscode.ThreeAddressCodeForm;
@@ -107,8 +115,9 @@
 import com.android.sched.util.log.TracerFactory;
 
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
-
+import java.util.Map;
 import javax.annotation.Nonnegative;
 import javax.annotation.Nonnull;
 
@@ -120,39 +129,40 @@
 @Description("Builds CodeItem from JMethod")
 @Name("CodeItemBuilder")
 @Constraint(
-  need = {
-    ControlFlowGraph.class,
-    JExceptionRuntimeValue.class,
-    NewInstanceRemoved.class,
-    ThreeAddressCodeForm.class,
-    RopLegalCast.class,
-    InnerAccessor.class,
-    InvalidDefaultBridgeInInterfaceRemoved.class
-  },
-  no = {
-    BooleanTestOutsideIf.class,
-    InitInNewArray.class,
-    JAsgOperation.class,
-    JPrimitiveClassLiteral.class,
-    JMultiExpression.class,
-    JConditionalExpression.class,
-    JFieldInitializer.class,
-    JConcatOperation.class,
-    JLoop.class,
-    SideEffectOperation.class,
-    UnassignedValues.class,
-    RefAsStatement.class,
-    MultiDimensionNewArray.class,
-    JSwitchStatement.SwitchWithEnum.class,
-    ImplicitBoxingAndUnboxing.class,
-    ImplicitCast.class,
-    JAssertStatement.class,
-    JConditionalOperation.class,
-    EmptyClinit.class,
-    UselessSwitches.class,
-    SourceCast.class,
-    JCastOperation.WithIntersectionType.class
-  }
+    need = {
+        JMethodBodyCfg.class,
+        JExceptionRuntimeValue.class,
+        NewInstanceRemoved.class,
+        ThreeAddressCodeForm.class,
+        RopLegalCast.class,
+        InnerAccessor.class,
+        InvalidDefaultBridgeInInterfaceRemoved.class
+    },
+    no = {
+        JSsaVariableRef.class,
+        BooleanTestOutsideIf.class,
+        InitInNewArray.class,
+        JAsgOperation.class,
+        JPrimitiveClassLiteral.class,
+        JMultiExpression.class,
+        JConditionalExpression.class,
+        JFieldInitializer.class,
+        JConcatOperation.class,
+        JLoop.class,
+        SideEffectOperation.class,
+        UnassignedValues.class,
+        RefAsStatement.class,
+        MultiDimensionNewArray.class,
+        JSwitchStatement.SwitchWithEnum.class,
+        ImplicitBoxingAndUnboxing.class,
+        ImplicitCast.class,
+        JAssertStatement.class,
+        JConditionalOperation.class,
+        EmptyClinit.class,
+        UselessSwitches.class,
+        SourceCast.class,
+        JCastOperation.WithIntersectionType.class
+    }
 )
 @Transform(add = DexCodeMarker.class)
 @Use(RopHelper.class)
@@ -207,7 +217,7 @@
       ThreadConfig.get(Options.EMIT_LINE_NUMBER_DEBUG_INFO).booleanValue();
 
   @Nonnull
-  private final  Tracer tracer = TracerFactory.getTracer();
+  private final Tracer tracer = TracerFactory.getTracer();
 
   @Override
   public void run(@Nonnull JMethod method) {
@@ -221,171 +231,207 @@
       RopRegisterManager ropReg =
           new RopRegisterManager(emitLocalDebugInfo, emitSyntheticLocalDebugInfo);
 
-      ControlFlowGraph cfg = method.getMarker(ControlFlowGraph.class);
-      assert cfg != null;
-
-      RopBasicBlockManager ropBb = new RopBasicBlockManager(getMaxLabel(cfg));
-      assert cfg.getEntryNode().getSuccessors().size() == 1;
-      BasicBlock firstBlockOfCode = cfg.getEntryNode().getSuccessors().get(0);
-      assert firstBlockOfCode != null;
-      addSetupBlocks(method, ropReg, ropBb, firstBlockOfCode.getId());
-
       JAbstractMethodBody body = method.getBody();
-      assert body instanceof JMethodBody;
+      assert body instanceof JMethodBodyCfg;
+      JControlFlowGraph cfg = ((JMethodBodyCfg) body).getCfg();
+
+      final JEntryBasicBlock entryBasicBlock = cfg.getEntryBlock();
+      final JExitBasicBlock exitBasicBlock = cfg.getExitBlock();
+
+      // Before building code item, we clean all exception handling
+      // context and all weakly referenced catch blocks.
+      removeExceptionHandlingContext(cfg);
+
+      final Map<JBasicBlock, Integer> basicBlocks = new LinkedHashMap<>();
+      int blockId = 1; // 0 is reserved for entry block
+      for (JBasicBlock block : cfg.getReachableBlocksDepthFirst()) {
+        if (block != entryBasicBlock && block != exitBasicBlock) {
+          basicBlocks.put(block, Integer.valueOf(blockId++));
+        }
+      }
+
+      // We assume that there are no *internal* blocks in cfg
+      // that are not reachable from entry block
+      assert basicBlocks.size() == cfg.getInternalBlocksUnordered().size();
+
+      final RopBasicBlockManager ropBb = new RopBasicBlockManager(blockId + 1);
+      JBasicBlock firstBlockOfCode = entryBasicBlock.getOnlySuccessor();
+      addSetupBlocks(method, ropReg, ropBb, basicBlocks.get(firstBlockOfCode).intValue());
 
       if (method.getType() != JPrimitiveTypeEnum.VOID.getType()) {
         ropReg.createReturnReg(method.getType());
       }
 
-      for (BasicBlock bb : cfg.getNodes()) {
-        if (bb == cfg.getEntryNode()) {
-          continue;
-        }
-        RopBuilderVisitor ropBuilder = new RopBuilderVisitor(ropReg, bb, apiLevel);
+      for (JBasicBlock bb : basicBlocks.keySet()) {
+        assert bb != entryBasicBlock && bb != exitBasicBlock;
+        final RopBuilderVisitor ropBuilder = new RopBuilderVisitor(ropReg, bb, apiLevel);
 
-        assert !bb.getStatements().isEmpty();
-
-        ropBuilder.accept(bb.getStatements());
-        List<Insn> instructions = ropBuilder.getInstructions();
+        ropBuilder.processBasicBlockElements();
+        final List<Insn> instructions = ropBuilder.getInstructions();
         assert instructions != null;
-        JStatement lastStmt = bb.getLastInstruction();
-        SourcePosition lastStmtsourcePosition = RopHelper.getSourcePosition(lastStmt);
 
-        // TODO(mikaelpeltier) Think about a better solution to take into account control flow
-        // (perhaps with meta on cfg).
-        if (bb instanceof ReturnBasicBlock) {
-          InsnList il = createInsnList(instructions, 0);
-          il.setImmutable();
-          ropBb.createBasicBlock(bb.getId(), il, IntList.EMPTY, -1);
-        } else if (bb instanceof ConditionalBasicBlock) {
-          InsnList il = createInsnList(instructions, 0);
-          il.setImmutable();
-          BasicBlock primary = ((ConditionalBasicBlock) bb).getThenBlock();
-          BasicBlock secondary = ((ConditionalBasicBlock) bb).getElseBlock();
+        JVisitor visitor = new JVisitor() {
+          @Override
+          public boolean visit(@Nonnull JRegularBasicBlock bb) {
+            assert bb instanceof JSimpleBasicBlock
+                || bb instanceof JCatchBasicBlock
+                || bb instanceof JCaseBasicBlock;
+            assert bb.hasPrimarySuccessor();
 
-          FallThroughMarker ftm = lastStmt.getMarker(FallThroughMarker.class);
-          if (ftm != null) {
-            switch (ftm.getFallThrough()) {
-              case ELSE: {
-                primary = ((ConditionalBasicBlock) bb).getElseBlock();
-                secondary = ((ConditionalBasicBlock) bb).getThenBlock();
-                break;
+            JBasicBlock primarySuccessor = bb.getPrimarySuccessor();
+            IntList successors = IntList.makeImmutable(getBlockId(primarySuccessor));
+            InsnList il = createInsnList(instructions, 1);
+            Insn gotoInstruction = new PlainInsn(
+                Rops.GOTO, getLastElementPosition(bb), null, RegisterSpecList.EMPTY);
+            il.set(instructions.size(), gotoInstruction);
+            il.setImmutable();
+            ropBb.createBasicBlock(getBlockId(bb), il, successors, getBlockId(primarySuccessor));
+            return false;
+          }
+
+          @Override
+          public boolean visit(@Nonnull JReturnBasicBlock bb) {
+            InsnList il = createInsnList(instructions, 0);
+            il.setImmutable();
+            ropBb.createBasicBlock(getBlockId(bb), il, IntList.EMPTY, -1);
+            return false;
+          }
+
+          @Override
+          public boolean visit(@Nonnull JThrowBasicBlock bb) {
+            InsnList il = createInsnList(instructions, 0);
+            il.setImmutable();
+
+            IntList successors = new IntList();
+            int primarySuccessor = -1;
+
+            if (!bb.getCatchBlocks().isEmpty()) {
+              addCatchBlockSuccessors(bb.getCatchBlocks(), successors);
+            }
+            successors.setImmutable();
+
+            ropBb.createBasicBlock(getBlockId(bb), il, successors, primarySuccessor);
+            return false;
+          }
+
+          @Override
+          public boolean visit(@Nonnull JThrowingExpressionBasicBlock bb) {
+            Insn lastInstruction = instructions.get(instructions.size() - 1);
+            List<Insn> extraInstructions = ropBuilder.getExtraInstructions();
+            assert extraInstructions != null;
+
+            InsnList il = createInsnList(instructions, 0);
+            il.setImmutable();
+
+            int extraBlockLabel = ropBb.getAvailableLabel();
+
+            IntList successors = new IntList();
+            addCatchBlockSuccessors(bb.getCatchBlocks(), successors);
+
+            successors.add(extraBlockLabel);
+            successors.setImmutable();
+
+            ropBb.createBasicBlock(getBlockId(bb), il, successors, extraBlockLabel);
+
+            int indexInstruction = 0;
+            boolean needsGoto;
+            SourcePosition sourcePosition;
+            if (extraInstructions.isEmpty()) {
+              needsGoto = true;
+              sourcePosition = lastInstruction.getPosition();
+              il = new InsnList(1);
+            } else {
+              Insn extraInsn = extraInstructions.get(0);
+              needsGoto =
+                  extraInstructions.get(extraInstructions.size() - 1)
+                      .getOpcode().getBranchingness() == Rop.BRANCH_NONE;
+              il = new InsnList(extraInstructions.size() + (needsGoto ? 1 : 0));
+              for (Insn inst : extraInstructions) {
+                il.set(indexInstruction++, inst);
               }
-              case THEN: {
-                primary = ((ConditionalBasicBlock) bb).getThenBlock();
-                secondary = ((ConditionalBasicBlock) bb).getElseBlock();
-                break;
-              }
-              default: {
-                throw new AssertionError();
+              sourcePosition = extraInsn.getPosition();
+            }
+
+            if (needsGoto) {
+              il.set(indexInstruction,
+                  new PlainInsn(Rops.GOTO, sourcePosition, null, RegisterSpecList.EMPTY));
+            }
+
+            il.setImmutable();
+
+            JBasicBlock primary = bb.getPrimarySuccessor();
+            successors = IntList.makeImmutable(getBlockId(primary));
+
+            ropBb.createBasicBlock(extraBlockLabel, il, successors, getBlockId(primary));
+            return false;
+          }
+
+          @Override
+          public boolean visit(@Nonnull JConditionalBasicBlock bb) {
+            InsnList il = createInsnList(instructions, 0);
+            il.setImmutable();
+
+            JBasicBlock primary = bb.getPrimarySuccessor();
+            JBasicBlock secondary = bb.getAlternativeSuccessor();
+
+            int primarySuccessor = getBlockId(primary);
+            IntList successors = IntList.makeImmutable(primarySuccessor, getBlockId(secondary));
+
+            ropBb.createBasicBlock(getBlockId(bb), il, successors, primarySuccessor);
+            return false;
+          }
+
+          @Override
+          public boolean visit(@Nonnull JSwitchBasicBlock bb) {
+            IntList successors = new IntList();
+            for (JBasicBlock caseBb : bb.getCases()) {
+              successors.add(getBlockId(caseBb));
+            }
+
+            successors.add(getBlockId(bb.getDefaultCase()));
+
+            successors.setImmutable();
+            InsnList il = createInsnList(instructions, 0);
+            il.setImmutable();
+            ropBb.createBasicBlock(
+                getBlockId(bb), il, successors, successors.get(successors.size() - 1));
+            return false;
+          }
+
+          @Override
+          public boolean visit(@Nonnull JBasicBlock x) {
+            throw new AssertionError("Not implemented yet: " + x.toString());
+          }
+
+          private SourcePosition getLastElementPosition(@Nonnull JBasicBlock bb) {
+            return RopHelper.getSourcePosition(bb.hasElements() ?
+                bb.getLastElement().getSourceInfo() : SourceInfo.UNKNOWN);
+          }
+
+          private void addCatchBlockSuccessors(
+              @Nonnull List<JBasicBlock> catchBlocks,
+              @Nonnull IntList successors) {
+            for (JBasicBlock catchBlock : catchBlocks) {
+              int catchTypeCount = 0;
+              int catchTypesSize = ((JCatchBasicBlock) catchBlock).getCatchTypes().size();
+              while (catchTypeCount++ < catchTypesSize) {
+                successors.add(getBlockId(catchBlock));
               }
             }
-          } else {
-            primary = ((ConditionalBasicBlock) bb).getThenBlock();
-            secondary = ((ConditionalBasicBlock) bb).getElseBlock();
           }
 
-          assert primary != null;
-          assert secondary != null;
-          int primarySuccessor = primary.getId();
-          IntList successors = IntList.makeImmutable(primarySuccessor, secondary.getId());
-
-          ropBb.createBasicBlock(bb.getId(), il, successors, primarySuccessor);
-        } else if (bb instanceof ThrowBasicBlock) {
-          assert bb.getSuccessors().size() >= 1;
-          ThrowBasicBlock throwBlock = (ThrowBasicBlock) bb;
-          InsnList il = createInsnList(instructions, 0);
-          il.setImmutable();
-
-          IntList successors = new IntList();
-          int primarySuccessor = -1;
-
-          if (!throwBlock.getExceptionBlocks().isEmpty()) {
-            addCatchBlockSuccessors(throwBlock.getExceptionBlocks(), successors);
-          }
-          successors.setImmutable();
-
-          ropBb.createBasicBlock(bb.getId(), il, successors, primarySuccessor);
-
-        } else if (bb instanceof PeiBasicBlock) {
-          assert bb.getSuccessors().size() >= 2;
-          PeiBasicBlock peiBlock = (PeiBasicBlock) bb;
-          Insn lastInstruction = instructions.get(instructions.size() - 1);
-
-          List<Insn> extraInstructions = ropBuilder.getExtraInstructions();
-          assert extraInstructions != null;
-
-          InsnList il = createInsnList(instructions, 0);
-          il.setImmutable();
-
-          int extraBlockLabel = ropBb.getAvailableLabel();
-
-          IntList successors = new IntList();
-          addCatchBlockSuccessors(peiBlock.getExceptionBlocks(), successors);
-
-          successors.add(extraBlockLabel);
-          successors.setImmutable();
-
-          ropBb.createBasicBlock(bb.getId(), il, successors, extraBlockLabel);
-
-          int indexInstruction = 0;
-          boolean needsGoto;
-          SourcePosition sourcePosition;
-          if (extraInstructions.isEmpty()) {
-            needsGoto = true;
-            sourcePosition = lastInstruction.getPosition();
-            il = new InsnList(1);
-          } else {
-            Insn extraInsn = extraInstructions.get(0);
-            needsGoto =
-                extraInstructions.get(extraInstructions.size() - 1).getOpcode().getBranchingness()
-                == Rop.BRANCH_NONE;
-            il = new InsnList(extraInstructions.size() + (needsGoto ? 1 : 0));
-            for (Insn inst : extraInstructions) {
-              il.set(indexInstruction++, inst);
+          private int getBlockId(@Nonnull JBasicBlock block) {
+            if (block == entryBasicBlock) {
+              return 0;
             }
-            sourcePosition = extraInsn.getPosition();
+            if (block == exitBasicBlock) {
+              return Integer.MAX_VALUE;
+            }
+            return basicBlocks.get(block).intValue();
           }
+        };
 
-          if (needsGoto) {
-            il.set(indexInstruction++, new PlainInsn(Rops.GOTO, sourcePosition, null,
-                RegisterSpecList.EMPTY));
-          }
-
-          il.setImmutable();
-          BasicBlock primarySuccessor = ((PeiBasicBlock) bb).getTarget();
-          assert primarySuccessor != null;
-
-          successors = IntList.makeImmutable(primarySuccessor.getId());
-
-          ropBb.createBasicBlock(extraBlockLabel, il, successors, primarySuccessor.getId());
-        } else if (bb instanceof SwitchBasicBlock) {
-          IntList successors = new IntList();
-          for (BasicBlock succ : ((SwitchBasicBlock) bb).getCasesBlock()) {
-            successors.add(succ.getId());
-          }
-
-          int defaultIdBlock = ((SwitchBasicBlock) bb).getDefaultBlock().getId();
-          successors.add(defaultIdBlock);
-
-          successors.setImmutable();
-          InsnList il = createInsnList(instructions, 0);
-          il.setImmutable();
-          ropBb.createBasicBlock(bb.getId(), il, successors, successors.get(successors.size() - 1));
-        } else if (bb instanceof NormalBasicBlock) {
-          List<BasicBlock> bbSuccessors = bb.getSuccessors();
-          assert bbSuccessors.size() == 1;
-          int primarySuccessor = bbSuccessors.get(0).getId();
-          IntList successors = IntList.makeImmutable(primarySuccessor);
-          InsnList il = createInsnList(instructions, 1);
-          Insn gotoInstruction =
-              new PlainInsn(Rops.GOTO, lastStmtsourcePosition, null, RegisterSpecList.EMPTY);
-          il.set(instructions.size(), gotoInstruction);
-          il.setImmutable();
-          ropBb.createBasicBlock(bb.getId(), il, successors, primarySuccessor);
-        } else {
-          throw new AssertionError("Not yet supported");
-        }
+        visitor.accept(bb);
       }
 
       RopMethod ropMethod = new RopMethod(ropBb.getBasicBlockList(),
@@ -415,15 +461,13 @@
     }
   }
 
-  private void addCatchBlockSuccessors(@Nonnull List<CatchBasicBlock> catchBlocks,
-      @Nonnull IntList successors) {
-    for (CatchBasicBlock catchblock : catchBlocks) {
-      int catchTypeCount = 0;
-      int catchTypesSize = catchblock.getCatchTypes().size();
-      while (catchTypeCount++ < catchTypesSize) {
-        successors.add(catchblock.getId());
+  private void removeExceptionHandlingContext(@Nonnull JControlFlowGraph cfg) {
+    for (JBasicBlock block : cfg.getInternalBlocksUnordered()) {
+      for (JBasicBlockElement element : block.getElements(true)) {
+        element.resetEHContext(ExceptionHandlingContext.EMPTY);
       }
     }
+    new CfgBasicBlockUtils(cfg).removeUnreachableBlocks();
   }
 
   @Nonnull
@@ -436,21 +480,6 @@
     }
   }
 
-  private int getMaxLabel(ControlFlowGraph cfg) {
-    int maxLabel = -1;
-
-    for (BasicBlock bb : cfg.getNodes()) {
-      int bbId = bb.getId();
-      if (bbId > maxLabel) {
-        maxLabel = bbId;
-      }
-    }
-
-    // maxLabel is exclusive, thus add +1
-    maxLabel++;
-    return maxLabel;
-  }
-
   @Nonnull
   private InsnList createInsnList(@Nonnull List<Insn> instructions, @Nonnegative int extraSize) {
     InsnList il = new InsnList(instructions.size() + extraSize);
diff --git a/jack/src/com/android/jack/backend/dex/rop/RopBuilderVisitor.java b/jack/src/com/android/jack/backend/dex/rop/RopBuilderVisitor.java
index ef831b9..ff7fdf3 100644
--- a/jack/src/com/android/jack/backend/dex/rop/RopBuilderVisitor.java
+++ b/jack/src/com/android/jack/backend/dex/rop/RopBuilderVisitor.java
@@ -16,11 +16,9 @@
 
 package com.android.jack.backend.dex.rop;
 
+import com.google.common.collect.Lists;
+
 import com.android.jack.backend.dex.invokecustom.InvokeCustomHelper;
-import com.android.jack.cfg.BasicBlock;
-import com.android.jack.cfg.CatchBasicBlock;
-import com.android.jack.cfg.PeiBasicBlock;
-import com.android.jack.cfg.SwitchBasicBlock;
 import com.android.jack.dx.rop.code.FillArrayDataInsn;
 import com.android.jack.dx.rop.code.Insn;
 import com.android.jack.dx.rop.code.PlainCstInsn;
@@ -61,12 +59,11 @@
 import com.android.jack.ir.ast.JAnnotation;
 import com.android.jack.ir.ast.JArrayLength;
 import com.android.jack.ir.ast.JArrayRef;
+import com.android.jack.ir.ast.JAsgOperation;
 import com.android.jack.ir.ast.JBinaryOperation;
 import com.android.jack.ir.ast.JBinaryOperator;
 import com.android.jack.ir.ast.JBooleanLiteral;
 import com.android.jack.ir.ast.JByteLiteral;
-import com.android.jack.ir.ast.JCaseStatement;
-import com.android.jack.ir.ast.JCatchBlock;
 import com.android.jack.ir.ast.JCharLiteral;
 import com.android.jack.ir.ast.JClass;
 import com.android.jack.ir.ast.JClassLiteral;
@@ -74,18 +71,14 @@
 import com.android.jack.ir.ast.JDynamicCastOperation;
 import com.android.jack.ir.ast.JExceptionRuntimeValue;
 import com.android.jack.ir.ast.JExpression;
-import com.android.jack.ir.ast.JExpressionStatement;
 import com.android.jack.ir.ast.JFieldRef;
 import com.android.jack.ir.ast.JFloatLiteral;
-import com.android.jack.ir.ast.JIfStatement;
 import com.android.jack.ir.ast.JInstanceOf;
 import com.android.jack.ir.ast.JIntLiteral;
 import com.android.jack.ir.ast.JIntegralConstant32;
 import com.android.jack.ir.ast.JInterface;
 import com.android.jack.ir.ast.JLambda;
 import com.android.jack.ir.ast.JLiteral;
-import com.android.jack.ir.ast.JLocalRef;
-import com.android.jack.ir.ast.JLock;
 import com.android.jack.ir.ast.JLongLiteral;
 import com.android.jack.ir.ast.JMethodCall;
 import com.android.jack.ir.ast.JMethodCall.DispatchKind;
@@ -98,20 +91,32 @@
 import com.android.jack.ir.ast.JPrimitiveType.JPrimitiveTypeEnum;
 import com.android.jack.ir.ast.JReferenceType;
 import com.android.jack.ir.ast.JReinterpretCastOperation;
-import com.android.jack.ir.ast.JReturnStatement;
 import com.android.jack.ir.ast.JShortLiteral;
-import com.android.jack.ir.ast.JStatement;
-import com.android.jack.ir.ast.JSwitchStatement;
-import com.android.jack.ir.ast.JThrowStatement;
 import com.android.jack.ir.ast.JType;
 import com.android.jack.ir.ast.JUnaryOperation;
-import com.android.jack.ir.ast.JUnlock;
 import com.android.jack.ir.ast.JValueLiteral;
 import com.android.jack.ir.ast.JVariableRef;
 import com.android.jack.ir.ast.JVisitor;
 import com.android.jack.ir.ast.MethodKind;
+import com.android.jack.ir.ast.cfg.JBasicBlock;
+import com.android.jack.ir.ast.cfg.JBasicBlockElement;
+import com.android.jack.ir.ast.cfg.JCaseBlockElement;
+import com.android.jack.ir.ast.cfg.JCatchBasicBlock;
+import com.android.jack.ir.ast.cfg.JConditionalBasicBlock;
+import com.android.jack.ir.ast.cfg.JConditionalBlockElement;
+import com.android.jack.ir.ast.cfg.JGotoBlockElement;
+import com.android.jack.ir.ast.cfg.JLockBlockElement;
+import com.android.jack.ir.ast.cfg.JMethodCallBlockElement;
+import com.android.jack.ir.ast.cfg.JPolymorphicMethodCallBlockElement;
+import com.android.jack.ir.ast.cfg.JReturnBlockElement;
+import com.android.jack.ir.ast.cfg.JStoreBlockElement;
+import com.android.jack.ir.ast.cfg.JSwitchBasicBlock;
+import com.android.jack.ir.ast.cfg.JSwitchBlockElement;
+import com.android.jack.ir.ast.cfg.JThrowBlockElement;
+import com.android.jack.ir.ast.cfg.JThrowingBasicBlock;
+import com.android.jack.ir.ast.cfg.JUnlockBlockElement;
+import com.android.jack.ir.ast.cfg.JVariableAsgBlockElement;
 import com.android.jack.ir.types.JIntegralType32;
-import com.android.jack.transformations.booleanoperators.FallThroughMarker;
 import com.android.jack.util.AndroidApiLevel;
 import com.android.jack.util.AndroidApiLevel.ProvisionalLevel;
 
@@ -138,7 +143,7 @@
   private List<Insn> extraInstructions;
 
   @Nonnull
-  private final BasicBlock currentBasicBlock;
+  private final JBasicBlock currentBasicBlock;
 
   /**
    * A guard for {@code instructions}. Does not protect {@code extraInstructions}.
@@ -146,23 +151,21 @@
   private boolean noMoreInstruction = true;
 
   private class AssignBuilderVisitor extends JVisitor {
-
-    @Nonnull
-    private final JStatement declaration;
     @Nonnull
     private final RegisterSpec destReg;
     @Nonnull
-    SourcePosition sourcePosition;
+    private final SourcePosition sourcePosition;
 
-    public AssignBuilderVisitor(@Nonnull JStatement declaration, @Nonnull JVariableRef destRef) {
-      this.declaration = declaration;
+    public AssignBuilderVisitor(
+        @Nonnull SourcePosition sourcePosition,
+        @Nonnull JVariableRef destRef) {
       this.destReg = ropReg.getOrCreateRegisterSpec(destRef);
-      this.sourcePosition = RopHelper.getSourcePosition(declaration);
+      this.sourcePosition = sourcePosition;
     }
 
     @Override
     public boolean visit(@Nonnull JNode node) {
-      throw new AssertionError(declaration.toSource() + " not yet supported.");
+      throw new AssertionError(node.toSource() + " not yet supported.");
     }
 
     @Override
@@ -273,7 +276,7 @@
       List<JExpression> initializers = newArray.getInitializers();
       if (!initializers.isEmpty() && initializers.size() <= 5 && newArray.getDims().size() == 1
           && elementType == JPrimitiveTypeEnum.INT.getType()) {
-            return true;
+        return true;
       }
       return false;
     }
@@ -390,7 +393,7 @@
     }
   }
 
-  RopBuilderVisitor(@Nonnull RopRegisterManager ropReg, @Nonnull BasicBlock currentBasicBlock,
+  RopBuilderVisitor(@Nonnull RopRegisterManager ropReg, @Nonnull JBasicBlock currentBasicBlock,
       @Nonnull AndroidApiLevel apiLevel) {
     this.ropReg = ropReg;
     this.currentBasicBlock = currentBasicBlock;
@@ -407,52 +410,127 @@
     return extraInstructions;
   }
 
-  public void accept(@Nonnull List<JStatement> list) {
-    instructions = new LinkedList<Insn>();
-    extraInstructions = new LinkedList<Insn>();
+  public void processBasicBlockElements() {
+    instructions = new LinkedList<>();
+    extraInstructions = new LinkedList<>();
     noMoreInstruction = false;
 
-    super.accept(list);
+    ArrayList<JBasicBlockElement> elements =
+        Lists.newArrayList(this.currentBasicBlock.getElements(true));
+    super.accept(elements);
   }
 
-  @Override
-  public boolean visit(@Nonnull JExpressionStatement exprStmt) {
-    JExpression expr = exprStmt.getExpr();
-
-    if (expr instanceof JPolymorphicMethodCall) {
-      buildInvokePolymorphic(null, (JPolymorphicMethodCall) expr);
-    } else if (expr instanceof JMethodCall) {
-      buildCall(null, (JMethodCall) expr);
-    } else if (expr instanceof JLocalRef) {
-      // "lv;" This is a nop, do nothing
-    } else if (expr instanceof JBinaryOperation) {
-      JBinaryOperation binaryOperation = (JBinaryOperation) expr;
-
-      // ensured by ThreeAddressCodeForm
-      assert binaryOperation.getOp() == JBinaryOperator.ASG;
-      JExpression lhs = binaryOperation.getLhs();
-      assert   (lhs instanceof JVariableRef)
-            || (lhs instanceof JFieldRef)
-            || (lhs instanceof JArrayRef);
-
-      buildAssign(exprStmt, lhs, binaryOperation.getRhs());
-    } else {
-      throw new AssertionError(exprStmt.toSource() + " not yet supported.");
-    }
-
+  public boolean visit(@Nonnull JGotoBlockElement element) {
     return false;
   }
 
   @Override
-  public boolean visit(@Nonnull JIfStatement ifStmt) {
-    JExpression condExpr = ifStmt.getIfExpr();
-    SourcePosition ifStmtSrcPos = RopHelper.getSourcePosition(ifStmt);
-    Rop ifOp;
-    RegisterSpecList sources;
-    JBinaryOperator op = null;
+  public boolean visit(@Nonnull JCaseBlockElement element) {
+    return false;
+  }
 
-    if (condExpr instanceof JBinaryOperation) {
-      JBinaryOperation binCondExpr = (JBinaryOperation) condExpr;
+  @Override
+  public boolean visit(@Nonnull JThrowBlockElement element) {
+    addInstruction(new ThrowingInsn(
+        Rops.THROW, RopHelper.getSourcePosition(element.getSourceInfo()),
+        RegisterSpecList.make(getRegisterSpec(element.getExpression())), getCatchTypes()));
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JStoreBlockElement element) {
+    JAsgOperation expression = element.getAssignment();
+    JExpression lhs = expression.getLhs();
+    JExpression rhs = expression.getRhs();
+
+    assert lhs instanceof JFieldRef || lhs instanceof JArrayRef;
+    assert !(rhs instanceof JExceptionRuntimeValue);
+
+    if (lhs instanceof JFieldRef) {
+      buildWriteField((JFieldRef) lhs, rhs, RopHelper.getSourcePosition(element.getSourceInfo()));
+    } else {
+      buildArrayWrite((JArrayRef) lhs, rhs, RopHelper.getSourcePosition(element.getSourceInfo()));
+    }
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JReturnBlockElement element) {
+    JExpression expression = element.getExpression();
+    RegisterSpecList sources = expression != null ?
+        RegisterSpecList.make(getRegisterSpec(expression)) :
+        RegisterSpecList.EMPTY;
+
+    JType type = expression != null ?
+        expression.getType() : JPrimitiveTypeEnum.VOID.getType();
+
+    addInstruction(new PlainInsn(
+        Rops.opReturn(RopHelper.convertTypeToDx(type)),
+        RopHelper.getSourcePosition(element.getSourceInfo()), null, sources));
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JVariableAsgBlockElement element) {
+    JAsgOperation asg = element.getAssignment();
+    JVariableRef local = (JVariableRef) asg.getLhs();
+    JExpression value = asg.getRhs();
+
+    if (value instanceof JExceptionRuntimeValue) {
+      RegisterSpec exceptionReg =
+          ropReg.getOrCreateRegisterSpec(local);
+      addInstruction(new PlainInsn(
+          Rops.opMoveException(exceptionReg.getTypeBearer()),
+          RopHelper.getSourcePosition(local),
+          exceptionReg,
+          RegisterSpecList.EMPTY));
+
+    } else {
+      new AssignBuilderVisitor(
+          RopHelper.getSourcePosition(element.getSourceInfo()),
+          local).accept(value);
+    }
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JMethodCallBlockElement element) {
+    buildCall(null, element.getCall());
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JPolymorphicMethodCallBlockElement element) {
+    buildInvokePolymorphic(null, element.getCall());
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JLockBlockElement element) {
+    addInstruction(new ThrowingInsn(
+        Rops.MONITOR_ENTER, RopHelper.getSourcePosition(element.getSourceInfo()),
+        RegisterSpecList.make(getRegisterSpec(element.getExpression())), getCatchTypes()));
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JUnlockBlockElement element) {
+    addInstruction(new ThrowingInsn(
+        Rops.MONITOR_EXIT, RopHelper.getSourcePosition(element.getSourceInfo()),
+        RegisterSpecList.make(getRegisterSpec(element.getExpression())), getCatchTypes()));
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JConditionalBlockElement element) {
+    SourcePosition ifStmtSrcPos = RopHelper.getSourcePosition(element.getSourceInfo());
+    RegisterSpecList sources;
+    JBinaryOperator op;
+
+    JExpression expr = element.getCondition();
+
+    if (expr instanceof JBinaryOperation) {
+      JBinaryOperation binCondExpr = (JBinaryOperation) expr;
       JExpression right = binCondExpr.getRhs();
       RegisterSpec rightReg = getRegisterSpec(right);
 
@@ -472,14 +550,10 @@
           case FLOAT:
           case DOUBLE: {
             RegisterSpec dest = ropReg.createRegisterSpec(JPrimitiveTypeEnum.BOOLEAN.getType());
-            Rop cmpOp = null;
             Type dxType = RopHelper.convertTypeToDx(type);
 
-            if (type == JPrimitiveTypeEnum.LONG.getType()) {
-              cmpOp = Rops.opCmpl(dxType);
-            } else {
-              cmpOp = getCmpOperatorForFloatDouble(op, dxType);
-            }
+            Rop cmpOp = (type == JPrimitiveTypeEnum.LONG.getType())
+                ? Rops.opCmpl(dxType) : getCmpOperatorForFloatDouble(op, dxType);
 
             Insn ifInst = new PlainInsn(cmpOp, ifStmtSrcPos, dest, sources);
             addInstruction(ifInst);
@@ -497,43 +571,69 @@
             throw new AssertionError("Void type not supported.");
         }
       }
-    } else if (condExpr instanceof JPrefixNotOperation) {
-      RegisterSpec sourceReg = getRegisterSpec(((JPrefixNotOperation) condExpr).getArg());
+    } else if (expr instanceof JPrefixNotOperation) {
+      RegisterSpec sourceReg = getRegisterSpec(((JPrefixNotOperation) expr).getArg());
       sources = RegisterSpecList.make(sourceReg);
       op = JBinaryOperator.EQ;
     } else {
-      RegisterSpec sourceReg = getRegisterSpec(condExpr);
+      RegisterSpec sourceReg = getRegisterSpec(expr);
       sources = RegisterSpecList.make(sourceReg);
       op = JBinaryOperator.NEQ;
     }
 
-    assert op != null;
-    FallThroughMarker ftm = ifStmt.getMarker(FallThroughMarker.class);
-    if (ftm != null) {
-      switch (ftm.getFallThrough()) {
-        case ELSE: {
-          ifOp = getOperatorForIf(op, sources);
-          break;
-        }
-        case THEN: {
-          ifOp = getReverseOperatorForIf(op, sources);
-          break;
-        }
-        default: {
-          throw new AssertionError();
-        }
-      }
-    } else {
-      ifOp = getReverseOperatorForIf(op, sources);
+    Rop ifOp = getReverseOperatorForIf(op, sources);
+    assert this.currentBasicBlock instanceof JConditionalBasicBlock;
+    if (((JConditionalBasicBlock) this.currentBasicBlock).isInverted()) {
+      ifOp = getOperatorForIf(op, sources);
     }
-    assert ifOp != null;
 
     Insn ifInst = new PlainInsn(ifOp, ifStmtSrcPos, null, sources);
     addInstruction(ifInst);
-
     return false;
   }
 
+  @Override
+  public boolean visit(@Nonnull JSwitchBlockElement element) {
+    assert currentBasicBlock instanceof JSwitchBasicBlock;
+
+    SourcePosition switchStmtSrcPos = RopHelper.getSourcePosition(element.getSourceInfo());
+    IntList cases = new IntList();
+    for (JBasicBlock caseBb : ((JSwitchBasicBlock) currentBasicBlock).getCases()) {
+      assert caseBb.hasElements();
+      JBasicBlockElement firstElement = caseBb.getFirstElement();
+      assert firstElement instanceof JCaseBlockElement;
+
+      JLiteral caseValue = ((JCaseBlockElement) firstElement).getLiteral();
+      if (caseValue instanceof JIntLiteral) {
+        cases.add(((JIntLiteral) caseValue).getValue());
+      } else if (caseValue instanceof JCharLiteral) {
+        cases.add(((JCharLiteral) caseValue).getValue());
+      } else if (caseValue instanceof JShortLiteral) {
+        cases.add(((JShortLiteral) caseValue).getValue());
+      } else if (caseValue instanceof JByteLiteral) {
+        cases.add(((JByteLiteral) caseValue).getValue());
+      } else {
+        throw new AssertionError("Unsupported value");
+      }
+    }
+
+    RegisterSpecList sources = RegisterSpecList.make(getRegisterSpec(element.getExpression()));
+
+    Insn switchInst = new SwitchInsn(Rops.SWITCH, switchStmtSrcPos, null, sources, cases);
+    addInstruction(switchInst);
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JBasicBlockElement element) {
+    throw new AssertionError();
+  }
+
+  @Override
+  public boolean visit(@Nonnull JNode node) {
+    throw new AssertionError("Not supported: " + node.toSource());
+  }
+
   /**
    * Get the @link{Rop} corresponding to the inverse of the @link{JBinaryOperator} provided for
    * float or double type.
@@ -549,11 +649,11 @@
       case LTE:
       case LT:
         return Rops.opCmpg(type);
-       case GT:
-       case GTE:
-       case EQ:
-       case NEQ:
-         return Rops.opCmpl(type);
+      case GT:
+      case GTE:
+      case EQ:
+      case NEQ:
+        return Rops.opCmpl(type);
       default:
         throw new AssertionError("Operator " + op.toString() + " not yet supported into IfStmt.");
     }
@@ -571,16 +671,16 @@
     switch (op) {
       case LT:
         return Rops.opIfLt(sources);
-       case GT:
-         return Rops.opIfGt(sources);
-       case LTE:
-         return Rops.opIfLe(sources);
-       case GTE:
-         return Rops.opIfGe(sources);
-       case EQ:
-         return Rops.opIfEq(sources);
-       case NEQ:
-         return Rops.opIfNe(sources);
+      case GT:
+        return Rops.opIfGt(sources);
+      case LTE:
+        return Rops.opIfLe(sources);
+      case GTE:
+        return Rops.opIfGe(sources);
+      case EQ:
+        return Rops.opIfEq(sources);
+      case NEQ:
+        return Rops.opIfNe(sources);
       default:
         throw new AssertionError("Operator " + op.toString()
             + " not yet supported into IfStmt.");
@@ -599,107 +699,22 @@
     switch (op) {
       case LT:
         return Rops.opIfGe(sources);
-       case GT:
-         return Rops.opIfLe(sources);
-       case LTE:
-         return Rops.opIfGt(sources);
-       case GTE:
-         return Rops.opIfLt(sources);
-       case EQ:
-         return Rops.opIfNe(sources);
-       case NEQ:
-         return Rops.opIfEq(sources);
+      case GT:
+        return Rops.opIfLe(sources);
+      case LTE:
+        return Rops.opIfGt(sources);
+      case GTE:
+        return Rops.opIfLt(sources);
+      case EQ:
+        return Rops.opIfNe(sources);
+      case NEQ:
+        return Rops.opIfEq(sources);
       default:
         throw new AssertionError("Operator " + op.toString()
             + " not yet supported into IfStmt.");
     }
   }
 
-  @Override
-  public boolean visit(@Nonnull JReturnStatement retStmt) {
-    JExpression returnedExpr = retStmt.getExpr();
-    RegisterSpecList sources;
-
-    if (returnedExpr != null) {
-      sources = RegisterSpecList.make(getRegisterSpec(returnedExpr));
-    } else {
-      sources = RegisterSpecList.EMPTY;
-    }
-
-    Insn retInst = new PlainInsn(Rops.opReturn(RopHelper.convertTypeToDx(
-        returnedExpr == null ? JPrimitiveTypeEnum.VOID.getType() : returnedExpr.getType())),
-        RopHelper.getSourcePosition(retStmt), null, sources);
-    addInstruction(retInst);
-
-    return false;
-  }
-
-  @Override
-  public boolean visit(@Nonnull JSwitchStatement jswitch) {
-    assert currentBasicBlock instanceof SwitchBasicBlock;
-
-    SourcePosition switchStmtSrcPos = RopHelper.getSourcePosition(jswitch);
-    IntList cases = new IntList();
-    for (BasicBlock caseBb : ((SwitchBasicBlock) currentBasicBlock).getCasesBlock()) {
-          JStatement firstStatement = caseBb.getStatements().get(0);
-          assert firstStatement instanceof JCaseStatement;
-          JLiteral caseValue = ((JCaseStatement) firstStatement).getExpr();
-          if (caseValue instanceof JIntLiteral) {
-            cases.add(((JIntLiteral) caseValue).getValue());
-          } else if (caseValue instanceof JCharLiteral) {
-            cases.add(((JCharLiteral) caseValue).getValue());
-          } else if (caseValue instanceof JShortLiteral) {
-            cases.add(((JShortLiteral) caseValue).getValue());
-          } else if (caseValue instanceof JByteLiteral) {
-            cases.add(((JByteLiteral) caseValue).getValue());
-          } else {
-            throw new AssertionError("Unsupported value");
-          }
-    }
-
-    RegisterSpecList sources = RegisterSpecList.make(getRegisterSpec(jswitch.getExpr()));
-
-    Insn switchInst = new SwitchInsn(Rops.SWITCH, switchStmtSrcPos, null, sources, cases);
-    addInstruction(switchInst);
-    return false;
-  }
-
-  @Override
-  public boolean visit(@Nonnull JThrowStatement throwStmt) {
-    Insn throwInsn = new ThrowingInsn(Rops.THROW, RopHelper.getSourcePosition(throwStmt),
-        RegisterSpecList.make(getRegisterSpec(throwStmt.getExpr())),
-        getCatchTypes());
-
-    addInstruction(throwInsn);
-
-    return false;
-  }
-
-  @Override
-  public boolean visit(@Nonnull JLock lockStmt) {
-    SourcePosition srcPosition = RopHelper.getSourcePosition(lockStmt);
-    RegisterSpec lockReg = getRegisterSpec(lockStmt.getLockExpr());
-
-    Insn lockInsn = new ThrowingInsn(Rops.MONITOR_ENTER, srcPosition,
-        RegisterSpecList.make(lockReg), getCatchTypes());
-
-    addInstruction(lockInsn);
-
-    return false;
-  }
-
-  @Override
-  public boolean visit(@Nonnull JUnlock unlockStmt) {
-    RegisterSpec unlockReg = getRegisterSpec(unlockStmt.getLockExpr());
-
-    Insn unlockInsn = new ThrowingInsn(Rops.MONITOR_EXIT, RopHelper.getSourcePosition(unlockStmt),
-        RegisterSpecList.make(unlockReg), getCatchTypes());
-
-    addInstruction(unlockInsn);
-
-    return false;
-  }
-
   private void buildAlloc(@Nonnull RegisterSpec destReg, @Nonnull JAlloc alloc,
       @Nonnull SourcePosition sourcePosition) {
     addInstruction(new ThrowingCstInsn(Rops.NEW_INSTANCE, sourcePosition, RegisterSpecList.EMPTY,
@@ -707,36 +722,10 @@
     addMoveResultPseudoAsExtraInstruction(destReg, sourcePosition);
   }
 
-  private void buildAssign(
-      @Nonnull JStatement declaration,
-      @Nonnull JExpression dest,
-      @Nonnull JExpression value)
-          throws AssertionError {
-    if (value instanceof JExceptionRuntimeValue) {
-      assert dest instanceof JVariableRef;
-      assert declaration.getParent() instanceof JCatchBlock
-          && ((JCatchBlock) declaration.getParent()).getStatements().get(0) == declaration;
-      RegisterSpec exceptionReg = ropReg.getOrCreateRegisterSpec((JVariableRef) dest);
-      addInstruction(new PlainInsn(
-          Rops.opMoveException(exceptionReg.getTypeBearer()), RopHelper.getSourcePosition(dest),
-          exceptionReg, RegisterSpecList.EMPTY));
-    } else if (dest instanceof JFieldRef) {
-      buildWriteField((JFieldRef) dest, value, RopHelper.getSourcePosition(declaration));
-    } else if (dest instanceof JArrayRef) {
-      buildArrayWrite((JArrayRef) dest, value, RopHelper.getSourcePosition(declaration));
-    } else {
-      assert dest instanceof JVariableRef;
-      JVariableRef destRef = (JVariableRef) dest;
-
-      JVisitor rhsHandler = new AssignBuilderVisitor(declaration, destRef);
-      rhsHandler.accept(value);
-    }
-  }
-
   private void buildArrayWrite(JArrayRef arrayRef, JExpression value,
       SourcePosition sourcePosition) {
     assert arrayRef.getInstance() instanceof JVariableRef
-    || isNullOrReinterpretCastOfNull(arrayRef.getInstance());
+        || isNullOrReinterpretCastOfNull(arrayRef.getInstance());
     RegisterSpec valueReg = getRegisterSpec(value);
     RegisterSpec instanceReg = getRegisterSpec(arrayRef.getInstance());
     RegisterSpec indexReg = getRegisterSpec(arrayRef.getIndexExpr());
@@ -937,7 +926,7 @@
     JType type = literal.getType();
     if (type instanceof JPrimitiveType) {
       cst = buildPrimitiveConstant(literal);
-    } else  if (literal instanceof JAbstractStringLiteral){
+    } else if (literal instanceof JAbstractStringLiteral) {
       cst = RopHelper.createString((JAbstractStringLiteral) literal);
     } else if (literal instanceof JNullLiteral) {
       cst = CstKnownNull.THE_ONE;
@@ -958,7 +947,7 @@
           constOp, sourcePosition, destReg, RegisterSpecList.EMPTY,
           getConstant(literal));
       addInstruction(constInst);
-    } else  if (literal instanceof JAbstractStringLiteral){
+    } else if (literal instanceof JAbstractStringLiteral) {
       constInst = new ThrowingCstInsn(constOp, sourcePosition,
           RegisterSpecList.EMPTY, getCatchTypes(), getConstant(literal));
       addInstruction(constInst);
@@ -982,7 +971,7 @@
 
     Rop opcode = null;
 
-    switch(unary.getOp()) {
+    switch (unary.getOp()) {
       case NEG: {
         assert unary.getType() == JPrimitiveTypeEnum.BYTE.getType()
             || unary.getType() == JPrimitiveTypeEnum.CHAR.getType()
@@ -1056,6 +1045,7 @@
             && lhs instanceof JIntegralConstant32 && ((JIntegralType32) JPrimitiveTypeEnum.SHORT
                 .getType()).isValidValue(((JIntegralConstant32) lhs).getIntValue())) {
           sources = RegisterSpecList.make(ropReg.getOrCreateRegisterSpec((JVariableRef) rhs));
+          assert lhs instanceof JValueLiteral;
           cst = getConstant((JValueLiteral) lhs);
         } else {
           sources = RegisterSpecList.make(getRegisterSpec(lhs),
@@ -1253,7 +1243,7 @@
       sources = new RegisterSpecList(1 + methodCall.getArgs().size());
     }
 
-    switch(methodKind) {
+    switch (methodKind) {
       case STATIC:
         callOp = Rops.opInvokeStatic(prototype);
         break;
@@ -1318,6 +1308,7 @@
 
     return regSpec;
   }
+
   private void addMoveResultAsExtraInstruction(@Nonnull TypeBearer type,
       @Nonnull RegisterSpec destReg, @Nonnull SourcePosition sourcePosition) {
     Rop moveResultOp = Rops.opMoveResult(type);
@@ -1351,13 +1342,13 @@
    * block.
    */
   private TypeList getCatchTypes() {
-    assert currentBasicBlock instanceof PeiBasicBlock;
-    PeiBasicBlock peiBlock = (PeiBasicBlock) currentBasicBlock;
+    assert currentBasicBlock instanceof JThrowingBasicBlock;
+    JThrowingBasicBlock block = (JThrowingBasicBlock) currentBasicBlock;
 
     List<JType> catchTypes = new ArrayList<JType>();
 
-    for (CatchBasicBlock bb : peiBlock.getExceptionBlocks()) {
-      for (JClass catchType : bb.getCatchTypes()) {
+    for (JBasicBlock bb : block.getCatchBlocks()) {
+      for (JClass catchType : ((JCatchBasicBlock) bb).getCatchTypes()) {
         catchTypes.add(catchType);
       }
     }
@@ -1370,7 +1361,7 @@
   }
 
   @Override
-  public void endVisit(@Nonnull JStatement x) {
+  public void endVisit(@Nonnull JBasicBlockElement x) {
     ropReg.resetFreeTmpRegister();
   }
 
diff --git a/jack/src/com/android/jack/backend/dex/rop/RopHelper.java b/jack/src/com/android/jack/backend/dex/rop/RopHelper.java
index d145c73..a3408c7 100644
--- a/jack/src/com/android/jack/backend/dex/rop/RopHelper.java
+++ b/jack/src/com/android/jack/backend/dex/rop/RopHelper.java
@@ -194,12 +194,24 @@
    */
   @Nonnull
   public static SourcePosition getSourcePosition(@Nonnull JNode stmt) {
-    if (stmt.getSourceInfo() == SourceInfo.UNKNOWN) {
+    return getSourcePosition(stmt.getSourceInfo());
+  }
+
+  /**
+   * Builds a {@code SourcePosition} for a {@code SourceInfo}.
+   *
+   * @param sourceInfo
+   *     The source info used to extract source position information.
+   * @return The built {@code SourcePosition}.
+   */
+  @Nonnull
+  public static SourcePosition getSourcePosition(@Nonnull SourceInfo sourceInfo) {
+    if (sourceInfo == SourceInfo.UNKNOWN) {
       return SourcePosition.NO_INFO;
     }
-    int startLine = stmt.getSourceInfo().getStartLine();
+    int startLine = sourceInfo.getStartLine();
     // Jack defines unknown line by UNKNOWN_LINE_NUMBER, but dx requires to use -1.
-    return (new SourcePosition(new CstString(stmt.getSourceInfo().getFileName()), -1,
+    return (new SourcePosition(new CstString(sourceInfo.getFileName()), -1,
         startLine == SourceInfo.UNKNOWN_LINE_NUMBER ? -1 : startLine));
   }
 
diff --git a/jack/src/com/android/jack/backend/dex/rop/SsaCodeItemBuilder.java b/jack/src/com/android/jack/backend/dex/rop/SsaCodeItemBuilder.java
new file mode 100644
index 0000000..79827a3
--- /dev/null
+++ b/jack/src/com/android/jack/backend/dex/rop/SsaCodeItemBuilder.java
@@ -0,0 +1,568 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * 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.android.jack.backend.dex.rop;
+
+import com.google.common.collect.Lists;
+
+import com.android.jack.JackEventType;
+import com.android.jack.Options;
+import com.android.jack.dx.dex.DexOptions;
+import com.android.jack.dx.dex.code.DalvCode;
+import com.android.jack.dx.dex.code.PositionList;
+import com.android.jack.dx.dex.code.RopTranslator;
+import com.android.jack.dx.dex.file.CodeItem;
+import com.android.jack.dx.rop.code.DexTranslationAdvice;
+import com.android.jack.dx.rop.code.Insn;
+import com.android.jack.dx.rop.code.LocalVariableExtractor;
+import com.android.jack.dx.rop.code.LocalVariableInfo;
+import com.android.jack.dx.rop.code.PlainCstInsn;
+import com.android.jack.dx.rop.code.PlainInsn;
+import com.android.jack.dx.rop.code.RegisterSpec;
+import com.android.jack.dx.rop.code.RegisterSpecList;
+import com.android.jack.dx.rop.code.Rop;
+import com.android.jack.dx.rop.code.RopMethod;
+import com.android.jack.dx.rop.code.Rops;
+import com.android.jack.dx.rop.code.SourcePosition;
+import com.android.jack.dx.rop.cst.CstInteger;
+import com.android.jack.dx.rop.type.StdTypeList;
+import com.android.jack.dx.rop.type.Type;
+import com.android.jack.dx.rop.type.TypeList;
+import com.android.jack.dx.ssa.NormalSsaInsn;
+import com.android.jack.dx.ssa.Optimizer;
+import com.android.jack.dx.ssa.Optimizer.OptionalStep;
+import com.android.jack.dx.ssa.PhiInsn;
+import com.android.jack.dx.ssa.SsaBasicBlock;
+import com.android.jack.dx.ssa.SsaInsn;
+import com.android.jack.dx.ssa.SsaMethod;
+import com.android.jack.dx.util.IntList;
+import com.android.jack.ir.SideEffectOperation;
+import com.android.jack.ir.ast.JAbstractMethodBody;
+import com.android.jack.ir.ast.JAsgOperation;
+import com.android.jack.ir.ast.JAssertStatement;
+import com.android.jack.ir.ast.JCastOperation;
+import com.android.jack.ir.ast.JConcatOperation;
+import com.android.jack.ir.ast.JConditionalExpression;
+import com.android.jack.ir.ast.JConditionalOperation;
+import com.android.jack.ir.ast.JExceptionRuntimeValue;
+import com.android.jack.ir.ast.JFieldInitializer;
+import com.android.jack.ir.ast.JLoop;
+import com.android.jack.ir.ast.JMethod;
+import com.android.jack.ir.ast.JMethodBodyCfg;
+import com.android.jack.ir.ast.JMultiExpression;
+import com.android.jack.ir.ast.JParameter;
+import com.android.jack.ir.ast.JPrimitiveType.JPrimitiveTypeEnum;
+import com.android.jack.ir.ast.JSsaVariableDefRef;
+import com.android.jack.ir.ast.JSsaVariableRef;
+import com.android.jack.ir.ast.JSwitchStatement;
+import com.android.jack.ir.ast.JThis;
+import com.android.jack.ir.ast.cfg.JBasicBlock;
+import com.android.jack.ir.ast.cfg.JCaseBasicBlock;
+import com.android.jack.ir.ast.cfg.JCatchBasicBlock;
+import com.android.jack.ir.ast.cfg.JConditionalBasicBlock;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.jack.ir.ast.cfg.JEntryBasicBlock;
+import com.android.jack.ir.ast.cfg.JExitBasicBlock;
+import com.android.jack.ir.ast.cfg.JRegularBasicBlock;
+import com.android.jack.ir.ast.cfg.JReturnBasicBlock;
+import com.android.jack.ir.ast.cfg.JSimpleBasicBlock;
+import com.android.jack.ir.ast.cfg.JSwitchBasicBlock;
+import com.android.jack.ir.ast.cfg.JThrowBasicBlock;
+import com.android.jack.ir.ast.cfg.JThrowingExpressionBasicBlock;
+import com.android.jack.ir.ast.marker.ThrownExceptionMarker;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
+import com.android.jack.scheduling.marker.DexCodeMarker;
+import com.android.jack.transformations.EmptyClinit;
+import com.android.jack.transformations.InvalidDefaultBridgeInInterfaceRemoved;
+import com.android.jack.transformations.ast.BooleanTestOutsideIf;
+import com.android.jack.transformations.ast.ImplicitBoxingAndUnboxing;
+import com.android.jack.transformations.ast.ImplicitCast;
+import com.android.jack.transformations.ast.InitInNewArray;
+import com.android.jack.transformations.ast.JPrimitiveClassLiteral;
+import com.android.jack.transformations.ast.MultiDimensionNewArray;
+import com.android.jack.transformations.ast.NewInstanceRemoved;
+import com.android.jack.transformations.ast.RefAsStatement;
+import com.android.jack.transformations.ast.UnassignedValues;
+import com.android.jack.transformations.ast.inner.InnerAccessor;
+import com.android.jack.transformations.ast.switches.UselessSwitches;
+import com.android.jack.transformations.cast.SourceCast;
+import com.android.jack.transformations.rop.cast.RopLegalCast;
+import com.android.jack.transformations.threeaddresscode.ThreeAddressCodeForm;
+import com.android.jack.util.AndroidApiLevel;
+import com.android.sched.item.Description;
+import com.android.sched.item.Name;
+import com.android.sched.schedulable.Constraint;
+import com.android.sched.schedulable.Filter;
+import com.android.sched.schedulable.RunnableSchedulable;
+import com.android.sched.schedulable.Transform;
+import com.android.sched.schedulable.Use;
+import com.android.sched.util.config.ThreadConfig;
+import com.android.sched.util.log.Event;
+import com.android.sched.util.log.Tracer;
+import com.android.sched.util.log.TracerFactory;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.EnumSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+
+/**
+ * CodeItemBuilder is a schedulable that generates {@link CodeItem} from {@link JMethod}. The
+ * generated {@link CodeItem} is saved into the {@link DexCodeMarker}.
+ */
+@Description("Builds CodeItem from JMethod in SSA form")
+@Name("SsaCodeItemBuilder")
+@Constraint(need = {JSsaVariableRef.class, JMethodBodyCfg.class, JExceptionRuntimeValue.class,
+                    NewInstanceRemoved.class, ThreeAddressCodeForm.class, RopLegalCast.class,
+                    InnerAccessor.class, InvalidDefaultBridgeInInterfaceRemoved.class},
+    no = {BooleanTestOutsideIf.class, InitInNewArray.class, JAsgOperation.class,
+          JPrimitiveClassLiteral.class, JMultiExpression.class, JConditionalExpression.class,
+          JFieldInitializer.class, JConcatOperation.class, JLoop.class, SideEffectOperation.class,
+          UnassignedValues.class, RefAsStatement.class, MultiDimensionNewArray.class,
+          JSwitchStatement.SwitchWithEnum.class, ImplicitBoxingAndUnboxing.class,
+          ImplicitCast.class, JAssertStatement.class, JConditionalOperation.class,
+          EmptyClinit.class, UselessSwitches.class, SourceCast.class,
+          JCastOperation.WithIntersectionType.class})
+@Transform(add = DexCodeMarker.class)
+@Use(RopHelper.class)
+@Filter(TypeWithoutPrebuiltFilter.class)
+public class SsaCodeItemBuilder implements RunnableSchedulable<JMethod> {
+  @Nonnull
+  private final com.android.jack.util.filter.Filter<JMethod> filter =
+      ThreadConfig.get(Options.METHOD_FILTER);
+  private final boolean emitSyntheticLocalDebugInfo =
+      ThreadConfig.get(CodeItemBuilder.EMIT_SYNTHETIC_LOCAL_DEBUG_INFO).booleanValue();
+  private final boolean emitLocalDebugInfo =
+      ThreadConfig.get(Options.EMIT_LOCAL_DEBUG_INFO).booleanValue();
+  private final boolean runDxOptimizations =
+      ThreadConfig.get(CodeItemBuilder.DEX_OPTIMIZE).booleanValue();
+  private final boolean forceJumbo = ThreadConfig.get(CodeItemBuilder.FORCE_JUMBO).booleanValue();
+  private final boolean removeRedundantConditionalBranch =
+      ThreadConfig.get(CodeItemBuilder.OPTIMIZE_BRANCHES).booleanValue();
+  private final AndroidApiLevel apiLevel = ThreadConfig.get(Options.ANDROID_MIN_API_LEVEL);
+  private final boolean emitLineNumberTable =
+      ThreadConfig.get(Options.EMIT_LINE_NUMBER_DEBUG_INFO).booleanValue();
+
+  @Nonnull
+  private final Tracer tracer = TracerFactory.getTracer();
+
+  @Override
+  public void run(@Nonnull JMethod method) {
+    if (method.isNative() || method.isAbstract() || !filter.accept(this.getClass(), method)) {
+      return;
+    }
+    buildSsaCodeItem(method, false);
+  }
+
+  @SuppressWarnings("boxing")
+  public void buildSsaCodeItem(@Nonnull JMethod method, boolean minimizeRegister) {
+    try (Event event = tracer.open(JackEventType.DX_BACKEND)) {
+      SsaRopRegisterManager ropReg =
+          new SsaRopRegisterManager(emitLocalDebugInfo, emitSyntheticLocalDebugInfo);
+
+      JAbstractMethodBody body = method.getBody();
+      assert body instanceof JMethodBodyCfg;
+      JControlFlowGraph cfg = ((JMethodBodyCfg) body).getCfg();
+
+      final JEntryBasicBlock entryBasicBlock = cfg.getEntryBlock();
+      final JExitBasicBlock exitBasicBlock = cfg.getExitBlock();
+
+      final Map<JBasicBlock, Integer> basicBlocks = new LinkedHashMap<>();
+      int blockId = 0; // 0 is reserved for entry block
+      // TODO(acleung): We don't need to generate blocks that is not reachable. We are only doing
+      // this for now because the Phi nodes will not have a valid prececessor otherwise. DX normally
+      // remove any unreachable nodes so this will not be a performance issue.
+      for (JBasicBlock block : cfg.getReachableBlocksDepthFirst()) {
+        if (block == exitBasicBlock) {
+          basicBlocks.put(block, Integer.MAX_VALUE);
+        } else {
+          basicBlocks.put(block, Integer.valueOf(blockId++));
+        }
+      }
+
+      int maxLabel = blockId + 2;
+      SsaMethod ssaMethod =
+          new SsaMethod(getParameterWordCount(method), method.isStatic(), maxLabel, 0);
+      final SsaRopBasicBlockManager ropBb = new SsaRopBasicBlockManager(ssaMethod, maxLabel);
+
+      JBasicBlock firstBlockOfCode = entryBasicBlock.getOnlySuccessor();
+
+      // Rop-SSA needs an extra entry block compare the ROP.
+      int firstJackBlockIndex = basicBlocks.get(firstBlockOfCode).intValue();
+      SsaBasicBlock initBb = ropBb.createBasicBlock();
+      initBb.setSuccessors(
+          IntList.makeImmutable(ropBb.getSpecialLabel(SsaRopBasicBlockManager.PARAM_ASSIGNMENT)),
+          ropBb.getSpecialLabel(SsaRopBasicBlockManager.PARAM_ASSIGNMENT));
+      List<SsaInsn> insns = Lists.newArrayList();
+      insns.add(new NormalSsaInsn(
+          new PlainInsn(Rops.GOTO, SourcePosition.NO_INFO, null, RegisterSpecList.EMPTY), initBb));
+      initBb.setInsns(insns);
+      initBb.setRopLabel(ropBb.getSpecialLabel(SsaRopBasicBlockManager.SSA_INIT));
+
+      addSetupBlocks(method, ropReg, ropBb, firstJackBlockIndex);
+
+      if (method.getType() != JPrimitiveTypeEnum.VOID.getType()) {
+        ropReg.createReturnReg(method.getType());
+      }
+
+      for (JBasicBlock b : basicBlocks.keySet()) {
+        if (b instanceof JExitBasicBlock || b instanceof JEntryBasicBlock) {
+          continue;
+        }
+        assert b != entryBasicBlock && b != exitBasicBlock;
+        JRegularBasicBlock bb = (JRegularBasicBlock) b;
+
+        final SsaBasicBlock ssaBb = ropBb.createBasicBlock();
+        final SsaRopBuilderVisitor ropBuilder =
+            new SsaRopBuilderVisitor(ropReg, bb, ssaBb, basicBlocks, apiLevel);
+
+        ropBuilder.processBasicBlockElements();
+        final List<SsaInsn> instructions = ropBuilder.getInstructions();
+        assert instructions != null;
+
+        if (bb instanceof JReturnBasicBlock) {
+          List<SsaInsn> il = instructions;
+          ssaBb.setRopLabel(basicBlocks.get(bb));
+          ssaBb.setInsns(il);
+          ssaBb.setSuccessors(IntList.EMPTY, -1);
+
+        } else if (bb instanceof JThrowBasicBlock) {
+          List<SsaInsn> il = instructions;
+
+          IntList successors = new IntList();
+          int primarySuccessor = -1;
+
+          if (!((JThrowBasicBlock) bb).getCatchBlocks().isEmpty()) {
+            addCatchBlockSuccessors(basicBlocks, ((JThrowBasicBlock) bb).getCatchBlocks(),
+                successors);
+          }
+          successors.setImmutable();
+          ssaBb.setRopLabel(basicBlocks.get(bb));
+          ssaBb.setInsns(il);
+          ssaBb.setSuccessors(successors, primarySuccessor);
+
+        } else if (bb instanceof JThrowingExpressionBasicBlock) {
+          SsaInsn lastInstruction = instructions.get(instructions.size() - 1);
+          List<Insn> extraInstructions = ropBuilder.getExtraInstructions();
+          assert extraInstructions != null;
+
+          List<SsaInsn> il = instructions;
+
+          int extraBlockLabel = ropBb.getAvailableLabel();
+
+          IntList successors = new IntList();
+          addCatchBlockSuccessors(basicBlocks,
+              ((JThrowingExpressionBasicBlock) bb).getCatchBlocks(), successors);
+
+          successors.add(extraBlockLabel);
+          successors.setImmutable();
+
+          ssaBb.setRopLabel(basicBlocks.get(bb));
+          ssaBb.setInsns(il);
+          ssaBb.setSuccessors(successors, extraBlockLabel);
+
+          boolean needsGoto;
+          SourcePosition sourcePosition;
+
+          SsaBasicBlock extraSsaBb = ropBb.createBasicBlock();
+
+          if (extraInstructions.isEmpty()) {
+            needsGoto = true;
+            sourcePosition = lastInstruction.getOriginalRopInsn().getPosition();
+            il = new ArrayList<SsaInsn>(1);
+          } else {
+            Insn extraInsn = extraInstructions.get(0);
+            needsGoto = extraInstructions.get(extraInstructions.size() - 1).getOpcode()
+                .getBranchingness() == Rop.BRANCH_NONE;
+            il = new ArrayList<SsaInsn>(extraInstructions.size() + (needsGoto ? 1 : 0));
+            for (Insn inst : extraInstructions) {
+              il.add(new NormalSsaInsn(inst, extraSsaBb));
+            }
+            sourcePosition = extraInsn.getPosition();
+          }
+
+          if (needsGoto) {
+            il.add(new NormalSsaInsn(
+                new PlainInsn(Rops.GOTO, sourcePosition, null, RegisterSpecList.EMPTY),
+                extraSsaBb));
+          }
+
+          JBasicBlock primary = bb.getPrimarySuccessor();
+          successors = IntList.makeImmutable(basicBlocks.get(primary));
+          extraSsaBb.setRopLabel(extraBlockLabel);
+          extraSsaBb.setInsns(il);
+          extraSsaBb.setSuccessors(successors, basicBlocks.get(primary));
+
+        } else if (bb instanceof JConditionalBasicBlock) {
+          List<SsaInsn> il = instructions;
+
+          JBasicBlock primary = bb.getPrimarySuccessor();
+          JBasicBlock secondary = ((JConditionalBasicBlock) bb).getAlternativeSuccessor();
+
+          int primarySuccessor = basicBlocks.get(primary);
+          IntList successors = IntList.makeImmutable(primarySuccessor, basicBlocks.get(secondary));
+
+          ssaBb.setRopLabel(basicBlocks.get(bb));
+          ssaBb.setInsns(il);
+          ssaBb.setSuccessors(successors, primarySuccessor);
+
+        } else if (bb instanceof JSwitchBasicBlock) {
+          IntList successors = new IntList();
+          for (JBasicBlock caseBb : ((JSwitchBasicBlock) bb).getCases()) {
+            successors.add(basicBlocks.get(caseBb));
+          }
+
+          successors.add(basicBlocks.get(((JSwitchBasicBlock) bb).getDefaultCase()));
+
+          successors.setImmutable();
+          List<SsaInsn> il = instructions;
+
+          ssaBb.setRopLabel(basicBlocks.get(bb));
+          ssaBb.setInsns(il);
+          ssaBb.setSuccessors(successors, successors.get(successors.size() - 1));
+        } else {
+          assert bb instanceof JSimpleBasicBlock || bb instanceof JCatchBasicBlock
+              || bb instanceof JCaseBasicBlock;
+          assert bb.hasPrimarySuccessor();
+
+          JBasicBlock primarySuccessor = bb.getPrimarySuccessor();
+          IntList successors = IntList.makeImmutable(basicBlocks.get(primarySuccessor));
+          List<SsaInsn> il = createInsnList(instructions, 1);
+          Insn gotoInstruction =
+              new PlainInsn(Rops.GOTO, getLastElementPosition(bb), null, RegisterSpecList.EMPTY);
+          il.add(new NormalSsaInsn(gotoInstruction, ssaBb));
+          ssaBb.setRopLabel(basicBlocks.get(bb));
+          ssaBb.setInsns(il);
+          ssaBb.setSuccessors(successors, basicBlocks.get(primarySuccessor));
+        }
+      }
+
+      ssaMethod.setBlocks(ropBb.computeSsaBasicBlockList());
+      ssaMethod.setEntryBlockIndex(initBb.getIndex());
+      ssaMethod.setRegisterCount(ropReg.getRegisterCount());
+      ssaMethod.makeExitBlock();
+
+      edgeSplitMoveExceptionsAndResults(ssaMethod);
+
+      RopMethod ropMethod = null;
+
+      if (runDxOptimizations) {
+        if (!minimizeRegister) {
+          ropMethod = Optimizer.optimize(ssaMethod, getParameterWordCount(method),
+              method.isStatic(), true /* inPreserveLocals */, removeRedundantConditionalBranch,
+              DexTranslationAdvice.THE_ONE);
+          if (ropMethod.getBlocks().getRegCount() > DexTranslationAdvice.THE_ONE
+              .getMaxOptimalRegisterCount()) {
+            // Try to see if we can squeeze it under the register count bar
+            buildSsaCodeItem(method, true);
+            // Abort the current SSA-ROP code generation, otherwise we end up with duplicates.
+            return;
+          }
+        } else {
+          EnumSet<OptionalStep> steps = EnumSet.allOf(OptionalStep.class);
+          steps.remove(OptionalStep.CONST_COLLECTOR);
+          ropMethod = Optimizer.optimizeMinimizeRegisters(ssaMethod, getParameterWordCount(method),
+              method.isStatic(), true /* inPreserveLocals */, removeRedundantConditionalBranch,
+              DexTranslationAdvice.THE_ONE);
+        }
+      } else {
+        ropMethod = Optimizer.optimize(ssaMethod, getParameterWordCount(method), method.isStatic(),
+            true /* inPreserveLocals */, removeRedundantConditionalBranch,
+            DexTranslationAdvice.THE_ONE, EnumSet.noneOf(OptionalStep.class));
+      }
+
+      DalvCode dalvCode;
+      try (Event dopEvent = tracer.open(JackEventType.DOP_CREATION)) {
+        dalvCode = createCode(method, ropMethod);
+      }
+
+      method.addMarker(new DexCodeMarker(new CodeItem(RopHelper.createMethodRef(method), dalvCode,
+          method.isStatic(), createThrows(method))));
+    }
+  }
+
+  private SourcePosition getLastElementPosition(@Nonnull JBasicBlock bb) {
+    return RopHelper.getSourcePosition(
+        bb.hasElements() ? bb.getLastElement().getSourceInfo() : SourceInfo.UNKNOWN);
+  }
+
+  private void addCatchBlockSuccessors(Map<JBasicBlock, Integer> basicBlocks,
+      @Nonnull List<JBasicBlock> catchBlocks, @Nonnull IntList successors) {
+    for (JBasicBlock catchBlock : catchBlocks) {
+      if (!(catchBlock instanceof JCatchBasicBlock)) {
+        // We must have either some Phi nodes here or just empty gotos.
+        assert catchBlock.getSuccessors().size() == 1;
+        catchBlock = catchBlock.getSuccessors().get(0);
+      }
+      int catchTypeCount = 0;
+      int catchTypesSize = ((JCatchBasicBlock) catchBlock).getCatchTypes().size();
+      while (catchTypeCount++ < catchTypesSize) {
+        successors.add(basicBlocks.get(catchBlock).intValue());
+      }
+    }
+  }
+
+  @Nonnull
+  private static TypeList createThrows(@Nonnull JMethod method) {
+    ThrownExceptionMarker marker = method.getMarker(ThrownExceptionMarker.class);
+    if (marker != null) {
+      return RopHelper.createTypeList(marker.getThrownExceptions());
+    } else {
+      return StdTypeList.EMPTY;
+    }
+  }
+
+  @Nonnull
+  private List<SsaInsn> createInsnList(@Nonnull List<SsaInsn> instructions,
+      @Nonnegative int extraSize) {
+    List<SsaInsn> il = Lists.newArrayListWithCapacity(instructions.size() + extraSize);
+    for (SsaInsn instruction : instructions) {
+      il.add(instruction);
+    }
+    return il;
+  }
+
+  /**
+   * Constructs and adds the blocks that perform setup for the rest of the method. This includes a
+   * first block which merely contains assignments from parameters to the same-numbered registers
+   * and a possible second block which deals with synchronization.
+   */
+  // TODO(mikaelpeltier) keep local variable information if required
+  private void addSetupBlocks(@Nonnull JMethod method, @Nonnull SsaRopRegisterManager ropReg,
+      @Nonnull SsaRopBasicBlockManager ropBb, @Nonnegative int entryNodeId) {
+    SsaBasicBlock ssaBb = ropBb.createBasicBlock();
+    SourcePosition pos = SourcePosition.NO_INFO;
+
+    List<JParameter> parameters = method.getParams();
+    int sz = parameters.size();
+    List<SsaInsn> insns;
+
+    if (method.isStatic()) {
+      // +1 is to reserve space for Goto instruction
+      insns = new ArrayList<SsaInsn>(sz + 1);
+    } else {
+      // +2 is to reserve space for Goto instruction, and parameter 'this'
+      insns = new ArrayList<SsaInsn>(sz + 2);
+      JThis jThis = method.getThis();
+      assert jThis != null;
+      RegisterSpec thisReg = ropReg.createThisReg(jThis);
+      Insn insn = new PlainCstInsn(Rops.opMoveParam(thisReg.getType()), pos, thisReg,
+          RegisterSpecList.EMPTY, CstInteger.make(thisReg.getReg()));
+      insns.add(new NormalSsaInsn(insn, ssaBb));
+    }
+
+    JMethodBodyCfg body = (JMethodBodyCfg) method.getBody();
+    assert body != null;
+    for (JSsaVariableDefRef def : body.getSsaParamsDefs()) {
+      RegisterSpec paramReg = ropReg.getOrCreateRegisterSpec(def);
+      Insn insn = new PlainCstInsn(Rops.opMoveParam(paramReg.getType()), pos, paramReg,
+          RegisterSpecList.EMPTY, CstInteger.make(paramReg.getReg()));
+      insns.add(new NormalSsaInsn(insn, ssaBb));
+    }
+
+    insns
+        .add(new NormalSsaInsn(new PlainInsn(Rops.GOTO, pos, null, RegisterSpecList.EMPTY), ssaBb));
+
+    ssaBb.setRopLabel(ropBb.getSpecialLabel(SsaRopBasicBlockManager.PARAM_ASSIGNMENT));
+    ssaBb.setInsns(insns);
+    ssaBb.setSuccessors(IntList.makeImmutable(entryNodeId), entryNodeId);
+  }
+
+  @Nonnull
+  private DalvCode createCode(@Nonnull JMethod method, @Nonnull RopMethod ropMethod) {
+    DexOptions options = new DexOptions(apiLevel, forceJumbo);
+    int paramSize = getParameterWordCount(method);
+    int positionListKind;
+    LocalVariableInfo lvInfo;
+    if (emitLocalDebugInfo) {
+      lvInfo = LocalVariableExtractor.extract(ropMethod);
+    } else {
+      lvInfo = null;
+    }
+    if (emitLineNumberTable) {
+      positionListKind = PositionList.LINES;
+    } else {
+      positionListKind = PositionList.NONE;
+    }
+
+    return RopTranslator.translate(ropMethod, positionListKind, lvInfo, paramSize, options);
+  }
+
+  @Nonnegative
+  private int getParameterWordCount(@Nonnull JMethod method) {
+    // Add size in word (1) to represent 'this' parameter if method is not static.
+    int wordCount = method.isStatic() ? 0 : Type.OBJECT.getWordCount();
+
+    for (JParameter param : method.getParams()) {
+      wordCount += RopHelper.convertTypeToDx(param.getType()).getWordCount();
+    }
+
+    return wordCount;
+  }
+
+  private static void edgeSplitMoveExceptionsAndResults(@Nonnull SsaMethod ssaMeth) {
+    ArrayList<SsaBasicBlock> blocks = ssaMeth.getBlocks();
+    /*
+     * New blocks are added to the end of the block list during this iteration.
+     */
+    for (int i = blocks.size() - 1; i >= 0; i--) {
+      SsaBasicBlock block = blocks.get(i);
+
+      /*
+       * Any block that starts with a move-exception and has more than one predecessor...
+       */
+      if (!block.isExitBlock() && block.getPredecessors().cardinality() > 1) {
+        int moveExceptionIndex = 0;
+        while (moveExceptionIndex < block.getInsns().size()
+            && block.getInsns().get(moveExceptionIndex) instanceof PhiInsn) {
+          moveExceptionIndex++;
+        }
+
+        if (moveExceptionIndex == block.getInsns().size()
+            || !block.getInsns().get(moveExceptionIndex).isMoveException()) {
+          continue;
+        }
+
+        // block.getPredecessors() is changed in the loop below.
+        BitSet preds = (BitSet) block.getPredecessors().clone();
+        for (int j = preds.nextSetBit(0); j >= 0; j = preds.nextSetBit(j + 1)) {
+          SsaBasicBlock predecessor = blocks.get(j);
+          SsaBasicBlock zNode = predecessor.insertNewSuccessor(block);
+
+          /*
+           * Make sure to place the move-exception as the first insn.
+           */
+          zNode.getInsns().add(0, block.getInsns().get(moveExceptionIndex).clone());
+
+          for (int p = 0; p < moveExceptionIndex; p++) {
+            // It must be a phi given how moveExceptionIndex is computed.
+            PhiInsn phi = (PhiInsn) block.getInsns().get(p);
+            phi.changeOperandPred(predecessor, zNode);
+          }
+        }
+
+        // Remove the move-exception from the original block.
+        block.getInsns().remove(moveExceptionIndex);
+
+      }
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/backend/dex/rop/SsaRopBasicBlockManager.java b/jack/src/com/android/jack/backend/dex/rop/SsaRopBasicBlockManager.java
new file mode 100644
index 0000000..f2b9064
--- /dev/null
+++ b/jack/src/com/android/jack/backend/dex/rop/SsaRopBasicBlockManager.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * 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.android.jack.backend.dex.rop;
+
+import com.android.jack.dx.ssa.PhiInsn;
+import com.android.jack.dx.ssa.PhiInsn.Visitor;
+import com.android.jack.dx.ssa.SsaBasicBlock;
+import com.android.jack.dx.ssa.SsaMethod;
+import com.android.jack.dx.util.IntList;
+
+import java.util.ArrayList;
+
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+
+class SsaRopBasicBlockManager {
+
+  /** label offset for the parameter assignment block */
+  static final int PARAM_ASSIGNMENT = -1;
+  /** label offset for the return block */
+  static final int RETURN = -2;
+  /** Reserved for SSA init */
+  static final int SSA_INIT = -3;
+  /** number of special label offsets */
+  static final int SPECIAL_LABEL_COUNT = 7;
+  /** max label (exclusive) of any original code block */
+  @Nonnegative
+  private final int maxLabel;
+  /** output block list in-progress */
+  @Nonnull
+  private final ArrayList<SsaBasicBlock> basicBlocks;
+  /** Result rop method */
+  @Nonnull
+  private final SsaMethod ssaMethod;
+
+  private boolean resolved = false;
+
+  SsaRopBasicBlockManager(SsaMethod ssaMethod, @Nonnegative int maxLabel) {
+    this.maxLabel = maxLabel;
+    this.ssaMethod = ssaMethod;
+
+    /*
+     * The "* 2 + 10" below is to conservatively believe that every block is an exception handler
+     * target and should also take care of enough other possible extra overhead such that the
+     * underlying array is unlikely to need resizing.
+     */
+    basicBlocks = new ArrayList<SsaBasicBlock>(maxLabel * 2 + 10);
+  }
+
+  @Nonnull
+  SsaBasicBlock createBasicBlock() {
+    assert !resolved;
+    SsaBasicBlock bb = new SsaBasicBlock(basicBlocks.size(), ssaMethod);
+    basicBlocks.add(bb);
+    return bb;
+  }
+
+  @Nonnull
+  ArrayList<SsaBasicBlock> computeSsaBasicBlockList() {
+    /*
+     * We start allocating index at maxlabel * 2 so, again, we can be believe that we only need
+     * maxLabel number of extra index.
+     */
+    int[] labelToIndex = new int[maxLabel * 3 + 10];
+    resolveBlockIndex(labelToIndex);
+    resolvePhiIndex(labelToIndex);
+    resolved = true;
+    return basicBlocks;
+  }
+
+  private void resolveBlockIndex(int[] labelToIndex) {
+    assert !resolved;
+
+    for (SsaBasicBlock bb : basicBlocks) {
+      labelToIndex[bb.getRopLabel()] = bb.getIndex();
+    }
+
+    for (SsaBasicBlock bb : basicBlocks) {
+      IntList oldSuccessors = bb.getSuccessorList();
+      IntList newSuccessorsList = new IntList(oldSuccessors.size());
+
+      for (int i = 0, size = oldSuccessors.size(); i < size; i++) {
+        int oldSuccessor = oldSuccessors.get(i);
+        int successorIdx = labelToIndex[oldSuccessor];
+        SsaBasicBlock successor = basicBlocks.get(successorIdx);
+        successor.addPredeccessors(bb.getIndex());
+        newSuccessorsList.add(successorIdx);
+      }
+      if (bb.getPrimarySuccessorIndex() == -1) {
+        bb.setSuccessors(newSuccessorsList, -1);
+      } else {
+        bb.setSuccessors(newSuccessorsList, labelToIndex[bb.getPrimarySuccessorIndex()]);
+      }
+    }
+  }
+
+  private void resolvePhiIndex(final int[] labelToIndex) {
+    assert !resolved;
+    for (final SsaBasicBlock bb : basicBlocks) {
+      bb.forEachPhiInsn(new Visitor() {
+        @Override
+        public void visitPhiInsn(PhiInsn phi) {
+          phi.resolveOperandBlockIndex(labelToIndex);
+        }
+      });
+    }
+  }
+
+  /**
+   * Gets the minimum label for unreserved use.
+   *
+   * @return the minimum label
+   */
+  @Nonnegative
+  private int getMinimumUnreservedLabel() {
+    /*
+     * The labels below ((maxLabel * 2) + SPECIAL_LABEL_COUNT) are reserved for particular uses.
+     */
+    return (maxLabel * 2) + SsaRopBasicBlockManager.SPECIAL_LABEL_COUNT;
+  }
+
+  /**
+   * Gets an arbitrary unreserved and available label.
+   *
+   * @return the label
+   */
+  @Nonnegative
+  int getAvailableLabel() {
+    int candidate = getMinimumUnreservedLabel();
+
+    for (SsaBasicBlock bb : basicBlocks) {
+      int label = bb.getRopLabel();
+      if (label >= candidate) {
+        candidate = label + 1;
+      }
+    }
+
+    return candidate;
+  }
+
+  /**
+   * Gets the label for the given special-purpose block. The given label should be one of the static
+   * constants defined by this class.
+   *
+   * @param label {@code < 0;} the special label constant
+   * @return the actual label value to use
+   */
+  @Nonnegative
+  int getSpecialLabel(int label) {
+    assert label < 0 : "label is supposed to be negative";
+    /*
+     * The label is bitwise-complemented so that mistakes where LABEL is used instead of
+     * getSpecialLabel(LABEL) cause a failure at block construction time, since negative labels are
+     * illegal. We multiply maxLabel by 2 since 0..maxLabel (exclusive) are the original blocks and
+     * maxLabel..(maxLabel*2) are reserved for exception handler setup blocks (see
+     * getExceptionSetupLabel(), above).
+     */
+    return (maxLabel * 2) + ~label;
+  }
+}
diff --git a/jack/src/com/android/jack/backend/dex/rop/SsaRopBuilderVisitor.java b/jack/src/com/android/jack/backend/dex/rop/SsaRopBuilderVisitor.java
new file mode 100644
index 0000000..1a99287
--- /dev/null
+++ b/jack/src/com/android/jack/backend/dex/rop/SsaRopBuilderVisitor.java
@@ -0,0 +1,1431 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * 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.android.jack.backend.dex.rop;
+
+import com.google.common.collect.Lists;
+
+import com.android.jack.backend.dex.invokecustom.InvokeCustomHelper;
+import com.android.jack.dx.rop.code.FillArrayDataInsn;
+import com.android.jack.dx.rop.code.Insn;
+import com.android.jack.dx.rop.code.PlainCstInsn;
+import com.android.jack.dx.rop.code.PlainInsn;
+import com.android.jack.dx.rop.code.RegisterSpec;
+import com.android.jack.dx.rop.code.RegisterSpecList;
+import com.android.jack.dx.rop.code.Rop;
+import com.android.jack.dx.rop.code.Rops;
+import com.android.jack.dx.rop.code.SourcePosition;
+import com.android.jack.dx.rop.code.SwitchInsn;
+import com.android.jack.dx.rop.code.ThrowingCstInsn;
+import com.android.jack.dx.rop.code.ThrowingDualCstInsn;
+import com.android.jack.dx.rop.code.ThrowingInsn;
+import com.android.jack.dx.rop.cst.Constant;
+import com.android.jack.dx.rop.cst.CstBoolean;
+import com.android.jack.dx.rop.cst.CstCallSiteRef;
+import com.android.jack.dx.rop.cst.CstDouble;
+import com.android.jack.dx.rop.cst.CstFieldRef;
+import com.android.jack.dx.rop.cst.CstFloat;
+import com.android.jack.dx.rop.cst.CstInteger;
+import com.android.jack.dx.rop.cst.CstKnownNull;
+import com.android.jack.dx.rop.cst.CstLiteral32;
+import com.android.jack.dx.rop.cst.CstLiteral64;
+import com.android.jack.dx.rop.cst.CstLong;
+import com.android.jack.dx.rop.cst.CstMethodRef;
+import com.android.jack.dx.rop.cst.CstPrototypeRef;
+import com.android.jack.dx.rop.cst.CstString;
+import com.android.jack.dx.rop.type.Prototype;
+import com.android.jack.dx.rop.type.StdTypeList;
+import com.android.jack.dx.rop.type.Type;
+import com.android.jack.dx.rop.type.TypeBearer;
+import com.android.jack.dx.rop.type.TypeList;
+import com.android.jack.dx.ssa.NormalSsaInsn;
+import com.android.jack.dx.ssa.PhiInsn;
+import com.android.jack.dx.ssa.SsaBasicBlock;
+import com.android.jack.dx.ssa.SsaInsn;
+import com.android.jack.dx.util.IntList;
+import com.android.jack.ir.ast.FieldKind;
+import com.android.jack.ir.ast.JAbsentArrayDimension;
+import com.android.jack.ir.ast.JAbstractStringLiteral;
+import com.android.jack.ir.ast.JAlloc;
+import com.android.jack.ir.ast.JAnnotation;
+import com.android.jack.ir.ast.JArrayLength;
+import com.android.jack.ir.ast.JArrayRef;
+import com.android.jack.ir.ast.JAsgOperation;
+import com.android.jack.ir.ast.JBinaryOperation;
+import com.android.jack.ir.ast.JBinaryOperator;
+import com.android.jack.ir.ast.JBooleanLiteral;
+import com.android.jack.ir.ast.JByteLiteral;
+import com.android.jack.ir.ast.JCharLiteral;
+import com.android.jack.ir.ast.JClass;
+import com.android.jack.ir.ast.JClassLiteral;
+import com.android.jack.ir.ast.JDoubleLiteral;
+import com.android.jack.ir.ast.JDynamicCastOperation;
+import com.android.jack.ir.ast.JExceptionRuntimeValue;
+import com.android.jack.ir.ast.JExpression;
+import com.android.jack.ir.ast.JFieldRef;
+import com.android.jack.ir.ast.JFloatLiteral;
+import com.android.jack.ir.ast.JInstanceOf;
+import com.android.jack.ir.ast.JIntLiteral;
+import com.android.jack.ir.ast.JIntegralConstant32;
+import com.android.jack.ir.ast.JInterface;
+import com.android.jack.ir.ast.JLambda;
+import com.android.jack.ir.ast.JLiteral;
+import com.android.jack.ir.ast.JLongLiteral;
+import com.android.jack.ir.ast.JMethodCall;
+import com.android.jack.ir.ast.JMethodCall.DispatchKind;
+import com.android.jack.ir.ast.JNewArray;
+import com.android.jack.ir.ast.JNode;
+import com.android.jack.ir.ast.JNullLiteral;
+import com.android.jack.ir.ast.JPolymorphicMethodCall;
+import com.android.jack.ir.ast.JPrefixNotOperation;
+import com.android.jack.ir.ast.JPrimitiveType;
+import com.android.jack.ir.ast.JPrimitiveType.JPrimitiveTypeEnum;
+import com.android.jack.ir.ast.JReferenceType;
+import com.android.jack.ir.ast.JReinterpretCastOperation;
+import com.android.jack.ir.ast.JShortLiteral;
+import com.android.jack.ir.ast.JSsaVariableRef;
+import com.android.jack.ir.ast.JThisRef;
+import com.android.jack.ir.ast.JType;
+import com.android.jack.ir.ast.JUnaryOperation;
+import com.android.jack.ir.ast.JValueLiteral;
+import com.android.jack.ir.ast.JVariableRef;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.ast.MethodKind;
+import com.android.jack.ir.ast.cfg.JBasicBlock;
+import com.android.jack.ir.ast.cfg.JBasicBlockElement;
+import com.android.jack.ir.ast.cfg.JCaseBlockElement;
+import com.android.jack.ir.ast.cfg.JCatchBasicBlock;
+import com.android.jack.ir.ast.cfg.JConditionalBasicBlock;
+import com.android.jack.ir.ast.cfg.JConditionalBlockElement;
+import com.android.jack.ir.ast.cfg.JGotoBlockElement;
+import com.android.jack.ir.ast.cfg.JLockBlockElement;
+import com.android.jack.ir.ast.cfg.JMethodCallBlockElement;
+import com.android.jack.ir.ast.cfg.JPhiBlockElement;
+import com.android.jack.ir.ast.cfg.JPolymorphicMethodCallBlockElement;
+import com.android.jack.ir.ast.cfg.JReturnBlockElement;
+import com.android.jack.ir.ast.cfg.JStoreBlockElement;
+import com.android.jack.ir.ast.cfg.JSwitchBasicBlock;
+import com.android.jack.ir.ast.cfg.JSwitchBlockElement;
+import com.android.jack.ir.ast.cfg.JThrowBlockElement;
+import com.android.jack.ir.ast.cfg.JThrowingBasicBlock;
+import com.android.jack.ir.ast.cfg.JUnlockBlockElement;
+import com.android.jack.ir.ast.cfg.JVariableAsgBlockElement;
+import com.android.jack.ir.types.JIntegralType32;
+import com.android.jack.util.AndroidApiLevel;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+class SsaRopBuilderVisitor extends JVisitor {
+
+  @Nonnull
+  private final AndroidApiLevel apiLevel;
+
+  @Nonnull
+  private final SsaRopRegisterManager ropReg;
+
+  @CheckForNull
+  private List<SsaInsn> instructions;
+
+  @CheckForNull
+  private List<Insn> extraInstructions;
+
+  @Nonnull
+  private final JBasicBlock currentBasicBlock;
+
+  @Nonnull
+  private final SsaBasicBlock ssaBb;
+
+  @Nonnull
+  private final Map<JBasicBlock, Integer> labelMap;
+
+  /**
+   * A guard for {@code instructions}. Does not protect {@code extraInstructions}.
+   */
+  private boolean noMoreInstruction = true;
+
+  private class AssignBuilderVisitor extends JVisitor {
+    @Nonnull
+    private final JSsaVariableRef destRef;
+    @Nonnull
+    private final RegisterSpec destReg;
+    @Nonnull
+    private final SourcePosition sourcePosition;
+
+    public AssignBuilderVisitor(
+        @Nonnull SourcePosition sourcePosition,
+        @Nonnull JSsaVariableRef destRef) {
+      this.destRef = destRef;
+      this.destReg = ropReg.getOrCreateRegisterSpec(destRef);
+      this.sourcePosition = sourcePosition;
+    }
+
+    @Override
+    public boolean visit(@Nonnull JNode node) {
+      throw new AssertionError(node.toSource() + " not yet supported.");
+    }
+
+    @Override
+    public boolean visit(@Nonnull JAlloc alloc) {
+      buildAlloc(destReg, alloc, sourcePosition);
+      return false;
+    }
+
+    @Override
+    public boolean visit(@Nonnull JArrayLength arrayLength) {
+      buildArrayLength(destReg, arrayLength);
+      return false;
+    }
+
+    @Override
+    public boolean visit(@Nonnull JArrayRef arrayRef) {
+      buildArrayRead(destReg, arrayRef, sourcePosition);
+      return false;
+    }
+
+    @Override
+    public boolean visit(@Nonnull JBinaryOperation binOp) {
+      buildBinaryOperation(destReg, binOp);
+      return false;
+    }
+
+    @Override
+    public boolean visit(@Nonnull JReinterpretCastOperation cast) {
+      // Nothing to do it is a nop operation, generate it by a mov instruction.
+      SourcePosition sourcePosition = RopHelper.getSourcePosition(cast);
+      RegisterSpec fromReg = getRegisterSpec(cast.getExpr());
+      RegisterSpecList sources = RegisterSpecList.make(fromReg);
+      addInstruction(new PlainInsn(
+          Rops.opMove(fromReg.getTypeBearer()), sourcePosition,
+          destReg, sources));
+      return false;
+    }
+
+    @Override
+    public boolean visit(@Nonnull JDynamicCastOperation cast) {
+      buildCast(destReg, cast);
+      return false;
+    }
+
+    @Override
+    public boolean visit(@Nonnull JFieldRef fieldRef) {
+      buildReadField(destReg, fieldRef, sourcePosition);
+      return false;
+    }
+
+    @Override
+    public boolean visit(@Nonnull JInstanceOf instanceOf) {
+      buildInstanceOf(destReg, instanceOf);
+      return false;
+    }
+
+    @Override
+    public boolean visit(@Nonnull JLambda lambda) {
+      throw new AssertionError();
+    }
+
+    @Override
+    public boolean visit(@Nonnull JPolymorphicMethodCall methodCall) {
+      buildInvokePolymorphic(destReg, methodCall);
+      return false;
+    }
+
+    @Override
+    public boolean visit(@Nonnull JMethodCall call) {
+      buildCall(destReg, call);
+      return false;
+    }
+
+    @Override
+    public boolean visit(@Nonnull JVariableRef varRef) {
+      throw new RuntimeException("Code is not in SSA form.");
+    }
+
+    @Override
+    public boolean visit(@Nonnull JThisRef thisRef) {
+      RegisterSpec valueReg = ropReg.getThisReg();
+      RegisterSpecList sources = RegisterSpecList.make(valueReg);
+      addInstruction(
+          new PlainInsn(Rops.opMove(valueReg.getTypeBearer()), sourcePosition, destReg, sources));
+      return false;
+    }
+
+    @Override
+    public boolean visit(@Nonnull JSsaVariableRef varRef) {
+      RegisterSpec valueReg = ropReg.getOrCreateRegisterSpec(varRef);
+      RegisterSpecList sources = RegisterSpecList.make(valueReg);
+      RegisterSpec copyDestReg;
+      if (destReg.getLocalItem() == null && valueReg.getLocalItem() != null) {
+        copyDestReg = ropReg.getOrCreateRegisterSpec(destRef, valueReg.getLocalItem());
+      } else {
+        copyDestReg = destReg;
+      }
+      addInstruction(new PlainInsn(Rops.opMove(valueReg.getTypeBearer()), sourcePosition,
+          copyDestReg, sources));
+      return false;
+    }
+
+    @Override
+    public boolean visit(@Nonnull JUnaryOperation unaryOp) {
+      buildUnaryOperation(destReg, unaryOp);
+      return false;
+    }
+
+    @Override
+    public boolean visit(@Nonnull JValueLiteral valueLit) {
+      buildConstant(destReg, valueLit);
+      return false;
+    }
+
+    @Override
+    public boolean visit(@Nonnull JClassLiteral literal) {
+      Rop constOp = Rops.opConst(RopHelper.convertTypeToDx(literal.getType()));
+      SourcePosition literalSrcPos = RopHelper.getSourcePosition(literal);
+      Insn constInst = new ThrowingCstInsn(constOp, literalSrcPos,
+          RegisterSpecList.EMPTY, getCatchTypes(), RopHelper.convertTypeToDx(literal.getRefType()));
+      addInstruction(constInst);
+      addMoveResultPseudoAsExtraInstruction(destReg, literalSrcPos);
+      return false;
+    }
+
+    private boolean isDexFilledNewArrayCompatible(@Nonnull JNewArray newArray) {
+      JType elementType = newArray.getArrayType().getElementType();
+      List<JExpression> initializers = newArray.getInitializers();
+      if (!initializers.isEmpty() && initializers.size() <= 5 && newArray.getDims().size() == 1
+          && elementType == JPrimitiveTypeEnum.INT.getType()) {
+        return true;
+      }
+      return false;
+    }
+
+    @Override
+    public boolean visit(@Nonnull JNewArray newArray) {
+      Type dxType = RopHelper.convertTypeToDx(newArray.getType());
+      SourcePosition newArraySourcePosition = RopHelper.getSourcePosition(newArray);
+      List<JExpression> valuesSize = newArray.getInitializers();
+
+      if (isDexFilledNewArrayCompatible(newArray)) {
+        // Array with few initializer uses filled-new-array instructions
+        int i = 0;
+        RegisterSpecList sources = new RegisterSpecList(valuesSize.size());
+        for (JExpression expr : valuesSize) {
+          sources.set(i++, getRegisterSpec(expr));
+        }
+
+        Type arrayType = RopHelper.convertTypeToDx(newArray.getType());
+        Rop op = Rops.opFilledNewArray(arrayType, valuesSize.size());
+
+        Insn insn =
+            new ThrowingCstInsn(op, newArraySourcePosition, sources, getCatchTypes(), dxType);
+        addInstruction(insn);
+        addMoveResultAsExtraInstruction(arrayType, destReg, newArraySourcePosition);
+      } else {
+        // Uses instructions new-array and fill-array-data
+
+        List<JExpression> dims = newArray.getDims();
+        assert dims.size() >= 1;
+        assert isDexNewArrayCompatible(newArray);
+        RegisterSpecList sources =
+            RegisterSpecList.make(getRegisterSpec(dims.get(0)));
+
+        Rop op = Rops.opNewArray(dxType);
+
+        Insn insn =
+            new ThrowingCstInsn(op, newArraySourcePosition, sources, getCatchTypes(), dxType);
+        addInstruction(insn);
+        addMoveResultPseudoAsExtraInstruction(destReg, newArraySourcePosition);
+
+        if (!newArray.getInitializers().isEmpty()) {
+          assert newArray.hasConstantInitializer();
+          ArrayList<Constant> initValues = new ArrayList<Constant>();
+          for (JExpression initializer : newArray.getInitializers()) {
+            initValues.add(buildPrimitiveConstant((JValueLiteral) initializer));
+          }
+          insn = new FillArrayDataInsn(
+              Rops.FILL_ARRAY_DATA, newArraySourcePosition, RegisterSpecList.make(destReg),
+              initValues, dxType);
+          addExtraInstruction(insn);
+        }
+      }
+
+      return false;
+    }
+
+    /**
+     * Return true if the given JNewArray is compatible with a translation to dex opcode new-array.
+     */
+    private boolean isDexNewArrayCompatible(JNewArray newArray) {
+      List<JExpression> dims = newArray.getDims();
+      if (dims.size() < 1) {
+        return false;
+      }
+      Iterator<JExpression> iter = dims.iterator();
+      if (iter.next() instanceof JAbsentArrayDimension) {
+        return false;
+      }
+
+      while (iter.hasNext()) {
+        if (!(iter.next() instanceof JAbsentArrayDimension)) {
+          return false;
+        }
+      }
+
+      return true;
+    }
+
+    private void buildArrayRead(@Nonnull RegisterSpec destReg, @Nonnull JArrayRef arrayRef,
+        @Nonnull SourcePosition sourcePosition) {
+      assert arrayRef.getInstance() instanceof JVariableRef
+          || arrayRef.getInstance() instanceof JNullLiteral;
+      RegisterSpec instanceReg = getRegisterSpec(arrayRef.getInstance());
+      RegisterSpec indexReg = getRegisterSpec(arrayRef.getIndexExpr());
+      RegisterSpecList sources = RegisterSpecList.make(instanceReg, indexReg);
+
+      Rop rop = Rops.opAget(getComponentType(instanceReg));
+      addInstruction(new ThrowingInsn(rop, sourcePosition, sources, getCatchTypes()));
+      addMoveResultPseudoAsExtraInstruction(destReg, sourcePosition);
+    }
+
+    private void buildReadField(@Nonnull RegisterSpec destReg, @Nonnull JFieldRef fieldRef,
+        @Nonnull SourcePosition sourcePosition) {
+      CstFieldRef cstField =
+          RopHelper.createFieldRef(fieldRef.getFieldId(), fieldRef.getReceiverType());
+      Type ropFieldType = RopHelper.convertTypeToDx(fieldRef.getType());
+      if (fieldRef.getFieldId().getKind() == FieldKind.STATIC) {
+        Rop rop = Rops.opGetStatic(ropFieldType);
+        addInstruction(new ThrowingCstInsn(rop, sourcePosition, RegisterSpecList.EMPTY,
+            getCatchTypes(), cstField));
+      } else {
+        JExpression instance = fieldRef.getInstance();
+        assert instance != null;
+        assert instance instanceof JVariableRef || instance instanceof JNullLiteral;
+        RegisterSpec instanceReg = getRegisterSpec(instance);
+        RegisterSpecList sources = RegisterSpecList.make(instanceReg);
+
+        Rop rop = Rops.opGetField(ropFieldType);
+        addInstruction(
+            new ThrowingCstInsn(rop, sourcePosition, sources, getCatchTypes(), cstField));
+      }
+      addMoveResultPseudoAsExtraInstruction(destReg, sourcePosition);
+    }
+  }
+
+  SsaRopBuilderVisitor(@Nonnull SsaRopRegisterManager ropReg,
+      @Nonnull JBasicBlock currentBasicBlock, @Nonnull SsaBasicBlock ssaBb,
+      @Nonnull Map<JBasicBlock, Integer> labelMap,
+      @Nonnull AndroidApiLevel apiLevel) {
+    this.ropReg = ropReg;
+    this.currentBasicBlock = currentBasicBlock;
+    this.ssaBb = ssaBb;
+    this.labelMap = labelMap;
+    this.apiLevel = apiLevel;
+  }
+
+  @CheckForNull
+  List<SsaInsn> getInstructions() {
+    return instructions;
+  }
+
+  @CheckForNull
+  List<Insn> getExtraInstructions() {
+    return extraInstructions;
+  }
+
+  public void processBasicBlockElements() {
+    instructions = new LinkedList<>();
+    extraInstructions = new LinkedList<>();
+    noMoreInstruction = false;
+
+    ArrayList<JBasicBlockElement> elements =
+        Lists.newArrayList(this.currentBasicBlock.getElements(true));
+    super.accept(elements);
+  }
+
+  @Override
+  public boolean visit(@Nonnull JPhiBlockElement phi) {
+    RegisterSpec result = ropReg.getOrCreateRegisterSpec(phi.getLhs());
+
+    PhiInsn phiInsn = new PhiInsn(result, ssaBb);
+    for (JBasicBlock pred : currentBasicBlock.getPredecessors()) {
+      Integer predLabel = labelMap.get(pred);
+      assert predLabel != null;
+
+      // Because we don't know the predIndex until the whole CFG is traversed, we are going to
+      // set the predIndex at the very end instead.
+      phiInsn.addPhiOperand(ropReg.getOrCreateRegisterSpec(phi.getRhs(pred)),
+          predLabel.intValue(), predLabel.intValue());
+    }
+    addInstruction(phiInsn);
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JGotoBlockElement element) {
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JCaseBlockElement element) {
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JThrowBlockElement element) {
+    addInstruction(new ThrowingInsn(
+        Rops.THROW, RopHelper.getSourcePosition(element.getSourceInfo()),
+        RegisterSpecList.make(getRegisterSpec(element.getExpression())), getCatchTypes()));
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JStoreBlockElement element) {
+    JAsgOperation expression = element.getAssignment();
+    JExpression lhs = expression.getLhs();
+    JExpression rhs = expression.getRhs();
+
+    assert lhs instanceof JFieldRef || lhs instanceof JArrayRef;
+    assert !(rhs instanceof JExceptionRuntimeValue);
+
+    if (lhs instanceof JFieldRef) {
+      buildWriteField((JFieldRef) lhs, rhs, RopHelper.getSourcePosition(element.getSourceInfo()));
+    } else {
+      buildArrayWrite((JArrayRef) lhs, rhs, RopHelper.getSourcePosition(element.getSourceInfo()));
+    }
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JReturnBlockElement element) {
+    JExpression expression = element.getExpression();
+    RegisterSpecList sources = expression != null ?
+        RegisterSpecList.make(getRegisterSpec(expression)) :
+        RegisterSpecList.EMPTY;
+
+    JType type = expression != null ?
+        expression.getType() : JPrimitiveTypeEnum.VOID.getType();
+
+    addInstruction(new PlainInsn(
+        Rops.opReturn(RopHelper.convertTypeToDx(type)),
+        RopHelper.getSourcePosition(element.getSourceInfo()), null, sources));
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JVariableAsgBlockElement element) {
+    JAsgOperation asg = element.getAssignment();
+    JSsaVariableRef local = (JSsaVariableRef) asg.getLhs();
+    JExpression value = asg.getRhs();
+
+    if (value instanceof JExceptionRuntimeValue) {
+      RegisterSpec exceptionReg =
+          ropReg.getOrCreateRegisterSpec(local);
+      addInstruction(new PlainInsn(
+          Rops.opMoveException(exceptionReg.getTypeBearer()),
+          RopHelper.getSourcePosition(local),
+          exceptionReg,
+          RegisterSpecList.EMPTY));
+
+    } else {
+      new AssignBuilderVisitor(
+          RopHelper.getSourcePosition(element.getSourceInfo()),
+          local).accept(value);
+    }
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JMethodCallBlockElement element) {
+    buildCall(null, element.getCall());
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JPolymorphicMethodCallBlockElement element) {
+    buildInvokePolymorphic(null, element.getCall());
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JLockBlockElement element) {
+    addInstruction(new ThrowingInsn(
+        Rops.MONITOR_ENTER, RopHelper.getSourcePosition(element.getSourceInfo()),
+        RegisterSpecList.make(getRegisterSpec(element.getExpression())), getCatchTypes()));
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JUnlockBlockElement element) {
+    addInstruction(new ThrowingInsn(
+        Rops.MONITOR_EXIT, RopHelper.getSourcePosition(element.getSourceInfo()),
+        RegisterSpecList.make(getRegisterSpec(element.getExpression())), getCatchTypes()));
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JConditionalBlockElement element) {
+    SourcePosition ifStmtSrcPos = RopHelper.getSourcePosition(element.getSourceInfo());
+    RegisterSpecList sources;
+    JBinaryOperator op;
+
+    JExpression expr = element.getCondition();
+
+    if (expr instanceof JBinaryOperation) {
+      JBinaryOperation binCondExpr = (JBinaryOperation) expr;
+      JExpression right = binCondExpr.getRhs();
+      RegisterSpec rightReg = getRegisterSpec(right);
+
+      JExpression left = binCondExpr.getLhs();
+      JType type = right.getType();
+      JType leftType = left.getType();
+      assert leftType.isSameType(type)
+          || (leftType instanceof JIntegralType32 && type instanceof JIntegralType32)
+          || (leftType instanceof JReferenceType && type instanceof JReferenceType);
+
+      op = binCondExpr.getOp();
+      RegisterSpec leftReg = getRegisterSpec(left);
+      sources = RegisterSpecList.make(leftReg, rightReg);
+      if (type instanceof JPrimitiveType) {
+        switch (((JPrimitiveType) type).getPrimitiveTypeEnum()) {
+          case LONG:
+          case FLOAT:
+          case DOUBLE: {
+            RegisterSpec dest = ropReg.createRegisterSpec(JPrimitiveTypeEnum.BOOLEAN.getType());
+            Type dxType = RopHelper.convertTypeToDx(type);
+
+            Rop cmpOp = (type == JPrimitiveTypeEnum.LONG.getType())
+                ? Rops.opCmpl(dxType) : getCmpOperatorForFloatDouble(op, dxType);
+
+            Insn ifInst = new PlainInsn(cmpOp, ifStmtSrcPos, dest, sources);
+            addInstruction(ifInst);
+            sources = RegisterSpecList.make(dest);
+            break;
+          }
+          case BOOLEAN:
+          case BYTE:
+          case CHAR:
+          case SHORT:
+          case INT:
+            // Nothing to do.
+            break;
+          case VOID:
+            throw new AssertionError("Void type not supported.");
+        }
+      }
+    } else if (expr instanceof JPrefixNotOperation) {
+      RegisterSpec sourceReg = getRegisterSpec(((JPrefixNotOperation) expr).getArg());
+      sources = RegisterSpecList.make(sourceReg);
+      op = JBinaryOperator.EQ;
+    } else {
+      RegisterSpec sourceReg = getRegisterSpec(expr);
+      sources = RegisterSpecList.make(sourceReg);
+      op = JBinaryOperator.NEQ;
+    }
+
+    Rop ifOp = getReverseOperatorForIf(op, sources);
+    assert this.currentBasicBlock instanceof JConditionalBasicBlock;
+    if (((JConditionalBasicBlock) this.currentBasicBlock).isInverted()) {
+      ifOp = getOperatorForIf(op, sources);
+    }
+
+    Insn ifInst = new PlainInsn(ifOp, ifStmtSrcPos, null, sources);
+    addInstruction(ifInst);
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JSwitchBlockElement element) {
+    assert currentBasicBlock instanceof JSwitchBasicBlock;
+
+    SourcePosition switchStmtSrcPos = RopHelper.getSourcePosition(element.getSourceInfo());
+    IntList cases = new IntList();
+    for (JBasicBlock caseBb : ((JSwitchBasicBlock) currentBasicBlock).getCases()) {
+      assert caseBb.hasElements();
+      JBasicBlockElement caseElement = null;
+      for (JBasicBlockElement e : caseBb.getElements()) {
+        if (!(e instanceof JPhiBlockElement)) {
+          caseElement = e;
+          break;
+        }
+      }
+
+      assert caseElement instanceof JCaseBlockElement;
+      JLiteral caseValue = ((JCaseBlockElement) caseElement).getLiteral();
+      if (caseValue instanceof JIntLiteral) {
+        cases.add(((JIntLiteral) caseValue).getValue());
+      } else if (caseValue instanceof JCharLiteral) {
+        cases.add(((JCharLiteral) caseValue).getValue());
+      } else if (caseValue instanceof JShortLiteral) {
+        cases.add(((JShortLiteral) caseValue).getValue());
+      } else if (caseValue instanceof JByteLiteral) {
+        cases.add(((JByteLiteral) caseValue).getValue());
+      } else {
+        throw new AssertionError("Unsupported value");
+      }
+    }
+
+    RegisterSpecList sources = RegisterSpecList.make(getRegisterSpec(element.getExpression()));
+
+    Insn switchInst = new SwitchInsn(Rops.SWITCH, switchStmtSrcPos, null, sources, cases);
+    addInstruction(switchInst);
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JBasicBlockElement element) {
+    throw new AssertionError();
+  }
+
+  @Override
+  public boolean visit(@Nonnull JNode node) {
+    throw new AssertionError("Not supported: " + node.toSource());
+  }
+
+  /**
+   * Get the @link{Rop} corresponding to the inverse of the @link{JBinaryOperator} provided for
+   * float or double type.
+   * @param op the operator to convert
+   * @param type dx type of operator
+   * @return the reverse @code{Rop}
+   */
+  @Nonnull
+  public Rop getCmpOperatorForFloatDouble(@Nonnull JBinaryOperator op,
+      @Nonnull Type type) {
+    assert type == Type.FLOAT || type == Type.DOUBLE;
+    switch (op) {
+      case LTE:
+      case LT:
+        return Rops.opCmpg(type);
+      case GT:
+      case GTE:
+      case EQ:
+      case NEQ:
+        return Rops.opCmpl(type);
+      default:
+        throw new AssertionError("Operator " + op.toString() + " not yet supported into IfStmt.");
+    }
+  }
+
+  /**
+   * Get the @link{Rop} corresponding of the @link{JBinaryOperator} provided.
+   * @param op the operator to convert
+   * @param sources the sources that will be used with the @code{Rop}
+   * @return the reverse @code{Rop}
+   */
+  @Nonnull
+  public Rop getOperatorForIf(@Nonnull JBinaryOperator op,
+      @Nonnull RegisterSpecList sources) {
+    switch (op) {
+      case LT:
+        return Rops.opIfLt(sources);
+      case GT:
+        return Rops.opIfGt(sources);
+      case LTE:
+        return Rops.opIfLe(sources);
+      case GTE:
+        return Rops.opIfGe(sources);
+      case EQ:
+        return Rops.opIfEq(sources);
+      case NEQ:
+        return Rops.opIfNe(sources);
+      default:
+        throw new AssertionError("Operator " + op.toString()
+            + " not yet supported into IfStmt.");
+    }
+  }
+
+  /**
+   * Get the @link{Rop} corresponding to the inverse of the @link{JBinaryOperator} provided.
+   * @param op the operator to convert
+   * @param sources the sources that will be used with the @code{Rop}
+   * @return the reverse @code{Rop}
+   */
+  @Nonnull
+  public Rop getReverseOperatorForIf(@Nonnull JBinaryOperator op,
+      @Nonnull RegisterSpecList sources) {
+    switch (op) {
+      case LT:
+        return Rops.opIfGe(sources);
+      case GT:
+        return Rops.opIfLe(sources);
+      case LTE:
+        return Rops.opIfGt(sources);
+      case GTE:
+        return Rops.opIfLt(sources);
+      case EQ:
+        return Rops.opIfNe(sources);
+      case NEQ:
+        return Rops.opIfEq(sources);
+      default:
+        throw new AssertionError("Operator " + op.toString()
+            + " not yet supported into IfStmt.");
+    }
+  }
+
+  private void buildAlloc(@Nonnull RegisterSpec destReg, @Nonnull JAlloc alloc,
+      @Nonnull SourcePosition sourcePosition) {
+    addInstruction(new ThrowingCstInsn(Rops.NEW_INSTANCE, sourcePosition, RegisterSpecList.EMPTY,
+        getCatchTypes(), RopHelper.convertTypeToDx(alloc.getInstanceType())));
+    addMoveResultPseudoAsExtraInstruction(destReg, sourcePosition);
+  }
+
+  private void buildArrayWrite(JArrayRef arrayRef, JExpression value,
+      SourcePosition sourcePosition) {
+    assert arrayRef.getInstance() instanceof JVariableRef
+        || arrayRef.getInstance() instanceof JNullLiteral;
+    RegisterSpec valueReg = getRegisterSpec(value);
+    RegisterSpec instanceReg = getRegisterSpec(arrayRef.getInstance());
+    RegisterSpec indexReg = getRegisterSpec(arrayRef.getIndexExpr());
+    RegisterSpecList sources = RegisterSpecList.make(valueReg, instanceReg, indexReg);
+
+    Rop rop = Rops.opAput(getComponentType(instanceReg));
+    addInstruction(new ThrowingInsn(rop, sourcePosition, sources, getCatchTypes()));
+  }
+
+  private void buildInstanceOf(RegisterSpec destReg, JInstanceOf instanceOf) {
+    SourcePosition srcPos = RopHelper.getSourcePosition(instanceOf);
+    addInstruction(new ThrowingCstInsn(Rops.INSTANCE_OF, srcPos,
+        RegisterSpecList.make(getRegisterSpec(instanceOf.getExpr())), getCatchTypes(),
+        RopHelper.convertTypeToDx(instanceOf.getTestType())));
+    addMoveResultPseudoAsExtraInstruction(destReg, srcPos);
+  }
+
+  @Nonnull
+  private static Type getComponentType(@Nonnull TypeBearer arrayTypeBearer) {
+    Type arrayType = arrayTypeBearer.getType();
+
+    if (arrayType.isArray()) {
+      return arrayType.getComponentType();
+    }
+
+    assert arrayType.equals(Type.KNOWN_NULL);
+    return arrayType.getType();
+  }
+
+  private void buildArrayLength(RegisterSpec destReg, JArrayLength value) {
+    RegisterSpec reg = getRegisterSpec(value.getInstance());
+    SourcePosition srcPos = RopHelper.getSourcePosition(value);
+    addInstruction(new ThrowingInsn(Rops.ARRAY_LENGTH, srcPos, RegisterSpecList.make(reg),
+        getCatchTypes()));
+    addMoveResultPseudoAsExtraInstruction(destReg, srcPos);
+  }
+
+  private void buildWriteField(@Nonnull JFieldRef fieldRef, @Nonnull JExpression value,
+      @Nonnull SourcePosition sourcePosition) {
+
+    RegisterSpec valueReg = getRegisterSpec(value);
+
+    CstFieldRef cstField = RopHelper.createFieldRef(fieldRef.getFieldId(),
+        fieldRef.getReceiverType());
+
+    if (fieldRef.getFieldId().getKind() == FieldKind.STATIC) {
+      Rop rop = Rops.opPutStatic(RopHelper.convertTypeToDx(fieldRef.getType()));
+      addInstruction(new ThrowingCstInsn(rop, sourcePosition, RegisterSpecList.make(valueReg),
+          getCatchTypes(), cstField));
+    } else {
+      JExpression instance = fieldRef.getInstance();
+      assert instance != null;
+      assert instance instanceof JVariableRef || instance instanceof JNullLiteral;
+      RegisterSpec instanceReg = getRegisterSpec(instance);
+      RegisterSpecList sources = RegisterSpecList.make(valueReg, instanceReg);
+
+      Rop rop = Rops.opPutField(RopHelper.convertTypeToDx(fieldRef.getType()));
+      addInstruction(new ThrowingCstInsn(rop, sourcePosition, sources, getCatchTypes(), cstField));
+    }
+  }
+
+
+  private void buildCast(@Nonnull RegisterSpec destReg, @Nonnull JDynamicCastOperation cast) {
+    JExpression from = cast.getExpr();
+    SourcePosition sourcePosition = RopHelper.getSourcePosition(cast);
+    RegisterSpec fromReg = getRegisterSpec(from);
+
+    JType castTo = cast.getType();
+    JType castedFrom = from.getType();
+
+    if (castTo instanceof JPrimitiveType) {
+
+      assert castedFrom instanceof JPrimitiveType;
+
+      if (castTo == castedFrom) {
+        RegisterSpecList sources = RegisterSpecList.make(fromReg);
+        addInstruction(new PlainInsn(
+            Rops.opMove(fromReg.getTypeBearer()), sourcePosition,
+            destReg, sources));
+        return;
+      }
+
+      /* Rop has 2 groups of cast instructions:
+       * - Casts form int to byte, char and short.
+       * - Casts between int, long, float and double.
+       * Casts from larger values than int to smaller values must be done with 2 instructions, one
+       * from each group. These two instructions are created by RopCastLegalier.
+       */
+
+      if (((castTo == JPrimitiveTypeEnum.BYTE.getType())
+            || (castTo == JPrimitiveTypeEnum.SHORT.getType())
+            || (castTo == JPrimitiveTypeEnum.CHAR.getType())
+            || (castTo == JPrimitiveTypeEnum.INT.getType())
+            || (castTo == JPrimitiveTypeEnum.BOOLEAN.getType())
+            )
+            &&
+            ((castedFrom == JPrimitiveTypeEnum.INT.getType())
+            || (castedFrom == JPrimitiveTypeEnum.BYTE.getType())
+            || (castedFrom == JPrimitiveTypeEnum.CHAR.getType())
+            || (castedFrom == JPrimitiveTypeEnum.SHORT.getType())
+            || (castedFrom == JPrimitiveTypeEnum.BOOLEAN.getType())
+            )) {
+        addTruncateIntOrMoveInstruction(sourcePosition,
+            ((JPrimitiveType) castTo).getPrimitiveTypeEnum(), fromReg, destReg);
+      } else {
+
+        Insn inst =
+            new PlainInsn(Rops.opConv(destReg, fromReg), sourcePosition, destReg,
+                RegisterSpecList.make(fromReg));
+        addInstruction(inst);
+
+      }
+    } else {
+      RegisterSpecList sources = RegisterSpecList.make(fromReg);
+
+      Insn insn =
+          new ThrowingCstInsn(Rops.CHECK_CAST, sourcePosition, sources, getCatchTypes(),
+              RopHelper.convertTypeToDx(castTo));
+      addInstruction(insn);
+
+      addMoveResultPseudoAsExtraInstruction(destReg, sourcePosition);
+    }
+  }
+
+  private void addTruncateIntOrMoveInstruction(@Nonnull SourcePosition sourcePosition,
+      @Nonnull JPrimitiveTypeEnum castTo, @Nonnull RegisterSpec fromReg,
+      @CheckForNull RegisterSpec destReg) throws AssertionError {
+    Rop rop;
+    switch (castTo) {
+      case BYTE:
+        rop = Rops.TO_BYTE;
+        break;
+      case CHAR:
+        rop = Rops.TO_CHAR;
+        break;
+      case SHORT:
+        rop = Rops.TO_SHORT;
+        break;
+      case BOOLEAN:
+      case INT:
+        rop = Rops.MOVE_INT;
+        break;
+      default:
+        throw new AssertionError(castTo + " not supported");
+    }
+
+    RegisterSpecList sources = RegisterSpecList.make(fromReg);
+    Insn inst = new PlainInsn(
+        rop, sourcePosition, destReg, sources);
+    addInstruction(inst);
+  }
+
+  @Nonnull
+  private Constant buildPrimitiveConstant(@Nonnull JValueLiteral literal) {
+    Constant cst = null;
+
+    assert literal.getType() instanceof JPrimitiveType;
+
+    JPrimitiveTypeEnum primitiveType = ((JPrimitiveType) literal.getType()).getPrimitiveTypeEnum();
+
+    switch (primitiveType) {
+      case BOOLEAN:
+        cst = CstInteger.make(((JBooleanLiteral) literal).getValue() ? 1 : 0);
+        break;
+      case BYTE:
+        cst = CstInteger.make(((JByteLiteral) literal).getValue());
+        break;
+      case CHAR:
+        cst = CstInteger.make(((JCharLiteral) literal).getValue());
+        break;
+      case DOUBLE:
+        cst = CstDouble.make(Double.doubleToLongBits(((JDoubleLiteral) literal).getValue()));
+        break;
+      case FLOAT:
+        cst = CstFloat.make(Float.floatToIntBits(((JFloatLiteral) literal).getValue()));
+        break;
+      case INT:
+        cst = CstInteger.make(((JIntLiteral) literal).getValue());
+        break;
+      case LONG:
+        cst = CstLong.make(((JLongLiteral) literal).getValue());
+        break;
+      case SHORT:
+        cst = CstInteger.make(((JShortLiteral) literal).getValue());
+        break;
+      case VOID:
+        throw new AssertionError(literal.toSource() + " not supported.");
+    }
+
+    assert cst != null;
+    return cst;
+  }
+
+  @Nonnull
+  private Constant getConstant(@Nonnull JValueLiteral literal) {
+    Constant cst = null;
+
+    JType type = literal.getType();
+    if (type instanceof JPrimitiveType) {
+      cst = buildPrimitiveConstant(literal);
+    } else if (literal instanceof JAbstractStringLiteral) {
+      cst = RopHelper.createString((JAbstractStringLiteral) literal);
+    } else if (literal instanceof JNullLiteral) {
+      cst = CstKnownNull.THE_ONE;
+    } else {
+      throw new AssertionError(literal.toSource() + " not supported.");
+    }
+
+    return cst;
+  }
+
+  private void buildConstant(@Nonnull RegisterSpec destReg, @Nonnull JValueLiteral literal) {
+    JType type = literal.getType();
+    Rop constOp = Rops.opConst(RopHelper.convertTypeToDx(type));
+    Insn constInst;
+    SourcePosition sourcePosition = RopHelper.getSourcePosition(literal);
+    if (type instanceof JPrimitiveType) {
+      constInst = new PlainCstInsn(
+          constOp, sourcePosition, destReg, RegisterSpecList.EMPTY,
+          getConstant(literal));
+      addInstruction(constInst);
+    } else if (literal instanceof JAbstractStringLiteral) {
+      constInst = new ThrowingCstInsn(constOp, sourcePosition,
+          RegisterSpecList.EMPTY, getCatchTypes(), getConstant(literal));
+      addInstruction(constInst);
+      addMoveResultPseudoAsExtraInstruction(destReg, sourcePosition);
+    } else if (literal instanceof JNullLiteral) {
+      constInst = new PlainCstInsn(
+          constOp, sourcePosition, destReg,
+          RegisterSpecList.EMPTY, getConstant(literal));
+      addInstruction(constInst);
+    } else {
+      throw new AssertionError(literal.toSource() + " not supported.");
+    }
+  }
+
+  private void buildUnaryOperation(@Nonnull RegisterSpec destReg,
+      @Nonnull JUnaryOperation unary) {
+    SourcePosition unarySrcPos = RopHelper.getSourcePosition(unary);
+
+    RegisterSpec srcRegisterSpec = getRegisterSpec(unary.getArg());
+    RegisterSpecList sources = RegisterSpecList.make(srcRegisterSpec);
+
+    Rop opcode = null;
+
+    switch (unary.getOp()) {
+      case NEG: {
+        assert unary.getType() == JPrimitiveTypeEnum.BYTE.getType()
+            || unary.getType() == JPrimitiveTypeEnum.CHAR.getType()
+            || unary.getType() == JPrimitiveTypeEnum.SHORT.getType()
+            || unary.getType() == JPrimitiveTypeEnum.INT.getType()
+            || unary.getType() == JPrimitiveTypeEnum.LONG.getType()
+            || unary.getType() == JPrimitiveTypeEnum.FLOAT.getType()
+            || unary.getType() == JPrimitiveTypeEnum.DOUBLE.getType();
+        opcode = Rops.opNeg(srcRegisterSpec);
+        break;
+      }
+      case BIT_NOT: {
+        assert unary.getType() == JPrimitiveTypeEnum.BYTE.getType()
+            || unary.getType() == JPrimitiveTypeEnum.CHAR.getType()
+            || unary.getType() == JPrimitiveTypeEnum.SHORT.getType()
+            || unary.getType() == JPrimitiveTypeEnum.INT.getType()
+            || unary.getType() == JPrimitiveTypeEnum.LONG.getType();
+        opcode = Rops.opNot(srcRegisterSpec);
+        break;
+      }
+      case NOT: {
+        // Since Dalvik code does not have NOT operator, we will use
+        // x = y ^ true to represent x = !y
+        assert unary.getType() == JPrimitiveTypeEnum.BOOLEAN.getType();
+        addInstruction(
+            new PlainCstInsn(
+                Rops.opXor(sources), unarySrcPos, destReg, sources, CstBoolean.make(true)));
+        return;
+      }
+      default: {
+        throw new AssertionError("Unary operation not supported.");
+      }
+    }
+
+    addInstruction(new PlainInsn(opcode, unarySrcPos, destReg, sources));
+  }
+
+  private void buildBinaryOperation(
+      @Nonnull RegisterSpec destReg, @Nonnull JBinaryOperation binary) {
+
+    RegisterSpecList sources;
+    SourcePosition declarationSrcPos = RopHelper.getSourcePosition(binary);
+    Constant cst = null;
+    JBinaryOperator binOp = binary.getOp();
+    JExpression rhs = binary.getRhs();
+    JExpression lhs = binary.getLhs();
+
+    if (lhs instanceof JSsaVariableRef && binary.getType() instanceof JIntegralType32
+        && rhs instanceof JIntegralConstant32 && ((JIntegralType32) JPrimitiveTypeEnum.SHORT
+            .getType()).isValidValue(((JIntegralConstant32) rhs).getIntValue())) {
+      assert rhs instanceof JValueLiteral;
+
+      // Sub with constant does not exist, check if it can be replace by an add
+      if (binOp == JBinaryOperator.SUB) {
+        int newCst = -((JIntegralConstant32) rhs).getIntValue();
+        if (((JIntegralType32) JPrimitiveTypeEnum.SHORT.getType()).isValidValue(newCst)) {
+          binOp = JBinaryOperator.ADD;
+          sources = RegisterSpecList.make(ropReg.getOrCreateRegisterSpec((JSsaVariableRef) lhs));
+          cst = CstInteger.make(newCst);
+        } else {
+          sources = RegisterSpecList.make(getRegisterSpec(lhs), getRegisterSpec(rhs));
+        }
+      } else {
+        sources = RegisterSpecList.make(ropReg.getOrCreateRegisterSpec((JSsaVariableRef) lhs));
+        cst = getConstant((JValueLiteral) rhs);
+      }
+    } else {
+      if (rhs instanceof JSsaVariableRef) {
+        // Check if rsub can be generated
+        if (binOp == JBinaryOperator.SUB
+            && lhs instanceof JIntegralConstant32 && ((JIntegralType32) JPrimitiveTypeEnum.SHORT
+                .getType()).isValidValue(((JIntegralConstant32) lhs).getIntValue())) {
+          sources = RegisterSpecList.make(ropReg.getOrCreateRegisterSpec((JSsaVariableRef) rhs));
+          assert lhs instanceof JValueLiteral;
+          cst = getConstant((JValueLiteral) lhs);
+        } else {
+          sources = RegisterSpecList.make(getRegisterSpec(lhs),
+            ropReg.getOrCreateRegisterSpec((JSsaVariableRef) rhs));
+        }
+      } else {
+        assert rhs instanceof JValueLiteral;
+        sources = RegisterSpecList.make(
+            getRegisterSpec(lhs), getRegisterSpec(rhs));
+      }
+    }
+
+    Rop opcode;
+
+    switch (binOp) {
+      case ADD:
+        opcode = Rops.opAdd(sources);
+        break;
+      case SUB:
+        opcode = Rops.opSub(sources);
+        break;
+      case ASG: // all assign not removed in ThreeAddressCodeForm are to be handled by buildAssign
+      case ASG_ADD: // assigns with operation are removed in ThreeAddressCodeForm
+      case ASG_BIT_AND:
+      case ASG_BIT_OR:
+      case ASG_BIT_XOR:
+      case ASG_CONCAT:
+      case ASG_DIV:
+      case ASG_MOD:
+      case ASG_MUL:
+      case ASG_SHL:
+      case ASG_SHR:
+      case ASG_SHRU:
+      case ASG_SUB:
+      case EQ: // TODO(yroussel) add constraint on Ropper to ensure no boolean BinaryOperation
+      case GT:
+      case GTE:
+      case LT:
+      case LTE:
+      case NEQ:
+      case OR:
+      case AND:
+        throw new AssertionError();
+      case BIT_AND:
+        opcode = Rops.opAnd(sources);
+        break;
+      case BIT_OR:
+        opcode = Rops.opOr(sources);
+        break;
+      case BIT_XOR:
+        opcode = Rops.opXor(sources);
+        break;
+      case DIV:
+        opcode = Rops.opDiv(sources);
+        break;
+      case MOD:
+        opcode = Rops.opRem(sources);
+        break;
+      case MUL:
+        opcode = Rops.opMul(sources);
+        break;
+      case SHL:
+        opcode = Rops.opShl(sources);
+        if (opcode.equals(Rops.SHL_CONST_INT)) {
+          assert cst != null;
+          CstLiteral32 lit = (CstLiteral32) cst;
+          cst = CstInteger.make(lit.getIntBits() & 0b11111);
+        } else if (opcode.equals(Rops.SHL_CONST_LONG)) {
+          assert cst != null;
+          CstLiteral64 lit = (CstLiteral64) cst;
+          cst = CstInteger.make(lit.getIntBits() & 0b111111);
+        }
+        break;
+      case SHR:
+        opcode = Rops.opShr(sources);
+        if (opcode.equals(Rops.SHR_CONST_INT)) {
+          assert cst != null;
+          CstLiteral32 lit = (CstLiteral32) cst;
+          cst = CstInteger.make(lit.getIntBits() & 0b11111);
+        } else if (opcode.equals(Rops.SHR_CONST_LONG)) {
+          assert cst != null;
+          CstLiteral64 lit = (CstLiteral64) cst;
+          cst = CstInteger.make(lit.getIntBits() & 0b111111);
+        }
+        break;
+      case SHRU:
+        opcode = Rops.opUshr(sources);
+        if (opcode.equals(Rops.USHR_CONST_INT)) {
+          assert cst != null;
+          CstLiteral32 lit = (CstLiteral32) cst;
+          cst = CstInteger.make(lit.getIntBits() & 0b11111);
+        } else if (opcode.equals(Rops.USHR_CONST_LONG)) {
+          assert cst != null;
+          CstLiteral64 lit = (CstLiteral64) cst;
+          cst = CstInteger.make(lit.getIntBits() & 0b111111);
+        }
+        break;
+      default:
+        throw new AssertionError();
+    }
+    if (opcode.canThrow()) {
+      if (cst == null) {
+        addInstruction(new ThrowingInsn(opcode, declarationSrcPos, sources, getCatchTypes()));
+      } else {
+        addInstruction(
+            new ThrowingCstInsn(opcode, declarationSrcPos, sources, getCatchTypes(), cst));
+      }
+      addMoveResultPseudoAsExtraInstruction(destReg, declarationSrcPos);
+    } else {
+      if (cst == null) {
+        addInstruction(new PlainInsn(opcode, declarationSrcPos, destReg, sources));
+      } else {
+        addInstruction(new PlainCstInsn(opcode, declarationSrcPos, destReg, sources, cst));
+      }
+    }
+  }
+
+  private void buildInvokePolymorphic(@CheckForNull RegisterSpec result,
+      @Nonnull JPolymorphicMethodCall methodCall) {
+    CstMethodRef methodRef =
+        new CstMethodRef(RopHelper.convertTypeToDx(methodCall.getReceiverType()),
+            new CstString(methodCall.getMethodName()),
+            RopHelper.getPrototypeFromPolymorphicCall(methodCall));
+
+    SourcePosition methodCallSrcPos = RopHelper.getSourcePosition(methodCall);
+    Prototype prototype =
+        Prototype.intern(RopHelper.getPolymorphicCallSiteSymbolicDescriptor(methodCall));
+    Rop callOp = Rops.opInvokePolymorphic(prototype);
+
+    /* 1 means that first register is always an instance of MethodHandle. */
+    RegisterSpecList sources = new RegisterSpecList(1 + methodCall.getArgs().size());
+
+    /* Set MethodHandle as first parameter */
+    JExpression instance = methodCall.getInstance();
+    assert instance != null;
+    int paramIndex = 0;
+    sources.set(paramIndex++, getRegisterSpec(instance));
+
+    for (JExpression exprArg : methodCall.getArgs()) {
+      sources.set(paramIndex++, getRegisterSpec(exprArg));
+    }
+
+    Insn callInst = new ThrowingDualCstInsn(callOp, methodCallSrcPos, sources, getCatchTypes(),
+        methodRef, new CstPrototypeRef(prototype));
+    addInstruction(callInst);
+
+    if (result != null) {
+      addMoveResultAsExtraInstruction(prototype.getReturnType(), result, methodCallSrcPos);
+    }
+  }
+
+  private void buildCall(@CheckForNull RegisterSpec result, @Nonnull JMethodCall methodCall) {
+    if (InvokeCustomHelper.isInvokeCustom(methodCall)
+        && apiLevel.getProvisionalLevel() == AndroidApiLevel.ProvisionalLevel.O_BETA2) {
+      JAnnotation invokeCustomCallSite = InvokeCustomHelper
+          .getInvokeCustomCallsite(methodCall.getMethodId().getMethods().iterator().next());
+      assert invokeCustomCallSite != null;
+      CstCallSiteRef callSiteRef =
+          InvokeCustomHelper.readInvokeCustomCallSite(invokeCustomCallSite);
+
+      SourcePosition methodCallSrcPos = RopHelper.getSourcePosition(methodCall);
+
+      RegisterSpecList sources = new RegisterSpecList(methodCall.getArgs().size());
+      int paramIndex = 0;
+      for (JExpression exprArg : methodCall.getArgs()) {
+        sources.set(paramIndex++, getRegisterSpec(exprArg));
+      }
+
+      Insn invokeCustom = new ThrowingCstInsn(
+          Rops.opInvokeCustom(callSiteRef.getCallSitePrototype().getPrototype()), methodCallSrcPos,
+          sources, getCatchTypes(), callSiteRef);
+      addInstruction(invokeCustom);
+
+      if (result != null) {
+        addMoveResultAsExtraInstruction(callSiteRef.getType(), result, methodCallSrcPos);
+      }
+
+      return;
+    }
+
+    SourcePosition methodCallSrcPos = RopHelper.getSourcePosition(methodCall);
+
+    Prototype prototype = RopHelper.getPrototype(methodCall.getMethodId());
+
+    RegisterSpecList sources;
+    int paramIndex = 0;
+
+    Rop callOp;
+    MethodKind methodKind = methodCall.getMethodIdWide().getKind();
+    if (methodKind == MethodKind.STATIC) {
+      // Reserve space for the method arguments
+      sources = new RegisterSpecList(methodCall.getArgs().size());
+    } else {
+      // Reserve space for the instance and the method arguments
+      sources = new RegisterSpecList(1 + methodCall.getArgs().size());
+    }
+
+    switch (methodKind) {
+      case STATIC:
+        callOp = Rops.opInvokeStatic(prototype);
+        break;
+      case INSTANCE_NON_VIRTUAL: {
+        callOp = Rops.opInvokeDirect(prototype);
+        // Add the instance as first parameter
+        JExpression instance = methodCall.getInstance();
+        assert instance != null;
+        sources.set(paramIndex++, getRegisterSpec(instance));
+        break;
+      }
+      case INSTANCE_VIRTUAL: {
+        JExpression instance = methodCall.getInstance();
+        assert instance != null;
+        RegisterSpec instanceReg = getRegisterSpec(instance);
+        if (methodCall.getDispatchKind() == DispatchKind.DIRECT) {
+          callOp = Rops.opInvokeSuper(prototype);
+        } else {
+          if (methodCall.getReceiverType() instanceof JInterface) {
+            callOp = Rops.opInvokeInterface(prototype);
+          } else {
+            callOp = Rops.opInvokeVirtual(prototype);
+          }
+        }
+        sources.set(paramIndex++, instanceReg);
+        break;
+      }
+      default:
+        throw new AssertionError(methodCall.toSource() + " not yet supported.");
+    }
+
+    assert prototype.getParameterTypes().size() == methodCall.getArgs().size();
+    for (JExpression exprArg : methodCall.getArgs()) {
+      sources.set(paramIndex++, getRegisterSpec(exprArg));
+    }
+
+    CstMethodRef methodRef = RopHelper.createMethodRef(methodCall);
+    Insn callInst =
+        new ThrowingCstInsn(callOp, methodCallSrcPos, sources, getCatchTypes(), methodRef);
+    addInstruction(callInst);
+
+    if (result != null) {
+      addMoveResultAsExtraInstruction(prototype.getReturnType(), result, methodCallSrcPos);
+    }
+  }
+
+  @Nonnull
+  private RegisterSpec getRegisterSpec(@Nonnull JExpression expr) {
+    RegisterSpec regSpec;
+    if (expr instanceof JSsaVariableRef) {
+      regSpec = ropReg.getOrCreateRegisterSpec((JSsaVariableRef) expr);
+    } else if (expr instanceof JThisRef) {
+      return ropReg.getThisReg();
+    } else {
+      assert expr instanceof JValueLiteral;
+      regSpec =
+          ropReg.getOrCreateTmpRegister(RopHelper.convertTypeToDx(expr.getType()));
+      buildConstant(regSpec, (JValueLiteral) expr);
+    }
+
+    return regSpec;
+  }
+
+  private void addMoveResultAsExtraInstruction(@Nonnull TypeBearer type,
+      @Nonnull RegisterSpec destReg, @Nonnull SourcePosition sourcePosition) {
+    Rop moveResultOp = Rops.opMoveResult(type);
+    Insn moveResultInst =
+        new PlainInsn(moveResultOp, sourcePosition, destReg, RegisterSpecList.EMPTY);
+    addExtraInstruction(moveResultInst);
+  }
+
+  private void addMoveResultPseudoAsExtraInstruction(
+      @Nonnull RegisterSpec destReg, @Nonnull SourcePosition sourcePosition) {
+    PlainInsn moveResult = new PlainInsn(
+        Rops.opMoveResultPseudo(destReg.getTypeBearer()),
+        sourcePosition, destReg, RegisterSpecList.EMPTY);
+    addExtraInstruction(moveResult);
+  }
+
+  private void addExtraInstruction(@Nonnull Insn insn) {
+    assert extraInstructions != null;
+    extraInstructions.add(insn);
+    noMoreInstruction = true;
+  }
+
+  private boolean addInstruction(@Nonnull Insn insn) {
+    assert instructions != null;
+    assert !noMoreInstruction;
+    return instructions.add(new NormalSsaInsn(insn, ssaBb));
+  }
+
+  private boolean addInstruction(@Nonnull SsaInsn insn) {
+    assert instructions != null;
+    assert !noMoreInstruction;
+    return instructions.add(insn);
+  }
+
+  /**
+   * Get the catch types list containing the types of every catch block accessible from the given
+   * block.
+   */
+  private TypeList getCatchTypes() {
+    assert currentBasicBlock instanceof JThrowingBasicBlock;
+    JThrowingBasicBlock block = (JThrowingBasicBlock) currentBasicBlock;
+
+    List<JType> catchTypes = new ArrayList<JType>();
+
+    for (JBasicBlock bb : block.getCatchBlocks()) {
+      for (JClass catchType : ((JCatchBasicBlock) bb).getCatchTypes()) {
+        catchTypes.add(catchType);
+      }
+    }
+
+    if (catchTypes.isEmpty()) {
+      return (StdTypeList.EMPTY);
+    } else {
+      return (RopHelper.createTypeList(catchTypes));
+    }
+  }
+}
\ No newline at end of file
diff --git a/jack/src/com/android/jack/backend/dex/rop/SsaRopRegisterManager.java b/jack/src/com/android/jack/backend/dex/rop/SsaRopRegisterManager.java
new file mode 100644
index 0000000..8ba07df
--- /dev/null
+++ b/jack/src/com/android/jack/backend/dex/rop/SsaRopRegisterManager.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * 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.android.jack.backend.dex.rop;
+
+import com.android.jack.debug.DebugVariableInfoMarker;
+import com.android.jack.dx.rop.code.LocalItem;
+import com.android.jack.dx.rop.code.RegisterSpec;
+import com.android.jack.dx.rop.cst.CstString;
+import com.android.jack.dx.rop.type.Type;
+import com.android.jack.ir.ast.JDefinedClassOrInterface;
+import com.android.jack.ir.ast.JParameter;
+import com.android.jack.ir.ast.JSsaVariableDefRef;
+import com.android.jack.ir.ast.JSsaVariableRef;
+import com.android.jack.ir.ast.JSsaVariableUseRef;
+import com.android.jack.ir.ast.JThis;
+import com.android.jack.ir.ast.JType;
+import com.android.jack.ir.ast.JVariable;
+import com.android.jack.ir.ast.marker.GenericSignature;
+import com.android.jack.ir.ast.marker.ThisRefTypeInfo;
+
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+
+class SsaRopRegisterManager {
+
+  private int nextFreeReg = 0;
+
+  /**
+   * Keep a list of temporary register for each dex type.
+   */
+  @Nonnull
+  private final Map<Type, List<RegisterSpec>> typeToTmpRegister =
+      new Hashtable<Type, List<RegisterSpec>>();
+
+  /**
+   * Keep position of the next free register into {@code typeToTmpRegister}.
+   */
+  @Nonnull
+  private final Map<Type, Integer> typeToNextPosFreeRegister = new Hashtable<Type, Integer>();
+
+  @CheckForNull
+  private RegisterSpec returnReg = null;
+  @CheckForNull
+  private RegisterSpec thisReg = null;
+
+  private final boolean emitSyntheticDebugInfo;
+
+  private final boolean emitDebugInfo;
+
+  public SsaRopRegisterManager(boolean emitDebugInfo, boolean emitSyntheticDebugInfo) {
+    this.emitDebugInfo = emitDebugInfo;
+    this.emitSyntheticDebugInfo = emitSyntheticDebugInfo;
+  }
+
+  public int getRegisterCount() {
+    return nextFreeReg;
+  }
+
+  /**
+   * Create a {@link RegisterSpec} representing the variable {@code this}.
+   * @param jThis The {@link JThis} we want the {@link RegisterSpec} for.
+   * @return The built {@link RegisterSpec}.
+   */
+  @Nonnull
+  RegisterSpec createThisReg(@Nonnull JThis jThis) {
+    assert thisReg == null : "This register was already created.";
+    JDefinedClassOrInterface type = (JDefinedClassOrInterface) jThis.getType();
+
+    Type dexRegType = RopHelper.convertTypeToDx(type);
+    String name = jThis.getName();
+    if (emitDebugInfo && name != null) {
+      assert jThis.getMarker(GenericSignature.class) == null;
+      CstString cstSignature = null;
+      ThisRefTypeInfo thisMarker = type.getMarker(ThisRefTypeInfo.class);
+      if (thisMarker != null && !thisMarker.getGenericSignature().isEmpty()) {
+        cstSignature = new CstString(thisMarker.getGenericSignature());
+      }
+      LocalItem localItem =
+          LocalItem.make(new CstString(name), RopHelper.convertTypeToDx(type), cstSignature);
+      thisReg = RegisterSpec.make(nextFreeReg, dexRegType, localItem);
+    } else {
+      thisReg = RegisterSpec.make(nextFreeReg, dexRegType);
+    }
+    nextFreeReg += dexRegType.getCategory();
+
+    assert thisReg != null;
+    return (thisReg);
+  }
+
+  /**
+   * Create a {@code RegisterSpec} from a {@code JType}.
+   *
+   * @param type The {@code JType} we want the {@code RegisterSpec} of.
+   * @return The built {@code RegisterSpec}.
+   */
+  @Nonnull
+  RegisterSpec createRegisterSpec(@Nonnull JType type) {
+    Type dexRegType = RopHelper.convertTypeToDx(type);
+    RegisterSpec reg = RegisterSpec.make(nextFreeReg, dexRegType);
+    nextFreeReg += dexRegType.getCategory();
+    return (reg);
+  }
+
+  /**
+   * Return the register number associated with a {@link JVariable}
+   * @return The register number of a  {@link JVariable}.
+   */
+  @Nonnegative
+  int getRegisterNumber(@Nonnull JSsaVariableRef ref) {
+    JVariable variable = ref.getTarget();
+    Type dexRegType = RopHelper.convertTypeToDx(variable.getType());
+    JSsaVariableDefRef def = ref instanceof JSsaVariableUseRef ? ((JSsaVariableUseRef) ref).getDef()
+        : (JSsaVariableDefRef) ref;
+    int regNum = def.getRopRegister();
+    if (regNum != -1) {
+      if (nextFreeReg <= regNum) {
+        nextFreeReg = regNum + dexRegType.getCategory();
+      }
+      return regNum;
+    } else {
+      def.setRopRegister(nextFreeReg);
+      nextFreeReg += dexRegType.getCategory();
+      return def.getRopRegister();
+    }
+  }
+
+  @Nonnull
+  RegisterSpec getOrCreateRegisterSpec(@Nonnull JParameter param) {
+    return getOrCreateRegisterSpec(
+        new JSsaVariableDefRef(param.getSourceInfo(), param, 0));
+  }
+
+  /**
+   * Get a {@code RegisterSpec} from a {@code JVariableRef}.
+   *
+   * @param varRef The {@code JVariableRef} we want the {@code RegisterSpec} of.
+   * @return The previously built {@code RegisterSpec}.
+   */
+  @Nonnull
+  RegisterSpec getOrCreateRegisterSpec(@Nonnull JSsaVariableRef varRef) {
+    if (varRef.getTarget() instanceof JThis) {
+      assert thisReg != null : "This register was not created.";
+      return (thisReg);
+    }
+
+    JVariable variable = varRef.getTarget();
+    RegisterSpec register = getRegisterSpec(getRegisterNumber(varRef), variable,
+        varRef.getMarker(DebugVariableInfoMarker.class));
+
+    assert RopHelper.areTypeCompatible(
+        RopHelper.convertTypeToDx(varRef.getType()),
+        register.getType());
+
+    return register;
+  }
+
+  @Nonnull
+  RegisterSpec getOrCreateRegisterSpec(@Nonnull JSsaVariableRef varRef,
+      @CheckForNull LocalItem localItem) {
+    if (varRef.getTarget() instanceof JThis) {
+      assert thisReg != null : "This register was not created.";
+      return (thisReg);
+    }
+
+    JVariable variable = varRef.getTarget();
+    RegisterSpec register = getRegisterSpec(getRegisterNumber(varRef), variable, localItem);
+
+    assert RopHelper.areTypeCompatible(
+        RopHelper.convertTypeToDx(varRef.getType()),
+        register.getType());
+
+    return register;
+  }
+
+  @Nonnull
+  private RegisterSpec getRegisterSpec(@Nonnegative int regNum, @Nonnull JVariable variable,
+      @CheckForNull DebugVariableInfoMarker debugInfo) {
+    RegisterSpec reg;
+    JType variableType = variable.getType();
+    Type regType = RopHelper.convertTypeToDx(variableType);
+
+    String name = variable.getName();
+    if (emitDebugInfo && name != null
+        && (emitSyntheticDebugInfo || !variable.isSynthetic())) {
+      if (debugInfo != null) {
+        // Debug info marker exists, uses debug information from it
+        if (debugInfo == DebugVariableInfoMarker.NO_DEBUG_INFO) {
+          // There is no debug information when coming from Jill, do not get name from JVariable
+          reg = RegisterSpec.make(regNum, regType);
+        } else {
+          CstString cstSignature = null;
+          String genericSignature = debugInfo.getGenericSignature();
+          if (genericSignature != null) {
+            cstSignature = new CstString(genericSignature);
+          }
+          String debugName = debugInfo.getName();
+          assert debugName != null;
+          JType debugType = debugInfo.getType();
+          assert debugType != null;
+          LocalItem localItem = LocalItem.make(new CstString(debugName),
+              RopHelper.convertTypeToDx(debugType), cstSignature);
+          reg = RegisterSpec.make(regNum, regType, localItem);
+        }
+      } else {
+        CstString cstSignature = null;
+        GenericSignature infoMarker = variable.getMarker(GenericSignature.class);
+        if (infoMarker != null) {
+          cstSignature = new CstString(infoMarker.getGenericSignature());
+        }
+        LocalItem localItem = LocalItem.make(new CstString(name), regType, cstSignature);
+        reg = RegisterSpec.make(regNum, regType, localItem);
+      }
+    } else {
+      reg = RegisterSpec.make(regNum, regType);
+    }
+
+    return (reg);
+  }
+
+  @Nonnull
+  private RegisterSpec getRegisterSpec(@Nonnegative int regNum, @Nonnull JVariable variable,
+      @Nonnull LocalItem localItem) {
+    RegisterSpec reg;
+    JType variableType = variable.getType();
+    Type regType = RopHelper.convertTypeToDx(variableType);
+    reg = RegisterSpec.make(regNum, regType, localItem);
+    return reg;
+  }
+
+  /**
+   * Get the {@code RegisterSpec} with the type {@code type} to return value from method.
+   *
+   * @param returnType The return type of the method.
+   * @return The {@code RegisterSpec} used to return result.
+   */
+  @Nonnull
+  RegisterSpec getReturnReg(@Nonnull JType returnType) {
+    RegisterSpec localReturnReg = returnReg;
+    assert localReturnReg != null : "Return reg must be firstly created.";
+    assert RopHelper.areTypeCompatible(
+        RopHelper.convertTypeToDx(returnType), localReturnReg.getType());
+    return (localReturnReg);
+  }
+
+  /**
+   * Create a {@code RegisterSpec} with the type {@code type} to return value from method. The
+   * register number for this register must be 0.
+   *
+   * @param returnType The return type of the method.
+   * @return The {@code RegisterSpec} used to return result.
+   */
+  @Nonnull
+  RegisterSpec createReturnReg(@Nonnull JType returnType) {
+    assert returnReg == null;
+    Type dexRegType = RopHelper.convertTypeToDx(returnType);
+    returnReg = RegisterSpec.make(0, dexRegType);
+    assert returnReg != null;
+    return (returnReg);
+  }
+
+  @Nonnull
+  RegisterSpec getOrCreateTmpRegister(@Nonnull Type dexRegType) {
+    RegisterSpec regSpec = RegisterSpec.make(nextFreeReg, dexRegType);
+    nextFreeReg += dexRegType.getCategory();
+    return regSpec;
+  }
+
+  void resetFreeTmpRegister() {
+    for (Type type : typeToNextPosFreeRegister.keySet()) {
+      typeToNextPosFreeRegister.put(type, Integer.valueOf(0));
+    }
+  }
+
+  @Nonnull
+  RegisterSpec getThisReg() {
+    assert thisReg != null;
+    return thisReg;
+  }
+}
\ No newline at end of file
diff --git a/jack/src/com/android/jack/debug/DebugVariableInfoMarker.java b/jack/src/com/android/jack/debug/DebugVariableInfoMarker.java
index 5687bf0..0821212 100644
--- a/jack/src/com/android/jack/debug/DebugVariableInfoMarker.java
+++ b/jack/src/com/android/jack/debug/DebugVariableInfoMarker.java
@@ -18,6 +18,8 @@
 
 import com.android.jack.ir.ast.JLocalRef;
 import com.android.jack.ir.ast.JParameterRef;
+import com.android.jack.ir.ast.JSsaVariableDefRef;
+import com.android.jack.ir.ast.JSsaVariableUseRef;
 import com.android.jack.ir.ast.JType;
 import com.android.sched.item.Description;
 import com.android.sched.marker.Marker;
@@ -30,7 +32,7 @@
 /**
  * This {@link Marker} contains debug information related to variable.
  */
-@ValidOn({JLocalRef.class, JParameterRef.class})
+@ValidOn({JLocalRef.class, JSsaVariableDefRef.class, JSsaVariableUseRef.class, JParameterRef.class})
 @Description("This marker contains debug information related to variable.")
 public class DebugVariableInfoMarker implements SerializableMarker {
 
diff --git a/jack/src/com/android/jack/dx/ssa/DomFront.java b/jack/src/com/android/jack/dx/ssa/DomFront.java
index 2de1687..648599d 100644
--- a/jack/src/com/android/jack/dx/ssa/DomFront.java
+++ b/jack/src/com/android/jack/dx/ssa/DomFront.java
@@ -18,8 +18,8 @@
 
 import com.android.jack.dx.util.IntSet;
 
-import java.util.ArrayList;
 import java.util.BitSet;
+import java.util.List;
 
 /**
  * Calculates the dominance-frontiers of a methot's basic blocks.
@@ -33,7 +33,7 @@
   /** {@code non-null;} method being processed */
   private final SsaMethod meth;
 
-  private final ArrayList<SsaBasicBlock> nodes;
+  private final List<SsaBasicBlock> nodes;
 
   private final DomInfo[] domInfos;
 
diff --git a/jack/src/com/android/jack/dx/ssa/NormalSsaInsn.java b/jack/src/com/android/jack/dx/ssa/NormalSsaInsn.java
index 026617f..e5940db 100644
--- a/jack/src/com/android/jack/dx/ssa/NormalSsaInsn.java
+++ b/jack/src/com/android/jack/dx/ssa/NormalSsaInsn.java
@@ -36,7 +36,7 @@
    * @param insn Rop insn to wrap
    * @param block block that contains this insn
    */
-  NormalSsaInsn(final Insn insn, final SsaBasicBlock block) {
+  public NormalSsaInsn(final Insn insn, final SsaBasicBlock block) {
     super(insn.getResult(), block);
     this.insn = insn;
   }
diff --git a/jack/src/com/android/jack/dx/ssa/Optimizer.java b/jack/src/com/android/jack/dx/ssa/Optimizer.java
index 8502ae2..6371971 100644
--- a/jack/src/com/android/jack/dx/ssa/Optimizer.java
+++ b/jack/src/com/android/jack/dx/ssa/Optimizer.java
@@ -18,6 +18,7 @@
 
 import com.android.jack.dx.rop.code.RopMethod;
 import com.android.jack.dx.rop.code.TranslationAdvice;
+import com.android.jack.dx.ssa.Optimizer.OptionalStep;
 import com.android.jack.dx.ssa.back.LivenessAnalyzer;
 import com.android.jack.dx.ssa.back.SsaToRop;
 
@@ -126,6 +127,85 @@
   }
 
   /**
+   * Runs optimization algorthims over this SSA method, and returns a new
+   * instance of RopMethod with the changes.
+   *
+   * @param ssaMethod method to process
+   * @param paramWidth the total width, in register-units, of this method's
+   * parameters
+   * @param isStatic true if this method has no 'this' pointer argument.
+   * @param inPreserveLocals true if local variable info should be preserved,
+   * at the cost of some registers and insns
+   * @param removeRedundantConditionalBranch true if we should optimize unneccesary conditional
+   * branches.
+   * @param inAdvice {@code non-null;} translation advice
+   * @return optimized method
+   */
+  public static RopMethod optimize(
+      SsaMethod ssaMethod,
+      int paramWidth,
+      boolean isStatic,
+      boolean inPreserveLocals,
+      boolean removeRedundantConditionalBranch,
+      TranslationAdvice inAdvice) {
+    EnumSet<OptionalStep> steps = EnumSet.allOf(OptionalStep.class);
+    return optimize(ssaMethod, paramWidth, isStatic, inPreserveLocals,
+        removeRedundantConditionalBranch, inAdvice, steps);
+  }
+  /**
+   * Dex bytecode does not have instruction forms that take register numbers larger than 15 for all
+   * instructions. If we've produced a method that uses more than 16 registers, try again by
+   * removing the CONST_COLLECTOR step to see if we can get under the bar. The end result will be
+   * much more efficient.
+   *
+   * @param ssaMethod method to process
+   * @param paramWidth the total width, in register-units, of this method's parameters
+   * @param isStatic true if this method has no 'this' pointer argument.
+   * @param removeRedundantConditionalBranch true if we should optimize unneccesary conditional
+   *     branches.
+   * @return optimized method
+   */
+  public static RopMethod optimizeMinimizeRegisters(SsaMethod ssaMethod,
+      int paramWidth,
+      boolean isStatic,
+      boolean inPreserveLocals,
+      boolean removeRedundantConditionalBranch,
+      TranslationAdvice inAdvice) {
+    EnumSet<OptionalStep> steps = EnumSet.allOf(OptionalStep.class);
+    steps.remove(OptionalStep.CONST_COLLECTOR);
+    return optimize(ssaMethod, paramWidth, isStatic, inPreserveLocals,
+        removeRedundantConditionalBranch, inAdvice, steps);
+  }
+  /**
+   * Runs optimization algorthims over this SSA method, and returns a new
+   * instance of RopMethod with the changes.
+   *
+   * @param ssaMethod method to process
+   * @param paramWidth the total width, in register-units, of this method's
+   * parameters
+   * @param isStatic true if this method has no 'this' pointer argument.
+   * @param inPreserveLocals true if local variable info should be preserved,
+   * at the cost of some registers and insns
+   * @param removeRedundantConditionalBranch true if we should optimize unneccesary conditional
+   * branches.
+   * @param inAdvice {@code non-null;} translation advice
+   * @param steps set of optional optimization steps to run
+   * @return optimized method
+   */
+  public static RopMethod optimize(SsaMethod ssaMethod,
+      int paramWidth,
+      boolean isStatic,
+      boolean inPreserveLocals,
+      boolean removeRedundantConditionalBranch,
+      TranslationAdvice inAdvice,
+      EnumSet<OptionalStep> steps) {
+    preserveLocals = inPreserveLocals;
+    advice = inAdvice;
+    runSsaFormSteps(ssaMethod, steps);
+    return SsaToRop.convertToRopMethod(ssaMethod, removeRedundantConditionalBranch);
+  }
+
+  /**
    * Dex bytecode does not have instruction forms that take register numbers larger than 15 for all
    * instructions. If we've produced a method that uses more than 16 registers, try again by
    * removing the CONST_COLLECTOR step to see if we can get under the bar. The end result will be
diff --git a/jack/src/com/android/jack/dx/ssa/PhiInsn.java b/jack/src/com/android/jack/dx/ssa/PhiInsn.java
index d7419d6..f5f8014 100644
--- a/jack/src/com/android/jack/dx/ssa/PhiInsn.java
+++ b/jack/src/com/android/jack/dx/ssa/PhiInsn.java
@@ -128,13 +128,49 @@
    * @param predBlock predecessor block to be associated with this operand
    */
   public void addPhiOperand(RegisterSpec registerSpec, SsaBasicBlock predBlock) {
-    operands.add(new Operand(registerSpec, predBlock.getIndex(), predBlock.getRopLabel()));
+    addPhiOperand(registerSpec, predBlock.getIndex(), predBlock.getRopLabel());
+  }
 
+  /**
+   * Adds an operand to this phi instruction.
+   *
+   * @param registerSpec register spec, including type and reg of operand
+   * @param predIndex predecessor index.
+   * @param predLabel predecessor ROP label.
+   */
+  public void addPhiOperand(RegisterSpec registerSpec, int predIndex, int predLabel) {
+    operands.add(new Operand(registerSpec, predIndex, predLabel));
     // Un-cache sources, in case someone has already called getSources().
     sources = null;
   }
 
   /**
+   * Switches the operand ID from ROP block label to SSA block index.
+   *
+   * @param labelToIndex A map that proves ROP block label to SSA block index.
+   */
+  public void resolveOperandBlockIndex(int[] labelToIndex) {
+    for (Operand o : operands) {
+      o.blockIndex = labelToIndex[o.ropLabel];
+    }
+  }
+
+  public void changeOperandPred(SsaBasicBlock oldPred, SsaBasicBlock newPred) {
+    boolean found = false;
+    for (Operand o : operands) {
+      if (o.blockIndex == oldPred.getIndex()) {
+        assert o.ropLabel == oldPred.getRopLabel();
+        o.blockIndex = newPred.getIndex();
+        o.ropLabel = newPred.getRopLabel();
+        found = true;
+      }
+    }
+    if (!found) {
+      throw new RuntimeException("Predecessor does not exist in phi node!");
+    }
+  }
+
+  /**
    * Removes all operand uses of a register from this phi instruction.
    *
    * @param registerSpec register spec, including type and reg of operand
@@ -377,8 +413,8 @@
    */
   private static class Operand {
     public RegisterSpec regSpec;
-    public final int blockIndex;
-    public final int ropLabel; // only used for debugging
+    public int blockIndex;
+    public int ropLabel; // only used for debugging
 
     public Operand(RegisterSpec regSpec, int blockIndex, int ropLabel) {
       this.regSpec = regSpec;
diff --git a/jack/src/com/android/jack/dx/ssa/SsaBasicBlock.java b/jack/src/com/android/jack/dx/ssa/SsaBasicBlock.java
index c6b97c2..202c614 100644
--- a/jack/src/com/android/jack/dx/ssa/SsaBasicBlock.java
+++ b/jack/src/com/android/jack/dx/ssa/SsaBasicBlock.java
@@ -135,6 +135,14 @@
     domChildren = new ArrayList<SsaBasicBlock>();
   }
 
+  public SsaBasicBlock(final int basicBlockIndex, final SsaMethod parent) {
+    this.parent = parent;
+    this.index = basicBlockIndex;
+    this.predecessors = new BitSet();
+    this.successors = new BitSet();
+    domChildren = new ArrayList<SsaBasicBlock>();
+  }
+
   /**
    * Creates a new SSA basic block from a ROP form basic block.
    *
@@ -864,6 +872,39 @@
   }
 
   /**
+   * Sets the instructions in the block all at once.
+   *
+   * @param insns new list of instructions.
+   */
+  public void setInsns(List<SsaInsn> insns) {
+    this.insns = insns;
+  }
+
+  /**
+   * Sets the label for branching.
+   *
+   * @param ropLabel Rop branching label.
+   */
+  public void setRopLabel(int ropLabel) {
+    this.ropLabel = ropLabel;
+  }
+
+  public void setSuccessors(final IntList successorList, final int primarySuccessor) {
+    this.successorList = successorList;
+    this.primarySuccessor = primarySuccessor;
+    this.primarySuccessor = primarySuccessor;
+    this.successorList = successorList;
+    successors = new BitSet();
+    for (int i = 0; i < successorList.size(); i++) {
+      successors.set(successorList.get(i));
+    }
+  }
+
+  public void addPredeccessors(int index) {
+    predecessors.set(index);
+  }
+
+  /**
    * Sorts move instructions added via {@code addMoveToEnd} during
    * phi removal so that results don't overwrite sources that are used.
    * For use after all phis have been removed and all calls to
diff --git a/jack/src/com/android/jack/dx/ssa/SsaMethod.java b/jack/src/com/android/jack/dx/ssa/SsaMethod.java
index 0ed0ed0..b16f2bb 100644
--- a/jack/src/com/android/jack/dx/ssa/SsaMethod.java
+++ b/jack/src/com/android/jack/dx/ssa/SsaMethod.java
@@ -124,6 +124,16 @@
     this.spareRegisterBase = registerCount;
   }
 
+  public SsaMethod(int paramWidth, boolean isStatic, int maxLabel, int registerCount) {
+    this.paramWidth = paramWidth;
+    this.isStatic = isStatic;
+    this.backMode = false;
+    this.maxLabel = maxLabel;
+    this.registerCount = registerCount;
+    this.spareRegisterBase = registerCount;
+    this.exitBlockIndex = -1; // This gets made later.
+  }
+
   /**
    * Builds a BitSet of block indices from a basic block list and a list
    * of labels taken from Rop form.
@@ -187,7 +197,7 @@
    * is called after edge-splitting and phi insertion, since the edges
    * going into the exit block should not be considered in those steps.
    */
-  /*package*/void makeExitBlock() {
+  public void makeExitBlock() {
     if (exitBlockIndex >= 0) {
       throw new RuntimeException("must be called at most once");
     }
@@ -866,6 +876,37 @@
   }
 
   /**
+   * Sets the blocks.
+   *
+   * @param blocks list of all the SSA blocks.
+   */
+  public void setBlocks(ArrayList<SsaBasicBlock> blocks) {
+    this.blocks = blocks;
+    for (SsaBasicBlock b : blocks) {
+      if (b.getRopLabel() >= maxLabel) {
+        maxLabel = b.getRopLabel() + 1;
+      }
+    }
+  }
+
+  /**
+   * Sets number of SSA register.
+   *
+   * @param count total number of SSA register.
+   */
+  public void setRegisterCount(int count) {
+    this.registerCount = count;
+  }
+
+  public void setEntryBlockIndex(int entryBlockIndex) {
+    this.entryBlockIndex = entryBlockIndex;
+  }
+
+  public void setMaxLabel(int maxLabel) {
+    this.maxLabel = maxLabel;
+  }
+
+  /**
    * Sets "back-convert mode". Set during back-conversion when registers
    * are about to be mapped into a non-SSA namespace. When true,
    * use and def lists are unavailable.
@@ -875,4 +916,4 @@
     useList = null;
     definitionList = null;
   }
-}
+}
\ No newline at end of file
diff --git a/jack/src/com/android/jack/ir/ast/JAbstractStringLiteral.java b/jack/src/com/android/jack/ir/ast/JAbstractStringLiteral.java
index 8f911fc..17350c2 100644
--- a/jack/src/com/android/jack/ir/ast/JAbstractStringLiteral.java
+++ b/jack/src/com/android/jack/ir/ast/JAbstractStringLiteral.java
@@ -17,6 +17,9 @@
 
 import com.android.jack.Jack;
 import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.cfg.JCaseBlockElement;
+import com.android.jack.ir.ast.cfg.JReturnBlockElement;
+import com.android.jack.ir.ast.cfg.JSwitchBlockElement;
 import com.android.jack.ir.sourceinfo.SourceInfo;
 import com.android.jack.lookup.CommonTypes;
 import com.android.sched.item.Description;
@@ -57,8 +60,11 @@
         || parent instanceof JNameValuePair
         || parent instanceof JAssertStatement
         || parent instanceof JCaseStatement
+        || parent instanceof JCaseBlockElement
         || parent instanceof JReturnStatement
+        || parent instanceof JReturnBlockElement
         || parent instanceof JSwitchStatement
+        || parent instanceof JSwitchBlockElement
         || parent instanceof JAnnotationMethod
         || parent instanceof JFieldInitializer
         || parent instanceof JSynchronizedBlock)) {
diff --git a/jack/src/com/android/jack/ir/ast/JBooleanLiteral.java b/jack/src/com/android/jack/ir/ast/JBooleanLiteral.java
index abc5ff3..9a3e878 100644
--- a/jack/src/com/android/jack/ir/ast/JBooleanLiteral.java
+++ b/jack/src/com/android/jack/ir/ast/JBooleanLiteral.java
@@ -17,6 +17,8 @@
 
 import com.android.jack.ir.JNodeInternalError;
 import com.android.jack.ir.ast.JPrimitiveType.JPrimitiveTypeEnum;
+import com.android.jack.ir.ast.cfg.JConditionalBlockElement;
+import com.android.jack.ir.ast.cfg.JReturnBlockElement;
 import com.android.jack.ir.sourceinfo.SourceInfo;
 import com.android.sched.item.Component;
 import com.android.sched.item.Description;
@@ -74,7 +76,9 @@
         || parent instanceof JDoStatement
         || parent instanceof JForStatement
         || parent instanceof JIfStatement
+        || parent instanceof JConditionalBlockElement
         || parent instanceof JReturnStatement
+        || parent instanceof JReturnBlockElement
         || parent instanceof JFieldInitializer
         || parent instanceof JWhileStatement)) {
       throw new JNodeInternalError(this, "Invalid parent");
diff --git a/jack/src/com/android/jack/ir/ast/JByteLiteral.java b/jack/src/com/android/jack/ir/ast/JByteLiteral.java
index 20347a3..a0aef56 100644
--- a/jack/src/com/android/jack/ir/ast/JByteLiteral.java
+++ b/jack/src/com/android/jack/ir/ast/JByteLiteral.java
@@ -16,6 +16,8 @@
 package com.android.jack.ir.ast;
 
 import com.android.jack.ir.ast.JPrimitiveType.JPrimitiveTypeEnum;
+import com.android.jack.ir.ast.cfg.JCaseBlockElement;
+import com.android.jack.ir.ast.cfg.JSwitchBlockElement;
 import com.android.jack.ir.sourceinfo.SourceInfo;
 import com.android.sched.item.Component;
 import com.android.sched.item.Description;
@@ -80,7 +82,10 @@
 
   @Override
   public void checkValidity() {
-    if (!(parent instanceof JSwitchStatement || parent instanceof JCaseStatement)) {
+    if (!(parent instanceof JSwitchStatement
+        || parent instanceof JSwitchBlockElement
+        || parent instanceof JCaseStatement
+        || parent instanceof JCaseBlockElement)) {
       super.checkValidity();
     }
   }
diff --git a/jack/src/com/android/jack/ir/ast/JCharLiteral.java b/jack/src/com/android/jack/ir/ast/JCharLiteral.java
index 0e0446b..abded1e 100644
--- a/jack/src/com/android/jack/ir/ast/JCharLiteral.java
+++ b/jack/src/com/android/jack/ir/ast/JCharLiteral.java
@@ -16,6 +16,8 @@
 package com.android.jack.ir.ast;
 
 import com.android.jack.ir.ast.JPrimitiveType.JPrimitiveTypeEnum;
+import com.android.jack.ir.ast.cfg.JCaseBlockElement;
+import com.android.jack.ir.ast.cfg.JSwitchBlockElement;
 import com.android.jack.ir.sourceinfo.SourceInfo;
 import com.android.sched.item.Component;
 import com.android.sched.item.Description;
@@ -78,7 +80,10 @@
 
   @Override
   public void checkValidity() {
-    if (!(parent instanceof JSwitchStatement || parent instanceof JCaseStatement)) {
+    if (!(parent instanceof JSwitchStatement
+        || parent instanceof JSwitchBlockElement
+        || parent instanceof JCaseStatement
+        || parent instanceof JCaseBlockElement)) {
       super.checkValidity();
     }
   }
diff --git a/jack/src/com/android/jack/ir/ast/JClassLiteral.java b/jack/src/com/android/jack/ir/ast/JClassLiteral.java
index ec04608..24177e7 100644
--- a/jack/src/com/android/jack/ir/ast/JClassLiteral.java
+++ b/jack/src/com/android/jack/ir/ast/JClassLiteral.java
@@ -17,6 +17,7 @@
 
 import com.android.jack.Jack;
 import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.cfg.JReturnBlockElement;
 import com.android.jack.ir.sourceinfo.SourceInfo;
 import com.android.jack.lookup.CommonTypes;
 import com.android.sched.item.Component;
@@ -91,6 +92,7 @@
         || parent instanceof JAnnotationMethod
         || parent instanceof JFieldInitializer
         || parent instanceof JReturnStatement
+        || parent instanceof JReturnBlockElement
         || parent instanceof JSynchronizedBlock
         || parent instanceof JLock
         || parent instanceof JUnlock)) {
diff --git a/jack/src/com/android/jack/ir/ast/JConcreteMethodBody.java b/jack/src/com/android/jack/ir/ast/JConcreteMethodBody.java
new file mode 100644
index 0000000..16092b1
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/JConcreteMethodBody.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast;
+
+import com.android.jack.Jack;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/**
+ * Base implementation for regular and cfg method body,
+ * represents a the body of a method. Can be Java or JSNI.
+ */
+public abstract class JConcreteMethodBody extends JAbstractMethodBody {
+  @Nonnull
+  protected final List<JLocal> locals = new LinkedList<>();
+
+  public JConcreteMethodBody(@Nonnull SourceInfo info) {
+    this(info, Collections.<JLocal>emptyList());
+  }
+
+  public JConcreteMethodBody(@Nonnull SourceInfo info, @Nonnull List<JLocal> locals) {
+    super(info);
+    this.locals.addAll(locals);
+  }
+
+  /**
+   * Adds a local to this method body.
+   */
+  public void addLocal(@Nonnull JLocal local) {
+    locals.add(local);
+  }
+
+  /**
+   * Returns this method's local variables.
+   */
+  @Nonnull
+  public List<JLocal> getLocals() {
+    return Jack.getUnmodifiableCollections().getUnmodifiableList(locals);
+  }
+
+  @Override
+  public boolean isNative() {
+    return false;
+  }
+
+  /**
+   * Removes a local from this method body.
+   */
+  public void removeLocal(@Nonnull JLocal localToDelete) {
+    locals.remove(localToDelete);
+  }
+
+  /**
+   * Removes locals from this method body.
+   */
+  public void removeLocals(@Nonnull List<JLocal> localsToDelete) {
+    locals.removeAll(localsToDelete);
+  }
+
+  final void acceptLocals(@Nonnull JVisitor visitor) {
+    visitor.accept(locals);
+  }
+
+  final void traverseLocals(
+      @Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    for (JLocal local : locals) {
+      local.traverse(schedule);
+    }
+  }
+
+  @Override
+  protected void transform(@Nonnull JNode existingNode, @CheckForNull JNode newNode,
+      @Nonnull Transformation transformation) throws UnsupportedOperationException {
+    if (!transform(locals, existingNode, (JLocal) newNode, transformation)) {
+      super.transform(existingNode, newNode, transformation);
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/JConstructor.java b/jack/src/com/android/jack/ir/ast/JConstructor.java
index 1cfa1bd..9fc3cd8 100644
--- a/jack/src/com/android/jack/ir/ast/JConstructor.java
+++ b/jack/src/com/android/jack/ir/ast/JConstructor.java
@@ -69,11 +69,6 @@
     return false;
   }
 
-  @Override
-  public JMethodBody getBody() {
-    return (JMethodBody) super.getBody();
-  }
-
   @Nonnull
   @Override
   public JDefinedClass getEnclosingType() {
diff --git a/jack/src/com/android/jack/ir/ast/JEnumLiteral.java b/jack/src/com/android/jack/ir/ast/JEnumLiteral.java
index f605d0d..4926198 100644
--- a/jack/src/com/android/jack/ir/ast/JEnumLiteral.java
+++ b/jack/src/com/android/jack/ir/ast/JEnumLiteral.java
@@ -16,6 +16,9 @@
 
 package com.android.jack.ir.ast;
 
+import com.android.jack.ir.ast.cfg.JCaseBlockElement;
+import com.android.jack.ir.ast.cfg.JReturnBlockElement;
+import com.android.jack.ir.ast.cfg.JSwitchBlockElement;
 import com.android.jack.ir.sourceinfo.SourceInfo;
 import com.android.sched.item.Component;
 import com.android.sched.item.Description;
@@ -79,8 +82,11 @@
         || parent instanceof JNameValuePair
         || parent instanceof JAnnotationMethod
         || parent instanceof JCaseStatement
+        || parent instanceof JCaseBlockElement
         || parent instanceof JSwitchStatement
+        || parent instanceof JSwitchBlockElement
         || parent instanceof JReturnStatement
+        || parent instanceof JReturnBlockElement
         || parent instanceof JFieldInitializer
         || parent instanceof JSynchronizedBlock)) {
       super.checkValidity();
diff --git a/jack/src/com/android/jack/ir/ast/JExpression.java b/jack/src/com/android/jack/ir/ast/JExpression.java
index ab285b2..c2d8716 100644
--- a/jack/src/com/android/jack/ir/ast/JExpression.java
+++ b/jack/src/com/android/jack/ir/ast/JExpression.java
@@ -16,6 +16,7 @@
 package com.android.jack.ir.ast;
 
 import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.cfg.JBasicBlockElement;
 import com.android.jack.ir.sourceinfo.SourceInfo;
 import com.android.sched.item.Description;
 
@@ -46,7 +47,9 @@
 
   @Override
   public void checkValidity() {
-    if (!(parent instanceof JExpression || parent instanceof JStatement)) {
+    if (!(parent instanceof JExpression
+        || parent instanceof JStatement
+        || parent instanceof JBasicBlockElement)) {
       throw new JNodeInternalError(this, "Invalid parent");
     }
   }
diff --git a/jack/src/com/android/jack/ir/ast/JIntLiteral.java b/jack/src/com/android/jack/ir/ast/JIntLiteral.java
index 854d52d..5dbce2c 100644
--- a/jack/src/com/android/jack/ir/ast/JIntLiteral.java
+++ b/jack/src/com/android/jack/ir/ast/JIntLiteral.java
@@ -16,6 +16,8 @@
 package com.android.jack.ir.ast;
 
 import com.android.jack.ir.ast.JPrimitiveType.JPrimitiveTypeEnum;
+import com.android.jack.ir.ast.cfg.JCaseBlockElement;
+import com.android.jack.ir.ast.cfg.JSwitchBlockElement;
 import com.android.jack.ir.sourceinfo.SourceInfo;
 import com.android.sched.item.Component;
 import com.android.sched.item.Description;
@@ -78,7 +80,10 @@
 
   @Override
   public void checkValidity() {
-    if (!(parent instanceof JSwitchStatement || parent instanceof JCaseStatement)) {
+    if (!(parent instanceof JSwitchStatement
+        || parent instanceof JSwitchBlockElement
+        || parent instanceof JCaseStatement
+        || parent instanceof JCaseBlockElement)) {
       super.checkValidity();
     }
   }
diff --git a/jack/src/com/android/jack/ir/ast/JLocal.java b/jack/src/com/android/jack/ir/ast/JLocal.java
index 86ccd79..e12c414 100644
--- a/jack/src/com/android/jack/ir/ast/JLocal.java
+++ b/jack/src/com/android/jack/ir/ast/JLocal.java
@@ -16,6 +16,7 @@
 package com.android.jack.ir.ast;
 
 import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.cfg.JCatchBasicBlock;
 import com.android.jack.ir.sourceinfo.SourceInfo;
 import com.android.sched.item.Component;
 import com.android.sched.item.Description;
@@ -32,10 +33,10 @@
 public class JLocal extends JVariable implements HasEnclosingMethod {
 
   @CheckForNull
-  private JMethodBody enclosingMethodBody;
+  private JConcreteMethodBody enclosingMethodBody;
 
   public JLocal(SourceInfo info, String name, JType type, int modifier,
-      @CheckForNull JMethodBody enclosingMethodBody) {
+      @CheckForNull JConcreteMethodBody enclosingMethodBody) {
     super(info, name, type, modifier);
     assert JModifier.isLocalModifier(modifier);
     this.enclosingMethodBody = enclosingMethodBody;
@@ -53,7 +54,7 @@
     return null;
   }
 
-  public void setEnclosingMethodBody(@Nonnull JMethodBody enclosingMethodBody) {
+  public void setEnclosingMethodBody(@Nonnull JConcreteMethodBody enclosingMethodBody) {
     this.enclosingMethodBody = enclosingMethodBody;
   }
 
@@ -81,7 +82,8 @@
 
   @Override
   public void checkValidity() {
-    if (!(parent instanceof JMethodBody || parent instanceof JCatchBlock)) {
+    if (!(parent instanceof JConcreteMethodBody ||
+        parent instanceof JCatchBlock || parent instanceof JCatchBasicBlock)) {
       throw new JNodeInternalError(this, "Invalid parent");
     }
   }
diff --git a/jack/src/com/android/jack/ir/ast/JMethodBody.java b/jack/src/com/android/jack/ir/ast/JMethodBody.java
index 148e4d3..ccf518f 100644
--- a/jack/src/com/android/jack/ir/ast/JMethodBody.java
+++ b/jack/src/com/android/jack/ir/ast/JMethodBody.java
@@ -16,83 +16,43 @@
 package com.android.jack.ir.ast;
 
 
-import com.android.jack.Jack;
 import com.android.jack.ir.sourceinfo.SourceInfo;
 import com.android.sched.item.Component;
 import com.android.sched.item.Description;
 import com.android.sched.scheduler.ScheduleInstance;
 import com.android.sched.transform.TransformRequest;
 
-import java.util.LinkedList;
 import java.util.List;
-
-import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 
 /**
  * Represents a the body of a method. Can be Java or JSNI.
  */
-@Description("Represents a the body of a Java method")
-public class JMethodBody extends JAbstractMethodBody {
+@Description("Represents a the body of a Java method as regular IR")
+public class JMethodBody extends JConcreteMethodBody {
 
   @Nonnull
   private JBlock block;
-  @Nonnull
-  private final List<JLocal> locals = new LinkedList<JLocal>();
 
   public JMethodBody(@Nonnull SourceInfo info, @Nonnull JBlock block) {
     super(info);
     this.block = block;
   }
 
-  /**
-   * Adds a local to this method body.
-   */
-  public void addLocal(@Nonnull JLocal local) {
-    locals.add(local);
-  }
-
   @Nonnull
   public JBlock getBlock() {
     return block;
   }
 
-  /**
-   * Returns this method's local variables.
-   */
-  @Nonnull
-  public List<JLocal> getLocals() {
-    return Jack.getUnmodifiableCollections().getUnmodifiableList(locals);
-  }
-
   @Nonnull
   public List<JStatement> getStatements() {
     return block.getStatements();
   }
 
   @Override
-  public boolean isNative() {
-    return false;
-  }
-
-  /**
-   * Removes a local from this method body.
-   */
-  public void removeLocal(@Nonnull JLocal localToDelete) {
-    locals.remove(localToDelete);
-  }
-
-  /**
-   * Removes locals from this method body.
-   */
-  public void removeLocals(@Nonnull List<JLocal> localsToDelete) {
-    locals.removeAll(localsToDelete);
-  }
-
-  @Override
   public void traverse(@Nonnull JVisitor visitor) {
     if (visitor.visit(this)) {
-      visitor.accept(locals);
+      acceptLocals(visitor);
       visitor.accept(block);
     }
     visitor.endVisit(this);
@@ -101,25 +61,13 @@
   @Override
   public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
     schedule.process(this);
-    for (JLocal local : locals) {
-      local.traverse(schedule);
-    }
+    traverseLocals(schedule);
     block.traverse(schedule);
   }
 
   @Override
-  protected void transform(@Nonnull JNode existingNode, @CheckForNull JNode newNode,
-      @Nonnull Transformation transformation) throws UnsupportedOperationException {
-    if (!transform(locals, existingNode, (JLocal) newNode, transformation)) {
-      super.transform(existingNode, newNode, transformation);
-    }
-  }
-
-  @Override
   protected void replaceImpl(@Nonnull JNode existingNode, @Nonnull JNode newNode)
       throws UnsupportedOperationException {
-    assert newNode != null;
-
     if (block == existingNode) {
       block = (JBlock) newNode;
     } else {
@@ -128,8 +76,8 @@
   }
 
   @Override
-  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest transformRequest)
-      throws Exception {
+  public void visit(
+      @Nonnull JVisitor visitor, @Nonnull TransformRequest transformRequest) throws Exception {
     visitor.visit(this, transformRequest);
   }
 }
diff --git a/jack/src/com/android/jack/ir/ast/JMethodBodyCfg.java b/jack/src/com/android/jack/ir/ast/JMethodBodyCfg.java
new file mode 100644
index 0000000..43fbc42
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/JMethodBodyCfg.java
@@ -0,0 +1,127 @@
+/*
+ * 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.android.jack.ir.ast;
+
+import com.google.common.collect.Lists;
+
+import com.android.jack.Jack;
+import com.android.jack.ir.ast.cfg.JCatchBasicBlock;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.sched.item.Component;
+import com.android.sched.item.Description;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+
+/**
+ * Represents a the body of a method as a CFG. Can be Java or JSNI.
+ */
+@Description("Represents a the body of a Java method as CFG")
+public class JMethodBodyCfg extends JConcreteMethodBody {
+  @Nonnull
+  private JControlFlowGraph cfg;
+
+  @Nonnull
+  private final List<JLocal> catchLocals = Lists.newLinkedList();
+
+  @Nonnull
+  private final List<JSsaVariableDefRef> params = new ArrayList<>();
+
+  public JMethodBodyCfg(@Nonnull SourceInfo info, @Nonnull List<JLocal> locals) {
+    super(info, locals);
+    cfg = new JControlFlowGraph(this.getSourceInfo());
+    cfg.updateParents(this);
+  }
+
+  @Override
+  @Nonnull
+  public JMethod getMethod() {
+    JNode parent = getParent();
+    assert parent instanceof JMethod;
+    return (JMethod) parent;
+  }
+
+  @Nonnull
+  public JControlFlowGraph getCfg() {
+    return cfg;
+  }
+
+  public void addCatchLocal(@Nonnull JLocal catchLocal) {
+    assert catchLocal.getParent() instanceof JCatchBasicBlock;
+    assert !catchLocals.contains(catchLocal);
+    catchLocals.add(catchLocal);
+  }
+
+  public void removeCatchLocal(@Nonnull JLocal catchLocal) {
+    assert catchLocals.contains(catchLocal);
+    catchLocals.remove(catchLocal);
+  }
+
+  @Nonnegative
+  public int getNumCatchLocals() {
+    return catchLocals.size();
+  }
+
+  @Nonnull
+  public List<JLocal> getCatchLocals() {
+    return Jack.getUnmodifiableCollections().getUnmodifiableList(catchLocals);
+  }
+
+  public void addSsaParamDef(@Nonnull JSsaVariableDefRef param) {
+    params.add(param);
+  }
+
+  @Nonnull
+  public List<JSsaVariableDefRef> getSsaParamsDefs() {
+    return params;
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    if (visitor.visit(this)) {
+      acceptLocals(visitor);
+      visitor.accept(cfg);
+    }
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+    traverseLocals(schedule);
+    cfg.traverse(schedule);
+  }
+
+  @Override
+  protected void replaceImpl(@Nonnull JNode existingNode, @Nonnull JNode newNode) {
+    if (cfg == existingNode) {
+      cfg = (JControlFlowGraph) newNode;
+    } else {
+      super.replaceImpl(existingNode, newNode);
+    }
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+    visitor.visit(this, request);
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/JNullLiteral.java b/jack/src/com/android/jack/ir/ast/JNullLiteral.java
index 5c37305..5688236 100644
--- a/jack/src/com/android/jack/ir/ast/JNullLiteral.java
+++ b/jack/src/com/android/jack/ir/ast/JNullLiteral.java
@@ -15,6 +15,7 @@
  */
 package com.android.jack.ir.ast;
 
+import com.android.jack.ir.ast.cfg.JReturnBlockElement;
 import com.android.jack.ir.sourceinfo.SourceInfo;
 import com.android.sched.item.Component;
 import com.android.sched.item.Description;
@@ -62,6 +63,7 @@
         || parent instanceof JThrowStatement
         || parent instanceof JNameValuePair
         || parent instanceof JReturnStatement
+        || parent instanceof JReturnBlockElement
         || parent instanceof JFieldInitializer)) {
       super.checkValidity();
     }
diff --git a/jack/src/com/android/jack/ir/ast/JNumberValueLiteral.java b/jack/src/com/android/jack/ir/ast/JNumberValueLiteral.java
index 28b6eca..a99c871 100644
--- a/jack/src/com/android/jack/ir/ast/JNumberValueLiteral.java
+++ b/jack/src/com/android/jack/ir/ast/JNumberValueLiteral.java
@@ -17,6 +17,7 @@
 package com.android.jack.ir.ast;
 
 import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.cfg.JReturnBlockElement;
 import com.android.jack.ir.sourceinfo.SourceInfo;
 
 /**
@@ -34,6 +35,7 @@
         || parent instanceof JNameValuePair
         || parent instanceof JAnnotationMethod
         || parent instanceof JReturnStatement
+        || parent instanceof JReturnBlockElement
         || parent instanceof JFieldInitializer)) {
       throw new JNodeInternalError(this, "Invalid parent");
     }
diff --git a/jack/src/com/android/jack/ir/ast/JShortLiteral.java b/jack/src/com/android/jack/ir/ast/JShortLiteral.java
index d7889db..a64bd06 100644
--- a/jack/src/com/android/jack/ir/ast/JShortLiteral.java
+++ b/jack/src/com/android/jack/ir/ast/JShortLiteral.java
@@ -16,6 +16,8 @@
 package com.android.jack.ir.ast;
 
 import com.android.jack.ir.ast.JPrimitiveType.JPrimitiveTypeEnum;
+import com.android.jack.ir.ast.cfg.JCaseBlockElement;
+import com.android.jack.ir.ast.cfg.JSwitchBlockElement;
 import com.android.jack.ir.sourceinfo.SourceInfo;
 import com.android.sched.item.Component;
 import com.android.sched.item.Description;
@@ -80,7 +82,10 @@
 
   @Override
   public void checkValidity() {
-    if (!(parent instanceof JSwitchStatement || parent instanceof JCaseStatement)) {
+    if (!(parent instanceof JSwitchStatement
+        || parent instanceof JSwitchBlockElement
+        || parent instanceof JCaseStatement
+        || parent instanceof JCaseBlockElement)) {
       super.checkValidity();
     }
   }
diff --git a/jack/src/com/android/jack/ir/ast/JSsaVariableDefRef.java b/jack/src/com/android/jack/ir/ast/JSsaVariableDefRef.java
new file mode 100644
index 0000000..e5537cb
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/JSsaVariableDefRef.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast;
+
+import com.google.common.collect.Lists;
+
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import java.util.List;
+
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+
+/**
+ * This version of the SSA variable reference only appears on the left hand side of an assignment
+ * except for a few exceptions.
+ *
+ */
+public class JSsaVariableDefRef extends JSsaVariableRef {
+
+  @Nonnull
+  private final List<JSsaVariableUseRef> uses;
+
+  private int regNum = -1;
+
+  public JSsaVariableDefRef(@Nonnull SourceInfo info, @Nonnull JVariable target,
+      @Nonnegative int version) {
+    super(info, target, version);
+    this.uses = Lists.newLinkedList();
+  }
+
+  public JSsaVariableUseRef makeRef(@Nonnull SourceInfo info) {
+    JSsaVariableUseRef use = new JSsaVariableUseRef(info, target, this.getVersion(), this);
+    uses.add(use);
+    return use;
+  }
+
+  @Nonnull
+  public List<JSsaVariableUseRef> getUses() {
+    return uses;
+  }
+
+  public boolean removeUse(JSsaVariableUseRef use) {
+    int index = uses.indexOf(use);
+    if (index == -1) {
+      return false;
+    }
+    uses.remove(index);
+    return true;
+  }
+
+  /**
+   * Removes all referenced {@link JSsaVariableUseRef} objects.
+   */
+  public void removeUses() {
+    uses.clear();
+  }
+
+  public boolean hasUses() {
+    return !uses.isEmpty();
+  }
+
+  public boolean hasUsesOutsideOfPhis() {
+    for (JSsaVariableUseRef use : uses) {
+      if (use.isPhiUse()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public void setRopRegister(@Nonnegative int reg) {
+    assert this.regNum == -1;
+    this.regNum = reg;
+  }
+
+  public int getRopRegister() {
+    return this.regNum;
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    visitor.visit(this);
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest transformRequest)
+      throws Exception {
+    visitor.visit(this, transformRequest);
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/JSsaVariableDefRefPlaceHolder.java b/jack/src/com/android/jack/ir/ast/JSsaVariableDefRefPlaceHolder.java
new file mode 100644
index 0000000..38bbbb5
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/JSsaVariableDefRefPlaceHolder.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast;
+
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+/**
+ * This version of the SSA variable reference only appears on the left hand side of an assignment
+ * except for a few exceptions.
+ *
+ */
+public class JSsaVariableDefRefPlaceHolder extends JSsaVariableDefRef {
+
+  public JSsaVariableDefRefPlaceHolder(@Nonnull SourceInfo info, @Nonnull JVariable target) {
+    super(info, target, 0);
+  }
+
+  @Override
+  public JSsaVariableUseRef makeRef(@Nonnull SourceInfo info) {
+    return new JSsaVariableUseRefPlaceHolder(info, this.getTarget(), this);
+  }
+
+  @Override
+  @Nonnull
+  public List<JSsaVariableUseRef> getUses() {
+    throw new UnsupportedOperationException("Should not be called on place holder variables");
+  }
+
+  @Override
+  public boolean removeUse(JSsaVariableUseRef use) {
+    throw new UnsupportedOperationException("Should not be called on place holder variables");
+  }
+
+  @Override
+  public boolean hasUses() {
+    throw new UnsupportedOperationException("Should not be called on place holder variables");
+  }
+
+  @Override
+  public boolean hasUsesOutsideOfPhis() {
+    throw new UnsupportedOperationException("Should not be called on place holder variables");
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    visitor.visit(this);
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest transformRequest)
+      throws Exception {
+    visitor.visit(this, transformRequest);
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/JSsaVariableRef.java b/jack/src/com/android/jack/ir/ast/JSsaVariableRef.java
new file mode 100644
index 0000000..fa41544
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/JSsaVariableRef.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast;
+
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.sched.item.Component;
+import com.android.sched.item.Description;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+
+/**
+ * Presents a variable reference that is in SSA form.
+ *
+ * For all variables X in the AST, a renamed variable X_1 will be a {@link JVariableRef} with a
+ * version number 1.
+ *
+ * JVariableRef of different version is considered to be behave like completely different variables
+ * in SSA form.
+ */
+@Description("Represents a reference to an SSA variable.")
+public abstract class JSsaVariableRef extends JVariableRef {
+
+  @Nonnegative
+  private final int version;
+
+  /**
+   * Constructs a JSsaVariableRef.
+   *
+   * @param target The original variable without renaming / versioning.
+   * @Param version The version number of the variable if it is renamed.
+   */
+  public JSsaVariableRef(@Nonnull SourceInfo info, @Nonnull JVariable target,
+      @Nonnegative int version) {
+    super(info, target);
+    this.version = version;
+  }
+
+  /**
+   * @return The version number of the variable it is referencing.
+   */
+  @Nonnegative
+  public int getVersion() {
+    return version;
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    visitor.visit(this);
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest transformRequest)
+      throws Exception {
+    visitor.visit(this, transformRequest);
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/JSsaVariableUseRef.java b/jack/src/com/android/jack/ir/ast/JSsaVariableUseRef.java
new file mode 100644
index 0000000..85151f3
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/JSsaVariableUseRef.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast;
+
+import com.android.jack.ir.ast.cfg.JPhiBlockElement;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+
+/**
+ * This version of the SSA variable reference only appears on the right hand side of any
+ * assignments.
+ */
+public class JSsaVariableUseRef extends JSsaVariableRef {
+
+  @Nonnull
+  private final JSsaVariableDefRef def;
+
+  /* package */ JSsaVariableUseRef(@Nonnull SourceInfo info, @Nonnull JVariable target,
+      @Nonnegative int version, JSsaVariableDefRef def) {
+    super(info, target, version);
+    this.def = def;
+  }
+
+  /**
+   * @return true if it is used in a Phi element.
+   */
+  public boolean isPhiUse() {
+    JNode parent = getParent();
+    return parent instanceof JPhiBlockElement;
+  }
+
+  @Nonnull
+  public JSsaVariableDefRef getDef() {
+    return def;
+  }
+
+  public void deleteUseFromDef() {
+    boolean result = def.removeUse(this);
+    assert result;
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    visitor.visit(this);
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest transformRequest)
+      throws Exception {
+    visitor.visit(this, transformRequest);
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/JSsaVariableUseRefPlaceHolder.java b/jack/src/com/android/jack/ir/ast/JSsaVariableUseRefPlaceHolder.java
new file mode 100644
index 0000000..2245374
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/JSsaVariableUseRefPlaceHolder.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast;
+
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import javax.annotation.Nonnull;
+
+/**
+ * This version of the SSA variable reference only appears on the right hand side of any
+ * assignments.
+ */
+public class JSsaVariableUseRefPlaceHolder extends JSsaVariableUseRef {
+
+  @Nonnull
+  private final JSsaVariableDefRef def;
+
+  /* package */ JSsaVariableUseRefPlaceHolder(@Nonnull SourceInfo info, @Nonnull JVariable target,
+      JSsaVariableDefRef def) {
+    super(info, target, 0, def);
+    this.def = def;
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    visitor.visit(this);
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest transformRequest)
+      throws Exception {
+    visitor.visit(this, transformRequest);
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/JVisitor.java b/jack/src/com/android/jack/ir/ast/JVisitor.java
index f4bb893..91a2b86 100644
--- a/jack/src/com/android/jack/ir/ast/JVisitor.java
+++ b/jack/src/com/android/jack/ir/ast/JVisitor.java
@@ -19,6 +19,35 @@
 import com.android.jack.JackAbortException;
 import com.android.jack.ir.HasSourceInfo;
 import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.cfg.JBasicBlock;
+import com.android.jack.ir.ast.cfg.JBasicBlockElement;
+import com.android.jack.ir.ast.cfg.JCaseBasicBlock;
+import com.android.jack.ir.ast.cfg.JCaseBlockElement;
+import com.android.jack.ir.ast.cfg.JCatchBasicBlock;
+import com.android.jack.ir.ast.cfg.JConditionalBasicBlock;
+import com.android.jack.ir.ast.cfg.JConditionalBlockElement;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.jack.ir.ast.cfg.JEntryBasicBlock;
+import com.android.jack.ir.ast.cfg.JExitBasicBlock;
+import com.android.jack.ir.ast.cfg.JGotoBlockElement;
+import com.android.jack.ir.ast.cfg.JLockBlockElement;
+import com.android.jack.ir.ast.cfg.JMethodCallBlockElement;
+import com.android.jack.ir.ast.cfg.JPhiBlockElement;
+import com.android.jack.ir.ast.cfg.JPlaceholderBasicBlock;
+import com.android.jack.ir.ast.cfg.JPolymorphicMethodCallBlockElement;
+import com.android.jack.ir.ast.cfg.JRegularBasicBlock;
+import com.android.jack.ir.ast.cfg.JReturnBasicBlock;
+import com.android.jack.ir.ast.cfg.JReturnBlockElement;
+import com.android.jack.ir.ast.cfg.JSimpleBasicBlock;
+import com.android.jack.ir.ast.cfg.JStoreBlockElement;
+import com.android.jack.ir.ast.cfg.JSwitchBasicBlock;
+import com.android.jack.ir.ast.cfg.JSwitchBlockElement;
+import com.android.jack.ir.ast.cfg.JThrowBasicBlock;
+import com.android.jack.ir.ast.cfg.JThrowBlockElement;
+import com.android.jack.ir.ast.cfg.JThrowingBasicBlock;
+import com.android.jack.ir.ast.cfg.JThrowingExpressionBasicBlock;
+import com.android.jack.ir.ast.cfg.JUnlockBlockElement;
+import com.android.jack.ir.ast.cfg.JVariableAsgBlockElement;
 import com.android.jack.load.JackLoadingException;
 import com.android.sched.transform.TransformRequest;
 
@@ -149,6 +178,126 @@
     endVisit((JStatement) assertStatement);
   }
 
+  public void endVisit(@Nonnull JConcreteMethodBody concreteMethodBody) {
+    endVisit((JAbstractMethodBody) concreteMethodBody);
+  }
+
+  public void endVisit(@Nonnull JMethodBodyCfg methodBodyCfg) {
+    endVisit((JConcreteMethodBody) methodBodyCfg);
+  }
+
+  public void endVisit(@Nonnull JControlFlowGraph controlFlowGraph) {
+    endVisit((JNode) controlFlowGraph);
+  }
+
+  public void endVisit(@Nonnull JBasicBlock basicBlock) {
+    endVisit((JNode) basicBlock);
+  }
+
+  public void endVisit(@Nonnull JCaseBasicBlock caseBasicBlock) {
+    endVisit((JRegularBasicBlock) caseBasicBlock);
+  }
+
+  public void endVisit(@Nonnull JCatchBasicBlock catchBasicBlock) {
+    endVisit((JRegularBasicBlock) catchBasicBlock);
+  }
+
+  public void endVisit(@Nonnull JConditionalBasicBlock conditionalBasicBlock) {
+    endVisit((JRegularBasicBlock) conditionalBasicBlock);
+  }
+
+  public void endVisit(@Nonnull JEntryBasicBlock entryBasicBlock) {
+    endVisit((JBasicBlock) entryBasicBlock);
+  }
+
+  public void endVisit(@Nonnull JExitBasicBlock exitBasicBlock) {
+    endVisit((JBasicBlock) exitBasicBlock);
+  }
+
+  public void endVisit(@Nonnull JRegularBasicBlock regularBasicBlock) {
+    endVisit((JBasicBlock) regularBasicBlock);
+  }
+
+  public void endVisit(@Nonnull JPlaceholderBasicBlock blockUnderConstruction) {
+    endVisit((JBasicBlock) blockUnderConstruction);
+  }
+
+  public void endVisit(@Nonnull JReturnBasicBlock returnBasicBlock) {
+    endVisit((JRegularBasicBlock) returnBasicBlock);
+  }
+
+  public void endVisit(@Nonnull JSimpleBasicBlock simpleBasicBlock) {
+    endVisit((JRegularBasicBlock) simpleBasicBlock);
+  }
+
+  public void endVisit(@Nonnull JSwitchBasicBlock switchBasicBlock) {
+    endVisit((JRegularBasicBlock) switchBasicBlock);
+  }
+
+  public void endVisit(@Nonnull JThrowingBasicBlock throwingBasicBlock) {
+    endVisit((JRegularBasicBlock) throwingBasicBlock);
+  }
+
+  public void endVisit(@Nonnull JThrowingExpressionBasicBlock throwingExpressionBasicBlock) {
+    endVisit((JThrowingBasicBlock) throwingExpressionBasicBlock);
+  }
+
+  public void endVisit(@Nonnull JThrowBasicBlock throwBasicBlock) {
+    endVisit((JThrowingBasicBlock) throwBasicBlock);
+  }
+
+  public void endVisit(@Nonnull JBasicBlockElement basicBlockElement) {
+    endVisit((JNode) basicBlockElement);
+  }
+
+  public void endVisit(@Nonnull JGotoBlockElement gotoBlockElement) {
+    endVisit((JBasicBlockElement) gotoBlockElement);
+  }
+
+  public void endVisit(@Nonnull JThrowBlockElement throwBlockElement) {
+    endVisit((JBasicBlockElement) throwBlockElement);
+  }
+
+  public void endVisit(@Nonnull JStoreBlockElement storeBlockElement) {
+    endVisit((JBasicBlockElement) storeBlockElement);
+  }
+
+  public void endVisit(@Nonnull JVariableAsgBlockElement variableAsgBlockElement) {
+    endVisit((JBasicBlockElement) variableAsgBlockElement);
+  }
+
+  public void endVisit(@Nonnull JReturnBlockElement returnBlockElement) {
+    endVisit((JBasicBlockElement) returnBlockElement);
+  }
+
+  public void endVisit(@Nonnull JCaseBlockElement caseBlockElement) {
+    endVisit((JBasicBlockElement) caseBlockElement);
+  }
+
+  public void endVisit(@Nonnull JMethodCallBlockElement methodCallBlockElement) {
+    endVisit((JBasicBlockElement) methodCallBlockElement);
+  }
+
+  public void endVisit(@Nonnull JPolymorphicMethodCallBlockElement methodCallBlockElement) {
+    endVisit((JBasicBlockElement) methodCallBlockElement);
+  }
+
+  public void endVisit(@Nonnull JLockBlockElement lockBlockElement) {
+    endVisit((JBasicBlockElement) lockBlockElement);
+  }
+
+  public void endVisit(@Nonnull JUnlockBlockElement unlockBlockElement) {
+    endVisit((JBasicBlockElement) unlockBlockElement);
+  }
+
+  public void endVisit(@Nonnull JSwitchBlockElement switchBlockElement) {
+    endVisit((JBasicBlockElement) switchBlockElement);
+  }
+
+  public void endVisit(@Nonnull JConditionalBlockElement conditionalBlockElement) {
+    endVisit((JBasicBlockElement) conditionalBlockElement);
+  }
+
   public void endVisit(@Nonnull JBinaryOperation binaryOperation) {
     endVisit((JExpression) binaryOperation);
   }
@@ -326,7 +475,7 @@
   }
 
   public void endVisit(@Nonnull JMethodBody methodBody) {
-    endVisit((JAbstractMethodBody) methodBody);
+    endVisit((JConcreteMethodBody) methodBody);
   }
 
   public void endVisit(@Nonnull JPolymorphicMethodCall polymorphicMethodCall) {
@@ -367,7 +516,9 @@
 
   /**
    * End visit of a {@link JNode}
-   * @param jnode visited {@link JNode}
+   *
+   * @param jnode
+   *     visited {@link JNode}
    */
   public void endVisit(@Nonnull JNode jnode) {
     // empty block
@@ -413,6 +564,10 @@
     endVisit((JPhantomClassOrInterface) phantomInterface);
   }
 
+  public void endVisit(@Nonnull JPhiBlockElement phi) {
+    endVisit((JBasicBlockElement) phi);
+  }
+
   public void endVisit(@Nonnull JPostfixOperation postfixOperation) {
     endVisit((JUnaryOperation) postfixOperation);
   }
@@ -441,6 +596,18 @@
     endVisit((JNumberValueLiteral) shortLiteral);
   }
 
+  public void endVisit(@Nonnull JSsaVariableRef ref) {
+    endVisit((JVariableRef) ref);
+  }
+
+  public void endVisit(@Nonnull JSsaVariableDefRef ref) {
+    endVisit((JSsaVariableRef) ref);
+  }
+
+  public void endVisit(@Nonnull JSsaVariableUseRef ref) {
+    endVisit((JSsaVariableRef) ref);
+  }
+
   public void endVisit(@Nonnull JStatement statement) {
     endVisit((JNode) statement);
   }
@@ -549,6 +716,126 @@
     return visit((JExpression) binaryOperation);
   }
 
+  public boolean visit(@Nonnull JConcreteMethodBody concreteMethodBody) {
+    return visit((JAbstractMethodBody) concreteMethodBody);
+  }
+
+  public boolean visit(@Nonnull JMethodBodyCfg methodBodyCfg) {
+    return visit((JConcreteMethodBody) methodBodyCfg);
+  }
+
+  public boolean visit(@Nonnull JControlFlowGraph controlFlowGraph) {
+    return visit((JNode) controlFlowGraph);
+  }
+
+  public boolean visit(@Nonnull JBasicBlock basicBlock) {
+    return visit((JNode) basicBlock);
+  }
+
+  public boolean visit(@Nonnull JCatchBasicBlock catchBasicBlock) {
+    return visit((JRegularBasicBlock) catchBasicBlock);
+  }
+
+  public boolean visit(@Nonnull JCaseBasicBlock caseBasicBlock) {
+    return visit((JRegularBasicBlock) caseBasicBlock);
+  }
+
+  public boolean visit(@Nonnull JConditionalBasicBlock conditionalBasicBlock) {
+    return visit((JRegularBasicBlock) conditionalBasicBlock);
+  }
+
+  public boolean visit(@Nonnull JEntryBasicBlock entryBasicBlock) {
+    return visit((JBasicBlock) entryBasicBlock);
+  }
+
+  public boolean visit(@Nonnull JExitBasicBlock exitBasicBlock) {
+    return visit((JBasicBlock) exitBasicBlock);
+  }
+
+  public boolean visit(@Nonnull JRegularBasicBlock regularBasicBlock) {
+    return visit((JBasicBlock) regularBasicBlock);
+  }
+
+  public boolean visit(@Nonnull JPlaceholderBasicBlock blockUnderConstruction) {
+    return visit((JBasicBlock) blockUnderConstruction);
+  }
+
+  public boolean visit(@Nonnull JReturnBasicBlock returnBasicBlock) {
+    return visit((JRegularBasicBlock) returnBasicBlock);
+  }
+
+  public boolean visit(@Nonnull JSimpleBasicBlock simpleBasicBlock) {
+    return visit((JRegularBasicBlock) simpleBasicBlock);
+  }
+
+  public boolean visit(@Nonnull JSwitchBasicBlock switchBasicBlock) {
+    return visit((JRegularBasicBlock) switchBasicBlock);
+  }
+
+  public boolean visit(@Nonnull JThrowingBasicBlock throwingBasicBlock) {
+    return visit((JRegularBasicBlock) throwingBasicBlock);
+  }
+
+  public boolean visit(@Nonnull JThrowingExpressionBasicBlock throwingExpressionBasicBlock) {
+    return visit((JThrowingBasicBlock) throwingExpressionBasicBlock);
+  }
+
+  public boolean visit(@Nonnull JThrowBasicBlock throwBasicBlock) {
+    return visit((JThrowingBasicBlock) throwBasicBlock);
+  }
+
+  public boolean visit(@Nonnull JBasicBlockElement basicBlockElement) {
+    return visit((JNode) basicBlockElement);
+  }
+
+  public boolean visit(@Nonnull JGotoBlockElement gotoBlockElement) {
+    return visit((JBasicBlockElement) gotoBlockElement);
+  }
+
+  public boolean visit(@Nonnull JThrowBlockElement throwBlockElement) {
+    return visit((JBasicBlockElement) throwBlockElement);
+  }
+
+  public boolean visit(@Nonnull JStoreBlockElement storeBlockElement) {
+    return visit((JBasicBlockElement) storeBlockElement);
+  }
+
+  public boolean visit(@Nonnull JVariableAsgBlockElement variableAsgBlockElement) {
+    return visit((JBasicBlockElement) variableAsgBlockElement);
+  }
+
+  public boolean visit(@Nonnull JReturnBlockElement returnBlockElement) {
+    return visit((JBasicBlockElement) returnBlockElement);
+  }
+
+  public boolean visit(@Nonnull JCaseBlockElement caseBlockElement) {
+    return visit((JBasicBlockElement) caseBlockElement);
+  }
+
+  public boolean visit(@Nonnull JMethodCallBlockElement methodCallBlockElement) {
+    return visit((JBasicBlockElement) methodCallBlockElement);
+  }
+
+  public boolean visit(@Nonnull JPolymorphicMethodCallBlockElement methodCallBlockElement) {
+    return visit((JBasicBlockElement) methodCallBlockElement);
+  }
+
+  public boolean visit(@Nonnull JLockBlockElement lockBlockElement) {
+    return visit((JBasicBlockElement) lockBlockElement);
+  }
+
+  public boolean visit(@Nonnull JUnlockBlockElement unlockBlockElement) {
+    return visit((JBasicBlockElement) unlockBlockElement);
+  }
+
+  public boolean visit(@Nonnull JSwitchBlockElement switchBlockElement) {
+    return visit((JBasicBlockElement) switchBlockElement);
+  }
+
+  public boolean visit(@Nonnull JConditionalBlockElement conditionalBlockElement) {
+    return visit((JBasicBlockElement) conditionalBlockElement);
+  }
+
   public boolean visit(@Nonnull JReinterpretCastOperation reinterpretCastOperation) {
     return visit((JCastOperation) reinterpretCastOperation);
   }
@@ -722,7 +1009,7 @@
   }
 
   public boolean visit(@Nonnull JMethodBody methodBody) {
-    return visit((JAbstractMethodBody) methodBody);
+    return visit((JConcreteMethodBody) methodBody);
   }
 
   public boolean visit(@Nonnull JPolymorphicMethodCall polymorphicMethodCall) {
@@ -763,7 +1050,9 @@
 
   /**
    * Visit of a {@link JNode}
-   * @param jnode visited {@link JNode}
+   *
+   * @param jnode
+   *     visited {@link JNode}
    */
   public boolean visit(@Nonnull JNode jnode) {
     return true;
@@ -809,6 +1098,10 @@
     return visit((JPhantomClassOrInterface) phantomInterface);
   }
 
+  public boolean visit(@Nonnull JPhiBlockElement phi) {
+    return visit((JBasicBlockElement) phi);
+  }
+
   public boolean visit(@Nonnull JPostfixOperation postfixOperation) {
     return visit((JUnaryOperation) postfixOperation);
   }
@@ -841,6 +1134,18 @@
     return visit((JNode) statement);
   }
 
+  public boolean visit(@Nonnull JSsaVariableRef ref) {
+    return visit((JVariableRef) ref);
+  }
+
+  public boolean visit(@Nonnull JSsaVariableDefRef ref) {
+    return visit((JSsaVariableRef) ref);
+  }
+
+  public boolean visit(@Nonnull JSsaVariableUseRef ref) {
+    return visit((JSsaVariableRef) ref);
+  }
+
   public boolean visit(@Nonnull JStringLiteral stringLiteral) {
     return visit((JAbstractStringLiteral) stringLiteral);
   }
@@ -960,6 +1265,157 @@
     visit((JExpression) binaryOperation, transformRequest);
   }
 
+  public void visit(@Nonnull JConcreteMethodBody concreteMethodBody,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JAbstractMethodBody) concreteMethodBody, transformRequest);
+  }
+
+  public void visit(@Nonnull JMethodBodyCfg x, @Nonnull TransformRequest transformRequest)
+      throws Exception {
+    visit((JConcreteMethodBody) x, transformRequest);
+  }
+
+  public void visit(@Nonnull JControlFlowGraph controlFlowGraph,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JNode) controlFlowGraph, transformRequest);
+  }
+
+  public void visit(@Nonnull JBasicBlock basicBlock,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JNode) basicBlock, transformRequest);
+  }
+
+  public void visit(@Nonnull JCatchBasicBlock catchBasicBlock,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JRegularBasicBlock) catchBasicBlock, transformRequest);
+  }
+
+  public void visit(@Nonnull JCaseBasicBlock caseBasicBlock,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JRegularBasicBlock) caseBasicBlock, transformRequest);
+  }
+
+  public void visit(@Nonnull JConditionalBasicBlock conditionalBasicBlock,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JRegularBasicBlock) conditionalBasicBlock, transformRequest);
+  }
+
+  public void visit(@Nonnull JEntryBasicBlock entryBasicBlock,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JBasicBlock) entryBasicBlock, transformRequest);
+  }
+
+  public void visit(@Nonnull JExitBasicBlock exitBasicBlock,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JBasicBlock) exitBasicBlock, transformRequest);
+  }
+
+  public void visit(@Nonnull JRegularBasicBlock regularBasicBlock,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JBasicBlock) regularBasicBlock, transformRequest);
+  }
+
+  public void visit(@Nonnull JPlaceholderBasicBlock blockUnderConstruction,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JBasicBlock) blockUnderConstruction, transformRequest);
+  }
+
+  public void visit(@Nonnull JReturnBasicBlock returnBasicBlock,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JRegularBasicBlock) returnBasicBlock, transformRequest);
+  }
+
+  public void visit(@Nonnull JSimpleBasicBlock simpleBasicBlock,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JRegularBasicBlock) simpleBasicBlock, transformRequest);
+  }
+
+  public void visit(@Nonnull JSwitchBasicBlock switchBasicBlock,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JRegularBasicBlock) switchBasicBlock, transformRequest);
+  }
+
+  public void visit(@Nonnull JThrowingBasicBlock throwingBasicBlock,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JRegularBasicBlock) throwingBasicBlock, transformRequest);
+  }
+
+  public void visit(
+      @Nonnull JThrowingExpressionBasicBlock throwingExpressionBasicBlock,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JThrowingBasicBlock) throwingExpressionBasicBlock, transformRequest);
+  }
+
+  public void visit(@Nonnull JThrowBasicBlock throwBasicBlock,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JThrowingBasicBlock) throwBasicBlock, transformRequest);
+  }
+
+  public void visit(@Nonnull JBasicBlockElement basicBlockElement,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JNode) basicBlockElement, transformRequest);
+  }
+
+  public void visit(@Nonnull JGotoBlockElement gotoBlockElement,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JBasicBlockElement) gotoBlockElement, transformRequest);
+  }
+
+  public void visit(@Nonnull JThrowBlockElement throwBlockElement,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JBasicBlockElement) throwBlockElement, transformRequest);
+  }
+
+  public void visit(@Nonnull JStoreBlockElement storeBlockElement,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JBasicBlockElement) storeBlockElement, transformRequest);
+  }
+
+  public void visit(@Nonnull JVariableAsgBlockElement variableAsgBlockElement,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JBasicBlockElement) variableAsgBlockElement, transformRequest);
+  }
+
+  public void visit(@Nonnull JReturnBlockElement returnBlockElement,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JBasicBlockElement) returnBlockElement, transformRequest);
+  }
+
+  public void visit(@Nonnull JCaseBlockElement caseBlockElement,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JBasicBlockElement) caseBlockElement, transformRequest);
+  }
+
+  public void visit(@Nonnull JMethodCallBlockElement methodCallBlockElement,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JBasicBlockElement) methodCallBlockElement, transformRequest);
+  }
+
+  public void visit(@Nonnull JPolymorphicMethodCallBlockElement methodCallBlockElement,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JBasicBlockElement) methodCallBlockElement, transformRequest);
+  }
+
+  public void visit(@Nonnull JLockBlockElement lockBlockElement,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JBasicBlockElement) lockBlockElement, transformRequest);
+  }
+
+  public void visit(@Nonnull JUnlockBlockElement unlockBlockElement,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JBasicBlockElement) unlockBlockElement, transformRequest);
+  }
+
+  public void visit(@Nonnull JSwitchBlockElement switchBlockElement,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JBasicBlockElement) switchBlockElement, transformRequest);
+  }
+
+  public void visit(@Nonnull JConditionalBlockElement conditionalBlockElement,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JBasicBlockElement) conditionalBlockElement, transformRequest);
+  }
+
   public void visit(
       @Nonnull JReinterpretCastOperation reinterpretCastOperation,
       @Nonnull TransformRequest transformRequest) throws Exception {
@@ -1174,7 +1630,7 @@
 
   public void visit(@Nonnull JMethodBody methodBody, @Nonnull TransformRequest transformRequest)
       throws Exception {
-    visit((JAbstractMethodBody) methodBody, transformRequest);
+    visit((JConcreteMethodBody) methodBody, transformRequest);
   }
 
   public void visit(@Nonnull JPolymorphicMethodCall polymorphicMethodCall,
@@ -1228,9 +1684,9 @@
    * Visit of a {@link JNode} with a {@link TransformRequest} to apply on.
    * @param jnode visited {@link JNode}
    * @param transformRequest {@link TransformRequest} to apply on.
-   */
+   **/
   public void visit(@Nonnull JNode jnode, @Nonnull TransformRequest transformRequest)
-      throws Exception {}
+      throws Exception { }
 
   public void visit(@Nonnull JNullLiteral nullLiteral, @Nonnull TransformRequest transformRequest)
       throws Exception {
@@ -1278,6 +1734,11 @@
     visit((JPhantomClassOrInterface) phantomInterface, transformRequest);
   }
 
+  public void visit(@Nonnull JPhiBlockElement phi,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JBasicBlockElement) phi, transformRequest);
+  }
+
   public void visit(@Nonnull JPostfixOperation postfixOperation,
       @Nonnull TransformRequest transformRequest) throws Exception {
     visit((JUnaryOperation) postfixOperation, transformRequest);
@@ -1313,6 +1774,21 @@
     visit((JNode) statement, transformRequest);
   }
 
+  public void visit(@Nonnull JSsaVariableRef ref,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JVariableRef) ref, transformRequest);
+  }
+
+  public void visit(@Nonnull JSsaVariableDefRef ref,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JSsaVariableRef) ref, transformRequest);
+  }
+
+  public void visit(@Nonnull JSsaVariableUseRef ref,
+      @Nonnull TransformRequest transformRequest) throws Exception {
+    visit((JSsaVariableRef) ref, transformRequest);
+  }
+
   public void visit(@Nonnull JStringLiteral stringLiteral,
       @Nonnull TransformRequest transformRequest) throws Exception {
     visit((JAbstractStringLiteral) stringLiteral, transformRequest);
diff --git a/jack/src/com/android/jack/ir/ast/cfg/BasicBlockComparator.java b/jack/src/com/android/jack/ir/ast/cfg/BasicBlockComparator.java
new file mode 100644
index 0000000..2365f28
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/BasicBlockComparator.java
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JAbstractStringLiteral;
+import com.android.jack.ir.ast.JAlloc;
+import com.android.jack.ir.ast.JArrayLength;
+import com.android.jack.ir.ast.JArrayRef;
+import com.android.jack.ir.ast.JBinaryOperation;
+import com.android.jack.ir.ast.JBooleanLiteral;
+import com.android.jack.ir.ast.JByteLiteral;
+import com.android.jack.ir.ast.JCharLiteral;
+import com.android.jack.ir.ast.JClassLiteral;
+import com.android.jack.ir.ast.JDoubleLiteral;
+import com.android.jack.ir.ast.JDynamicCastOperation;
+import com.android.jack.ir.ast.JEnumLiteral;
+import com.android.jack.ir.ast.JExceptionRuntimeValue;
+import com.android.jack.ir.ast.JExpression;
+import com.android.jack.ir.ast.JFieldRef;
+import com.android.jack.ir.ast.JFloatLiteral;
+import com.android.jack.ir.ast.JInstanceOf;
+import com.android.jack.ir.ast.JIntLiteral;
+import com.android.jack.ir.ast.JLiteral;
+import com.android.jack.ir.ast.JLongLiteral;
+import com.android.jack.ir.ast.JMethodCall;
+import com.android.jack.ir.ast.JMethodLiteral;
+import com.android.jack.ir.ast.JNewArray;
+import com.android.jack.ir.ast.JNode;
+import com.android.jack.ir.ast.JNullLiteral;
+import com.android.jack.ir.ast.JPolymorphicMethodCall;
+import com.android.jack.ir.ast.JReinterpretCastOperation;
+import com.android.jack.ir.ast.JShortLiteral;
+import com.android.jack.ir.ast.JType;
+import com.android.jack.ir.ast.JUnaryOperation;
+import com.android.jack.ir.ast.JValueLiteral;
+import com.android.jack.ir.ast.JVariable;
+import com.android.jack.ir.ast.JVariableRef;
+import com.android.jack.ir.ast.JVisitor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/** Basic implementation of the comparator for basic blocks */
+public class BasicBlockComparator {
+  /** Comparator visitor implements 'shallow' comparison of two nodes */
+  protected static class Comparator extends JVisitor {
+    private boolean differenceFound = false;
+
+    @CheckForNull
+    private JNode other;
+
+    /**
+     * Get the node representing comparison target, ensures the exact node type.
+     * If node types don't match, marks the difference and returns the original node.
+     */
+    @Nonnull
+    @SuppressWarnings(value = "unchecked")
+    protected <T extends JNode> T otherOrMe(@Nonnull T expr) {
+      assert other != null;
+      if (expr.getClass() != other.getClass()) {
+        differenceFound = true;
+        return expr;
+      }
+      return (T) other;
+    }
+
+    /** Check if the value if true, marks the difference otherwise */
+    protected boolean ensure(boolean expectedToBeTrue) {
+      if (!expectedToBeTrue) {
+        differenceFound = true;
+      }
+      return expectedToBeTrue;
+    }
+
+    /** Compare two nodes w/o children */
+    protected boolean areShallowlyEqual(@Nonnull JNode a, @Nonnull JNode b) {
+      assert !differenceFound;
+      other = b;
+      accept(a);
+      return !differenceFound;
+    }
+
+    /** Performs basic checks valid for all kinds of the nodes */
+    protected void performCommonChecks(@Nonnull JNode node) {
+    }
+
+    /** Performs basic checks valid for all kinds of the block elements */
+    protected void performCommonChecks(@Nonnull JBasicBlockElement element) {
+      performCommonChecks((JNode) element);
+
+      List<JCatchBasicBlock> thisContext = element.getEHContext().getCatchBlocks();
+      List<JCatchBasicBlock> otherContext = otherOrMe(element).getEHContext().getCatchBlocks();
+
+      int size = thisContext.size();
+      if (ensure(size == otherContext.size())) {
+        for (int i = 0; i < size; i++) {
+          ensure(thisContext.get(i) == otherContext.get(i));
+        }
+      }
+    }
+
+    /** Performs basic checks valid for all kinds of the basic blocks */
+    protected void performCommonChecks(@Nonnull JBasicBlock block) {
+      performCommonChecks((JNode) block);
+    }
+
+    /** Performs basic checks valid for all kinds of the expressions */
+    protected void performCommonChecks(@Nonnull JExpression expr) {
+      performCommonChecks((JNode) expr);
+      ensure(equal(expr.getType(), otherOrMe(expr).getType()));
+    }
+
+    protected <T extends JType> boolean equal(@Nonnull List<T> a, @Nonnull List<T> b) {
+      if (a.size() != b.size()) {
+        return false;
+      }
+      for (int i = 0; i < a.size(); i++) {
+        if (!equal(a.get(i), b.get(i))) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    protected boolean equal(@Nonnull JType a, @Nonnull JType b) {
+      return a.isSameType(b);
+    }
+
+    protected boolean equal(@Nonnull JVariable a, @Nonnull JVariable b) {
+      return a == b;
+    }
+
+    @Override public boolean visit(@Nonnull JBasicBlock block) {
+      return false;
+    }
+
+    @Override public boolean visit(@Nonnull JBasicBlockElement element) {
+      return false;
+    }
+
+    @Override public void endVisit(@Nonnull JBasicBlock block) {
+      performCommonChecks(block);
+      otherOrMe(block);
+    }
+
+    @Override public void endVisit(@Nonnull JBasicBlockElement element) {
+      performCommonChecks(element);
+      otherOrMe(element);
+    }
+
+    @Override public void endVisit(@Nonnull JConditionalBasicBlock block) {
+      super.endVisit(block);
+      JConditionalBasicBlock other = otherOrMe(block);
+      ensure(other.isInverted() == block.isInverted());
+    }
+
+    @Override public void endVisit(@Nonnull JCatchBasicBlock block) {
+      super.endVisit(block);
+      JCatchBasicBlock other = otherOrMe(block);
+      ensure(equal(other.getCatchTypes(), block.getCatchTypes()));
+    }
+
+    @Override public boolean visit(@Nonnull JExpression expr) {
+      return false;
+    }
+
+    @Override public void endVisit(@Nonnull JExpression expr) {
+      throw new JNodeInternalError(expr,
+          "Unexpected expression in CFG: " + expr.toSource() +
+              " (" + expr.getClass().getSimpleName() + ")");
+    }
+
+    @Override public void endVisit(@Nonnull JMethodCall expr) {
+      performCommonChecks(expr);
+      JMethodCall other = otherOrMe(expr);
+      ensure((expr.getInstance() == null) == (other.getInstance() == null));
+      ensure(equal(expr.getReceiverType(), other.getReceiverType()));
+      ensure(expr.getMethodId().equals(other.getMethodId()));
+      ensure(expr.getDispatchKind() == other.getDispatchKind());
+      ensure(expr.getArgs().size() == other.getArgs().size());
+    }
+
+    @Override public void endVisit(@Nonnull JPolymorphicMethodCall expr) {
+      performCommonChecks(expr);
+      JPolymorphicMethodCall other = otherOrMe(expr);
+      ensure((expr.getInstance() == null) == (other.getInstance() == null));
+      ensure(equal(expr.getReceiverType(), other.getReceiverType()));
+      ensure(expr.getMethodId().equals(other.getMethodId()));
+      ensure(equal(expr.getCallSiteParameterTypes(), other.getCallSiteParameterTypes()));
+      ensure(expr.getArgs().size() == other.getArgs().size());
+    }
+
+    @Override public void endVisit(@Nonnull JFieldRef expr) {
+      performCommonChecks(expr);
+      JFieldRef other = otherOrMe(expr);
+      ensure((expr.getInstance() == null) == (other.getInstance() == null));
+      ensure(equal(expr.getReceiverType(), other.getReceiverType()));
+      ensure(expr.getFieldId().equals(other.getFieldId()));
+    }
+
+    @Override public void endVisit(@Nonnull JArrayRef expr) {
+      performCommonChecks(expr);
+      otherOrMe(expr);
+    }
+
+    @Override public void endVisit(@Nonnull JBinaryOperation expr) {
+      performCommonChecks(expr);
+      JBinaryOperation other = otherOrMe(expr);
+      ensure(expr.getOp() == other.getOp());
+    }
+
+    @Override public void endVisit(@Nonnull JUnaryOperation expr) {
+      performCommonChecks(expr);
+      JUnaryOperation other = otherOrMe(expr);
+      ensure(expr.getOp() == other.getOp());
+    }
+
+    @Override public void endVisit(@Nonnull JExceptionRuntimeValue expr) {
+      performCommonChecks(expr);
+      otherOrMe(expr);
+    }
+
+    @Override public void endVisit(@Nonnull JVariableRef expr) {
+      performCommonChecks(expr);
+      JVariableRef other = otherOrMe(expr);
+      ensure(equal(expr.getTarget(), other.getTarget()));
+    }
+
+    @Override public void endVisit(@Nonnull JLiteral expr) {
+      throw new AssertionError(expr.getClass()); // should be implemented in derived classes
+    }
+
+    @Override public void endVisit(@Nonnull JValueLiteral expr) {
+      throw new AssertionError(expr.getClass()); // should be implemented in derived classes
+    }
+
+    @Override public void endVisit(@Nonnull JClassLiteral expr) {
+      performCommonChecks(expr);
+      JClassLiteral other = otherOrMe(expr);
+      ensure(equal(expr.getRefType(), other.getRefType()));
+    }
+
+    @Override public void endVisit(@Nonnull JMethodLiteral expr) {
+      performCommonChecks(expr);
+      JMethodLiteral other = otherOrMe(expr);
+      ensure(expr.getMethod() == other.getMethod());
+    }
+
+    @Override public void endVisit(@Nonnull JEnumLiteral expr) {
+      performCommonChecks(expr);
+      JEnumLiteral other = otherOrMe(expr);
+      ensure(expr.getFieldId().equals(other.getFieldId()));
+    }
+
+    @Override public void endVisit(@Nonnull JNullLiteral expr) {
+      performCommonChecks(expr);
+      otherOrMe(expr);
+    }
+
+    @Override public void endVisit(@Nonnull JBooleanLiteral expr) {
+      performCommonChecks(expr);
+      JBooleanLiteral other = otherOrMe(expr);
+      ensure(expr.getValue() == other.getValue());
+    }
+
+    @Override public void endVisit(@Nonnull JAbstractStringLiteral expr) {
+      performCommonChecks(expr);
+      JAbstractStringLiteral other = otherOrMe(expr);
+      ensure(expr.getValue().equals(other.getValue()));
+    }
+
+    @Override public void endVisit(@Nonnull JShortLiteral expr) {
+      performCommonChecks(expr);
+      JShortLiteral other = otherOrMe(expr);
+      ensure(expr.getValue() == other.getValue());
+    }
+
+    @Override public void endVisit(@Nonnull JByteLiteral expr) {
+      performCommonChecks(expr);
+      JByteLiteral other = otherOrMe(expr);
+      ensure(expr.getValue() == other.getValue());
+    }
+
+    @Override public void endVisit(@Nonnull JLongLiteral expr) {
+      performCommonChecks(expr);
+      JLongLiteral other = otherOrMe(expr);
+      ensure(expr.getValue() == other.getValue());
+    }
+
+    @Override public void endVisit(@Nonnull JIntLiteral expr) {
+      performCommonChecks(expr);
+      JIntLiteral other = otherOrMe(expr);
+      ensure(expr.getValue() == other.getValue());
+    }
+
+    @Override public void endVisit(@Nonnull JCharLiteral expr) {
+      performCommonChecks(expr);
+      JCharLiteral other = otherOrMe(expr);
+      ensure(expr.getValue() == other.getValue());
+    }
+
+    @Override public void endVisit(@Nonnull JFloatLiteral expr) {
+      performCommonChecks(expr);
+      JFloatLiteral other = otherOrMe(expr);
+      ensure(expr.getValue() == other.getValue());
+    }
+
+    @Override public void endVisit(@Nonnull JDoubleLiteral expr) {
+      performCommonChecks(expr);
+      JDoubleLiteral other = otherOrMe(expr);
+      ensure(expr.getValue() == other.getValue());
+    }
+
+    @Override public void endVisit(@Nonnull JAlloc expr) {
+      performCommonChecks(expr);
+      otherOrMe(expr);
+    }
+
+    @Override public void endVisit(@Nonnull JDynamicCastOperation expr) {
+      performCommonChecks(expr);
+      JDynamicCastOperation other = otherOrMe(expr);
+      ensure(equal(expr.getTypes(), other.getTypes()));
+    }
+
+    @Override public void endVisit(@Nonnull JReinterpretCastOperation expr) {
+      performCommonChecks(expr);
+      JReinterpretCastOperation other = otherOrMe(expr);
+      ensure(equal(expr.getType(), other.getType()));
+    }
+
+    @Override public void endVisit(@Nonnull JInstanceOf expr) {
+      performCommonChecks(expr);
+      JInstanceOf other = otherOrMe(expr);
+      ensure(equal(expr.getTestType(), other.getTestType()));
+    }
+
+    @Override public void endVisit(@Nonnull JArrayLength expr) {
+      performCommonChecks(expr);
+      otherOrMe(expr);
+    }
+
+    @Override public void endVisit(@Nonnull JNewArray expr) {
+      performCommonChecks(expr);
+      JNewArray other = otherOrMe(expr);
+      ensure(equal(expr.getArrayType(), other.getArrayType()));
+      ensure(expr.getDims().size() == other.getDims().size());
+      ensure(expr.getInitializers().size() == other.getInitializers().size());
+    }
+  }
+
+  /** Compares `a` to `b`, returns `true` if they are considered to be equal */
+  public boolean compare(@Nonnull JBasicBlock a, @Nonnull JBasicBlock b) {
+    List<JNode> aNodes = getAllNodesInPostOrder(a);
+    List<JNode> bNodes = getAllNodesInPostOrder(b);
+
+    int size = aNodes.size();
+    if (size != bNodes.size()) {
+      return false;
+    }
+
+    Comparator comparator = getComparator();
+    for (int i = 0; i < size; i++) {
+      if (!comparator.areShallowlyEqual(aNodes.get(i), bNodes.get(i))) {
+        return false;
+      }
+    }
+
+    return !comparator.differenceFound;
+  }
+
+  @Nonnull
+  protected Comparator getComparator() {
+    return new Comparator();
+  }
+
+  @Nonnull
+  private List<JNode> getAllNodesInPostOrder(@Nonnull JBasicBlock block) {
+    final List<JNode> result = new ArrayList<>();
+    new JVisitor() {
+      @Override
+      public void endVisit(@Nonnull JNode e) {
+        result.add(e);
+      }
+    }.accept(block);
+    return result;
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/BasicBlockIterator.java b/jack/src/com/android/jack/ir/ast/cfg/BasicBlockIterator.java
new file mode 100644
index 0000000..a4f892c
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/BasicBlockIterator.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.google.common.collect.Sets;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Stack;
+import javax.annotation.Nonnull;
+
+/** Simplifies iterations through basic blocks */
+public abstract class BasicBlockIterator {
+  @Nonnull
+  private final JControlFlowGraph cfg;
+
+  protected BasicBlockIterator(@Nonnull JControlFlowGraph cfg) {
+    this.cfg = cfg;
+  }
+
+  /** Processes basic block, returns `false` if the iterations should be stopped */
+  public abstract boolean process(@Nonnull JBasicBlock block);
+
+  /**
+   * Iterates basic blocks depth first with weak references. Weakly referenced
+   * blocks are iterated after regular referenced blocks.
+   */
+  public final void iterateDepthFirst() {
+    Stack<JBasicBlock> blocksStack = new Stack<>();
+    Set<JBasicBlock> blocksEverStacked = new HashSet<>();
+
+    Stack<ExceptionHandlingContext> ehcStack = new Stack<>();
+    Set<ExceptionHandlingContext> ehcEverStacked = Sets.newIdentityHashSet();
+
+    JBasicBlock start = cfg.getEntryBlock();
+    blocksStack.push(start);
+    blocksEverStacked.add(start);
+
+    while (true) {
+      if (!blocksStack.isEmpty()) {
+        // Block queue is not empty, process the next block
+        JBasicBlock block = blocksStack.pop();
+        assert blocksEverStacked.contains(block);
+        enqueueBlocks(block.getSuccessors(), blocksStack, blocksEverStacked);
+
+        // Compute the list of exception handling contexts
+        for (JBasicBlockElement element : block.getElements(/* forward: */ false)) {
+          ExceptionHandlingContext ehc = element.getEHContext();
+          if (!ehcEverStacked.contains(ehc)) {
+            ehcStack.push(ehc);
+            ehcEverStacked.add(ehc);
+          }
+        }
+
+        if (!process(block)) {
+          break;
+        }
+
+      } else if (!ehcStack.isEmpty()) {
+        // Otherwise, if the block queue is empty fetch check blocks from
+        // the next exception handling context
+        ExceptionHandlingContext ehc = ehcStack.pop();
+        assert ehcEverStacked.contains(ehc);
+        enqueueBlocks(ehc.getCatchBlocks(), blocksStack, blocksEverStacked);
+
+      } else {
+        // Both stacks are empty
+        break;
+      }
+    }
+  }
+
+  private void enqueueBlocks(@Nonnull List<? extends JBasicBlock> blocks,
+      @Nonnull Stack<JBasicBlock> blocksStack, @Nonnull Set<JBasicBlock> blocksEverStacked) {
+    // Since we use stack to hold the list of the blocks to be processed, we
+    // need to reverse successors lists to visit them not in reversed order
+    for (int i = blocks.size() - 1; i >= 0; i--) {
+      JBasicBlock next = blocks.get(i);
+      if (!blocksEverStacked.contains(next)) {
+        blocksStack.push(next);
+        blocksEverStacked.add(next);
+      }
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/BasicBlockLiveProcessor.java b/jack/src/com/android/jack/ir/ast/cfg/BasicBlockLiveProcessor.java
new file mode 100644
index 0000000..bc78acc
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/BasicBlockLiveProcessor.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.ast.JVisitor;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.Set;
+import javax.annotation.Nonnull;
+
+/**
+ * Simplifies iterations through basic blocks of possibly *mutating* CFG. Since the CFG
+ * may be mutated during iteration, does not guarantee any particular iteration order.
+ *
+ * If new basic blocks are added while mutating CFG, it is possible to add these
+ * newly created blocks with `enqueue(...)`.
+ */
+public abstract class BasicBlockLiveProcessor extends JVisitor {
+  @Nonnull
+  private final Queue<JBasicBlock> queue = new LinkedList<>();
+  @Nonnull
+  private final Set<JBasicBlock> everQueued = new HashSet<>();
+  private final boolean stepIntoElements;
+
+  public BasicBlockLiveProcessor(@Nonnull JControlFlowGraph cfg, boolean stepIntoElements) {
+    this.stepIntoElements = stepIntoElements;
+    new BasicBlockIterator(cfg) {
+      @Override
+      public boolean process(@Nonnull JBasicBlock block) {
+        enqueue(block);
+        return true;
+      }
+    }.iterateDepthFirst();
+  }
+
+  /** Enqueues the element if it was not queued before */
+  protected void enqueue(@Nonnull JBasicBlock block) {
+    if (!everQueued.contains(block)) {
+      everQueued.add(block);
+      queue.add(block);
+    }
+  }
+
+  @Override
+  public boolean visit(@Nonnull JBasicBlock basicBlock) {
+    return stepIntoElements;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JBasicBlockElement element) {
+    return false;
+  }
+
+  /** Process the blocks */
+  public final void process() {
+    while (!queue.isEmpty()) {
+      this.accept(queue.remove());
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/CfgBasicBlockTracker.java b/jack/src/com/android/jack/ir/ast/cfg/CfgBasicBlockTracker.java
new file mode 100644
index 0000000..8fa3b73
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/CfgBasicBlockTracker.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
+import com.android.sched.item.Description;
+import com.android.sched.schedulable.Filter;
+import com.android.sched.schedulable.RunnableSchedulable;
+import com.android.sched.util.log.Tracer;
+import com.android.sched.util.log.TracerFactory;
+import com.android.sched.util.log.stats.ExtendedSample;
+import com.android.sched.util.log.stats.ExtendedSampleImpl;
+import com.android.sched.util.log.stats.StatisticId;
+
+import javax.annotation.Nonnull;
+
+/** Counts and reports the statistics of different basic block kinds. */
+@Description("Counts and reports the statistics of different basic block kinds.")
+@Filter(TypeWithoutPrebuiltFilter.class)
+public class CfgBasicBlockTracker implements RunnableSchedulable<JBasicBlock> {
+  @Nonnull
+  private final Tracer tracer = TracerFactory.getTracer();
+
+  @Nonnull
+  private static final StatisticId<ExtendedSample> TOTAL_COUNT = new StatisticId<>(
+      "jack.cfg.bb.total", "Total basic block statistics",
+      ExtendedSampleImpl.class, ExtendedSample.class);
+  @Nonnull
+  private static final StatisticId<ExtendedSample> RETURN_COUNT = new StatisticId<>(
+      "jack.cfg.bb.return", "Return basic block statistics",
+      ExtendedSampleImpl.class, ExtendedSample.class);
+  @Nonnull
+  private static final StatisticId<ExtendedSample> ENTRY_COUNT = new StatisticId<>(
+      "jack.cfg.bb.entry", "Entry basic block statistics",
+      ExtendedSampleImpl.class, ExtendedSample.class);
+  @Nonnull
+  private static final StatisticId<ExtendedSample> EXIT_COUNT = new StatisticId<>(
+      "jack.cfg.bb.exit", "Exit basic block statistics",
+      ExtendedSampleImpl.class, ExtendedSample.class);
+  @Nonnull
+  private static final StatisticId<ExtendedSample> THROWING_EXPR_COUNT = new StatisticId<>(
+      "jack.cfg.bb.throwing-expr", "Throwing expression basic block statistics",
+      ExtendedSampleImpl.class, ExtendedSample.class);
+  @Nonnull
+  private static final StatisticId<ExtendedSample> THROW_COUNT = new StatisticId<>(
+      "jack.cfg.bb.throw", "Throw basic block statistics",
+      ExtendedSampleImpl.class, ExtendedSample.class);
+  @Nonnull
+  private static final StatisticId<ExtendedSample> SWITCH_COUNT = new StatisticId<>(
+      "jack.cfg.bb.switch", "Switch basic block statistics",
+      ExtendedSampleImpl.class, ExtendedSample.class);
+  @Nonnull
+  private static final StatisticId<ExtendedSample> CASE_COUNT = new StatisticId<>(
+      "jack.cfg.bb.catch", "Case basic block statistics",
+      ExtendedSampleImpl.class, ExtendedSample.class);
+  @Nonnull
+  private static final StatisticId<ExtendedSample> CATCH_COUNT = new StatisticId<>(
+      "jack.cfg.bb.catch", "Catch basic block statistics",
+      ExtendedSampleImpl.class, ExtendedSample.class);
+  @Nonnull
+  private static final StatisticId<ExtendedSample> SIMPLE_COUNT = new StatisticId<>(
+      "jack.cfg.bb.simple", "Simple basic block statistics",
+      ExtendedSampleImpl.class, ExtendedSample.class);
+  @Nonnull
+  private static final StatisticId<ExtendedSample> CONDITIONAL_COUNT = new StatisticId<>(
+      "jack.cfg.bb.conditional", "Conditional basic block statistics",
+      ExtendedSampleImpl.class, ExtendedSample.class);
+
+  private final JVisitor processor = new JVisitor() {
+    @Override public boolean visit(@Nonnull JBasicBlock block) {
+      throw new AssertionError(block.getClass().getSimpleName());
+    }
+
+    @Override public boolean visit(@Nonnull JPlaceholderBasicBlock block) {
+      throw new AssertionError();
+    }
+
+    @Override public boolean visit(@Nonnull JEntryBasicBlock block) {
+      // Don't count in total
+      tracer.getStatistic(ENTRY_COUNT).add(block.getElementCount());
+      return false;
+    }
+
+    @Override public boolean visit(@Nonnull JExitBasicBlock block) {
+      // Don't count in total
+      tracer.getStatistic(EXIT_COUNT).add(block.getElementCount());
+      return false;
+    }
+
+    @Override public boolean visit(@Nonnull JCaseBasicBlock block) {
+      tracer.getStatistic(TOTAL_COUNT).add(block.getElementCount());
+      tracer.getStatistic(CASE_COUNT).add(block.getElementCount());
+      return false;
+    }
+
+    @Override public boolean visit(@Nonnull JCatchBasicBlock block) {
+      tracer.getStatistic(TOTAL_COUNT).add(block.getElementCount());
+      tracer.getStatistic(CATCH_COUNT).add(block.getElementCount());
+      return false;
+    }
+
+    @Override public boolean visit(@Nonnull JSimpleBasicBlock block) {
+      tracer.getStatistic(TOTAL_COUNT).add(block.getElementCount());
+      tracer.getStatistic(SIMPLE_COUNT).add(block.getElementCount());
+      return false;
+    }
+
+    @Override public boolean visit(@Nonnull JConditionalBasicBlock block) {
+      tracer.getStatistic(TOTAL_COUNT).add(block.getElementCount());
+      tracer.getStatistic(CONDITIONAL_COUNT).add(block.getElementCount());
+      return false;
+    }
+
+    @Override public boolean visit(@Nonnull JReturnBasicBlock block) {
+      tracer.getStatistic(TOTAL_COUNT).add(block.getElementCount());
+      tracer.getStatistic(RETURN_COUNT).add(block.getElementCount());
+      return false;
+    }
+
+    @Override public boolean visit(@Nonnull JSwitchBasicBlock block) {
+      tracer.getStatistic(TOTAL_COUNT).add(block.getElementCount());
+      tracer.getStatistic(SWITCH_COUNT).add(block.getElementCount());
+      return false;
+    }
+
+    @Override public boolean visit(@Nonnull JThrowingExpressionBasicBlock block) {
+      tracer.getStatistic(TOTAL_COUNT).add(block.getElementCount());
+      tracer.getStatistic(THROWING_EXPR_COUNT).add(block.getElementCount());
+      return false;
+    }
+
+    @Override public boolean visit(@Nonnull JThrowBasicBlock block) {
+      tracer.getStatistic(TOTAL_COUNT).add(block.getElementCount());
+      tracer.getStatistic(THROW_COUNT).add(block.getElementCount());
+      return false;
+    }
+  };
+
+  @Override
+  public void run(@Nonnull JBasicBlock block) {
+    processor.accept(block);
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/CfgChecker.java b/jack/src/com/android/jack/ir/ast/cfg/CfgChecker.java
new file mode 100644
index 0000000..980cc24
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/CfgChecker.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.ast.JMethodBodyCfg;
+import com.android.jack.ir.ast.JNode;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
+import com.android.jack.transformations.SanityChecks;
+import com.android.sched.item.Description;
+import com.android.sched.schedulable.Filter;
+import com.android.sched.schedulable.RunnableSchedulable;
+import com.android.sched.schedulable.Support;
+
+import java.util.Stack;
+import javax.annotation.Nonnull;
+
+/** Check that AST of JControlFlowGraph nodes is correct. */
+@Description("Check that AST of JControlFlowGraph is correct.")
+@Support(SanityChecks.class)
+@Filter(TypeWithoutPrebuiltFilter.class)
+public class CfgChecker implements RunnableSchedulable<JMethodBodyCfg> {
+  @Override
+  public void run(@Nonnull JMethodBodyCfg body) {
+    new JVisitor(/* needLoading = */ false) {
+      @Nonnull
+      private final Stack<JNode> nodes = new Stack<JNode>();
+
+      @Override
+      public boolean visit(@Nonnull JNode node) {
+        node.checkValidity();
+
+        if (node instanceof JControlFlowGraph) {
+          if (node.getParent() == null) {
+            throw new AssertionError(
+                "Parent of " + JControlFlowGraph.class.getName() + " must not be null.");
+          }
+        } else {
+          if (node.getParent() != nodes.peek()) {
+            throw new AssertionError("Node with wrong parent.");
+          }
+        }
+
+        nodes.push(node);
+        return super.visit(node);
+      }
+
+      @Override
+      public void endVisit(@Nonnull JNode node) {
+        nodes.pop();
+        super.endVisit(node);
+      }
+    }.accept(body.getCfg());
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/CfgExpressionValidator.java b/jack/src/com/android/jack/ir/ast/cfg/CfgExpressionValidator.java
new file mode 100644
index 0000000..ecf4685
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/CfgExpressionValidator.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JAbstractMethodCall;
+import com.android.jack.ir.ast.JAlloc;
+import com.android.jack.ir.ast.JArrayLength;
+import com.android.jack.ir.ast.JArrayRef;
+import com.android.jack.ir.ast.JAsgOperation;
+import com.android.jack.ir.ast.JBinaryOperation;
+import com.android.jack.ir.ast.JConditionalOperation;
+import com.android.jack.ir.ast.JDynamicCastOperation;
+import com.android.jack.ir.ast.JExceptionRuntimeValue;
+import com.android.jack.ir.ast.JExpression;
+import com.android.jack.ir.ast.JFieldRef;
+import com.android.jack.ir.ast.JInstanceOf;
+import com.android.jack.ir.ast.JLiteral;
+import com.android.jack.ir.ast.JLocalRef;
+import com.android.jack.ir.ast.JMethodCall;
+import com.android.jack.ir.ast.JNewArray;
+import com.android.jack.ir.ast.JNode;
+import com.android.jack.ir.ast.JNullLiteral;
+import com.android.jack.ir.ast.JParameterRef;
+import com.android.jack.ir.ast.JPolymorphicMethodCall;
+import com.android.jack.ir.ast.JReinterpretCastOperation;
+import com.android.jack.ir.ast.JSsaVariableRef;
+import com.android.jack.ir.ast.JThisRef;
+import com.android.jack.ir.ast.JUnaryOperation;
+import com.android.jack.ir.ast.JVisitor;
+
+import java.util.NoSuchElementException;
+import javax.annotation.Nonnull;
+
+/** Validates expressions inside the CFG basic block elements */
+class CfgExpressionValidator extends JVisitor {
+  @Nonnull
+  private final JBasicBlockElement blockElement;
+  private final boolean isThrowingExpected;
+
+  private boolean seenThrowingExpression = false;
+
+  private CfgExpressionValidator(@Nonnull JBasicBlockElement element) {
+    this.blockElement = element;
+
+    JBasicBlock basicBlock = element.getBasicBlock();
+    this.isThrowingExpected =
+        basicBlock instanceof JThrowingBasicBlock &&
+            basicBlock.getLastElement() == element;
+  }
+
+  public static void validate(@Nonnull JBasicBlockElement element) {
+    new CfgExpressionValidator(element).accept(element);
+  }
+
+  @Override
+  public boolean visit(@Nonnull JBasicBlockElement element) {
+    if (this.blockElement != element) {
+      throw new JNodeInternalError(element, "Nested block element");
+    }
+    return true;
+  }
+
+  @Override
+  public void endVisit(@Nonnull JBasicBlockElement element) {
+    if (isThrowingExpected) {
+      assert this.blockElement.getBasicBlock() instanceof JThrowingBasicBlock;
+      boolean isImplicitThrow =
+          this.blockElement instanceof JUnlockBlockElement
+              || this.blockElement instanceof JLockBlockElement
+              || this.blockElement instanceof JThrowBlockElement;
+
+      if (isImplicitThrow) {
+        if (seenThrowingExpression) {
+          throw new JNodeInternalError(element,
+              "An unexpected exception is thrown in lock/unlock/throw block element");
+        }
+      } else {
+        if (!seenThrowingExpression) {
+          throw new JNodeInternalError(element,
+              "An exception is expected to be thrown in throwing block element");
+        }
+      }
+    } else {
+      if (seenThrowingExpression) {
+        throw new JNodeInternalError(element,
+            "An unexpected exception is thrown in block element");
+      }
+    }
+    super.endVisit(element);
+  }
+
+  @Override
+  public boolean visit(@Nonnull JExpression expr) {
+    throw new JNodeInternalError(expr,
+        "Unexpected expression in CFG: " + expr.toSource() +
+            " (" + expr.getClass().getSimpleName() + ")");
+  }
+
+  @Override
+  public boolean visit(@Nonnull JMethodCall expr) {
+    return visitMethodCall(expr, JMethodCallBlockElement.class);
+  }
+
+  @Override
+  public boolean visit(@Nonnull JPolymorphicMethodCall expr) {
+    return visitMethodCall(expr, JPolymorphicMethodCallBlockElement.class);
+  }
+
+  @Override
+  public boolean visit(@Nonnull JFieldRef expr) {
+    return visitFieldOrArrayRef(expr);
+  }
+
+  @Override
+  public boolean visit(@Nonnull JArrayRef expr) {
+    return visitFieldOrArrayRef(expr);
+  }
+
+  @Override
+  public boolean visit(@Nonnull JBinaryOperation expr) {
+    if (expr instanceof JConditionalOperation) {
+      throw new JNodeInternalError(expr,
+          "JConditionalOperation is not allowed in cfg-IR");
+    }
+    if (expr instanceof JAsgOperation) {
+      confirmBlockElement(expr);
+      confirmParent(expr, JVariableAsgBlockElement.class, JStoreBlockElement.class);
+
+    } else {
+      // Some of binary operations can throw
+      if (expr.canThrow()) {
+        visitThrowingRValue(expr);
+      } else {
+        visitNonThrowingOperation(expr);
+      }
+    }
+
+    return true;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JUnaryOperation expr) {
+    return visitNonThrowingOperation(expr);
+  }
+
+  @Override
+  public boolean visit(@Nonnull JExceptionRuntimeValue expr) {
+    assert !expr.canThrow();
+    confirmBlockElement(expr);
+    confirmVariableAssignmentRhs(expr, JVariableAsgBlockElement.class);
+    confirmParent(expr.getParent().getParent(), JCatchBasicBlock.class);
+    return true;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JLocalRef expr) {
+    return visitLocalOrParameter(expr);
+  }
+
+  @Override
+  public boolean visit(@Nonnull JSsaVariableRef ref) {
+    return visitLocalOrParameter(ref);
+  }
+
+  @Override
+  public boolean visit(@Nonnull JParameterRef expr) {
+    return visitLocalOrParameter(expr);
+  }
+
+  @Override
+  public boolean visit(@Nonnull JLiteral expr) {
+    return expr.canThrow() ? visitThrowingRValue(expr) : visitNonThrowingTriviaRValue(expr);
+  }
+
+  @Override
+  public boolean visit(@Nonnull JNullLiteral expr) {
+    return visitNonThrowingTriviaRValue(expr);
+  }
+
+  @Override
+  public boolean visit(@Nonnull JThisRef expr) {
+    return visitNonThrowingTriviaRValue(expr);
+  }
+
+  @Override
+  public boolean visit(@Nonnull JAlloc expr) {
+    return visitThrowingRValue(expr);
+  }
+
+  @Override
+  public boolean visit(@Nonnull JDynamicCastOperation expr) {
+    if (expr.getTypes().size() > 1) {
+      throw new JNodeInternalError(expr, "Intersection types are invalid on this level");
+    }
+    return expr.canThrow() ? visitThrowingRValue(expr) : visitNonThrowingOperation(expr);
+  }
+
+  @Override
+  public boolean visit(@Nonnull JReinterpretCastOperation expr) {
+    assert !expr.canThrow();
+    return visitNonThrowingOperation(expr);
+  }
+
+  @Override
+  public boolean visit(@Nonnull JInstanceOf expr) {
+    assert expr.canThrow();
+    return visitThrowingRValue(expr);
+  }
+
+  @Override
+  public boolean visit(@Nonnull JArrayLength expr) {
+    return visitThrowingRValue(expr);
+  }
+
+  @Override
+  public boolean visit(@Nonnull JNewArray expr) {
+    return visitThrowingRValue(expr);
+  }
+
+  private void confirmParent(@Nonnull JNode expr, @Nonnull Class... parents) {
+    JNode parent = expr.getParent();
+    assert parent != null;
+    Class<? extends JNode> actual = parent.getClass();
+    for (Class<?> clazz : parents) {
+      if (clazz.isAssignableFrom(actual)) {
+        return;
+      }
+    }
+
+    StringBuilder builder = new StringBuilder();
+    String sep = "{";
+    for (Class clazz : parents) {
+      builder.append(sep).append(clazz.getSimpleName());
+      sep = " or ";
+    }
+    builder.append("}");
+    throw new JNodeInternalError(expr,
+        "Node must be a child of " + builder.toString() +
+            ", but real parent is: " + parent.getClass().getSimpleName() +
+            ", cfg: " + expr.getParent(JControlFlowGraph.class).toSource());
+  }
+
+  private void confirmBlockElement(@Nonnull JExpression expr) {
+    try {
+      JBasicBlockElement element = expr.getParent(JBasicBlockElement.class);
+      if (element == blockElement) {
+        return;
+      }
+    } catch (NoSuchElementException e) {
+      /* Ignore the exception */
+    }
+
+    throw new JNodeInternalError(expr,
+        "Expression must be in basic block element: " + this.blockElement.toSource());
+  }
+
+  private void confirmThrowingIsAllowed(@Nonnull JExpression expr) {
+    assert expr.canThrow();
+    if (!isThrowingExpected) {
+      throw new JNodeInternalError(expr,
+          "Expression must be in the last element of the trowing basic block: "
+              + this.blockElement.toSource());
+    }
+
+    if (this.seenThrowingExpression) {
+      throw new JNodeInternalError(expr,
+          "Multiple throwing exceptions in block element");
+    }
+
+    this.seenThrowingExpression = true;
+  }
+
+  private void confirmVariableAssignmentRhs(
+      @Nonnull JExpression expr, @Nonnull Class expectedParent) {
+    JNode parent = expr.getParent();
+    if (!(parent instanceof JAsgOperation) ||
+        ((JAsgOperation) parent).getRhs() != expr) {
+      throw new JNodeInternalError(expr,
+          "Expression must be the value in the assignment inside variable "
+              + "assignment block element: " + this.blockElement.toSource());
+    }
+    confirmParent(parent, expectedParent);
+  }
+
+  private void confirmNotAssignmentTarget(@Nonnull JExpression expr) {
+    JNode parent = expr.getParent();
+    if (parent instanceof JAsgOperation &&
+        ((JAsgOperation) parent).getLhs() == expr) {
+      throw new JNodeInternalError(expr,
+          "Expression must NOT be assignment target, "
+              + "block element: " + this.blockElement.toSource());
+    }
+  }
+
+  private void confirmParentForTrivia(@Nonnull JExpression expr) {
+    assert !expr.canThrow();
+    confirmParent(expr,
+        // Methods: both arguments and receiver
+        JMethodCall.class, JPolymorphicMethodCall.class,
+        // Operators
+        JUnaryOperation.class, JBinaryOperation.class,
+        // Expression holding block elements
+        JCaseBlockElement.class, JSwitchBlockElement.class,
+        JConditionalBlockElement.class, JReturnBlockElement.class,
+        JLockBlockElement.class, JUnlockBlockElement.class,
+        JPhiBlockElement.class,
+        // Misc expressions
+        JFieldRef.class, JArrayRef.class, JArrayLength.class, JNewArray.class,
+        JDynamicCastOperation.class, JReinterpretCastOperation.class,
+        JThrowBlockElement.class, JInstanceOf.class);
+  }
+
+  private boolean visitMethodCall(@Nonnull JAbstractMethodCall expr, @Nonnull Class parentClass) {
+    assert expr.canThrow();
+    confirmBlockElement(expr);
+    confirmThrowingIsAllowed(expr);
+
+    // Should be either in standalone method call block element
+    // or in variable assignment block element
+    confirmParent(expr, JAsgOperation.class, parentClass);
+    confirmNotAssignmentTarget(expr);
+
+    return true;
+  }
+
+  private boolean visitFieldOrArrayRef(@Nonnull JExpression expr) {
+    assert expr.canThrow();
+    confirmBlockElement(expr);
+    confirmThrowingIsAllowed(expr);
+
+    // Array/field ref may be referenced either in assignment or load context
+    confirmParent(expr, JAsgOperation.class);
+    JAsgOperation asgExpr = (JAsgOperation) expr.getParent();
+    if (asgExpr.getLhs() == expr) {
+      // Assignment context, assignment must be a child of store block element
+      confirmParent(asgExpr, JStoreBlockElement.class);
+
+    } else {
+      // Load context, value in variable assignment block element
+      confirmVariableAssignmentRhs(expr, JVariableAsgBlockElement.class);
+    }
+    return true;
+  }
+
+  private boolean visitLocalOrParameter(@Nonnull JExpression expr) {
+    confirmBlockElement(expr);
+    confirmParentForTrivia(expr);
+
+    JNode parent = expr.getParent();
+    if (parent instanceof JAsgOperation) {
+      if (((JAsgOperation) parent).getLhs() == expr) {
+        // Assignment context, assignment must be part of variable assignment block element
+        confirmParent(parent, JVariableAsgBlockElement.class);
+      }
+    }
+    return true;
+  }
+
+  private boolean visitThrowingRValue(@Nonnull JExpression expr) {
+    assert expr.canThrow();
+    confirmBlockElement(expr);
+    confirmThrowingIsAllowed(expr);
+    confirmVariableAssignmentRhs(expr, JVariableAsgBlockElement.class);
+    return true;
+  }
+
+  private boolean visitNonThrowingTriviaRValue(@Nonnull JExpression expr) {
+    assert !expr.canThrow();
+    confirmBlockElement(expr);
+    confirmParentForTrivia(expr);
+    confirmNotAssignmentTarget(expr);
+    return true;
+  }
+
+  private boolean visitNonThrowingOperation(@Nonnull JExpression expr) {
+    assert !expr.canThrow();
+    confirmBlockElement(expr);
+    if (expr instanceof JReinterpretCastOperation) {
+      // It is probably not the right thing to allow reinterpret cast
+      // as an instance of array/field access or the receiver of the
+      // method call, keeping it this way until we discuss it
+      confirmParent(expr, JAsgOperation.class,
+          JArrayRef.class, JFieldRef.class, JMethodCall.class);
+
+    } else if (expr instanceof JDynamicCastOperation) {
+      confirmParent(expr, JAsgOperation.class);
+
+    } else {
+      confirmParent(expr, JConditionalBlockElement.class, JAsgOperation.class);
+    }
+    confirmNotAssignmentTarget(expr);
+    return true;
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/ControlFlowGraphSizeTracker.java b/jack/src/com/android/jack/ir/ast/cfg/ControlFlowGraphSizeTracker.java
new file mode 100644
index 0000000..fde2381
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/ControlFlowGraphSizeTracker.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.Jack;
+import com.android.jack.ir.ast.JMethod;
+import com.android.jack.ir.formatter.TypePackageAndMethodFormatter;
+import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
+import com.android.sched.item.Description;
+import com.android.sched.schedulable.Filter;
+import com.android.sched.schedulable.RunnableSchedulable;
+import com.android.sched.util.log.Tracer;
+import com.android.sched.util.log.TracerFactory;
+import com.android.sched.util.log.stats.ExtendedSample;
+import com.android.sched.util.log.stats.ExtendedSampleImpl;
+import com.android.sched.util.log.stats.StatisticId;
+
+import javax.annotation.Nonnull;
+
+/** Counts and reports the number of control flow graphs. */
+@Description("Counts and reports the number of control flow graphs.")
+@Filter(TypeWithoutPrebuiltFilter.class)
+public class ControlFlowGraphSizeTracker implements RunnableSchedulable<JControlFlowGraph> {
+  @Nonnull
+  public static final StatisticId<ExtendedSample> STATISTICS = new StatisticId<>(
+      "jack.cfg.statistics", "Control flow graphs statistics",
+      ExtendedSampleImpl.class, ExtendedSample.class);
+
+  @Nonnull
+  private final Tracer tracer = TracerFactory.getTracer();
+  @Nonnull
+  private final TypePackageAndMethodFormatter formatter = Jack.getUserFriendlyFormatter();
+
+  @Override
+  public void run(@Nonnull final JControlFlowGraph cfg) {
+    JMethod method = cfg.getMethod();
+    tracer.getStatistic(STATISTICS).add(cfg.getAllBlocksUnordered().size(),
+        formatter.getName(method) + " [" + formatter.getName(method.getEnclosingType()) + "]");
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/ExceptionHandlingContext.java b/jack/src/com/android/jack/ir/ast/cfg/ExceptionHandlingContext.java
new file mode 100644
index 0000000..b0287bd
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/ExceptionHandlingContext.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import com.android.jack.Jack;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nonnull;
+
+/** Specifies the ordered set of catch blocks handling exceptions */
+public final class ExceptionHandlingContext {
+  @Nonnull
+  public static final ExceptionHandlingContext EMPTY =
+      new ExceptionHandlingContext(Collections.<JCatchBasicBlock>emptyList());
+
+  @Nonnull
+  private final List<JCatchBasicBlock> catchBlocks;
+
+  private ExceptionHandlingContext(@Nonnull List<JCatchBasicBlock> catchBlocks) {
+    this.catchBlocks = catchBlocks.isEmpty()
+        ? Collections.<JCatchBasicBlock>emptyList() : ImmutableList.copyOf(catchBlocks);
+  }
+
+  @Nonnull
+  public static ExceptionHandlingContext create(@Nonnull List<JCatchBasicBlock> catchBlocks) {
+    return catchBlocks.isEmpty() ? EMPTY
+        : new ExceptionHandlingContext(Jack.getUnmodifiableCollections()
+            .getUnmodifiableList(Lists.newArrayList(catchBlocks)));
+  }
+
+  @Nonnull
+  public List<JCatchBasicBlock> getCatchBlocks() {
+    return catchBlocks;
+  }
+
+  /** Creates a pool of unique exception handler contexts */
+  static class Pool {
+    @Nonnull
+    private final Map<List<JCatchBasicBlock>, ExceptionHandlingContext> pool = new HashMap<>();
+
+    @Nonnull
+    ExceptionHandlingContext getOrCreate(@Nonnull List<JCatchBasicBlock> catchBlocks) {
+      ExceptionHandlingContext ehc = pool.get(catchBlocks);
+      if (ehc == null) {
+        ehc = ExceptionHandlingContext.create(catchBlocks);
+        pool.put(ehc.getCatchBlocks(), ehc);
+      }
+      return ehc;
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JBasicBlock.java b/jack/src/com/android/jack/ir/ast/cfg/JBasicBlock.java
new file mode 100644
index 0000000..016c38c
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JBasicBlock.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.google.common.collect.Lists;
+
+import com.android.jack.Jack;
+import com.android.jack.ir.ast.JNode;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.jack.util.graph.IGraphNode;
+import com.android.sched.item.Description;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+
+/** Represents an abstract CFG basic block implementation */
+@Description("CFG Basic Block")
+public abstract class JBasicBlock extends JNode implements IGraphNode<JBasicBlock> {
+  @Nonnull
+  private final ArrayList<JBasicBlock> predecessors = new ArrayList<>();
+
+  JBasicBlock() {
+    super(SourceInfo.UNKNOWN);
+    // NOTE: we cannot pass cfg to set up the parent at this phase,
+    // because call updateParents() will try to visit the basic block
+    // being constructed and it is not constructed yet.
+    //
+    // The CFG parameter must be a part of LEAF basic block constructor
+    // signature and must be set at the end of basic block construction
+  }
+
+  @Nonnull
+  public JControlFlowGraph getCfg() {
+    JNode parent = this.getParent();
+    assert parent instanceof JControlFlowGraph;
+    return (JControlFlowGraph) parent;
+  }
+
+  /** Immutable successors snapshot */
+  @Nonnull
+  public abstract List<JBasicBlock> getSuccessors();
+
+  /** Immutable list of all block elements */
+  @Nonnull
+  public final List<JBasicBlockElement> getElements() {
+    return getElements(true);
+  }
+
+  /** Immutable list of all block elements, can be forward or backward */
+  @Nonnull
+  public abstract List<JBasicBlockElement> getElements(boolean forward);
+
+  /** Elements count */
+  @Nonnegative
+  public abstract int getElementCount();
+
+  /** Returns the first element, element must exist */
+  @Nonnull
+  public abstract JBasicBlockElement getFirstElement();
+
+  /** Returns the last element, element must exist */
+  @Nonnull
+  public abstract JBasicBlockElement getLastElement();
+
+  /** Is the element list empty? */
+  public abstract boolean hasElements();
+
+  /** Appends a new basic block element at the end of the basic block */
+  public abstract void appendElement(@Nonnull JBasicBlockElement element);
+
+  /**
+   * Inserts a new basic block element into the list of this basic block's elements.
+   * <p>
+   * Index may be positive or negative:
+   * <li> <b>at == 0</b>: `element` is inserted in the beginning </li>
+   * <li> <b>at == elementCount()</b>: equal to appendElement(element) </li>
+   * <li> <b>at == 1</b>: `element` is inserted after the first element
+   * (there must be at least one element already) </li>
+   * <li> <b>at == -1</b>: `element` is inserted before the last element
+   * (there must be at least one element already) </li>
+   */
+  public abstract void insertElement(int at, @Nonnull JBasicBlockElement element);
+
+  /** Removes the specified element from the basic block */
+  public abstract void removeElement(@Nonnull JBasicBlockElement element);
+
+  /** Replace all the successors equal to 'what' with 'with' */
+  public abstract void replaceAllSuccessors(@Nonnull JBasicBlock what, @Nonnull JBasicBlock with);
+
+  /** The number of predecessors */
+  @Nonnegative
+  public final int getPredecessorCount() {
+    return predecessors.size();
+  }
+
+  /** Immutable predecessors' list */
+  public final List<JBasicBlock> getPredecessors() {
+    return Jack.getUnmodifiableCollections().getUnmodifiableList(predecessors);
+  }
+
+  /** Snapshot of predecessors' list */
+  public final List<JBasicBlock> getPredecessorsSnapshot() {
+    return Lists.newArrayList(predecessors);
+  }
+
+  @Override
+  @Nonnull
+  public Iterable<JBasicBlock> getSuccessorsIterable() {
+    return getSuccessors();
+  }
+
+  @Override
+  @Nonnull
+  public Iterable<JBasicBlock> getPredecessorsIterable() {
+    return getPredecessors();
+  }
+
+  /**
+   * Splits the block at the location `at` into two blocks:
+   * <ul>
+   * <li> a new {@link JSimpleBasicBlock} with the elements [0..at) of the original
+   * basic block and goto block element at the end</li>
+   * <li> the original block with remaining elements </li>
+   * </ul>
+   * After splitting, all the predecessors of the original block re-pint to
+   * the first block, which becomes the only predecessor of the original block.
+   * <p/>
+   * Note that `at` may be negative, with semantics same as in `insertElement(...)`.
+   * <p/>
+   * Value of `at` must not be pointing after the last element of
+   * the block, since the last element must stay in the original block.
+   *
+   * <pre>
+   *   Original:
+   *      block {e0, e1, ... eAt, ... eN}
+   *
+   *   Split at '0':
+   *      simple-block {goto-element} --->
+   *          block {e0, e1, ... eAt, ... eN}
+   *
+   *   Split at I:
+   *      simple-block {e0, e1, ... e(I-1), goto-element} --->
+   *          block {eI, ... eN}
+   * </pre>
+   *
+   * Note also that some of the block kinds don't support splitting.
+   */
+  @Nonnull
+  public abstract JSimpleBasicBlock split(int at);
+
+  /** Return the index of the block element, element must exist */
+  @Nonnegative
+  public abstract int indexOf(@Nonnull JBasicBlockElement element);
+
+  /**
+   * Detaches `this` from CFG by de-referencing all successors, the block
+   * must not have any predecessors.
+   */
+  public void detach() {
+    if (!this.predecessors.isEmpty()) {
+      throw new IllegalStateException("The basic block must not have predecessors");
+    }
+
+    // De-reference all successors
+    for (JBasicBlock successor : getSuccessors()) {
+      replaceAllSuccessors(successor, this);
+    }
+  }
+
+  /**
+   * Detaches `this` from CFG by replacing it with the `newBlock` in all
+   * predecessors of `this` block and also de-referencing all successors.
+   * Returns `newBlock`.
+   */
+  @Nonnull
+  public JBasicBlock detach(@Nonnull JBasicBlock newBlock) {
+    // Redirect all the predecessors to point `newBlock`
+    if (!this.predecessors.isEmpty()) {
+      for (JBasicBlock pre : this.getPredecessorsSnapshot()) {
+        pre.replaceAllSuccessors(this, newBlock);
+      }
+    }
+    // De-reference all successors
+    for (JBasicBlock successor : getSuccessors()) {
+      replaceAllSuccessors(successor, this);
+    }
+    return newBlock;
+  }
+
+  /**
+   * Resets successor/predecessor references for replacing
+   * `current` with `candidate`, returns `candidate`.
+   */
+  @Nonnull
+  JBasicBlock resetSuccessor(@Nonnull JBasicBlock current, @Nonnull JBasicBlock candidate) {
+    if (candidate != current) {
+      current.removePredecessor(this);
+      candidate.addPredecessor(this);
+    }
+    return candidate;
+  }
+
+  /** Removes the reference of a successor */
+  void removeSuccessor(@Nonnull JBasicBlock current) {
+    current.removePredecessor(this);
+  }
+
+  /** Remove predecessor */
+  private void removePredecessor(@Nonnull JBasicBlock predecessor) {
+    assert predecessors.contains(predecessor);
+    int sizeM1 = predecessors.size() - 1;
+    for (int idx = 0; idx < sizeM1; idx++) {
+      if (predecessors.get(idx) == predecessor) {
+        predecessors.set(idx, predecessors.get(sizeM1));
+        break;
+      }
+    }
+    predecessors.remove(sizeM1);
+  }
+
+  /** Add predecessor */
+  void addPredecessor(@Nonnull JBasicBlock predecessor) {
+    predecessors.add(predecessor);
+  }
+
+  public void checkValidity() {
+    // NOTE: only check basic-block validity, each block element will be validated later
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JBasicBlockElement.java b/jack/src/com/android/jack/ir/ast/cfg/JBasicBlockElement.java
new file mode 100644
index 0000000..4ee540e
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JBasicBlockElement.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.google.common.collect.Lists;
+
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JAsgOperation;
+import com.android.jack.ir.ast.JNode;
+import com.android.jack.ir.ast.JVariableRef;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/** Represents an abstract CFG basic block element. */
+public abstract class JBasicBlockElement extends JNode {
+  @Nonnull
+  private ExceptionHandlingContext ehc;
+
+  JBasicBlockElement(@Nonnull SourceInfo info, @Nonnull ExceptionHandlingContext ehc) {
+    super(info);
+    this.ehc = ehc;
+  }
+
+  @Nonnull
+  public JBasicBlock getBasicBlock() {
+    JNode parent = getParent();
+    assert parent instanceof JBasicBlock;
+    return (JBasicBlock) parent;
+  }
+
+  /** Is this a terminal basic block element */
+  public abstract boolean isTerminal();
+
+  /** Get exception handling context */
+  @Nonnull
+  public ExceptionHandlingContext getEHContext() {
+    return ehc;
+  }
+
+  /** Reset exception handling context, update basic block */
+  public void resetEHContext(@Nonnull ExceptionHandlingContext ehc) {
+    this.ehc = ehc;
+  }
+
+  @Nonnull
+  public List<JVariableRef> getUsedVariables() {
+    final List<JVariableRef> result = Lists.newArrayList();
+    (new JVisitor() {
+      @Override
+      public boolean visit(@Nonnull JVariableRef varRef) {
+        JNode parent = varRef.getParent();
+        if (!(parent instanceof JAsgOperation) || ((JAsgOperation) parent).getLhs() != varRef) {
+          result.add(varRef);
+        }
+        return super.visit(varRef);
+      }
+    }).accept(this);
+    return result;
+  }
+
+  @CheckForNull
+  public JVariableRef getDefinedVariable() {
+    return null;
+  }
+
+  @Override
+  public abstract void traverse(@Nonnull JVisitor visitor);
+
+  @Override
+  public abstract void traverse(@Nonnull ScheduleInstance<? super Component> schedule)
+      throws Exception;
+
+  @Override
+  public abstract void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request)
+      throws Exception;
+
+  @Override
+  public void checkValidity() {
+    CfgExpressionValidator.validate(this);
+
+    if (this.isTerminal()) {
+      if (this != getBasicBlock().getLastElement()) {
+        throw new JNodeInternalError(this,
+            "Terminal block element must be the last element of the block");
+      }
+    } else {
+      if (this == getBasicBlock().getLastElement()) {
+        throw new JNodeInternalError(this,
+            "Non-terminal block element must NOT be the last element of the block");
+      }
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JCaseBasicBlock.java b/jack/src/com/android/jack/ir/ast/cfg/JCaseBasicBlock.java
new file mode 100644
index 0000000..08e27fc
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JCaseBasicBlock.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import javax.annotation.Nonnull;
+
+/** Represents case basic block. */
+public final class JCaseBasicBlock extends JRegularBasicBlock {
+  public JCaseBasicBlock(@Nonnull JControlFlowGraph cfg, @Nonnull JBasicBlock primary) {
+    super(primary);
+    updateParents(cfg);
+  }
+
+  @Override
+  public boolean hasPrimarySuccessor() {
+    return true;
+  }
+
+  @Nonnull
+  @Override
+  public JSimpleBasicBlock split(int at) {
+    // Case blocks are referenced directly by JSwitchBasicBlock and cannot
+    // be split so that there is another block in between switch block and
+    // case block. Consider splitting the only successor of the case block.
+    // NOTE: this is also true in SSA form
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    if (visitor.visit(this)) {
+      acceptElements(visitor);
+    }
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+    traverseElements(schedule);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+    visitor.visit(this, request);
+  }
+
+  @Override
+  public void checkValidity() {
+    super.checkValidity();
+
+    if (!getCfg().isInSsaForm()) {
+      if (getElementCount() > 1) {
+        throw new JNodeInternalError(this,
+            "JCaseBasicBlock must always have one single element");
+      }
+    }
+
+    for (JBasicBlock predecessor : this.getPredecessors()) {
+      if (!(predecessor instanceof JSwitchBasicBlock)) {
+        throw new JNodeInternalError(this,
+            "JCaseBasicBlock must only have JSwitchBasicBlock predecessors");
+      }
+    }
+
+    if (!(getLastElement() instanceof JCaseBlockElement)) {
+      throw new JNodeInternalError(this,
+          "The last element of the block must be case element");
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JCaseBlockElement.java b/jack/src/com/android/jack/ir/ast/cfg/JCaseBlockElement.java
new file mode 100644
index 0000000..1099893
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JCaseBlockElement.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JLiteral;
+import com.android.jack.ir.ast.JNode;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/** Represents case branch value basic block element */
+public final class JCaseBlockElement extends JBasicBlockElement {
+  /** The value is null in case the block represents the default case of the switch */
+  @CheckForNull
+  private JLiteral literal;
+
+  JCaseBlockElement(@Nonnull SourceInfo info,
+      @Nonnull ExceptionHandlingContext ehc, @CheckForNull JLiteral literal) {
+    super(info, ehc);
+    this.literal = literal;
+  }
+
+  @CheckForNull
+  public JLiteral getLiteral() {
+    return literal;
+  }
+
+  @Override
+  @Nonnull
+  public JCaseBasicBlock getBasicBlock() {
+    JBasicBlock block = super.getBasicBlock();
+    assert block instanceof JCaseBasicBlock;
+    return (JCaseBasicBlock) block;
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    if (visitor.visit(this)) {
+      if (literal != null) {
+        visitor.accept(literal);
+      }
+    }
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+    if (literal != null) {
+      literal.traverse(schedule);
+    }
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+    visitor.visit(this, request);
+  }
+
+  @Override
+  public boolean isTerminal() {
+    return true;
+  }
+
+  @Override
+  public void checkValidity() {
+    super.checkValidity();
+
+    if (!(super.getBasicBlock() instanceof JCaseBasicBlock)) {
+      throw new JNodeInternalError(this, "The parent node must be JCaseBasicBlock");
+    }
+  }
+
+  @Override
+  protected void replaceImpl(
+      @Nonnull JNode existingNode, @Nonnull JNode newNode) throws UnsupportedOperationException {
+    if (literal == existingNode) {
+      literal = (JLiteral) newNode;
+    } else {
+      super.replaceImpl(existingNode, newNode);
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JCatchBasicBlock.java b/jack/src/com/android/jack/ir/ast/cfg/JCatchBasicBlock.java
new file mode 100644
index 0000000..1187ad5
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JCatchBasicBlock.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.Jack;
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JClass;
+import com.android.jack.ir.ast.JLocal;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import java.util.List;
+import javax.annotation.Nonnull;
+
+/**
+ * Represents catch basic block.
+ *
+ * Catch basic block is intended to carry catch specific information and
+ * always has one catch variable assignment element.
+ */
+public final class JCatchBasicBlock extends JRegularBasicBlock {
+  @Nonnull
+  private final List<JClass> catchTypes;
+  @Nonnull
+  private final JLocal catchLocal;
+
+  public JCatchBasicBlock(@Nonnull JControlFlowGraph cfg, @Nonnull JBasicBlock primary,
+      @Nonnull List<JClass> catchTypes, @Nonnull JLocal catchLocal) {
+    super(primary);
+    this.catchTypes = catchTypes;
+    this.catchLocal = catchLocal;
+    updateParents(cfg);
+    catchLocal.updateParents(this);
+    catchLocal.setEnclosingMethodBody(cfg.getMethodBody());
+    cfg.getMethodBody().addCatchLocal(catchLocal);
+  }
+
+  @Override
+  public boolean hasPrimarySuccessor() {
+    return true;
+  }
+
+  @Nonnull
+  @Override
+  public JSimpleBasicBlock split(int at) {
+    // Catch blocks are referenced directly by JThrowingBasicBlock and cannot
+    // be split so that there is another block in between throwing block and
+    // catch block. Consider splitting the only successor of the catch block.
+    // NOTE: this is also true in SSA form
+    throw new UnsupportedOperationException();
+  }
+
+  @Nonnull
+  public List<JClass> getCatchTypes() {
+    return Jack.getUnmodifiableCollections().getUnmodifiableList(catchTypes);
+  }
+
+  @Nonnull
+  public JLocal getCatchLocal() {
+    return catchLocal;
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    if (visitor.visit(this)) {
+      visitor.accept(catchLocal);
+      acceptElements(visitor);
+    }
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+    catchLocal.traverse(schedule);
+    traverseElements(schedule);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+    visitor.visit(this, request);
+  }
+
+  @Override
+  public void checkValidity() {
+    super.checkValidity();
+
+    if (!getCfg().isInSsaForm()) {
+      if (getElementCount() > 1) {
+        throw new JNodeInternalError(this,
+            "JCatchBasicBlock must always have one single element");
+      }
+    }
+
+    for (JBasicBlock predecessor : this.getPredecessors()) {
+      if (!(predecessor instanceof JThrowingBasicBlock)) {
+        throw new JNodeInternalError(this,
+            "JCatchBasicBlock must only have JThrowingBasicBlock predecessors");
+      }
+    }
+
+    if (!(getLastElement() instanceof JVariableAsgBlockElement) ||
+        !((JVariableAsgBlockElement) getLastElement()).isCatchVariableAssignment()) {
+      throw new JNodeInternalError(this,
+          "The last element of the block must be catch variable assignment element");
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JConditionalBasicBlock.java b/jack/src/com/android/jack/ir/ast/cfg/JConditionalBasicBlock.java
new file mode 100644
index 0000000..91c1b65
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JConditionalBasicBlock.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.Nonnull;
+
+/** Represents blocks ending with conditional branching. */
+public final class JConditionalBasicBlock extends JRegularBasicBlock {
+  @Nonnull
+  private JBasicBlock ifFalse;
+
+  /**
+   * If true, ensures that the primary successor is the one specified as isFalse,
+   * and the alternative successor if the one specified as ifTrue.
+   */
+  private boolean inverted = false;
+
+  public JConditionalBasicBlock(@Nonnull JControlFlowGraph cfg,
+      @Nonnull JBasicBlock ifTrue, @Nonnull JBasicBlock ifFalse) {
+    super(ifTrue);
+    this.ifFalse = ifFalse;
+    this.ifFalse.addPredecessor(this);
+    updateParents(cfg);
+  }
+
+  @Override
+  public boolean hasPrimarySuccessor() {
+    return true;
+  }
+
+  /** Primary successor is getIfTrue() unless the block marked as inverted */
+  @Override
+  @Nonnull
+  public JBasicBlock getPrimarySuccessor() {
+    return inverted ? getIfFalse() : getIfTrue();
+  }
+
+  /** The other, not-primary successor */
+  @Nonnull
+  public JBasicBlock getAlternativeSuccessor() {
+    return inverted ? getIfTrue() : getIfFalse();
+  }
+
+  /** Returns the branch taken if the condition is `true` */
+  @Nonnull
+  public final JBasicBlock getIfTrue() {
+    return super.getPrimarySuccessor();
+  }
+
+  /** Returns the branch taken if the condition is `false` */
+  @Nonnull
+  public final JBasicBlock getIfFalse() {
+    return ifFalse;
+  }
+
+  /**
+   * Is `true` if getIfFalse() successor should be considered a primary successor,
+   * is used in codegen to indicate if ifTrue or ifFalse branch is default.
+   */
+  public boolean isInverted() {
+    return inverted;
+  }
+
+  public void setInverted(boolean inverted) {
+    this.inverted = inverted;
+  }
+
+  @Nonnull
+  @Override
+  public List<JBasicBlock> getSuccessors() {
+    ArrayList<JBasicBlock> successors = new ArrayList<>();
+    successors.add(getIfTrue());
+    successors.add(ifFalse);
+    return successors;
+  }
+
+  @Override
+  public void replaceAllSuccessors(@Nonnull JBasicBlock what, @Nonnull JBasicBlock with) {
+    super.replaceAllSuccessors(what, with);
+    if (this.ifFalse == what) {
+      this.ifFalse = resetSuccessor(what, with);
+    }
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    if (visitor.visit(this)) {
+      acceptElements(visitor);
+    }
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+    traverseElements(schedule);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+    visitor.visit(this, request);
+  }
+
+  @Override
+  public void checkValidity() {
+    super.checkValidity();
+
+    if (!(getLastElement() instanceof JConditionalBlockElement)) {
+      throw new JNodeInternalError(this,
+          "The last element of the block must be conditional element");
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JConditionalBlockElement.java b/jack/src/com/android/jack/ir/ast/cfg/JConditionalBlockElement.java
new file mode 100644
index 0000000..0c889af
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JConditionalBlockElement.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JExpression;
+import com.android.jack.ir.ast.JNode;
+import com.android.jack.ir.ast.JPrimitiveType;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import javax.annotation.Nonnull;
+
+/** Represents conditional basic block element */
+public final class JConditionalBlockElement extends JBasicBlockElement {
+  @Nonnull
+  private JExpression cond;
+
+  public JConditionalBlockElement(@Nonnull SourceInfo info,
+      @Nonnull ExceptionHandlingContext ehc, @Nonnull JExpression cond) {
+    super(info, ehc);
+    assert !cond.canThrow();
+    assert JPrimitiveType.JPrimitiveTypeEnum.BOOLEAN.getType().isSameType(cond.getType());
+    this.cond = cond;
+  }
+
+  @Nonnull
+  public JExpression getCondition() {
+    return cond;
+  }
+
+  @Override
+  @Nonnull
+  public JConditionalBasicBlock getBasicBlock() {
+    JBasicBlock block = super.getBasicBlock();
+    assert block instanceof JConditionalBasicBlock;
+    return (JConditionalBasicBlock) block;
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    if (visitor.visit(this)) {
+      visitor.accept(cond);
+    }
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+    cond.traverse(schedule);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+    visitor.visit(this, request);
+  }
+
+  @Override
+  public boolean isTerminal() {
+    return true;
+  }
+
+  @Override
+  public void checkValidity() {
+    super.checkValidity();
+
+    if (!(super.getBasicBlock() instanceof JConditionalBasicBlock)) {
+      throw new JNodeInternalError(this, "The parent node must be JConditionalBasicBlock");
+    }
+  }
+
+  @Override
+  protected void replaceImpl(
+      @Nonnull JNode existingNode, @Nonnull JNode newNode) throws UnsupportedOperationException {
+    if (cond == existingNode) {
+      cond = (JExpression) newNode;
+    } else {
+      super.replaceImpl(existingNode, newNode);
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JControlFlowGraph.java b/jack/src/com/android/jack/ir/ast/cfg/JControlFlowGraph.java
new file mode 100644
index 0000000..d486fbb
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JControlFlowGraph.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.google.common.collect.Lists;
+
+import com.android.jack.ir.ast.JMethod;
+import com.android.jack.ir.ast.JMethodBodyCfg;
+import com.android.jack.ir.ast.JNode;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.jack.util.graph.IGraph;
+import com.android.sched.item.Component;
+import com.android.sched.item.Description;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Stack;
+import javax.annotation.Nonnull;
+
+/** Represents method control flow graph */
+@Description("Control Flow Graph")
+public final class JControlFlowGraph extends JNode implements IGraph<JBasicBlock> {
+  @Nonnull
+  private final JEntryBasicBlock entry;
+  @Nonnull
+  private final JExitBasicBlock exit;
+
+  /** Specifies if the CFG is in SSA form or not, used for enforcing constraints */
+  private boolean inSsaForm = false;
+
+  public JControlFlowGraph(@Nonnull SourceInfo info) {
+    super(info);
+    this.exit = new JExitBasicBlock(this);
+    this.entry = new JEntryBasicBlock(this, exit);
+  }
+
+  @Nonnull
+  public final JEntryBasicBlock getEntryBlock() {
+    return entry;
+  }
+
+  @Nonnull
+  public final JExitBasicBlock getExitBlock() {
+    return exit;
+  }
+
+  @Nonnull
+  public JMethod getMethod() {
+    return getMethodBody().getMethod();
+  }
+
+  /** Is the CFG is in SSA form or not */
+  public boolean isInSsaForm() {
+    return inSsaForm;
+  }
+
+  /** Sets or clears CFG SSA form status */
+  public void setInSsaForm(boolean inSsaForm) {
+    assert this.inSsaForm != inSsaForm;
+    this.inSsaForm = inSsaForm;
+  }
+
+  @Nonnull
+  public JMethodBodyCfg getMethodBody() {
+    JNode parent = getParent();
+    assert parent instanceof JMethodBodyCfg;
+    return (JMethodBodyCfg) parent;
+  }
+
+  /**
+   * Returns all basic blocks reachable from entry block in depth-first order,
+   * ALSO returns basic blocks 'weakly referenced' via exception handling context.
+   *
+   * Note that the entry block is always the first block of the list, and the
+   * exist block may not be present in this list.
+   */
+  @Nonnull
+  public List<JBasicBlock> getReachableBlocksDepthFirst() {
+    final List<JBasicBlock> blocks = new ArrayList<>();
+    new BasicBlockIterator(this) {
+      @Override public boolean process(@Nonnull JBasicBlock block) {
+        blocks.add(block);
+        return true;
+      }
+    }.iterateDepthFirst();
+    return blocks;
+  }
+
+  /**
+   * Returns all basic blocks reachable from entry or exit blocks via
+   * successor/predecessor edges OR 'weakly referenced' via exception
+   * handling context.
+   *
+   * Note that basic blocks returned by this method represent a superset
+   * of the blocks returned by getReachableBlocksDepthFirst().
+   *
+   * The blocks are returned in stable order, i.e. two calls to this method
+   * will return the same sequence of the blocks.
+   */
+  @Nonnull
+  public List<JBasicBlock> getAllBlocksUnordered() {
+    return Lists.newArrayList(getInternalBasicBlocks(false));
+  }
+
+  /**
+   * Returns all basic blocks reachable from entry or exit blocks via
+   * successor/predecessor edges OR 'weakly referenced' via exception
+   * handling context EXCEPT entry and exit nodes themselves.
+   *
+   * The blocks are returned in stable order, i.e. two calls to this method
+   * will return the same sequence of the blocks.
+   */
+  @Nonnull
+  public List<JBasicBlock> getInternalBlocksUnordered() {
+    return Lists.newArrayList(getInternalBasicBlocks(true));
+  }
+
+  @Nonnull
+  private Set<JBasicBlock> getInternalBasicBlocks(boolean internalOnly) {
+    Set<JBasicBlock> blocks = new LinkedHashSet<>();
+    Stack<JBasicBlock> queue = new Stack<>();
+
+    JEntryBasicBlock entry = this.getEntryBlock();
+    JExitBasicBlock exit = this.getExitBlock();
+
+    blocks.add(entry);
+    queue.push(entry);
+    blocks.add(exit);
+    queue.push(exit);
+
+    while (!queue.isEmpty()) {
+      JBasicBlock block = queue.pop();
+
+      // Successors, then predecessors
+      for (JBasicBlock successor : block.getSuccessors()) {
+        if (!blocks.contains(successor)) {
+          blocks.add(successor);
+          queue.push(successor);
+        }
+      }
+      for (JBasicBlock predecessor : block.getPredecessors()) {
+        if (!blocks.contains(predecessor)) {
+          blocks.add(predecessor);
+          queue.push(predecessor);
+        }
+      }
+
+      // Catch blocks referenced from EH contexts
+      for (JBasicBlockElement element : block.getElements(true)) {
+        ExceptionHandlingContext context = element.getEHContext();
+        for (JCatchBasicBlock catchBlock : context.getCatchBlocks()) {
+          if (!blocks.contains(catchBlock)) {
+            blocks.add(catchBlock);
+            queue.push(catchBlock);
+          }
+        }
+      }
+    }
+
+    if (internalOnly) {
+      blocks.remove(entry);
+      blocks.remove(exit);
+    }
+
+    return blocks;
+  }
+
+  /** Traverses the the blocks in the order provided by `getAllBlocksUnordered()` */
+  @Override
+  public void traverse(@Nonnull final JVisitor visitor) {
+    if (visitor.visit(this)) {
+      for (JBasicBlock block : getAllBlocksUnordered()) {
+        visitor.accept(block);
+      }
+    }
+    visitor.endVisit(this);
+  }
+
+  /** Traverses the the blocks in the order provided by `getAllBlocksUnordered()` */
+  @Override
+  public void traverse(
+      @Nonnull final ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+    for (JBasicBlock block : getAllBlocksUnordered()) {
+      block.traverse(schedule);
+    }
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+    visitor.visit(this, request);
+  }
+
+  @Override
+  public void checkValidity() {
+    // NOTE: only check cfg-level validity, each basic block will be validated later
+  }
+
+  @Override
+  @Nonnull
+  public List<JBasicBlock> getNodes() {
+    return getAllBlocksUnordered();
+  }
+
+  @Override
+  @Nonnull
+  public JBasicBlock getEntryNode() {
+    return getEntryBlock();
+  }
+
+  @Override
+  @Nonnull
+  public JBasicBlock getExitNode() {
+    return getExitBlock();
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JEntryBasicBlock.java b/jack/src/com/android/jack/ir/ast/cfg/JEntryBasicBlock.java
new file mode 100644
index 0000000..4d4e0dc
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JEntryBasicBlock.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.ast.JVisitor;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+
+/** Represents entry CFG basic block, has only one successor and no predecessors */
+public final class JEntryBasicBlock extends JBasicBlock {
+  @Nonnull
+  private JBasicBlock next;
+
+  JEntryBasicBlock(@Nonnull JControlFlowGraph cfg, @Nonnull JBasicBlock next) {
+    this.next = next;
+    next.addPredecessor(this);
+    updateParents(cfg);
+  }
+
+  @Override
+  @Nonnull
+  public List<JBasicBlock> getSuccessors() {
+    return Collections.singletonList(next);
+  }
+
+  @Override
+  @Nonnull
+  public List<JBasicBlockElement> getElements(boolean forward) {
+    return Collections.emptyList();
+  }
+
+  @Nonnegative
+  @Override
+  public int getElementCount() {
+    return 0;
+  }
+
+  @Override
+  @Nonnull
+  public JBasicBlockElement getLastElement() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Nonnull
+  public JBasicBlockElement getFirstElement() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean hasElements() {
+    return false;
+  }
+
+  @Nonnull
+  public JBasicBlock getOnlySuccessor() {
+    return next;
+  }
+
+  @Override
+  public void appendElement(@Nonnull JBasicBlockElement element) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Nonnegative
+  public int indexOf(@Nonnull JBasicBlockElement element) {
+    throw new AssertionError();
+  }
+
+  @Override
+  public void insertElement(int at, @Nonnull JBasicBlockElement element) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public void removeElement(@Nonnull JBasicBlockElement element) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public void replaceAllSuccessors(@Nonnull JBasicBlock what, @Nonnull JBasicBlock with) {
+    assert next == what;
+    this.next = resetSuccessor(what, with);
+  }
+
+  @Nonnull
+  @Override
+  public JSimpleBasicBlock split(int at) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    visitor.visit(this);
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+    visitor.visit(this, request);
+  }
+
+  @Override
+  @Nonnull
+  public JBasicBlock detach(@Nonnull JBasicBlock newBlock) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public void detach() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  void addPredecessor(@Nonnull JBasicBlock predecessor) {
+    throw new UnsupportedOperationException();
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JExitBasicBlock.java b/jack/src/com/android/jack/ir/ast/cfg/JExitBasicBlock.java
new file mode 100644
index 0000000..89de192
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JExitBasicBlock.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.ast.JVisitor;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+
+/** Represents exit CFG basic block */
+public final class JExitBasicBlock extends JBasicBlock {
+  JExitBasicBlock(@Nonnull JControlFlowGraph cfg) {
+    updateParents(cfg);
+  }
+
+  @Override
+  @Nonnull
+  public List<JBasicBlock> getSuccessors() {
+    return Collections.emptyList();
+  }
+
+  @Override
+  @Nonnull
+  public List<JBasicBlockElement> getElements(boolean forward) {
+    return Collections.emptyList();
+  }
+
+  @Nonnegative
+  @Override
+  public int getElementCount() {
+    return 0;
+  }
+
+  @Override
+  @Nonnull
+  public JBasicBlockElement getLastElement() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Nonnull
+  public JBasicBlockElement getFirstElement() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean hasElements() {
+    return false;
+  }
+
+  @Override
+  public void appendElement(@Nonnull JBasicBlockElement element) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Nonnegative
+  public int indexOf(@Nonnull JBasicBlockElement element) {
+    throw new AssertionError();
+  }
+
+  @Override
+  public void insertElement(int at, @Nonnull JBasicBlockElement element) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public void removeElement(@Nonnull JBasicBlockElement element) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public void replaceAllSuccessors(@Nonnull JBasicBlock what, @Nonnull JBasicBlock with) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    visitor.visit(this);
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+    visitor.visit(this, request);
+  }
+
+  @Nonnull
+  @Override
+  public JSimpleBasicBlock split(int at) {
+    // Splitting the exit basic block is not good at all, even if possible,
+    // since exception throwing blocks must reference it as their uncaught
+    // exception block and split(...) will break this invariant
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Nonnull
+  public JBasicBlock detach(@Nonnull JBasicBlock newBlock) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public void detach() {
+    throw new UnsupportedOperationException();
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JGotoBlockElement.java b/jack/src/com/android/jack/ir/ast/cfg/JGotoBlockElement.java
new file mode 100644
index 0000000..21ab0b9
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JGotoBlockElement.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import javax.annotation.Nonnull;
+
+/** Represents goto basic block element */
+public final class JGotoBlockElement extends JBasicBlockElement {
+  public JGotoBlockElement(@Nonnull SourceInfo info, @Nonnull ExceptionHandlingContext ehc) {
+    super(info, ehc);
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    visitor.visit(this);
+    visitor.endVisit(this);
+  }
+
+  @Nonnull
+  @Override
+  public JSimpleBasicBlock getBasicBlock() {
+    JBasicBlock block = super.getBasicBlock();
+    assert block instanceof JSimpleBasicBlock;
+    return (JSimpleBasicBlock) block;
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+    visitor.visit(this, request);
+  }
+
+  @Override
+  public boolean isTerminal() {
+    return true;
+  }
+
+  @Override
+  public void checkValidity() {
+    super.checkValidity();
+
+    if (!(super.getBasicBlock() instanceof JSimpleBasicBlock)) {
+      throw new JNodeInternalError(this, "The parent node must be JSimpleBasicBlock");
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JLockBlockElement.java b/jack/src/com/android/jack/ir/ast/cfg/JLockBlockElement.java
new file mode 100644
index 0000000..da075fb
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JLockBlockElement.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JExpression;
+import com.android.jack.ir.ast.JNode;
+import com.android.jack.ir.ast.JReferenceType;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import javax.annotation.Nonnull;
+
+/** Represents lock expression basic block element */
+public final class JLockBlockElement extends JBasicBlockElement {
+  @Nonnull
+  private JExpression expr;
+
+  JLockBlockElement(@Nonnull SourceInfo info,
+      @Nonnull ExceptionHandlingContext ehc, @Nonnull JExpression expr) {
+    super(info, ehc);
+    assert !expr.canThrow();
+    assert expr.getType() instanceof JReferenceType;
+    this.expr = expr;
+  }
+
+  @Nonnull
+  public JExpression getExpression() {
+    return expr;
+  }
+
+  @Override
+  @Nonnull
+  public JThrowingExpressionBasicBlock getBasicBlock() {
+    JBasicBlock block = super.getBasicBlock();
+    assert block instanceof JThrowingExpressionBasicBlock;
+    return (JThrowingExpressionBasicBlock) block;
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    if (visitor.visit(this)) {
+      visitor.accept(expr);
+    }
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+    expr.traverse(schedule);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+    visitor.visit(this, request);
+  }
+
+  @Override
+  public boolean isTerminal() {
+    return true; // Can throw
+  }
+
+  @Override
+  public void checkValidity() {
+    super.checkValidity();
+
+    if (!(super.getBasicBlock() instanceof JThrowingExpressionBasicBlock)) {
+      throw new JNodeInternalError(this, "The parent node must be JThrowingExpressionBasicBlock");
+    }
+  }
+
+  @Override
+  protected void replaceImpl(
+      @Nonnull JNode existingNode, @Nonnull JNode newNode) throws UnsupportedOperationException {
+    if (expr == existingNode) {
+      expr = (JExpression) newNode;
+    } else {
+      super.replaceImpl(existingNode, newNode);
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JMethodCallBlockElement.java b/jack/src/com/android/jack/ir/ast/cfg/JMethodCallBlockElement.java
new file mode 100644
index 0000000..65ee568
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JMethodCallBlockElement.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JMethodCall;
+import com.android.jack.ir.ast.JNode;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import javax.annotation.Nonnull;
+
+/** Represents method call basic block element */
+public final class JMethodCallBlockElement extends JBasicBlockElement {
+  @Nonnull
+  private JMethodCall call;
+
+  public JMethodCallBlockElement(@Nonnull SourceInfo info,
+      @Nonnull ExceptionHandlingContext ehc, @Nonnull JMethodCall call) {
+    super(info, ehc);
+    this.call = call;
+  }
+
+  @Nonnull
+  public JMethodCall getCall() {
+    return call;
+  }
+
+  @Nonnull
+  @Override
+  public JThrowingExpressionBasicBlock getBasicBlock() {
+    JBasicBlock block = super.getBasicBlock();
+    assert block instanceof JThrowingExpressionBasicBlock;
+    return (JThrowingExpressionBasicBlock) block;
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    if (visitor.visit(this)) {
+      visitor.accept(call);
+    }
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+    call.traverse(schedule);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+    visitor.visit(this, request);
+  }
+
+  @Override
+  public boolean isTerminal() {
+    return true;
+  }
+
+  @Override
+  public void checkValidity() {
+    super.checkValidity();
+
+    if (!(super.getBasicBlock() instanceof JThrowingExpressionBasicBlock)) {
+      throw new JNodeInternalError(this, "The parent node must be JThrowingExpressionBasicBlock");
+    }
+  }
+
+  @Override
+  protected void replaceImpl(
+      @Nonnull JNode existingNode, @Nonnull JNode newNode) throws UnsupportedOperationException {
+    if (call == existingNode) {
+      call = (JMethodCall) newNode;
+    } else {
+      super.replaceImpl(existingNode, newNode);
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JPhiBlockElement.java b/jack/src/com/android/jack/ir/ast/cfg/JPhiBlockElement.java
new file mode 100644
index 0000000..a3414c3
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JPhiBlockElement.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JNode;
+import com.android.jack.ir.ast.JSsaVariableDefRef;
+import com.android.jack.ir.ast.JSsaVariableDefRefPlaceHolder;
+import com.android.jack.ir.ast.JSsaVariableRef;
+import com.android.jack.ir.ast.JSsaVariableUseRef;
+import com.android.jack.ir.ast.JVariable;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.sched.item.Component;
+import com.android.sched.item.Description;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Represents a Phi statement in the SSA form.
+ */
+@Description("Phi elements for SSA.")
+public class JPhiBlockElement extends JBasicBlockElement {
+  @Nonnull
+  private final JVariable var;
+
+  @Nonnull
+  private JSsaVariableDefRef lhs;
+
+  @Nonnull
+  private final JBasicBlock[] preds;
+
+  @Nonnull
+  private final JSsaVariableUseRef[] rhs;
+  /**
+   * Creates a new Phi statement.
+   *
+   * @param target Non-ssa variable this phi node is to denote to.
+   * @param predsList Predecessors block list of this phi node.
+   * @param info SourceInfo
+   */
+  public JPhiBlockElement(JVariable target, List<JBasicBlock> predsList, SourceInfo info) {
+    // Phi instructions are not real instructions and should not throw any exception.
+    super(info, ExceptionHandlingContext.EMPTY);
+    this.lhs = new JSsaVariableDefRefPlaceHolder(info, target);
+    lhs.updateParents(this);
+    rhs = new JSsaVariableUseRef[predsList.size()];
+    preds = new JBasicBlock[predsList.size()];
+    int index = 0;
+    for (JBasicBlock pred : predsList) {
+      // We are going to insert a place holder first. The SSA renamer will replace the var ref
+      // with a proper version.
+      JSsaVariableUseRef use = lhs.makeRef(info);
+      rhs[index] = use;
+      preds[index] = pred;
+      use.updateParents(this);
+      index++;
+    }
+    this.var = lhs.getTarget();
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    if (visitor.visit(this)) {
+      visitor.accept(lhs);
+      for (int i = 0; i < rhs.length; i++) {
+        visitor.accept(rhs[i]);
+      }
+    }
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+    lhs.traverse(schedule);
+    for (JSsaVariableRef var : rhs) {
+      var.traverse(schedule);
+    }
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest transformRequest)
+      throws Exception {
+    visitor.visit(this, transformRequest);
+  }
+
+  @Override
+  protected void replaceImpl(@Nonnull JNode existingNode, @Nonnull JNode newNode)
+      throws UnsupportedOperationException {
+    if (lhs == existingNode) {
+      lhs = (JSsaVariableDefRef) newNode;
+      return;
+    }
+
+    for (int i = 0; i < rhs.length; i++) {
+      if (rhs[i] == existingNode) {
+        rhs[i] = (JSsaVariableUseRef) newNode;
+        return;
+      }
+    }
+    super.replaceImpl(existingNode, newNode);
+  }
+
+  @Nonnull
+  public JVariable getTarget() {
+    return var;
+  }
+
+  @Nonnull
+  public JSsaVariableDefRef getLhs() {
+    return lhs;
+  }
+
+  @Nonnull
+  public JSsaVariableUseRef[] getRhs() {
+    return Arrays.copyOf(rhs, rhs.length);
+  }
+
+  @Nonnull
+  public JSsaVariableUseRef getRhs(@Nonnull JBasicBlock pred) {
+    for (int i = 0; i < preds.length; i++) {
+      if (preds[i] == pred) {
+        return rhs[i];
+      }
+    }
+    throw new RuntimeException();
+  }
+
+  @Override
+  public boolean isTerminal() {
+    return false;
+  }
+
+  @Override
+  public void checkValidity() {
+    super.checkValidity();
+
+    if (!super.getBasicBlock().getCfg().isInSsaForm()) {
+      throw new JNodeInternalError(this,
+          "The node must not be used in non-SSA form of CFG");
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JPlaceholderBasicBlock.java b/jack/src/com/android/jack/ir/ast/cfg/JPlaceholderBasicBlock.java
new file mode 100644
index 0000000..d6bacdf
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JPlaceholderBasicBlock.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+
+/**
+ * Represents a special basic block to be used as a temporary block
+ * representation during CFG construction.
+ *
+ * NOTE: the block can have any number of predecessors, but no successors
+ * or elements.
+ */
+public final class JPlaceholderBasicBlock extends JBasicBlock {
+  public JPlaceholderBasicBlock(@Nonnull JControlFlowGraph cfg) {
+    updateParents(cfg);
+  }
+
+  @Override
+  @Nonnull
+  public List<JBasicBlock> getSuccessors() {
+    return Collections.emptyList();
+  }
+
+  @Override
+  @Nonnull
+  public List<JBasicBlockElement> getElements(boolean forward) {
+    return Collections.emptyList();
+  }
+
+  @Nonnegative
+  @Override
+  public int getElementCount() {
+    return 0;
+  }
+
+  @Override
+  @Nonnull
+  public JBasicBlockElement getLastElement() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Nonnull
+  public JBasicBlockElement getFirstElement() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean hasElements() {
+    return false;
+  }
+
+  @Override
+  public void appendElement(@Nonnull JBasicBlockElement element) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Nonnegative
+  public int indexOf(@Nonnull JBasicBlockElement element) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public void insertElement(int at, @Nonnull JBasicBlockElement element) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public void removeElement(@Nonnull JBasicBlockElement element) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public void replaceAllSuccessors(@Nonnull JBasicBlock what, @Nonnull JBasicBlock with) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Nonnull
+  @Override
+  public JSimpleBasicBlock split(int at) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public void checkValidity() {
+    throw new JNodeInternalError(this,
+        "Placeholder basic block is transient and should not exist during validation");
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JPolymorphicMethodCallBlockElement.java b/jack/src/com/android/jack/ir/ast/cfg/JPolymorphicMethodCallBlockElement.java
new file mode 100644
index 0000000..55556cb
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JPolymorphicMethodCallBlockElement.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JNode;
+import com.android.jack.ir.ast.JPolymorphicMethodCall;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import javax.annotation.Nonnull;
+
+/** Represents polymorphic method call basic block element */
+public final class JPolymorphicMethodCallBlockElement extends JBasicBlockElement {
+  @Nonnull
+  private JPolymorphicMethodCall call;
+
+  JPolymorphicMethodCallBlockElement(@Nonnull SourceInfo info,
+      @Nonnull ExceptionHandlingContext ehc, @Nonnull JPolymorphicMethodCall call) {
+    super(info, ehc);
+    this.call = call;
+  }
+
+  @Nonnull
+  public JPolymorphicMethodCall getCall() {
+    return call;
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    if (visitor.visit(this)) {
+      visitor.accept(call);
+    }
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+    call.traverse(schedule);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+    visitor.visit(this, request);
+  }
+
+  @Override
+  public boolean isTerminal() {
+    return true;
+  }
+
+  @Override
+  protected void replaceImpl(
+      @Nonnull JNode existingNode, @Nonnull JNode newNode) throws UnsupportedOperationException {
+    if (call == existingNode) {
+      call = (JPolymorphicMethodCall) newNode;
+    } else {
+      super.replaceImpl(existingNode, newNode);
+    }
+  }
+
+  @Override
+  public void checkValidity() {
+    super.checkValidity();
+
+    if (!(super.getBasicBlock() instanceof JThrowingExpressionBasicBlock)) {
+      throw new JNodeInternalError(this, "The parent node must be JThrowingExpressionBasicBlock");
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JRegularBasicBlock.java b/jack/src/com/android/jack/ir/ast/cfg/JRegularBasicBlock.java
new file mode 100644
index 0000000..d41db8d
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JRegularBasicBlock.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.google.common.collect.ImmutableList;
+
+import com.android.jack.Jack;
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+
+/**
+ * Represents a regular implementation of CFG basic block containing any
+ * number of basic block elements.
+ */
+public abstract class JRegularBasicBlock extends JBasicBlock {
+  @CheckForNull
+  private JBasicBlock primarySuccessor;
+
+  private final List<JBasicBlockElement> elements = new ArrayList<>();
+
+  JRegularBasicBlock(@CheckForNull JBasicBlock primarySuccessor) {
+    if ((primarySuccessor == null) == hasPrimarySuccessor()) {
+      throw new AssertionError();
+    }
+    this.primarySuccessor = primarySuccessor;
+    if (this.primarySuccessor != null) {
+      this.primarySuccessor.addPredecessor(this);
+    }
+  }
+
+  @Override
+  @Nonnull
+  public List<JBasicBlockElement> getElements(boolean forward) {
+    return forward
+        ? Jack.getUnmodifiableCollections().getUnmodifiableList(elements)
+        : ImmutableList.copyOf(elements).reverse();
+  }
+
+  @Override
+  @Nonnull
+  public List<JBasicBlock> getSuccessors() {
+    return hasPrimarySuccessor()
+        ? Collections.singletonList(primarySuccessor)
+        : Collections.<JBasicBlock>emptyList();
+  }
+
+  /** If the block has primary successor */
+  public abstract boolean hasPrimarySuccessor();
+
+  /**
+   * Returns block's primary successor. Used in codegen.
+   * Valid only if the primary successor exists, see hasPrimarySuccessor().
+   */
+  @Nonnull
+  public JBasicBlock getPrimarySuccessor() {
+    assert hasPrimarySuccessor();
+    assert primarySuccessor != null;
+    return primarySuccessor;
+  }
+
+  @Override
+  public void replaceAllSuccessors(@Nonnull JBasicBlock what, @Nonnull JBasicBlock with) {
+    if (this.primarySuccessor == what) {
+      this.primarySuccessor = resetSuccessor(what, with);
+    }
+  }
+
+  @Override
+  @Nonnull
+  public JBasicBlockElement getLastElement() {
+    assert elements.size() > 0;
+    return elements.get(elements.size() - 1);
+  }
+
+  @Override
+  @Nonnull
+  public JBasicBlockElement getFirstElement() {
+    assert elements.size() > 0;
+    return elements.get(0);
+  }
+
+  @Override
+  public boolean hasElements() {
+    return !elements.isEmpty();
+  }
+
+  @Nonnegative
+  @Override
+  public int getElementCount() {
+    return elements.size();
+  }
+
+  @Override
+  public void appendElement(@Nonnull JBasicBlockElement element) {
+    elements.add(element);
+    element.updateParents(this);
+  }
+
+  @Override
+  public void insertElement(int at, @Nonnull JBasicBlockElement element) {
+    int size = elements.size();
+    if (at < 0) {
+      at = size + at;
+    }
+    assert at >= 0 && at <= size;
+
+    if (at == size) {
+      appendElement(element);
+    } else {
+      elements.add(at, element);
+      element.updateParents(this);
+    }
+  }
+
+  @Override
+  public void removeElement(@Nonnull JBasicBlockElement element) {
+    int index = elements.indexOf(element);
+    assert index != -1;
+    elements.remove(index);
+  }
+
+  @Override
+  @Nonnegative
+  public int indexOf(@Nonnull JBasicBlockElement element) {
+    assert element.getBasicBlock() == this;
+    int index = elements.indexOf(element);
+    assert index >= 0;
+    return index;
+  }
+
+  final void acceptElements(@Nonnull JVisitor visitor) {
+    visitor.accept(elements);
+  }
+
+  final void traverseElements(
+      @Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    for (JBasicBlockElement element : elements) {
+      element.traverse(schedule);
+    }
+  }
+
+  @Override
+  public void checkValidity() {
+    super.checkValidity();
+
+    if (!hasElements()) {
+      throw new JNodeInternalError(this, "Regular block must not be empty");
+    }
+
+    if (!getLastElement().isTerminal()) {
+      throw new JNodeInternalError(this,
+          "The last element of the basic block must be terminal element: " + this.toSource());
+    }
+
+    // In SSA any regular block may start with arbitrary number of Phi block
+    // elements. I.e. if phi block elements are present, they must not have any
+    // non-phi block elements on the left.
+    if (getCfg().isInSsaForm()) {
+      boolean seenNonPhi = false;
+      for (JBasicBlockElement element : elements) {
+        if (element instanceof JPhiBlockElement) {
+          if (seenNonPhi) {
+            throw new JNodeInternalError(this,
+                "Phi block element must not have non-phi element before it: " + this.toSource());
+          }
+        } else {
+          seenNonPhi = true;
+        }
+      }
+    } else {
+      for (JBasicBlockElement element : elements) {
+        if (element instanceof JPhiBlockElement) {
+          throw new JNodeInternalError(this,
+              "Phi block element must NOT be present in non-SSA CFG: " + this.toSource());
+        }
+      }
+    }
+  }
+
+  @Nonnull
+  @Override
+  public JSimpleBasicBlock split(int at) {
+    List<JBasicBlockElement> elements = this.elements;
+
+    int size = elements.size();
+    if (at < 0) {
+      at = size + at;
+    }
+    assert at >= 0 && at < size;
+
+    JSimpleBasicBlock block = new JSimpleBasicBlock(getCfg(), this);
+
+    // Move elements to a new block and append goto element at the end
+    for (int i = 0; i < at; i++) {
+      block.appendElement(elements.get(i));
+    }
+    block.appendElement(new JGotoBlockElement(SourceInfo.UNKNOWN,
+        elements.get(at).getEHContext() /* The first element of the next block */));
+
+    // Remove elements from this block
+    if (at > 0) {
+      int dest = 0;
+      for (int src = at; src < size; src++) {
+        elements.set(dest++, elements.get(src));
+      }
+      while (dest < size) {
+        elements.remove(--size);
+      }
+    }
+
+    // Re-point all the predecessors to a newly created simple block
+    for (JBasicBlock pre : this.getPredecessorsSnapshot()) {
+      if (pre != block) {
+        pre.replaceAllSuccessors(this, block);
+      }
+    }
+
+    return block;
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JReturnBasicBlock.java b/jack/src/com/android/jack/ir/ast/cfg/JReturnBasicBlock.java
new file mode 100644
index 0000000..716c9e3
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JReturnBasicBlock.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import javax.annotation.Nonnull;
+
+/** Represents blocks ending with return. */
+public final class JReturnBasicBlock extends JRegularBasicBlock {
+  public JReturnBasicBlock(@Nonnull JControlFlowGraph cfg) {
+    super(cfg.getExitBlock());
+    updateParents(cfg);
+  }
+
+  @Override
+  public boolean hasPrimarySuccessor() {
+    return true;
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    if (visitor.visit(this)) {
+      acceptElements(visitor);
+    }
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+    traverseElements(schedule);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+    visitor.visit(this, request);
+  }
+
+  @Override
+  public void checkValidity() {
+    super.checkValidity();
+
+    if (!(getLastElement() instanceof JReturnBlockElement)) {
+      throw new JNodeInternalError(this,
+          "JReturnBasicBlock's last element must be JReturnBlockElement");
+    }
+
+    if (!(getPrimarySuccessor() instanceof JExitBasicBlock)) {
+      throw new JNodeInternalError(this,
+          "JReturnBasicBlock's primary successor must be JExitBasicBlock");
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JReturnBlockElement.java b/jack/src/com/android/jack/ir/ast/cfg/JReturnBlockElement.java
new file mode 100644
index 0000000..20a3dbb
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JReturnBlockElement.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JExpression;
+import com.android.jack.ir.ast.JNode;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/** Represents return operation basic block element */
+public final class JReturnBlockElement extends JBasicBlockElement {
+  @CheckForNull
+  private JExpression expr;
+
+  public JReturnBlockElement(@Nonnull SourceInfo info,
+      @Nonnull ExceptionHandlingContext ehc, @CheckForNull JExpression expr) {
+    super(info, ehc);
+    this.expr = expr;
+  }
+
+  @CheckForNull
+  public JExpression getExpression() {
+    return expr;
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    if (visitor.visit(this)) {
+      if (expr != null) {
+        visitor.accept(expr);
+      }
+    }
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+    if (expr != null) {
+      expr.traverse(schedule);
+    }
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+    visitor.visit(this, request);
+  }
+
+  @Override
+  public void checkValidity() {
+    super.checkValidity();
+
+    if (!(super.getBasicBlock() instanceof JReturnBasicBlock)) {
+      throw new JNodeInternalError(this, "The parent node must be JReturnBasicBlock");
+    }
+  }
+
+  @Override
+  public boolean isTerminal() {
+    return true;
+  }
+
+  @Override
+  protected void replaceImpl(
+      @Nonnull JNode existingNode, @Nonnull JNode newNode) throws UnsupportedOperationException {
+    if (expr == existingNode) {
+      expr = (JExpression) newNode;
+    } else {
+      super.replaceImpl(existingNode, newNode);
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JSimpleBasicBlock.java b/jack/src/com/android/jack/ir/ast/cfg/JSimpleBasicBlock.java
new file mode 100644
index 0000000..b8671c2
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JSimpleBasicBlock.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import java.util.List;
+import javax.annotation.Nonnull;
+
+/** Represents a simple blocks with just one successor ending with GOTO. */
+public final class JSimpleBasicBlock extends JRegularBasicBlock {
+  public JSimpleBasicBlock(@Nonnull JControlFlowGraph cfg, @Nonnull JBasicBlock primary) {
+    super(primary);
+    updateParents(cfg);
+  }
+
+  @Override
+  public boolean hasPrimarySuccessor() {
+    return true;
+  }
+
+  /** Merges the block into its primary successor, returns the successor */
+  @Nonnull
+  public JBasicBlock mergeIntoSuccessor() {
+    JBasicBlock successor = getPrimarySuccessor();
+    if (successor.getPredecessorCount() != 1) {
+      // Make sure we never merge into the block with multiple predecessors
+      throw new AssertionError();
+    }
+
+    // Move children
+    List<JBasicBlockElement> elements = this.getElements(true);
+    int count = elements.size() - 1; // Ignore trailing GOTO element
+    for (int i = 0; i < count; i++) {
+      successor.insertElement(i, elements.get(i));
+    }
+
+    this.detach(successor);
+    return successor;
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    if (visitor.visit(this)) {
+      acceptElements(visitor);
+    }
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+    traverseElements(schedule);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+    visitor.visit(this, request);
+  }
+
+  /**
+   * Removes the basic block by redirecting all the predecessors to point to the
+   * primary successor of this block.
+   */
+  public void delete() {
+    this.detach(getPrimarySuccessor());
+  }
+
+  @Override
+  public void checkValidity() {
+    super.checkValidity();
+
+    if (!(getLastElement() instanceof JGotoBlockElement)) {
+      throw new JNodeInternalError(this, "The last element of the block must be goto element");
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JStoreBlockElement.java b/jack/src/com/android/jack/ir/ast/cfg/JStoreBlockElement.java
new file mode 100644
index 0000000..99c9e66
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JStoreBlockElement.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JArrayRef;
+import com.android.jack.ir.ast.JAsgOperation;
+import com.android.jack.ir.ast.JExpression;
+import com.android.jack.ir.ast.JFieldRef;
+import com.android.jack.ir.ast.JNode;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/** Represents field or array element store basic block element */
+public final class JStoreBlockElement extends JBasicBlockElement {
+  @Nonnull
+  private JAsgOperation asg;
+
+  public JStoreBlockElement(@Nonnull SourceInfo info,
+      @Nonnull ExceptionHandlingContext ehc, @Nonnull JAsgOperation asg) {
+    super(info, ehc);
+    assert asg.getLhs().canThrow();
+    assert asg.getLhs() instanceof JFieldRef || asg.getLhs() instanceof JArrayRef;
+    this.asg = asg;
+  }
+
+  @Nonnull
+  public JAsgOperation getAssignment() {
+    return asg;
+  }
+
+  @CheckForNull
+  public JFieldRef getLhsAsFieldRef() {
+    JExpression lhs = asg.getLhs();
+    return (lhs instanceof JFieldRef) ? (JFieldRef) lhs : null;
+  }
+
+  @CheckForNull
+  public JArrayRef getLhsAsArrayRef() {
+    JExpression lhs = asg.getLhs();
+    return (lhs instanceof JArrayRef) ? (JArrayRef) lhs : null;
+  }
+
+  @Nonnull
+  public JExpression getValueExpression() {
+    return asg.getRhs();
+  }
+
+  @Override
+  @Nonnull
+  public JThrowingExpressionBasicBlock getBasicBlock() {
+    JBasicBlock block = super.getBasicBlock();
+    assert block instanceof JThrowingExpressionBasicBlock;
+    return (JThrowingExpressionBasicBlock) block;
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    if (visitor.visit(this)) {
+      visitor.accept(asg);
+    }
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+    asg.traverse(schedule);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+    visitor.visit(this, request);
+  }
+
+  @Override
+  public boolean isTerminal() {
+    return true;
+  }
+
+  @Override
+  public void checkValidity() {
+    super.checkValidity();
+
+    if (!(super.getBasicBlock() instanceof JThrowingExpressionBasicBlock)) {
+      throw new JNodeInternalError(this, "The parent node must be JThrowingExpressionBasicBlock");
+    }
+  }
+
+  @Override
+  protected void replaceImpl(
+      @Nonnull JNode existingNode, @Nonnull JNode newNode) throws UnsupportedOperationException {
+    if (asg == existingNode) {
+      asg = (JAsgOperation) newNode;
+    } else {
+      super.replaceImpl(existingNode, newNode);
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JSwitchBasicBlock.java b/jack/src/com/android/jack/ir/ast/cfg/JSwitchBasicBlock.java
new file mode 100644
index 0000000..c80efcb
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JSwitchBasicBlock.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.Jack;
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.Nonnull;
+
+/** Represents switch block. */
+public final class JSwitchBasicBlock extends JRegularBasicBlock {
+  @Nonnull
+  private final List<JBasicBlock> cases = new ArrayList<>();
+
+  public JSwitchBasicBlock(@Nonnull JControlFlowGraph cfg, @Nonnull JBasicBlock defaultCase) {
+    super(defaultCase);
+    defaultCase.addPredecessor(this);
+    updateParents(cfg);
+  }
+
+  @Override
+  public boolean hasPrimarySuccessor() {
+    return true;
+  }
+
+  @Nonnull
+  @Override
+  public List<JBasicBlock> getSuccessors() {
+    ArrayList<JBasicBlock> successors = new ArrayList<>();
+    successors.add(getPrimarySuccessor());
+    successors.addAll(cases);
+    return successors;
+  }
+
+  /** Add a new case block */
+  public void addCase(@Nonnull JBasicBlock block) {
+    cases.add(block);
+    block.addPredecessor(this);
+  }
+
+  @Nonnull
+  public List<JBasicBlock> getCases() {
+    return Jack.getUnmodifiableCollections().getUnmodifiableList(cases);
+  }
+
+  @Nonnull
+  public JBasicBlock getDefaultCase() {
+    return getPrimarySuccessor();
+  }
+
+  @Override
+  public void replaceAllSuccessors(@Nonnull JBasicBlock what, @Nonnull JBasicBlock with) {
+    super.replaceAllSuccessors(what, with);
+    for (int i = 0; i < cases.size(); i++) {
+      if (cases.get(i) == what) {
+        cases.set(i, resetSuccessor(what, with));
+      }
+    }
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    if (visitor.visit(this)) {
+      acceptElements(visitor);
+    }
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+    traverseElements(schedule);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+    visitor.visit(this, request);
+  }
+
+  @Override
+  public void checkValidity() {
+    super.checkValidity();
+
+    if (!(getLastElement() instanceof JSwitchBlockElement)) {
+      throw new JNodeInternalError(this,
+          "The last element of the block must be switch element");
+    }
+
+    for (JBasicBlock successor : cases) {
+      if (!(successor instanceof JCaseBasicBlock)) {
+        throw new JNodeInternalError(this,
+            "JSwitchBasicBlock must only have JCaseBasicBlock "
+                + "case successors: " + this.toSource());
+      }
+      JBasicBlockElement lastElement = successor.getLastElement();
+      if (lastElement instanceof JCaseBlockElement) {
+        // This will be asserted case block, checking to avoid invalid cast
+        if (((JCaseBlockElement) lastElement).getLiteral() == null) {
+          throw new JNodeInternalError(this, "JSwitchBasicBlock's case "
+              + "successor JCaseBasicBlock must NOT be default case");
+        }
+      }
+    }
+
+    // Primary successor may be non-case block if there is no
+    // catch-all case in the statement
+    JBasicBlock primarySuccessor = getPrimarySuccessor();
+    if (primarySuccessor instanceof JCaseBasicBlock) {
+      JBasicBlockElement lastElement = primarySuccessor.getLastElement();
+      if (lastElement instanceof JCaseBlockElement) {
+        // This will be asserted case block, checking to avoid invalid cast
+        if (((JCaseBlockElement) lastElement).getLiteral() != null) {
+          throw new JNodeInternalError(this, "If JSwitchBasicBlock's "
+              + "primary successor is JCaseBasicBlock it must be default case");
+        }
+      }
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JSwitchBlockElement.java b/jack/src/com/android/jack/ir/ast/cfg/JSwitchBlockElement.java
new file mode 100644
index 0000000..f87c921
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JSwitchBlockElement.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JExpression;
+import com.android.jack.ir.ast.JNode;
+import com.android.jack.ir.ast.JPrimitiveType;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import javax.annotation.Nonnull;
+
+/** Represents switch basic block element */
+public final class JSwitchBlockElement extends JBasicBlockElement {
+  @Nonnull
+  private JExpression expr;
+
+  JSwitchBlockElement(@Nonnull SourceInfo info,
+      @Nonnull ExceptionHandlingContext ehc, @Nonnull JExpression expr) {
+    super(info, ehc);
+    assert !expr.canThrow();
+    assert !JPrimitiveType.JPrimitiveTypeEnum.VOID.getType().isSameType(expr.getType());
+    assert expr.getType() instanceof JPrimitiveType;
+    this.expr = expr;
+  }
+
+  @Nonnull
+  public JExpression getExpression() {
+    return expr;
+  }
+
+  @Nonnull
+  @Override
+  public JSwitchBasicBlock getBasicBlock() {
+    JBasicBlock block = super.getBasicBlock();
+    assert block instanceof JSwitchBasicBlock;
+    return (JSwitchBasicBlock) block;
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    if (visitor.visit(this)) {
+      visitor.accept(expr);
+    }
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+    expr.traverse(schedule);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+    visitor.visit(this, request);
+  }
+
+  @Override
+  public boolean isTerminal() {
+    return true;
+  }
+
+  @Override
+  public void checkValidity() {
+    super.checkValidity();
+
+    if (!(super.getBasicBlock() instanceof JSwitchBasicBlock)) {
+      throw new JNodeInternalError(this, "The parent node must be JSwitchBasicBlock");
+    }
+  }
+
+  @Override
+  protected void replaceImpl(
+      @Nonnull JNode existingNode, @Nonnull JNode newNode) throws UnsupportedOperationException {
+    if (expr == existingNode) {
+      expr = (JExpression) newNode;
+    } else {
+      super.replaceImpl(existingNode, newNode);
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JThrowBasicBlock.java b/jack/src/com/android/jack/ir/ast/cfg/JThrowBasicBlock.java
new file mode 100644
index 0000000..26a401e
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JThrowBasicBlock.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import javax.annotation.Nonnull;
+
+/** Represents blocks ended by throw. */
+public final class JThrowBasicBlock extends JThrowingBasicBlock {
+  public JThrowBasicBlock(@Nonnull JControlFlowGraph cfg) {
+    super(null, cfg.getExitBlock());
+    updateParents(cfg);
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    if (visitor.visit(this)) {
+      acceptElements(visitor);
+    }
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+    traverseElements(schedule);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+    visitor.visit(this, request);
+  }
+
+  @Override
+  public boolean hasPrimarySuccessor() {
+    return false;
+  }
+
+  @Override
+  public void checkValidity() {
+    super.checkValidity();
+
+    if (!(getLastElement() instanceof JThrowBlockElement)) {
+      throw new JNodeInternalError(this,
+          "The last element of JThrowBasicBlock must be JThrowBlockElement");
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JThrowBlockElement.java b/jack/src/com/android/jack/ir/ast/cfg/JThrowBlockElement.java
new file mode 100644
index 0000000..32ce02f
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JThrowBlockElement.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JExpression;
+import com.android.jack.ir.ast.JNode;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import javax.annotation.Nonnull;
+
+/** Represents throw expression basic block element */
+public final class JThrowBlockElement extends JBasicBlockElement {
+  @Nonnull
+  private JExpression expr;
+
+  public JThrowBlockElement(@Nonnull SourceInfo info,
+      @Nonnull ExceptionHandlingContext ehc, @Nonnull JExpression expr) {
+    super(info, ehc);
+    assert !expr.canThrow();
+    this.expr = expr;
+  }
+
+  @Nonnull
+  public JExpression getExpression() {
+    return expr;
+  }
+
+  @Nonnull
+  @Override
+  public JThrowBasicBlock getBasicBlock() {
+    JBasicBlock block = super.getBasicBlock();
+    assert block instanceof JThrowBasicBlock;
+    return (JThrowBasicBlock) block;
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    if (visitor.visit(this)) {
+      visitor.accept(expr);
+    }
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+    expr.traverse(schedule);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+    visitor.visit(this, request);
+  }
+
+  @Override
+  public boolean isTerminal() {
+    return true;
+  }
+
+  @Override
+  public void checkValidity() {
+    super.checkValidity();
+
+    if (!(super.getBasicBlock() instanceof JThrowBasicBlock)) {
+      throw new JNodeInternalError(this, "The parent node must be JThrowBasicBlock");
+    }
+  }
+
+  @Override
+  protected void replaceImpl(
+      @Nonnull JNode existingNode, @Nonnull JNode newNode) throws UnsupportedOperationException {
+
+    if (expr == existingNode) {
+      expr = (JExpression) newNode;
+    } else {
+      super.replaceImpl(existingNode, newNode);
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JThrowingBasicBlock.java b/jack/src/com/android/jack/ir/ast/cfg/JThrowingBasicBlock.java
new file mode 100644
index 0000000..70be19a
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JThrowingBasicBlock.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.Jack;
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JVisitor;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/** Represents blocks which potentially may trigger exceptions. */
+public abstract class JThrowingBasicBlock extends JRegularBasicBlock {
+  /** Successor for unhandled exception */
+  @Nonnull
+  private JBasicBlock unhandledBlock;
+  @Nonnull
+  private final List<JBasicBlock> catchBlocks = new ArrayList<>();
+
+  JThrowingBasicBlock(@CheckForNull JBasicBlock primary, @Nonnull JBasicBlock unhandledBlock) {
+    super(primary);
+    this.unhandledBlock = unhandledBlock;
+    this.unhandledBlock.addPredecessor(this);
+  }
+
+  @Nonnull
+  @Override
+  public List<JBasicBlock> getSuccessors() {
+    ArrayList<JBasicBlock> successors = new ArrayList<>();
+    if (hasPrimarySuccessor()) {
+      successors.add(getPrimarySuccessor());
+    }
+    successors.add(unhandledBlock);
+    successors.addAll(catchBlocks);
+    return successors;
+  }
+
+  /** Resets exception catch blocks from the EH context of the last element */
+  public void resetCatchBlocks() {
+    // Remove old catch blocks
+    for (JBasicBlock block : catchBlocks) {
+      this.removeSuccessor(block);
+    }
+    catchBlocks.clear();
+
+    // Add new catch blocks
+    catchBlocks.addAll(getLastElement().getEHContext().getCatchBlocks());
+    for (JBasicBlock block : catchBlocks) {
+      block.addPredecessor(this);
+    }
+  }
+
+  @Nonnull
+  public List<JBasicBlock> getCatchBlocks() {
+    return Jack.getUnmodifiableCollections().getUnmodifiableList(catchBlocks);
+  }
+
+  @Nonnull
+  public JBasicBlock getUnhandledBlock() {
+    return unhandledBlock;
+  }
+
+  @Override
+  public void replaceAllSuccessors(@Nonnull JBasicBlock what, @Nonnull JBasicBlock with) {
+    super.replaceAllSuccessors(what, with);
+
+    if (this.unhandledBlock == what) {
+      this.unhandledBlock = resetSuccessor(what, with);
+    }
+    for (int i = 0; i < catchBlocks.size(); i++) {
+      if (catchBlocks.get(i) == what) {
+        catchBlocks.set(i, resetSuccessor(what, with));
+      }
+    }
+  }
+
+  @Override
+  public void checkValidity() {
+    super.checkValidity();
+
+    new JVisitor() {
+      @Override public boolean visit(@Nonnull JMethodCallBlockElement node) {
+        return false;
+      }
+
+      @Override public boolean visit(@Nonnull JPolymorphicMethodCallBlockElement node) {
+        return false;
+      }
+
+      @Override public boolean visit(@Nonnull JStoreBlockElement node) {
+        return false;
+      }
+
+      @Override public boolean visit(@Nonnull JLockBlockElement node) {
+        return false;
+      }
+
+      @Override public boolean visit(@Nonnull JUnlockBlockElement node) {
+        return false;
+      }
+
+      @Override public boolean visit(@Nonnull JThrowBlockElement node) {
+        return false;
+      }
+
+      @Override public boolean visit(@Nonnull JGotoBlockElement node) {
+        throw new JNodeInternalError(JThrowingBasicBlock.this,
+            "JThrowingBasicBlock must NOT end with JGotoBlockElement");
+      }
+
+      @Override public boolean visit(@Nonnull JSwitchBlockElement node) {
+        throw new JNodeInternalError(JThrowingBasicBlock.this,
+            "JThrowingBasicBlock must NOT end with JSwitchBlockElement");
+      }
+
+      @Override public boolean visit(@Nonnull JConditionalBlockElement node) {
+        throw new JNodeInternalError(JThrowingBasicBlock.this,
+            "JThrowingBasicBlock must NOT end with JConditionalBlockElement");
+      }
+
+      @Override public boolean visit(@Nonnull JCaseBlockElement node) {
+        throw new JNodeInternalError(JThrowingBasicBlock.this,
+            "JThrowingBasicBlock must NOT end with JCaseBlockElement");
+      }
+
+      @Override public boolean visit(@Nonnull JVariableAsgBlockElement node) {
+        if (!node.getAssignment().getRhs().canThrow()) {
+          throw new JNodeInternalError(JThrowingBasicBlock.this,
+              "JThrowingBasicBlock must NOT end with JVariableAsgBlockElement "
+                  + "which does not throw");
+        }
+        return false;
+      }
+
+      @Override public boolean visit(@Nonnull JReturnBlockElement node) {
+        throw new JNodeInternalError(JThrowingBasicBlock.this,
+            "JThrowingBasicBlock must NOT end with JReturnBlockElement");
+      }
+
+      @Override public boolean visit(@Nonnull JPhiBlockElement node) {
+        throw new JNodeInternalError(JThrowingBasicBlock.this,
+            "JThrowingBasicBlock must NOT end with JPhiBlockElement");
+      }
+    }.accept(getLastElement());
+
+    if (!(getUnhandledBlock() instanceof JExitBasicBlock)) {
+      throw new JNodeInternalError(this, "Unhandled exception block of "
+          + "JThrowingBasicBlock must always point to the exit block");
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JThrowingExpressionBasicBlock.java b/jack/src/com/android/jack/ir/ast/cfg/JThrowingExpressionBasicBlock.java
new file mode 100644
index 0000000..757e5af
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JThrowingExpressionBasicBlock.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import javax.annotation.Nonnull;
+
+/** Represents blocks which potentially may trigger exceptions. */
+public final class JThrowingExpressionBasicBlock extends JThrowingBasicBlock {
+  public JThrowingExpressionBasicBlock(
+      @Nonnull JControlFlowGraph cfg, @Nonnull JBasicBlock primary) {
+    super(primary, cfg.getExitBlock());
+    updateParents(cfg);
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    if (visitor.visit(this)) {
+      acceptElements(visitor);
+    }
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+    traverseElements(schedule);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+    visitor.visit(this, request);
+  }
+
+  @Override
+  public boolean hasPrimarySuccessor() {
+    return true;
+  }
+
+  /**
+   * Removes the basic block by redirecting all the predecessors to point to the
+   * primary successor of this block.
+   */
+  public void delete() {
+    this.detach(getPrimarySuccessor());
+  }
+
+  @Override
+  public void checkValidity() {
+    super.checkValidity();
+
+    if (getLastElement() instanceof JThrowBlockElement) {
+      throw new JNodeInternalError(this,
+          "The last element of JThrowingExpressionBasicBlock must NOT be JThrowBlockElement");
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JUnlockBlockElement.java b/jack/src/com/android/jack/ir/ast/cfg/JUnlockBlockElement.java
new file mode 100644
index 0000000..3d2ff8a
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JUnlockBlockElement.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JExpression;
+import com.android.jack.ir.ast.JNode;
+import com.android.jack.ir.ast.JReferenceType;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import javax.annotation.Nonnull;
+
+/** Represents unlock expression basic block element */
+public final class JUnlockBlockElement extends JBasicBlockElement {
+  @Nonnull
+  private JExpression expr;
+
+  JUnlockBlockElement(@Nonnull SourceInfo info,
+      @Nonnull ExceptionHandlingContext ehc, @Nonnull JExpression expr) {
+    super(info, ehc);
+    assert !expr.canThrow();
+    assert expr.getType() instanceof JReferenceType;
+    this.expr = expr;
+  }
+
+  @Nonnull
+  public JExpression getExpression() {
+    return expr;
+  }
+
+  @Override
+  @Nonnull
+  public JThrowingExpressionBasicBlock getBasicBlock() {
+    JBasicBlock block = super.getBasicBlock();
+    assert block instanceof JThrowingExpressionBasicBlock;
+    return (JThrowingExpressionBasicBlock) block;
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    if (visitor.visit(this)) {
+      visitor.accept(expr);
+    }
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+    expr.traverse(schedule);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+    visitor.visit(this, request);
+  }
+
+  @Override
+  public boolean isTerminal() {
+    return true; // Can throw
+  }
+
+  @Override
+  public void checkValidity() {
+    super.checkValidity();
+
+    if (!(super.getBasicBlock() instanceof JThrowingExpressionBasicBlock)) {
+      throw new JNodeInternalError(this, "The parent node must be JThrowingExpressionBasicBlock");
+    }
+  }
+
+  @Override
+  protected void replaceImpl(
+      @Nonnull JNode existingNode, @Nonnull JNode newNode) throws UnsupportedOperationException {
+    if (expr == existingNode) {
+      expr = (JExpression) newNode;
+    } else {
+      super.replaceImpl(existingNode, newNode);
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/JVariableAsgBlockElement.java b/jack/src/com/android/jack/ir/ast/cfg/JVariableAsgBlockElement.java
new file mode 100644
index 0000000..8a5dba6
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/JVariableAsgBlockElement.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.ast.JArrayRef;
+import com.android.jack.ir.ast.JAsgOperation;
+import com.android.jack.ir.ast.JExceptionRuntimeValue;
+import com.android.jack.ir.ast.JExpression;
+import com.android.jack.ir.ast.JFieldRef;
+import com.android.jack.ir.ast.JMethodCall;
+import com.android.jack.ir.ast.JNode;
+import com.android.jack.ir.ast.JPolymorphicMethodCall;
+import com.android.jack.ir.ast.JVariable;
+import com.android.jack.ir.ast.JVariableRef;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.sched.item.Component;
+import com.android.sched.scheduler.ScheduleInstance;
+import com.android.sched.transform.TransformRequest;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/** Represents a variable (local, this, parameter) assignment basic block element */
+public final class JVariableAsgBlockElement extends JBasicBlockElement {
+  @Nonnull
+  private JAsgOperation asg;
+
+  public JVariableAsgBlockElement(@Nonnull SourceInfo info,
+      @Nonnull ExceptionHandlingContext ehc, @Nonnull JAsgOperation asg) {
+    super(info, ehc);
+    assert !asg.getLhs().canThrow();
+    assert asg.getLhs() instanceof JVariableRef;
+    this.asg = asg;
+  }
+
+  @Nonnull
+  public JAsgOperation getAssignment() {
+    return asg;
+  }
+
+  @Nonnull
+  public JVariable getVariable() {
+    JExpression lhs = asg.getLhs();
+    assert lhs instanceof JVariableRef;
+    return ((JVariableRef) lhs).getTarget();
+  }
+
+  @Nonnull
+  public JExpression getValue() {
+    return asg.getRhs();
+  }
+
+  public boolean isCatchVariableAssignment() {
+    return asg.getRhs() instanceof JExceptionRuntimeValue;
+  }
+
+  public boolean isFieldLoad() {
+    return asg.getRhs() instanceof JFieldRef;
+  }
+
+  public boolean isArrayElementLoad() {
+    return asg.getRhs() instanceof JArrayRef;
+  }
+
+  public boolean isLoad() {
+    return isFieldLoad() || isArrayElementLoad();
+  }
+
+  public boolean isMethodCall() {
+    return asg.getRhs() instanceof JMethodCall;
+  }
+
+  public boolean isPolymorphicMethodCall() {
+    return asg.getRhs() instanceof JPolymorphicMethodCall;
+  }
+
+  @Override
+  @CheckForNull
+  public JVariableRef getDefinedVariable() {
+    JExpression lhs = getAssignment().getLhs();
+    if (!(lhs instanceof JVariableRef)) {
+      return null;
+    } else {
+      return (JVariableRef) lhs;
+    }
+  }
+
+  @Override
+  public void traverse(@Nonnull JVisitor visitor) {
+    if (visitor.visit(this)) {
+      visitor.accept(asg);
+    }
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
+    schedule.process(this);
+    asg.traverse(schedule);
+  }
+
+  @Override
+  public void visit(@Nonnull JVisitor visitor, @Nonnull TransformRequest request) throws Exception {
+    visitor.visit(this, request);
+  }
+
+  @Override
+  public boolean isTerminal() {
+    return asg.getRhs().canThrow() || isCatchVariableAssignment();
+  }
+
+  @Override
+  public void checkValidity() {
+    super.checkValidity();
+
+    if (isCatchVariableAssignment()) {
+      if (!(getBasicBlock() instanceof JCatchBasicBlock)) {
+        throw new JNodeInternalError(this, "Parent block must be JCatchBasicBlock");
+      }
+
+    } else if (asg.getRhs().canThrow()) {
+      if (!(getBasicBlock() instanceof JThrowingExpressionBasicBlock)) {
+        throw new JNodeInternalError(this, "Parent block must be JThrowingExpressionBasicBlock");
+      }
+    }
+  }
+
+  @Override
+  protected void replaceImpl(
+      @Nonnull JNode existingNode, @Nonnull JNode newNode) throws UnsupportedOperationException {
+    if (asg == existingNode) {
+      asg = (JAsgOperation) newNode;
+    } else {
+      super.replaceImpl(existingNode, newNode);
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/MethodBodyCfgBuilder.java b/jack/src/com/android/jack/ir/ast/cfg/MethodBodyCfgBuilder.java
new file mode 100644
index 0000000..538a6f9
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/MethodBodyCfgBuilder.java
@@ -0,0 +1,545 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg;
+
+import com.android.jack.Options;
+import com.android.jack.cfg.BasicBlock;
+import com.android.jack.cfg.BasicBlockMarker;
+import com.android.jack.cfg.CatchBasicBlock;
+import com.android.jack.cfg.ConditionalBasicBlock;
+import com.android.jack.cfg.ControlFlowGraph;
+import com.android.jack.cfg.EntryBlock;
+import com.android.jack.cfg.ExitBlock;
+import com.android.jack.cfg.NormalBasicBlock;
+import com.android.jack.cfg.PeiBasicBlock;
+import com.android.jack.cfg.ReturnBasicBlock;
+import com.android.jack.cfg.SwitchBasicBlock;
+import com.android.jack.cfg.ThrowBasicBlock;
+import com.android.jack.ir.SideEffectOperation;
+import com.android.jack.ir.ast.JAbstractMethodBody;
+import com.android.jack.ir.ast.JArrayRef;
+import com.android.jack.ir.ast.JAsgOperation;
+import com.android.jack.ir.ast.JAssertStatement;
+import com.android.jack.ir.ast.JCaseStatement;
+import com.android.jack.ir.ast.JCastOperation;
+import com.android.jack.ir.ast.JCatchBlock;
+import com.android.jack.ir.ast.JConcatOperation;
+import com.android.jack.ir.ast.JConditionalExpression;
+import com.android.jack.ir.ast.JConditionalOperation;
+import com.android.jack.ir.ast.JExceptionRuntimeValue;
+import com.android.jack.ir.ast.JExpression;
+import com.android.jack.ir.ast.JExpressionStatement;
+import com.android.jack.ir.ast.JFieldInitializer;
+import com.android.jack.ir.ast.JFieldRef;
+import com.android.jack.ir.ast.JGoto;
+import com.android.jack.ir.ast.JIfStatement;
+import com.android.jack.ir.ast.JLocal;
+import com.android.jack.ir.ast.JLock;
+import com.android.jack.ir.ast.JLoop;
+import com.android.jack.ir.ast.JMethod;
+import com.android.jack.ir.ast.JMethodBody;
+import com.android.jack.ir.ast.JMethodBodyCfg;
+import com.android.jack.ir.ast.JMethodCall;
+import com.android.jack.ir.ast.JMultiExpression;
+import com.android.jack.ir.ast.JNode;
+import com.android.jack.ir.ast.JPolymorphicMethodCall;
+import com.android.jack.ir.ast.JReturnStatement;
+import com.android.jack.ir.ast.JStatement;
+import com.android.jack.ir.ast.JSwitchStatement;
+import com.android.jack.ir.ast.JThrowStatement;
+import com.android.jack.ir.ast.JUnlock;
+import com.android.jack.ir.ast.JVariableRef;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.ast.cfg.mutations.BasicBlockBuilder;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
+import com.android.jack.transformations.EmptyClinit;
+import com.android.jack.transformations.InvalidDefaultBridgeInInterfaceRemoved;
+import com.android.jack.transformations.ast.BooleanTestOutsideIf;
+import com.android.jack.transformations.ast.ImplicitBoxingAndUnboxing;
+import com.android.jack.transformations.ast.ImplicitCast;
+import com.android.jack.transformations.ast.InitInNewArray;
+import com.android.jack.transformations.ast.JPrimitiveClassLiteral;
+import com.android.jack.transformations.ast.MultiDimensionNewArray;
+import com.android.jack.transformations.ast.NewInstanceRemoved;
+import com.android.jack.transformations.ast.RefAsStatement;
+import com.android.jack.transformations.ast.UnassignedValues;
+import com.android.jack.transformations.ast.inner.InnerAccessor;
+import com.android.jack.transformations.ast.switches.UselessSwitches;
+import com.android.jack.transformations.booleanoperators.FallThroughMarker;
+import com.android.jack.transformations.cast.SourceCast;
+import com.android.jack.transformations.request.Replace;
+import com.android.jack.transformations.request.TransformationRequest;
+import com.android.jack.transformations.rop.cast.RopLegalCast;
+import com.android.jack.transformations.threeaddresscode.ThreeAddressCodeForm;
+import com.android.sched.item.Description;
+import com.android.sched.schedulable.Constraint;
+import com.android.sched.schedulable.Filter;
+import com.android.sched.schedulable.RunnableSchedulable;
+import com.android.sched.schedulable.Transform;
+import com.android.sched.util.config.ThreadConfig;
+
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+import javax.annotation.Nonnull;
+
+/** Builds a ControlFlowGraph body representation for all methods. */
+@Description("Builds a ControlFlowGraph body representation for all methods.")
+@Constraint(
+    need = {
+        ControlFlowGraph.class,
+        JExceptionRuntimeValue.class,
+        NewInstanceRemoved.class,
+        ThreeAddressCodeForm.class,
+        RopLegalCast.class,
+        InnerAccessor.class,
+        InvalidDefaultBridgeInInterfaceRemoved.class },
+    no = {
+        BooleanTestOutsideIf.class,
+        InitInNewArray.class,
+        JAsgOperation.class,
+        JPrimitiveClassLiteral.class,
+        JMultiExpression.class,
+        JConditionalExpression.class,
+        JFieldInitializer.class,
+        JConcatOperation.class,
+        JLoop.class,
+        SideEffectOperation.class,
+        UnassignedValues.class,
+        RefAsStatement.class,
+        MultiDimensionNewArray.class,
+        JSwitchStatement.SwitchWithEnum.class,
+        ImplicitBoxingAndUnboxing.class,
+        ImplicitCast.class,
+        JAssertStatement.class,
+        JConditionalOperation.class,
+        EmptyClinit.class,
+        UselessSwitches.class,
+        SourceCast.class,
+        JCastOperation.WithIntersectionType.class
+    })
+@Transform(remove = JMethodBody.class,
+    add = { JMethodBodyCfg.class, JControlFlowGraph.class })
+@Filter(TypeWithoutPrebuiltFilter.class)
+public class MethodBodyCfgBuilder implements RunnableSchedulable<JMethod> {
+  @Nonnull
+  private final com.android.jack.util.filter.Filter<JMethod> filter =
+      ThreadConfig.get(Options.METHOD_FILTER);
+
+  @Override
+  public void run(@Nonnull JMethod method) {
+    if (method.isAbstract() ||
+        method.isNative() ||
+        !filter.accept(this.getClass(), method)) {
+      return;
+    }
+
+    ControlFlowGraph cfgMarker = method.getMarker(ControlFlowGraph.class);
+    assert cfgMarker != null;
+
+    JAbstractMethodBody body = method.getBody();
+    assert body instanceof JMethodBody;
+
+    List<JLocal> locals = ((JMethodBody) body).getLocals();
+    JMethodBodyCfg cfgBody = new JMethodBodyCfg(body.getSourceInfo(), locals);
+
+    new Builder(
+        cfgMarker.getEntryNode(),
+        (ExitBlock) cfgMarker.getExitNode(),
+        cfgBody).build();
+
+    TransformationRequest request = new TransformationRequest(method);
+    request.append(new Replace(body, cfgBody));
+    request.commit();
+
+    // Update locals to reflect new parent
+    for (JLocal local : locals) {
+      local.updateParents(cfgBody);
+      local.setEnclosingMethodBody(cfgBody);
+    }
+  }
+
+  /** Cfg builder */
+  private static class Builder {
+    @Nonnull
+    private final EntryBlock entry;
+    @Nonnull
+    private final ExitBlock exit;
+    @Nonnull
+    private final JControlFlowGraph cfg;
+    /** Maps CFG marker blocks into a created basic blocks or a placeholder */
+    @Nonnull
+    private final Map<BasicBlock, JBasicBlock> processed = new IdentityHashMap<>();
+    /** Processing queue */
+    @Nonnull
+    private final Stack<BasicBlock> queue = new Stack<>();
+    /** Maps all basic block elements into original statements */
+    @Nonnull
+    private final Map<JBasicBlockElement, JStatement> bbElements = new IdentityHashMap<>();
+
+    Builder(@Nonnull EntryBlock entry, @Nonnull ExitBlock exit, @Nonnull JMethodBodyCfg cfgBody) {
+      this.entry = entry;
+      this.exit = exit;
+      this.cfg = cfgBody.getCfg();
+    }
+
+    void build() {
+      assert entry.getSuccessors().size() == 1;
+      BasicBlock firstBlock = entry.getSuccessors().get(0);
+
+      getBlockOrEnqueue(firstBlock);
+      while (!queue.isEmpty()) {
+        buildBlock(queue.pop());
+      }
+
+      JBasicBlock block = getBlockOrEnqueue(firstBlock);
+      assert !(block instanceof JPlaceholderBasicBlock);
+      cfg.getEntryBlock().replaceAllSuccessors(cfg.getExitBlock(), block);
+
+      // Post-build steps
+      setUpCatchBlockReferences();
+      fixUpReferencesToCaseBlocks();
+    }
+
+    private void fixUpReferencesToCaseBlocks() {
+      // If the original switch statement had a fall-through case, we may
+      // end up with one case clause directly referencing another catch block,
+      // we fix this case to enforce switch block <--> case block invariants
+      // by changing the referencing block to point to the case block
+      // primary successor instead.
+      for (JBasicBlock block : processed.values()) {
+        if (block instanceof JCaseBasicBlock) {
+          for (JBasicBlock predecessor : block.getPredecessorsSnapshot()) {
+            if (!(predecessor instanceof JSwitchBasicBlock)) {
+              predecessor.replaceAllSuccessors(
+                  block, ((JCaseBasicBlock) block).getPrimarySuccessor());
+            }
+          }
+        }
+      }
+    }
+
+    /**
+     * Gets the block created to represent the cfg marker block, or enqueue it to
+     * process and return a placeholder.
+     */
+    private JBasicBlock getBlockOrEnqueue(@Nonnull BasicBlock block) {
+      if (block == exit) {
+        return cfg.getExitBlock();
+      }
+
+      JBasicBlock newBlock = processed.get(block);
+      if (newBlock != null) {
+        return newBlock;
+      }
+
+      // Create a placeholder pseudo-block
+      JPlaceholderBasicBlock placeholder = new JPlaceholderBasicBlock(cfg);
+      processed.put(block, placeholder);
+      queue.push(block);
+      return placeholder;
+    }
+
+    private void setUpCatchBlockReferences() {
+      // Use exception handling context pool to reduce allocations
+      ExceptionHandlingContext.Pool pool = new ExceptionHandlingContext.Pool();
+
+      // Assign exception handling context for all created basic block elements
+      for (Map.Entry<JBasicBlockElement, JStatement> entry : bbElements.entrySet()) {
+        List<JCatchBlock> origCatchBlocks = entry.getValue().getJCatchBlocks();
+
+        if (!origCatchBlocks.isEmpty()) {
+          // Create an ordered list of just created basic blocks to match the list of
+          // catch blocks returned by getJCatchBlocks() of the original statement.
+
+          List<JCatchBasicBlock> newCatchBlocks = new ArrayList<>(origCatchBlocks.size());
+          for (JCatchBlock origCatchBlock : origCatchBlocks) {
+            BasicBlockMarker marker = origCatchBlock.getMarker(BasicBlockMarker.class);
+            assert marker != null;
+            JBasicBlock newCatchBlock = processed.get(marker.getBasicBlock());
+            assert newCatchBlock != null;
+            assert newCatchBlock instanceof JCatchBasicBlock;
+            newCatchBlocks.add((JCatchBasicBlock) newCatchBlock);
+          }
+
+          // Reset exception handling context of the block element
+          entry.getKey().resetEHContext(pool.getOrCreate(newCatchBlocks));
+        }
+      }
+
+      // Refresh throwing basic block's catch block lists
+      for (JBasicBlock block : processed.values()) {
+        if (block instanceof JThrowingBasicBlock) {
+          ((JThrowingBasicBlock) block).resetCatchBlocks();
+        }
+      }
+    }
+
+    private void buildBlock(@Nonnull BasicBlock block) {
+      assert block != exit;
+      assert block != entry;
+
+      JBasicBlock placeholder = processed.get(block);
+      assert placeholder instanceof JPlaceholderBasicBlock;
+
+      JBasicBlock newBlock;
+
+      // Process each kind of CFG marker blocks
+      if (block instanceof PeiBasicBlock) {
+        // NOTE: this handles both PeiBasicBlock and ThrowBasicBlock
+        List<BasicBlock> successors = block.getSuccessors();
+        int index = 0;
+
+        JThrowingBasicBlock throwingBlock =
+            (block instanceof ThrowBasicBlock)
+                ? new JThrowBasicBlock(cfg)
+                : new JThrowingExpressionBasicBlock(cfg,
+                    getBlockOrEnqueue(successors.get(index++)));
+
+        // Build uncaught exception block (which should be CFGs exit block)
+        JBasicBlock uncaughtExceptionBlock = getBlockOrEnqueue(successors.get(index++));
+        assert uncaughtExceptionBlock == cfg.getExitBlock();
+
+        for (; index < successors.size(); index++) {
+          // Build the catch block, note that catch blocks will be assigned later
+          // when block elements are initialized with their exception handling context
+          getBlockOrEnqueue(successors.get(index));
+        }
+        newBlock = throwingBlock;
+
+      } else if (block instanceof SwitchBasicBlock) {
+        SwitchBasicBlock switchBasicBlock = (SwitchBasicBlock) block;
+
+        JSwitchBasicBlock switchBlock =
+            new JSwitchBasicBlock(cfg, getBlockOrEnqueue(switchBasicBlock.getDefaultBlock()));
+        for (BasicBlock caseBlock : switchBasicBlock.getCasesBlock()) {
+          switchBlock.addCase(getBlockOrEnqueue(caseBlock));
+        }
+        newBlock = switchBlock;
+
+      } else if (block instanceof ConditionalBasicBlock) {
+        List<BasicBlock> successors = block.getSuccessors();
+        assert successors.size() == 2;
+        newBlock = new JConditionalBasicBlock(cfg,
+            getBlockOrEnqueue(successors.get(0)), getBlockOrEnqueue(successors.get(1)));
+
+      } else if (block instanceof CatchBasicBlock) {
+        List<BasicBlock> successors = block.getSuccessors();
+        assert successors.size() == 1;
+        CatchBasicBlock catchBlock = (CatchBasicBlock) block;
+        JLocal catchVar = catchBlock.getCatchVar();
+        newBlock = new JCatchBasicBlock(cfg, getBlockOrEnqueue(successors.get(0)),
+            catchBlock.getCatchTypes(), catchVar);
+
+      } else if (block instanceof ReturnBasicBlock) {
+        List<BasicBlock> successors = block.getSuccessors();
+        assert successors.size() == 1;
+        newBlock = new JReturnBasicBlock(cfg);
+
+        // Must always point to the exit block
+        JBasicBlock exitBlock = getBlockOrEnqueue(successors.get(0));
+        assert exitBlock == cfg.getExitBlock();
+
+      } else if (block instanceof NormalBasicBlock) {
+        List<BasicBlock> successors = block.getSuccessors();
+        assert successors.size() == 1;
+        newBlock = new JSimpleBasicBlock(cfg, getBlockOrEnqueue(successors.get(0)));
+
+      } else {
+        throw new AssertionError();
+      }
+
+      // Move statements into newly created block
+      BasicBlockEntryBuilder builder = new BasicBlockEntryBuilder(bbElements, newBlock);
+      for (JStatement stmt : block.getStatements()) {
+        builder.accept(stmt);
+      }
+
+      // Make sure Simple block element always has a trailing goto element
+      if (newBlock instanceof JSimpleBasicBlock) {
+        assert newBlock.hasElements();
+        if (!(newBlock.getLastElement() instanceof JGotoBlockElement)) {
+          newBlock.appendElement(new JGotoBlockElement(SourceInfo.UNKNOWN,
+              newBlock.getLastElement().getEHContext()));
+        }
+      }
+
+      // We have to special-case creation of JCaseBasicBlock with does not exist in CFG marker.
+      // If the first element of the created basic block is JCaseBlockElement, we will
+      // split the block we just created into two blocks and make a new JCaseBasicBlock
+      // of the first one.
+      assert newBlock.hasElements();
+      if (newBlock.getFirstElement() instanceof JCaseBlockElement) {
+        JSimpleBasicBlock firstBlock = newBlock.split(1);
+        assert firstBlock.getElementCount() == 2;
+        assert firstBlock.getPredecessors().isEmpty();
+
+        // Note that the created basic block is going to
+        // represent the original CFG marker block.
+        newBlock = new BasicBlockBuilder(cfg)
+            .append(firstBlock).removeLast()
+            .createCaseBlock(firstBlock.getPrimarySuccessor());
+
+        firstBlock.detach(newBlock);
+      }
+
+      // Replace temp block with real one and fix-up references
+      placeholder.detach(newBlock);
+      processed.put(block, newBlock);
+    }
+
+    /** Builds all the elements of the basic block */
+    private static class BasicBlockEntryBuilder extends JVisitor {
+      /** Maps *all* created basic block elements into original statements */
+      @Nonnull
+      private final Map<JBasicBlockElement, JStatement> elements;
+      /** Basic block being built */
+      private final JBasicBlock block;
+      /**
+       * Indicates the block is sealed, i.e. already saw potentially
+       * throwing expression, for assertion purpose only.
+       */
+      private boolean sealed = false;
+
+      private BasicBlockEntryBuilder(
+          @Nonnull Map<JBasicBlockElement, JStatement> elements, @Nonnull JBasicBlock block) {
+        this.elements = elements;
+        this.block = block;
+      }
+
+      private void addElement(@Nonnull JStatement statement, @Nonnull JBasicBlockElement element) {
+        assert !sealed;
+        this.elements.put(element, statement);
+        this.block.appendElement(element);
+        // NOTE: don't consider case elements as terminal, the block will be split
+        //       later into a case basic block and rest representing the current block
+        this.sealed = element.isTerminal() &&
+            !(element instanceof JCaseBlockElement);
+      }
+
+      @Override
+      public boolean visit(@Nonnull JGoto x) {
+        assert block instanceof JSimpleBasicBlock;
+        addElement(x, new JGotoBlockElement(
+            x.getSourceInfo(), ExceptionHandlingContext.EMPTY));
+        return false;
+      }
+
+      @Override
+      public boolean visit(@Nonnull JReturnStatement x) {
+        assert block instanceof JReturnBasicBlock;
+        addElement(x, new JReturnBlockElement(
+            x.getSourceInfo(), ExceptionHandlingContext.EMPTY, x.getExpr()));
+        return false;
+      }
+
+      @Override
+      public boolean visit(@Nonnull JExpressionStatement x) {
+        JExpression expr = x.getExpr();
+        // Depending on the expression structure we create different basic block elements
+        if (expr instanceof JAsgOperation) {
+          JAsgOperation asg = (JAsgOperation) expr;
+          JExpression lhs = asg.getLhs();
+          if (lhs instanceof JArrayRef || lhs instanceof JFieldRef) {
+            addElement(x, new JStoreBlockElement(
+                x.getSourceInfo(), ExceptionHandlingContext.EMPTY, asg));
+          } else if (lhs instanceof JVariableRef) {
+            addElement(x, new JVariableAsgBlockElement(
+                x.getSourceInfo(), ExceptionHandlingContext.EMPTY, asg));
+          } else {
+            throw new AssertionError();
+          }
+
+        } else if (expr instanceof JMethodCall) {
+          addElement(x, new JMethodCallBlockElement(
+              x.getSourceInfo(), ExceptionHandlingContext.EMPTY, (JMethodCall) x.getExpr()));
+
+        } else if (expr instanceof JPolymorphicMethodCall) {
+          addElement(x, new JPolymorphicMethodCallBlockElement(x.getSourceInfo(),
+              ExceptionHandlingContext.EMPTY, (JPolymorphicMethodCall) x.getExpr()));
+
+        } else {
+          throw new AssertionError();
+        }
+        return false;
+      }
+
+      @Override
+      public boolean visit(@Nonnull JThrowStatement x) {
+        assert block instanceof JThrowingBasicBlock;
+        addElement(x, new JThrowBlockElement(
+            x.getSourceInfo(), ExceptionHandlingContext.EMPTY, x.getExpr()));
+        return false;
+      }
+
+      @Override
+      public boolean visit(@Nonnull JIfStatement x) {
+        // If statement must end JConditionalBasicBlock
+        assert block instanceof JConditionalBasicBlock;
+        addElement(x, new JConditionalBlockElement(
+            x.getSourceInfo(), ExceptionHandlingContext.EMPTY, x.getIfExpr()));
+
+        FallThroughMarker marker = x.getMarker(FallThroughMarker.class);
+        if (marker != null && marker.getFallThrough() == FallThroughMarker.FallThroughEnum.ELSE) {
+          ((JConditionalBasicBlock) this.block).setInverted(true);
+        }
+
+        return false;
+      }
+
+      @Override
+      public boolean visit(@Nonnull JCaseStatement x) {
+        addElement(x, new JCaseBlockElement(
+            x.getSourceInfo(), ExceptionHandlingContext.EMPTY, x.getExpr()));
+        return false;
+      }
+
+      @Override
+      public boolean visit(@Nonnull JUnlock x) {
+        assert block instanceof JThrowingBasicBlock;
+        addElement(x, new JUnlockBlockElement(
+            x.getSourceInfo(), ExceptionHandlingContext.EMPTY, x.getLockExpr()));
+        return false;
+      }
+
+      @Override
+      public boolean visit(@Nonnull JLock x) {
+        assert block instanceof JThrowingBasicBlock;
+        addElement(x, new JLockBlockElement(
+            x.getSourceInfo(), ExceptionHandlingContext.EMPTY, x.getLockExpr()));
+        return false;
+      }
+
+      @Override
+      public boolean visit(@Nonnull JSwitchStatement x) {
+        assert block instanceof JSwitchBasicBlock;
+        addElement(x, new JSwitchBlockElement(
+            x.getSourceInfo(), ExceptionHandlingContext.EMPTY, x.getExpr()));
+        return false;
+      }
+
+      @Override
+      public boolean visit(@Nonnull JNode jnode) {
+        throw new AssertionError(
+            "Unsupported JNode in JBasicBlock builder: " + jnode.getClass().getCanonicalName());
+      }
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/mutations/BasicBlockBuilder.java b/jack/src/com/android/jack/ir/ast/cfg/mutations/BasicBlockBuilder.java
new file mode 100644
index 0000000..cb9c795
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/mutations/BasicBlockBuilder.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg.mutations;
+
+import com.android.jack.ir.ast.JClass;
+import com.android.jack.ir.ast.JLocal;
+import com.android.jack.ir.ast.cfg.JBasicBlock;
+import com.android.jack.ir.ast.cfg.JBasicBlockElement;
+import com.android.jack.ir.ast.cfg.JCaseBasicBlock;
+import com.android.jack.ir.ast.cfg.JCatchBasicBlock;
+import com.android.jack.ir.ast.cfg.JConditionalBasicBlock;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.jack.ir.ast.cfg.JReturnBasicBlock;
+import com.android.jack.ir.ast.cfg.JSimpleBasicBlock;
+import com.android.jack.ir.ast.cfg.JSwitchBasicBlock;
+import com.android.jack.ir.ast.cfg.JThrowBasicBlock;
+import com.android.jack.ir.ast.cfg.JThrowingExpressionBasicBlock;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/**
+ * A basic block builder, simplifies building the list of block
+ * elements and initializing the basic block.
+ */
+public class BasicBlockBuilder {
+  @Nonnull
+  private final JControlFlowGraph cfg;
+  @CheckForNull
+  private List<JBasicBlockElement> elements = new ArrayList<>();
+
+  public BasicBlockBuilder(@Nonnull JControlFlowGraph cfg) {
+    this.cfg = cfg;
+  }
+
+  @Nonnull
+  private List<JBasicBlockElement> elements() {
+    List<JBasicBlockElement> elements = this.elements;
+    if (elements == null) {
+      throw new IllegalStateException("The builder has already been closed");
+    }
+    return elements;
+  }
+
+  /** Append element */
+  @Nonnull
+  public BasicBlockBuilder append(@Nonnull JBasicBlockElement element) {
+    elements().add(element);
+    return this;
+  }
+
+  /** Append elements */
+  @Nonnull
+  public BasicBlockBuilder append(@Nonnull List<JBasicBlockElement> elements) {
+    for (JBasicBlockElement element : elements) {
+      elements().add(element);
+    }
+    return this;
+  }
+
+  /** Append elements from basic block */
+  @Nonnull
+  public BasicBlockBuilder append(@Nonnull JBasicBlock block) {
+    return append(block.getElements(true));
+  }
+
+  /** Remove the last element */
+  @Nonnull
+  public BasicBlockBuilder removeLast() {
+    List<JBasicBlockElement> elements = elements();
+    assert elements.size() > 0;
+    elements.remove(elements.size() - 1);
+    return this;
+  }
+
+  @Nonnull
+  public JSimpleBasicBlock createSimpleBlock(@Nonnull JBasicBlock primary) {
+    return commit(new JSimpleBasicBlock(cfg, primary));
+  }
+
+  @Nonnull
+  public JConditionalBasicBlock createConditionalBlock(
+      @Nonnull JBasicBlock ifTrue, @Nonnull JBasicBlock ifFalse) {
+    return commit(new JConditionalBasicBlock(cfg, ifTrue, ifFalse));
+  }
+
+  @Nonnull
+  public JCaseBasicBlock createCaseBlock(@Nonnull JBasicBlock primary) {
+    return commit(new JCaseBasicBlock(cfg, primary));
+  }
+
+  @Nonnull
+  public JSwitchBasicBlock createSwitchBlock(@Nonnull JBasicBlock defaultCase) {
+    return commit(new JSwitchBasicBlock(cfg, defaultCase));
+  }
+
+  @Nonnull
+  public JCatchBasicBlock createCatchBlock(
+      @Nonnull JBasicBlock primary, @Nonnull List<JClass> catchTypes, @Nonnull JLocal catchLocal) {
+    return commit(new JCatchBasicBlock(cfg, primary, catchTypes, catchLocal));
+  }
+
+  @Nonnull
+  public JReturnBasicBlock createReturnBlock() {
+    return commit(new JReturnBasicBlock(cfg));
+  }
+
+  @Nonnull
+  public JThrowBasicBlock createThrowBlock() {
+    JThrowBasicBlock block = commit(new JThrowBasicBlock(cfg));
+    block.resetCatchBlocks();
+    return block;
+  }
+
+  @Nonnull
+  public JThrowingExpressionBasicBlock createThrowingExprBlock(@Nonnull JBasicBlock primary) {
+    JThrowingExpressionBasicBlock block = commit(new JThrowingExpressionBasicBlock(cfg, primary));
+    block.resetCatchBlocks();
+    return block;
+  }
+
+  /** Initializes the block and closes the builder */
+  @Nonnull
+  public <T extends JBasicBlock> T commit(@Nonnull T block) {
+    for (JBasicBlockElement element : elements()) {
+      block.appendElement(element);
+    }
+    this.elements = null;
+    return block;
+  }
+}
diff --git a/jack/src/com/android/jack/ir/ast/cfg/mutations/CfgFragment.java b/jack/src/com/android/jack/ir/ast/cfg/mutations/CfgFragment.java
new file mode 100644
index 0000000..9a9361e
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/cfg/mutations/CfgFragment.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.ir.ast.cfg.mutations;
+
+import com.android.jack.ir.ast.cfg.JBasicBlock;
+import com.android.jack.ir.ast.cfg.JPlaceholderBasicBlock;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/**
+ * Represents a CFG fragment with one entry block and zero, one or many exit
+ * blocks. All exit blocks must converge on one single JPlaceholderBasicBlock
+ * block representing one single 'sink' point.
+ *
+ * It is not enforced that the basic blocks of the fragment represent an isolated
+ * fragment of the CFG, thus some of the basic blocks may reference outside blocks.
+ *
+ * This may be important in some cases, for example when the fragment includes
+ * JThrowingBasicBlock blocks with already properly assigned unhandled exception
+ * handler block.
+ *
+ * In case there a no edges coming out of the block, the exit block may be `null`.
+ */
+public class CfgFragment {
+  @Nonnull
+  public final JBasicBlock entry;
+  @CheckForNull
+  public final JPlaceholderBasicBlock exit;
+
+  public CfgFragment(@Nonnull JBasicBlock entry, @CheckForNull JPlaceholderBasicBlock exit) {
+    this.entry = entry;
+    this.exit = exit;
+  }
+
+  /**
+   * Replaces all the successors of `source` pointing to `target` with the entry
+   * block of the fragment, and replaces the exit block of the fragment with `target`.
+   */
+  public void insert(@Nonnull JBasicBlock source, @Nonnull JBasicBlock target) {
+    // Replace all the successor-references pointing to `target` with
+    // CFG fragment entry block.
+    source.replaceAllSuccessors(target, this.entry);
+
+    // If there is an exit block, replace it with the target
+    if (this.exit != null) {
+      this.exit.detach(target);
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/ir/impl/BaseGenerationVisitor.java b/jack/src/com/android/jack/ir/impl/BaseGenerationVisitor.java
index 52090e9..1056276 100644
--- a/jack/src/com/android/jack/ir/impl/BaseGenerationVisitor.java
+++ b/jack/src/com/android/jack/ir/impl/BaseGenerationVisitor.java
@@ -77,6 +77,7 @@
 import com.android.jack.ir.ast.JLongLiteral;
 import com.android.jack.ir.ast.JMethod;
 import com.android.jack.ir.ast.JMethodBody;
+import com.android.jack.ir.ast.JMethodBodyCfg;
 import com.android.jack.ir.ast.JMethodCall;
 import com.android.jack.ir.ast.JMethodId;
 import com.android.jack.ir.ast.JMethodIdRef;
@@ -102,6 +103,9 @@
 import com.android.jack.ir.ast.JReturnStatement;
 import com.android.jack.ir.ast.JSession;
 import com.android.jack.ir.ast.JShortLiteral;
+import com.android.jack.ir.ast.JSsaVariableDefRef;
+import com.android.jack.ir.ast.JSsaVariableRef;
+import com.android.jack.ir.ast.JSsaVariableUseRef;
 import com.android.jack.ir.ast.JStatement;
 import com.android.jack.ir.ast.JSwitchStatement;
 import com.android.jack.ir.ast.JSynchronizedBlock;
@@ -112,6 +116,33 @@
 import com.android.jack.ir.ast.JType;
 import com.android.jack.ir.ast.JUnlock;
 import com.android.jack.ir.ast.JWhileStatement;
+import com.android.jack.ir.ast.cfg.JBasicBlock;
+import com.android.jack.ir.ast.cfg.JBasicBlockElement;
+import com.android.jack.ir.ast.cfg.JCaseBasicBlock;
+import com.android.jack.ir.ast.cfg.JCaseBlockElement;
+import com.android.jack.ir.ast.cfg.JCatchBasicBlock;
+import com.android.jack.ir.ast.cfg.JConditionalBasicBlock;
+import com.android.jack.ir.ast.cfg.JConditionalBlockElement;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.jack.ir.ast.cfg.JEntryBasicBlock;
+import com.android.jack.ir.ast.cfg.JExitBasicBlock;
+import com.android.jack.ir.ast.cfg.JGotoBlockElement;
+import com.android.jack.ir.ast.cfg.JLockBlockElement;
+import com.android.jack.ir.ast.cfg.JMethodCallBlockElement;
+import com.android.jack.ir.ast.cfg.JPhiBlockElement;
+import com.android.jack.ir.ast.cfg.JPlaceholderBasicBlock;
+import com.android.jack.ir.ast.cfg.JPolymorphicMethodCallBlockElement;
+import com.android.jack.ir.ast.cfg.JRegularBasicBlock;
+import com.android.jack.ir.ast.cfg.JReturnBlockElement;
+import com.android.jack.ir.ast.cfg.JSimpleBasicBlock;
+import com.android.jack.ir.ast.cfg.JStoreBlockElement;
+import com.android.jack.ir.ast.cfg.JSwitchBasicBlock;
+import com.android.jack.ir.ast.cfg.JSwitchBlockElement;
+import com.android.jack.ir.ast.cfg.JThrowBasicBlock;
+import com.android.jack.ir.ast.cfg.JThrowBlockElement;
+import com.android.jack.ir.ast.cfg.JThrowingExpressionBasicBlock;
+import com.android.jack.ir.ast.cfg.JUnlockBlockElement;
+import com.android.jack.ir.ast.cfg.JVariableAsgBlockElement;
 import com.android.jack.ir.formatter.SourceFormatter;
 import com.android.jack.lookup.CommonTypes;
 import com.android.jack.util.TextOutput;
@@ -121,8 +152,12 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnegative;
 import javax.annotation.Nonnull;
 
 /**
@@ -181,6 +216,9 @@
 
   protected boolean suppressType = false;
 
+  @CheckForNull
+  protected Map<JBasicBlock, String> cfgBlocks = null;
+
   public BaseGenerationVisitor(TextOutput textOutput) {
     super(textOutput);
   }
@@ -517,7 +555,7 @@
       semi();
       newlineOpt();
     } else {
-      JMethodBody body = x.getBody();
+      JAbstractMethodBody body = x.getBody();
       assert body != null;
       accept(body);
     }
@@ -888,6 +926,271 @@
   }
 
   @Override
+  public boolean visit(@Nonnull JMethodBodyCfg x) {
+    accept(x.getCfg());
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JControlFlowGraph x) {
+    openBlock();
+    needSemi = false;
+    assert this.cfgBlocks == null;
+
+    Map<JBasicBlock, String> cfgBlocks = new LinkedHashMap<>();
+    this.cfgBlocks = cfgBlocks;
+
+    // Collect basic blocks backwards to include unreachable ones
+    cfgBlocks.put(x.getEntryBlock(), getStringId(0));
+    List<JBasicBlock> blocks = x.getReachableBlocksDepthFirst();
+    blocks.addAll(x.getInternalBlocksUnordered()); // Add unreachable blocks
+    for (JBasicBlock block : blocks) {
+      if (block != x.getExitBlock() && !cfgBlocks.containsKey(block)) {
+        cfgBlocks.put(block, getStringId(cfgBlocks.size()));
+      }
+    }
+    cfgBlocks.put(x.getExitBlock(), getStringId(cfgBlocks.size()));
+
+    for (Map.Entry<JBasicBlock, String> entry : cfgBlocks.entrySet()) {
+      print(entry.getValue());
+      print(": ");
+      accept(entry.getKey());
+    }
+    closeBlock();
+
+    this.cfgBlocks = null;
+    return false;
+  }
+
+  private String getStringId(@Nonnegative int id) {
+    return String.format("bb#%1$03d", Integer.valueOf(id));
+  }
+
+  private void printBlockIds(
+      @Nonnull Iterable<JBasicBlock> blocks, @CheckForNull JBasicBlock primary) {
+    print("{");
+    String sep = " ";
+    for (JBasicBlock block : blocks) {
+      print(sep);
+      if (cfgBlocks != null && cfgBlocks.containsKey(block)) {
+        print(cfgBlocks.get(block));
+      } else {
+        print("???");
+      }
+      if (block == primary) {
+        print("**");
+      }
+      sep = ", ";
+    }
+    print(" }");
+  }
+
+  private void printBlockCommon(@Nonnull JRegularBasicBlock block) {
+    printBlockCommon(block,
+        block.hasPrimarySuccessor() ? block.getPrimarySuccessor() : null);
+  }
+
+  private void printBlockCommon(@Nonnull JBasicBlock block, @CheckForNull JBasicBlock primary) {
+    print("; suc: ");
+    printBlockIds(block.getSuccessors(), primary);
+    print("; pre: ");
+    printBlockIds(block.getPredecessors(), null);
+    newline();
+
+    List<JBasicBlockElement> elements = block.getElements(true);
+    for (int i = 0; i < elements.size(); i++) {
+      print(String.format("  e%1$03d: ", Integer.valueOf(i)));
+      accept(elements.get(i));
+      newline();
+    }
+  }
+
+  @Override
+  public boolean visit(@Nonnull JBasicBlock x) {
+    throw new AssertionError(x.getClass());
+  }
+
+  @Override
+  public boolean visit(@Nonnull JPlaceholderBasicBlock x) {
+    print("placeholder");
+    printBlockCommon(x, null);
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JEntryBasicBlock x) {
+    print("entry");
+    printBlockCommon(x, null);
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JExitBasicBlock x) {
+    print("exit");
+    printBlockCommon(x, null);
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JPhiBlockElement x) {
+    JSsaVariableDefRef lhs = x.getLhs();
+    visit(lhs);
+    print(" = phi (");
+
+    for (JSsaVariableUseRef rhs : x.getRhs()) {
+      if (rhs == null) {
+        print("?");
+      } else {
+        visit(rhs);
+      }
+      space();
+    }
+    print(")");
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JRegularBasicBlock x) {
+    print("return");
+    printBlockCommon(x);
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JThrowBasicBlock x) {
+    print("throw");
+    printBlockCommon(x);
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JThrowingExpressionBasicBlock x) {
+    print("throwing expr");
+    printBlockCommon(x);
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JSwitchBasicBlock x) {
+    print("switch");
+    printBlockCommon(x);
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JCatchBasicBlock x) {
+    print("catch");
+    printBlockCommon(x);
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JCaseBasicBlock x) {
+    print("case");
+    printBlockCommon(x);
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JSimpleBasicBlock x) {
+    print("simple");
+    printBlockCommon(x);
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JConditionalBasicBlock x) {
+    print("conditional");
+    printBlockCommon(x);
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JBasicBlockElement x) {
+    throw new AssertionError(x.getClass());
+  }
+
+  private void printElementCommon(@Nonnull String name, @CheckForNull JExpression expr) {
+    print(name);
+    if (expr != null) {
+      print("; expr: ");
+      accept(expr);
+    }
+  }
+
+  @Override
+  public boolean visit(@Nonnull JMethodCallBlockElement x) {
+    printElementCommon("method call", x.getCall());
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JStoreBlockElement x) {
+    printElementCommon("store", x.getAssignment());
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JVariableAsgBlockElement x) {
+    printElementCommon("var", x.getAssignment());
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JLockBlockElement x) {
+    printElementCommon("lock", x.getExpression());
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JUnlockBlockElement x) {
+    printElementCommon("unlock", x.getExpression());
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JSwitchBlockElement x) {
+    printElementCommon("switch", x.getExpression());
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JReturnBlockElement x) {
+    printElementCommon("return", x.getExpression());
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JConditionalBlockElement x) {
+    printElementCommon("condition", x.getCondition());
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JThrowBlockElement x) {
+    printElementCommon("throw", x.getExpression());
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JPolymorphicMethodCallBlockElement x) {
+    printElementCommon("poly-call", x.getCall());
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JGotoBlockElement x) {
+    printElementCommon("goto", null);
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JCaseBlockElement x) {
+    printElementCommon("case", x.getLiteral());
+    return false;
+  }
+
+  @Override
   public boolean visit(@Nonnull JMethodIdRef x) {
     printTypeName(x.getEnclosingType());
     print('.');
@@ -1101,6 +1404,35 @@
   }
 
   @Override
+  public boolean visit(@Nonnull JSsaVariableRef x) {
+    print(x.getTarget().getName());
+    print("{");
+    print("" + x.getVersion());
+    print("}");
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JSsaVariableDefRef x) {
+    print(x.getTarget().getName());
+    print("{");
+    print("" + x.getVersion());
+    int reg = x.getRopRegister();
+    if (reg != -1) {
+      print("(v");
+      print("" + reg);
+      print(")");
+    }
+    print("}");
+    return false;
+  }
+
+  @Override
+  public boolean visit(@Nonnull JSsaVariableUseRef x) {
+    return visit(x.getDef());
+  }
+
+  @Override
   public boolean visit(@Nonnull JSwitchStatement x) {
     print(CHARS_SWITCH);
     lparen();
diff --git a/jack/src/com/android/jack/ir/impl/JavaPrecedenceVisitor.java b/jack/src/com/android/jack/ir/impl/JavaPrecedenceVisitor.java
index c40bb9e..c84c32a 100644
--- a/jack/src/com/android/jack/ir/impl/JavaPrecedenceVisitor.java
+++ b/jack/src/com/android/jack/ir/impl/JavaPrecedenceVisitor.java
@@ -48,6 +48,7 @@
 import com.android.jack.ir.ast.JPostfixOperation;
 import com.android.jack.ir.ast.JPrefixOperation;
 import com.android.jack.ir.ast.JShortLiteral;
+import com.android.jack.ir.ast.JSsaVariableRef;
 import com.android.jack.ir.ast.JThisRef;
 import com.android.jack.ir.ast.JVisitor;
 
@@ -184,6 +185,12 @@
   }
 
   @Override
+  public boolean visit(@Nonnull JSsaVariableRef x) {
+    answer = 0;
+    return false;
+  }
+
+  @Override
   public boolean visit(@Nonnull JLongLiteral x) {
     answer = 0;
     return false;
diff --git a/jack/src/com/android/jack/optimizations/Optimizations.java b/jack/src/com/android/jack/optimizations/Optimizations.java
index 7063f17..e146b3b 100644
--- a/jack/src/com/android/jack/optimizations/Optimizations.java
+++ b/jack/src/com/android/jack/optimizations/Optimizations.java
@@ -18,11 +18,13 @@
 
 import com.android.jack.library.DumpInLibrary;
 import com.android.jack.library.PrebuiltCompatibility;
+import com.android.jack.optimizations.cfg.VariablesScope;
 import com.android.sched.item.Description;
 import com.android.sched.item.Feature;
 import com.android.sched.util.config.HasKeyId;
 import com.android.sched.util.config.category.Private;
 import com.android.sched.util.config.id.BooleanPropertyId;
+import com.android.sched.util.config.id.EnumPropertyId;
 import com.android.sched.util.config.id.PropertyId;
 
 import javax.annotation.Nonnull;
@@ -326,6 +328,43 @@
         .addCategory(Private.class);
   }
 
+  /**
+   * A {@link Feature} that represents simple block merging optimization.
+   */
+  @HasKeyId
+  @Description("Apply simple block merging optimization")
+  public static class SimpleBasicBlockMerging implements Feature {
+    @Nonnull
+    public static final BooleanPropertyId ENABLE = BooleanPropertyId
+        .create("jack.optimization.simple-block-merging",
+            "Apply simple block merging optimization")
+        .addDefaultValue(Boolean.FALSE)
+        .addCategory(DumpInLibrary.class)
+        .addCategory(PrebuiltCompatibility.class)
+        .addCategory(Private.class);
+
+    @Nonnull
+    public static final BooleanPropertyId PRESERVE_SOURCE_INFO = BooleanPropertyId
+        .create("jack.optimization.simple-block-merging.preserve-source-info",
+            "Preserves source info while merging blocks")
+        .addDefaultValue(Boolean.TRUE)
+        .requiredIf(ENABLE.getValue().isTrue())
+        .addCategory(DumpInLibrary.class)
+        .addCategory(PrebuiltCompatibility.class)
+        .addCategory(Private.class);
+
+    @Nonnull
+    public static final EnumPropertyId<VariablesScope> MERGE_VARIABLES = EnumPropertyId
+        .create("jack.optimization.simple-block-merging.merge-vars",
+            "Merge variables before merging blocks", VariablesScope.class)
+        .ignoreCase()
+        .addDefaultValue(VariablesScope.SYNTHETIC)
+        .requiredIf(ENABLE.getValue().isTrue())
+        .addCategory(DumpInLibrary.class)
+        .addCategory(PrebuiltCompatibility.class)
+        .addCategory(Private.class);
+  }
+
   @Nonnull
   public static final BooleanPropertyId ENABLE_NULL_INSTANCEOF =
       BooleanPropertyId.create(
diff --git a/jack/src/com/android/jack/optimizations/blockmerger/CfgSimpleBasicBlockMerger.java b/jack/src/com/android/jack/optimizations/blockmerger/CfgSimpleBasicBlockMerger.java
new file mode 100644
index 0000000..3edce1e
--- /dev/null
+++ b/jack/src/com/android/jack/optimizations/blockmerger/CfgSimpleBasicBlockMerger.java
@@ -0,0 +1,495 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.optimizations.blockmerger;
+
+import com.android.jack.ir.ast.JAsgOperation;
+import com.android.jack.ir.ast.JLocal;
+import com.android.jack.ir.ast.JLocalRef;
+import com.android.jack.ir.ast.JMethodBodyCfg;
+import com.android.jack.ir.ast.JNode;
+import com.android.jack.ir.ast.JVariable;
+import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.ast.cfg.BasicBlockComparator;
+import com.android.jack.ir.ast.cfg.JBasicBlock;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.jack.ir.ast.cfg.JEntryBasicBlock;
+import com.android.jack.ir.ast.cfg.JRegularBasicBlock;
+import com.android.jack.ir.ast.cfg.JSimpleBasicBlock;
+import com.android.jack.ir.ast.cfg.JVariableAsgBlockElement;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.jack.optimizations.Optimizations;
+import com.android.jack.optimizations.cfg.CfgBasicBlockUtils;
+import com.android.jack.optimizations.cfg.VariablesScope;
+import com.android.sched.item.Description;
+import com.android.sched.schedulable.RunnableSchedulable;
+import com.android.sched.schedulable.Transform;
+import com.android.sched.schedulable.Use;
+import com.android.sched.util.config.ThreadConfig;
+import com.android.sched.util.log.Tracer;
+import com.android.sched.util.log.TracerFactory;
+import com.android.sched.util.log.stats.Counter;
+import com.android.sched.util.log.stats.CounterImpl;
+import com.android.sched.util.log.stats.StatisticId;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+
+/** Simple CFG basic block merger */
+@Description("Simple CFG basic block merger")
+@Transform(modify = JControlFlowGraph.class)
+@Use(CfgBasicBlockUtils.class)
+public class CfgSimpleBasicBlockMerger
+    implements RunnableSchedulable<JMethodBodyCfg> {
+
+  @Nonnull
+  public static final StatisticId<Counter> BLOCKS_MERGED = new StatisticId<>(
+      "jack.optimization.simple-block-merging.blocks-merged", "Blocks merged",
+      CounterImpl.class, Counter.class);
+
+  private final boolean preserveSourceInfo =
+      ThreadConfig.get(Optimizations.SimpleBasicBlockMerging.PRESERVE_SOURCE_INFO).booleanValue();
+  @Nonnull
+  private final VariablesScope mergeVarsScope =
+      ThreadConfig.get(Optimizations.SimpleBasicBlockMerging.MERGE_VARIABLES);
+
+  @Override
+  public void run(@Nonnull final JMethodBodyCfg cfg) {
+    CfgBasicBlockUtils basicBlockUtils = new CfgBasicBlockUtils(cfg.getCfg());
+
+    // #1 Maximally split basic blocks
+    basicBlockUtils.maximallySplitAllBasicBlocks();
+
+    // #2 Merge basic blocks
+    mergeBlocks(cfg.getCfg());
+
+    // #3 Maximally merge simple basic blocks
+    basicBlockUtils.mergeSimpleBlocks(preserveSourceInfo);
+  }
+
+  private void mergeBlocks(@Nonnull final JControlFlowGraph cfg) {
+    new Processor(cfg).process();
+  }
+
+  /** Processing class */
+  private class Processor {
+    @Nonnull
+    private final Tracer tracer = TracerFactory.getTracer();
+    @Nonnull
+    private final JControlFlowGraph cfg;
+
+    /** Maps list of successors into a (stable) set of blocks having such successors */
+    @Nonnull
+    private final
+    Map<List<JBasicBlock>, Set<JBasicBlock>> groupsBySuccessors = new LinkedHashMap<>();
+    /**
+     * Maps basic blocks into the cached list of their successors, this cache is
+     * also used to access successors list after the block's successors are updated.
+     */
+    @Nonnull
+    private final Map<JBasicBlock, List<JBasicBlock>> successorsCache = new LinkedHashMap<>();
+    /** Processing queue */
+    @Nonnull
+    private final Set<List<JBasicBlock>> queue = new LinkedHashSet<>();
+
+    /** Caches last used independent variables set */
+    @CheckForNull
+    private IndependentVariables independentVariablesCache = null;
+
+    Processor(@Nonnull JControlFlowGraph cfg) {
+      this.cfg = cfg;
+
+      for (JBasicBlock block : cfg.getInternalBlocksUnordered()) {
+        assert block instanceof JRegularBasicBlock;
+        successorsCache.put(block, block.getSuccessors());
+        addBlockToSuccessorsGroup(block);
+      }
+
+      // We process all groups of the basic blocks sharing the same list of
+      // successors in stable order. In case we merged some of the blocks
+      // we might need to update `groupsBySuccessors` map, and re-process
+      // groups we have already processed.
+      queue.addAll(groupsBySuccessors.keySet());
+    }
+
+    private void addBlockToSuccessorsGroup(@Nonnull JBasicBlock block) {
+      List<JBasicBlock> successors = successorsCache.get(block);
+      assert successors != null;
+      Set<JBasicBlock> blocks = groupsBySuccessors.get(successors);
+      if (blocks == null) {
+        blocks = new LinkedHashSet<>();
+        groupsBySuccessors.put(successors, blocks);
+      }
+      blocks.add(block);
+    }
+
+    void process() {
+      while (!queue.isEmpty()) {
+        Iterator<List<JBasicBlock>> iterator = queue.iterator();
+        assert iterator.hasNext();
+        List<JBasicBlock> group = iterator.next();
+        iterator.remove();
+        processGroup(group);
+      }
+    }
+
+    private void processGroup(@Nonnull List<JBasicBlock> group) {
+      Set<JBasicBlock> blocks = groupsBySuccessors.get(group);
+      assert blocks != null;
+
+      // Only process groups with 2 or more blocks
+      if (blocks.size() < 2) {
+        return;
+      }
+
+      List<JBasicBlock> blocksOfLength = new ArrayList<>();
+
+      // We can only merge blocks of the same kind and of the same size.
+      // Iterate blocks with sizes from 1 to max.
+      int length = 1;
+      doneWithGroup:
+      while (true) {
+        // Process the blocks of the specified length only, if we find two
+        // blocks that can be be merged, do so.
+        Iterator<JBasicBlock> iterator = blocks.iterator();
+
+        sameLength:
+        while (iterator.hasNext()) {
+          JBasicBlock candidate = iterator.next();
+          if (candidate.getElementCount() == length) {
+
+            // Check if we can merge with any of the existing blocks
+            for (JBasicBlock existing : blocksOfLength) {
+
+              if (shouldReplaceCandidateWithReplacement(candidate, existing)) {
+                // Merge the blocks
+                replaceOriginalBlockWithReplacement(candidate, existing);
+
+                // Merging blocks (actually replacing `candidate` block with `existing`
+                // block) may change the current group in following ways:
+                //
+                //  1. If `candidate` is part of the group 'signature' (`group` list), *all*
+                //     the blocks should be removed from this group since their successors
+                //     have changed.
+                //
+                //  NOTE: deleting `candidate` basic block may only result in removing basic
+                //        blocks other than `candidate` from `blocks` set if these blocks
+                //        are predecessors of `candidate`, meaning `candidate` is part of
+                //        group signature.
+                if (blocks.isEmpty()) {
+                  break doneWithGroup;
+                }
+
+                //  2. If removing `candidate` caused adding *new* blocks to `blocks` list it
+                //     will also lead to this group being re-scheduled for processing, so we
+                //     just stop here and let the whole group be re-processed later.
+                if (queue.contains(group)) {
+                  break doneWithGroup;
+                }
+
+                //  3. Otherwise we can just remove `candidate` from `blocks` list and continue
+                //     processing blocks with the same length.
+                iterator.remove();
+                continue sameLength;
+              }
+            }
+
+            // We didn't merge `candidate`
+            blocksOfLength.add(candidate);
+          }
+        }
+
+        // Try to grow the blocks in the group if possible
+        if (!addTrivialPredecessors(blocks, length)) {
+          break;
+        }
+
+        length++;
+        blocksOfLength.clear();
+      }
+    }
+
+    private void resetIndependentVariables() {
+      independentVariablesCache = null;
+    }
+
+    /**
+     * Returns true if `a` and `b` can substitute each other.
+     * They can if they are considered to be independent and appropriate
+     * options are set.
+     */
+    private boolean canSubstituteVariables(@Nonnull JLocal a, @Nonnull JLocal b) {
+      if (mergeVarsScope == VariablesScope.NONE) {
+        return false;
+      }
+      if (mergeVarsScope == VariablesScope.SYNTHETIC && (!a.isSynthetic() || !b.isSynthetic())) {
+        return false;
+      }
+
+      // Get variables info
+      if (independentVariablesCache == null) {
+        independentVariablesCache = new IndependentVariables(cfg);
+      }
+      return independentVariablesCache.areIsolatedAndIndependent(a, b);
+    }
+
+    private boolean shouldReplaceCandidateWithReplacement(
+        @Nonnull JBasicBlock candidate, @Nonnull JBasicBlock replacement) {
+      // `candidate` and be replaced with `replacement` if these two blocks are
+      // considered equal, except for allowed local variable differences.
+      BasicBlockComparator comparator = new BasicBlockComparator() {
+        @Nonnull @Override protected Comparator getComparator() {
+          @Nonnull final Map<JLocal, JLocal> substitutions = new HashMap<>();
+
+          return new Comparator() {
+            @Override protected void performCommonChecks(@Nonnull JNode node) {
+              super.performCommonChecks(node);
+              if (preserveSourceInfo) {
+                // Make sure the two nodes have same source info
+                ensure(node.getSourceInfo().equals(otherOrMe(node).getSourceInfo()));
+              }
+            }
+
+            @Override protected boolean equal(@Nonnull JVariable a, @Nonnull JVariable b) {
+              if (a == b) {
+                return true;
+              }
+              if (a instanceof JLocal && b instanceof JLocal) {
+                if (!a.getType().isSameType(b.getType())) {
+                  return false; // Should be of different types
+                }
+                JLocal local = substitutions.get(a);
+                if (local != null) {
+                  return b == local; // Should not be mapped into different `b`
+                }
+                local = substitutions.get(b);
+                if (local != null) {
+                  return a == local; // Should not be mapped into different `a`
+                }
+                if (canSubstituteVariables((JLocal) a, (JLocal) b)) {
+                  substitutions.put((JLocal) a, (JLocal) b);
+                  substitutions.put((JLocal) b, (JLocal) a);
+                  return true;
+                }
+              }
+              return false;
+            }
+          };
+        }
+      };
+
+      return comparator.compare(candidate, replacement);
+    }
+
+    /**
+     * For all blocks of given length checks if their have one simple predecessor of
+     * kind JSimpleBasicBlock. Such a predecessor can me merged into their successors.
+     */
+    private boolean addTrivialPredecessors(
+        @Nonnull Set<JBasicBlock> blocks, @Nonnegative int length) {
+
+      boolean seenLonger = false;
+      for (JBasicBlock block : blocks) {
+        if (block.getElementCount() == length && block.getPredecessorCount() == 1) {
+          JBasicBlock predecessor = block.getPredecessors().get(0);
+          assert predecessor != block;
+          assert !blocks.contains(predecessor);
+
+          if (predecessor instanceof JSimpleBasicBlock) {
+            // We increase the length of the block by merging its
+            // simple predecessor into the block
+            JSimpleBasicBlock simple = (JSimpleBasicBlock) predecessor;
+            if (!preserveSourceInfo
+                || simple.getLastElement().getSourceInfo() == SourceInfo.UNKNOWN) {
+
+              // Clean up the singleton group `simple` belongs to (should be
+              // single-basic-block group with successors being { block }).
+              Set<JBasicBlock> groupBlocks = groupsBySuccessors.get(successorsCache.get(simple));
+              assert groupBlocks != null && groupBlocks.size() == 1;
+              groupBlocks.remove(simple);
+
+              // Merge `simple` into its only successor. Note that this operation
+              // should not change the blocks in the current group.
+              List<JBasicBlock> predecessors = simple.getPredecessorsSnapshot();
+              simple.mergeIntoSuccessor();
+              handleRemovedBlock(simple, predecessors);
+
+              // Merging blocks may result in more independent variables, clear the cache
+              resetIndependentVariables();
+            }
+          }
+        }
+        if (block.getElementCount() > length) {
+          seenLonger = true;
+        }
+      }
+      return seenLonger;
+    }
+
+    /**
+     * Merge `original` and `replacement` blocks, such that the original block is deleted
+     * and replaced with `replacement` block. Properly updates all maps and processing queue.
+     *
+     * After replacing with `replacement` block, `original` is removed from the CFG and all
+     * its predecessors are remapped to point into `replacement`, their successor maps are
+     * updated accordingly.
+     */
+    private void replaceOriginalBlockWithReplacement(
+        @Nonnull JBasicBlock original, @Nonnull JBasicBlock replacement) {
+
+      // Replace the block, but don't remove it from
+      // `groupsBySuccessors` (it'll be done by the caller)
+      List<JBasicBlock> originalPredecessors = original.getPredecessorsSnapshot();
+      original.detach(replacement);
+      handleRemovedBlock(original, originalPredecessors);
+      tracer.getStatistic(BLOCKS_MERGED).incValue();
+    }
+
+    private void handleRemovedBlock(
+        @Nonnull JBasicBlock block, @Nonnull List<JBasicBlock> predecessors) {
+      successorsCache.remove(block);
+
+      // Update predecessors
+      for (JBasicBlock predecessor : predecessors) {
+        List<JBasicBlock> successors = successorsCache.get(predecessor);
+        if (successors == null) {
+          assert predecessor instanceof JEntryBasicBlock || predecessor == block;
+          continue;
+        }
+
+        // Remove the predecessor from the old group. Don't need to reprocess
+        // `successors` group it was part of, since we only remove its element
+        // and if it was processed before it should not change the merging result
+        Set<JBasicBlock> blocks = groupsBySuccessors.get(successors);
+        assert blocks != null;
+        blocks.remove(predecessor);
+
+        // Put the predecessor into a new group, schedule it for reprocessing
+        successorsCache.put(predecessor, predecessor.getSuccessors());
+        addBlockToSuccessorsGroup(predecessor);
+        queue.add(successorsCache.get(predecessor));
+      }
+    }
+  }
+
+  /**
+   * Represents a set of isolated variables which can be replaced with each other
+   * during block merge (substitution).
+   *
+   * Isolated variable is a variable which value does not flow between basic blocks,
+   * i.e. if the variable is used in the basic block it is assigned in this basic
+   * block before any read.
+   */
+  private static class IndependentVariables {
+    @Nonnull
+    private final Map<JLocal, Set<JBasicBlock>> isolatedVariables = new HashMap<>();
+
+    IndependentVariables(@Nonnull JControlFlowGraph cfg) {
+      new JVisitor() {
+        @Nonnull
+        final Set<JLocal> assignedInsideBlock = new HashSet<>();
+        @Nonnull
+        final Set<JLocal> notIsolated = new HashSet<>();
+        @CheckForNull
+        JBasicBlock currentBlock = null;
+
+        @Override public boolean visit(@Nonnull JBasicBlock block) {
+          assert currentBlock == null;
+          currentBlock = block;
+          assignedInsideBlock.clear();
+          return super.visit(block);
+        }
+
+        @Override public boolean visit(@Nonnull JLocalRef ref) {
+          assert currentBlock != null;
+          JLocal local = ref.getLocal();
+
+          // Read before assignment?
+          JNode parent = ref.getParent();
+          boolean isAssignmentTarget =
+              parent instanceof JAsgOperation && ((JAsgOperation) parent).getLhs() == ref;
+          if (!isAssignmentTarget) {
+            // Local is a read ...
+            if (!assignedInsideBlock.contains(local)) {
+              // ... and is not assigned yet in this block
+              notIsolated.add(local);
+            }
+          }
+
+          // Mark referencing block
+          Set<JBasicBlock> blocks = isolatedVariables.get(local);
+          if (blocks == null) {
+            blocks = new HashSet<>();
+            isolatedVariables.put(local, blocks);
+          }
+          blocks.add(currentBlock);
+
+          return super.visit(ref);
+        }
+
+        @Override public void endVisit(@Nonnull JVariableAsgBlockElement element) {
+          assert currentBlock != null;
+          JVariable variable = element.getVariable();
+          if (variable instanceof JLocal) {
+            assignedInsideBlock.add((JLocal) variable);
+          }
+          super.endVisit(element);
+        }
+
+        @Override public void endVisit(@Nonnull JBasicBlock block) {
+          assert currentBlock != null;
+          currentBlock = null;
+          super.endVisit(block);
+        }
+
+        @Override public void endVisit(@Nonnull JControlFlowGraph cfg) {
+          // Remove non isolated locals
+          for (JLocal local : notIsolated) {
+            isolatedVariables.remove(local);
+          }
+          super.endVisit(cfg);
+        }
+      }.accept(cfg);
+    }
+
+    boolean areIsolatedAndIndependent(@Nonnull JLocal what, @Nonnull JLocal with) {
+      Set<JBasicBlock> whatBlocks = isolatedVariables.get(what);
+      if (whatBlocks == null) {
+        return false;
+      }
+      Set<JBasicBlock> withBlocks = isolatedVariables.get(with);
+      if (withBlocks == null) {
+        return false;
+      }
+      for (JBasicBlock block : whatBlocks) {
+        if (withBlocks.contains(block)) {
+          return false;
+        }
+      }
+      return true;
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/optimizations/cfg/CfgBasicBlockUtils.java b/jack/src/com/android/jack/optimizations/cfg/CfgBasicBlockUtils.java
new file mode 100644
index 0000000..db8a346
--- /dev/null
+++ b/jack/src/com/android/jack/optimizations/cfg/CfgBasicBlockUtils.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.optimizations.cfg;
+
+import com.google.common.collect.Sets;
+
+import com.android.jack.ir.ast.cfg.BasicBlockLiveProcessor;
+import com.android.jack.ir.ast.cfg.JBasicBlock;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.jack.ir.ast.cfg.JPlaceholderBasicBlock;
+import com.android.jack.ir.ast.cfg.JRegularBasicBlock;
+import com.android.jack.ir.ast.cfg.JSimpleBasicBlock;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.jack.transformations.LocalVarCreator;
+import com.android.sched.schedulable.Transform;
+import com.android.sched.schedulable.Use;
+
+import java.util.Set;
+import javax.annotation.Nonnull;
+
+/** Implements series of basic block related utilities. */
+@Transform(modify = JControlFlowGraph.class)
+@Use(LocalVarCreator.class)
+public final class CfgBasicBlockUtils {
+  @Nonnull
+  private final JControlFlowGraph cfg;
+
+  public CfgBasicBlockUtils(@Nonnull JControlFlowGraph cfg) {
+    this.cfg = cfg;
+  }
+
+  /** Mergers all simple blocks into their successors when possible */
+  public void mergeSimpleBlocks(final boolean preserveSourceInfo) {
+    new BasicBlockLiveProcessor(cfg, false) {
+      @Override
+      public boolean visit(@Nonnull JSimpleBasicBlock simple) {
+        JBasicBlock primary = simple.getPrimarySuccessor();
+        if (primary.getPredecessorCount() == 1) {
+          if (!preserveSourceInfo
+              || simple.getLastElement().getSourceInfo() == SourceInfo.UNKNOWN) {
+            simple.mergeIntoSuccessor();
+          }
+        }
+        return false;
+      }
+    }.process();
+  }
+
+  /** Maximally split all basic blocks */
+  public void maximallySplitAllBasicBlocks() {
+    new BasicBlockLiveProcessor(cfg, false) {
+      @Override
+      public boolean visit(@Nonnull JSimpleBasicBlock simple) {
+        while (simple.getElementCount() > 2) {
+          simple.split(1);
+        }
+        return false;
+      }
+
+      @Override
+      public boolean visit(@Nonnull JRegularBasicBlock regular) {
+        while (regular.getElementCount() > 1) {
+          regular.split(1);
+        }
+        return false;
+      }
+    }.process();
+  }
+
+  /**
+   * Removes basic blocks unreachable from the entry node, treats catch basic
+   * blocks referenced only from exception handling contexts as still referenced.
+   */
+  public void removeUnreachableBlocks() {
+    Set<JBasicBlock> reachable = Sets.newHashSet(cfg.getReachableBlocksDepthFirst());
+    JPlaceholderBasicBlock placeholder = new JPlaceholderBasicBlock(cfg);
+    for (JBasicBlock block : cfg.getInternalBlocksUnordered()) {
+      if (!reachable.contains(block)) {
+        block.detach(placeholder);
+      }
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/optimizations/cfg/CfgJlsNullabilityChecker.java b/jack/src/com/android/jack/optimizations/cfg/CfgJlsNullabilityChecker.java
new file mode 100644
index 0000000..e6f2743
--- /dev/null
+++ b/jack/src/com/android/jack/optimizations/cfg/CfgJlsNullabilityChecker.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.optimizations.cfg;
+
+import com.android.jack.ir.ast.JAlloc;
+import com.android.jack.ir.ast.JAsgOperation;
+import com.android.jack.ir.ast.JClass;
+import com.android.jack.ir.ast.JEqOperation;
+import com.android.jack.ir.ast.JExpression;
+import com.android.jack.ir.ast.JLocal;
+import com.android.jack.ir.ast.JMethodCall;
+import com.android.jack.ir.ast.JNullLiteral;
+import com.android.jack.ir.ast.JPrimitiveType;
+import com.android.jack.ir.ast.JType;
+import com.android.jack.ir.ast.MethodKind;
+import com.android.jack.ir.ast.cfg.ExceptionHandlingContext;
+import com.android.jack.ir.ast.cfg.JConditionalBasicBlock;
+import com.android.jack.ir.ast.cfg.JConditionalBlockElement;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.jack.ir.ast.cfg.JMethodCallBlockElement;
+import com.android.jack.ir.ast.cfg.JPlaceholderBasicBlock;
+import com.android.jack.ir.ast.cfg.JThrowBasicBlock;
+import com.android.jack.ir.ast.cfg.JThrowBlockElement;
+import com.android.jack.ir.ast.cfg.JThrowingExpressionBasicBlock;
+import com.android.jack.ir.ast.cfg.JVariableAsgBlockElement;
+import com.android.jack.ir.ast.cfg.mutations.CfgFragment;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.jack.lookup.CommonTypes;
+import com.android.jack.lookup.JPhantomLookup;
+import com.android.jack.transformations.LocalVarCreator;
+import com.android.jack.transformations.request.TransformationRequest;
+import com.android.jack.util.NamingTools;
+import com.android.sched.schedulable.Transform;
+import com.android.sched.schedulable.Use;
+
+import java.util.Collections;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Helps create nullability checks for preserving JLS-compliance needed
+ * in some optimizations. Works on cfg-IR, rather than regular-IR.
+ */
+@Transform(add = { JAlloc.class, JAsgOperation.NonReusedAsg.class,
+                   JEqOperation.class, JMethodCall.class, JNullLiteral.class })
+@Use({ LocalVarCreator.class })
+public final class CfgJlsNullabilityChecker {
+  @Nonnull
+  private final JControlFlowGraph cfg;
+  @Nonnull
+  private final LocalVarCreator varCreator;
+  @Nonnull
+  private final JPhantomLookup getPhantomLookup;
+
+  public CfgJlsNullabilityChecker(
+      @Nonnull JControlFlowGraph cfg,
+      @Nonnull LocalVarCreator varCreator,
+      @Nonnull JPhantomLookup getPhantomLookup) {
+    this.cfg = cfg;
+    this.varCreator = varCreator;
+    this.getPhantomLookup = getPhantomLookup;
+  }
+
+  /**
+   * Creates a CFG fragment representing the following 'if' statement:
+   * <pre>
+   * if ([expr] == null) {
+   *   throw new NullPointerException();
+   * }
+   * </pre>
+   */
+  @Nonnull
+  public CfgFragment createNullCheck(@Nonnull ExceptionHandlingContext ehContext,
+      @Nonnull JExpression expr, @Nonnull TransformationRequest request) {
+    assert !expr.canThrow();
+
+    // We build the following CFG fragment:
+    //
+    //              bbEntry (cond: expr == null)
+    //           (true)                  (false)
+    //             /                        |
+    //            /                         |
+    //          bbT1                        |
+    //    (-t = alloc <NPE>)                |
+    //            |                         |
+    //          bbT2                        |
+    //      (-t.<init>())                   |
+    //            |                         |
+    //          bbT3                        |
+    //        (throw -t)                    |
+    //            X                         |
+    //                                      |
+    //                        bbDone  <-----o
+    //
+    JClass exceptionType = getPhantomLookup
+        .getClass(CommonTypes.JAVA_LANG_NULL_POINTER_EXCEPTION);
+
+    JLocal tmp = varCreator.createTempLocal(exceptionType, SourceInfo.UNKNOWN, request);
+
+    // throw -tmp
+    JThrowBasicBlock bbT3 = new JThrowBasicBlock(cfg);
+    JThrowBlockElement throwExpr = new JThrowBlockElement(
+        SourceInfo.UNKNOWN, ehContext, tmp.makeRef(SourceInfo.UNKNOWN));
+    bbT3.appendElement(throwExpr);
+    bbT3.resetCatchBlocks();
+
+    // -tmp.<init>()
+    JMethodCallBlockElement constructorCall =
+        new JMethodCallBlockElement(SourceInfo.UNKNOWN, ehContext,
+            new JMethodCall(SourceInfo.UNKNOWN,
+                tmp.makeRef(SourceInfo.UNKNOWN),
+                exceptionType,
+                exceptionType.getOrCreateMethodId(
+                    NamingTools.INIT_NAME,
+                    Collections.<JType>emptyList(),
+                    MethodKind.INSTANCE_NON_VIRTUAL,
+                    JPrimitiveType.JPrimitiveTypeEnum.VOID.getType()),
+                false));
+
+    JThrowingExpressionBasicBlock bbT2 =
+        new JThrowingExpressionBasicBlock(cfg, bbT3);
+    bbT2.appendElement(constructorCall);
+    bbT2.resetCatchBlocks();
+
+    // -tmp = alloc <NPE>
+    JVariableAsgBlockElement alloc =
+        new JVariableAsgBlockElement(SourceInfo.UNKNOWN, ehContext,
+            new JAsgOperation(SourceInfo.UNKNOWN, tmp.makeRef(SourceInfo.UNKNOWN),
+                new JAlloc(SourceInfo.UNKNOWN, exceptionType)));
+
+    JThrowingExpressionBasicBlock bbT1 =
+        new JThrowingExpressionBasicBlock(cfg, bbT2);
+    bbT1.appendElement(alloc);
+    bbT1.resetCatchBlocks();
+
+    // Exit block
+    JPlaceholderBasicBlock exit = new JPlaceholderBasicBlock(cfg);
+
+    // Conditional block
+    JConditionalBasicBlock cond =
+        new JConditionalBasicBlock(cfg, bbT1, exit);
+    cond.appendElement(
+        new JConditionalBlockElement(SourceInfo.UNKNOWN, ehContext,
+            new JEqOperation(SourceInfo.UNKNOWN, expr, new JNullLiteral(SourceInfo.UNKNOWN))));
+
+    return new CfgFragment(cond, exit);
+  }
+}
diff --git a/jack/src/com/android/jack/optimizations/cfg/CfgVarUtils.java b/jack/src/com/android/jack/optimizations/cfg/CfgVarUtils.java
new file mode 100644
index 0000000..8e6956d
--- /dev/null
+++ b/jack/src/com/android/jack/optimizations/cfg/CfgVarUtils.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.optimizations.cfg;
+
+import com.android.jack.ir.ast.JAsgOperation;
+import com.android.jack.ir.ast.JExpression;
+import com.android.jack.ir.ast.JLocal;
+import com.android.jack.ir.ast.JLocalRef;
+import com.android.jack.ir.ast.cfg.JBasicBlock;
+import com.android.jack.ir.ast.cfg.JBasicBlockElement;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.jack.ir.ast.cfg.JVariableAsgBlockElement;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.jack.transformations.LocalVarCreator;
+import com.android.jack.transformations.request.Replace;
+import com.android.jack.transformations.request.TransformationRequest;
+import com.android.sched.schedulable.Transform;
+import com.android.sched.schedulable.Use;
+
+import javax.annotation.Nonnull;
+
+/** Implements series of variable related utilities. */
+public final class CfgVarUtils {
+  /** Defines sets of transformations for method replaceWithLocal(...) */
+  @Transform(add = { JAsgOperation.NonReusedAsg.class, JLocalRef.class },
+      modify = JControlFlowGraph.class)
+  @Use(LocalVarCreator.class)
+  public static class ReplaceWithLocal {
+  }
+
+  /**
+   * Replaces the non-throwing subexpression with a newly created local.
+   *
+   * Reuses EH context from the current block element.
+   */
+  @Nonnull
+  public JLocalRef replaceWithLocal(
+      @Nonnull LocalVarCreator varCreator, @Nonnull JExpression expr) {
+    assert !expr.canThrow(); // Non-throwing expression only
+    SourceInfo srcInfo = expr.getSourceInfo();
+
+    // Get outer block element and basic block
+    JBasicBlockElement element = expr.getParent(JBasicBlockElement.class);
+    JBasicBlock block = element.getBasicBlock();
+    int index = block.indexOf(element);
+
+    // Create a new local
+    TransformationRequest request = new TransformationRequest(block.getCfg().getMethod());
+    JLocal tmp = varCreator.createTempLocal(expr.getType(), SourceInfo.UNKNOWN, request);
+
+    // Replace the expression with local reference
+    JLocalRef result = tmp.makeRef(srcInfo);
+    request.append(new Replace(expr, result));
+
+    // Commit the operation to apply changes, not that the following operation
+    // resets `expr`s parent
+    request.commit();
+
+    // Create and insert the local initialization
+    block.insertElement(index,
+        new JVariableAsgBlockElement(srcInfo,
+            element.getEHContext(), new JAsgOperation(srcInfo, tmp.makeRef(srcInfo), expr)));
+
+    return result;
+  }
+}
diff --git a/jack/src/com/android/jack/optimizations/cfg/ClearAllExceptionHandlingContexts.java b/jack/src/com/android/jack/optimizations/cfg/ClearAllExceptionHandlingContexts.java
new file mode 100644
index 0000000..e04e225
--- /dev/null
+++ b/jack/src/com/android/jack/optimizations/cfg/ClearAllExceptionHandlingContexts.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.optimizations.cfg;
+
+import com.android.jack.ir.ast.cfg.ExceptionHandlingContext;
+import com.android.jack.ir.ast.cfg.JBasicBlock;
+import com.android.jack.ir.ast.cfg.JBasicBlockElement;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
+import com.android.sched.item.Description;
+import com.android.sched.schedulable.Filter;
+import com.android.sched.schedulable.RunnableSchedulable;
+import com.android.sched.schedulable.Transform;
+
+import javax.annotation.Nonnull;
+
+/** Clear all exception handling contexts */
+@Description("Clear all exception handling contexts")
+@Transform(modify = JControlFlowGraph.class)
+@Filter(TypeWithoutPrebuiltFilter.class)
+public class ClearAllExceptionHandlingContexts
+    implements RunnableSchedulable<JControlFlowGraph> {
+
+  @Override
+  public void run(@Nonnull final JControlFlowGraph cfg) {
+    for (JBasicBlock block : cfg.getInternalBlocksUnordered()) {
+      for (JBasicBlockElement element : block.getElements(true)) {
+        element.resetEHContext(ExceptionHandlingContext.EMPTY);
+      }
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/optimizations/cfg/OptimizeConditionalPrimarySuccessor.java b/jack/src/com/android/jack/optimizations/cfg/OptimizeConditionalPrimarySuccessor.java
new file mode 100644
index 0000000..ce1c9d5
--- /dev/null
+++ b/jack/src/com/android/jack/optimizations/cfg/OptimizeConditionalPrimarySuccessor.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.optimizations.cfg;
+
+import com.android.jack.ir.ast.JMethodBodyCfg;
+import com.android.jack.ir.ast.cfg.BasicBlockLiveProcessor;
+import com.android.jack.ir.ast.cfg.JBasicBlock;
+import com.android.jack.ir.ast.cfg.JConditionalBasicBlock;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.jack.ir.ast.cfg.JReturnBasicBlock;
+import com.android.jack.ir.ast.cfg.JSimpleBasicBlock;
+import com.android.jack.ir.ast.cfg.JThrowingExpressionBasicBlock;
+import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
+import com.android.jack.transformations.request.TransformationRequest;
+import com.android.sched.item.Description;
+import com.android.sched.schedulable.Filter;
+import com.android.sched.schedulable.RunnableSchedulable;
+import com.android.sched.schedulable.Transform;
+import com.android.sched.util.log.Tracer;
+import com.android.sched.util.log.TracerFactory;
+import com.android.sched.util.log.stats.Counter;
+import com.android.sched.util.log.stats.CounterImpl;
+import com.android.sched.util.log.stats.StatisticId;
+
+import java.util.HashSet;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/** Optimizes primary successor of the conditional block */
+@Description("Optimizes primary successor of the conditional block")
+@Transform(modify = JControlFlowGraph.class)
+@Filter(TypeWithoutPrebuiltFilter.class)
+public class OptimizeConditionalPrimarySuccessor
+    implements RunnableSchedulable<JMethodBodyCfg> {
+
+  @Nonnull
+  public static final StatisticId<Counter> CONDITIONAL_BLOCKS_OPTIMIZED = new StatisticId<>(
+      "jack.cfg.conditional-block-optimized", "Conditional blocks optimized",
+      CounterImpl.class, Counter.class);
+
+  @Nonnull
+  private final Tracer tracer = TracerFactory.getTracer();
+
+  @Override
+  public void run(@Nonnull JMethodBodyCfg body) {
+    final TransformationRequest request = new TransformationRequest(body);
+    new BasicBlockLiveProcessor(body.getCfg(), /* stepIntoElements = */ false) {
+      @Override
+      public boolean visit(@Nonnull JConditionalBasicBlock block) {
+        JBasicBlock primary = block.getPrimarySuccessor();
+        JBasicBlock alternative = block.getAlternativeSuccessor();
+
+        // If primary block successor is also a primary successor of the
+        // alternative successor or its own primary successor, we can
+        // invert primary/secondary successors, since it usually result in
+        // better code being generated.
+        Set<JBasicBlock> blocksInChain = new HashSet<>();
+        JBasicBlock pointer = alternative;
+        while (pointer != null) {
+          if (pointer == primary) {
+            if (pointer != alternative) {
+              tracer.getStatistic(CONDITIONAL_BLOCKS_OPTIMIZED).incValue();
+              block.setInverted(!block.isInverted());
+            }
+            break;
+          } else if (blocksInChain.contains(pointer)) {
+            break;
+          }
+
+          blocksInChain.add(pointer);
+          pointer = getNextPrimary(pointer);
+        }
+
+        return false;
+      }
+    }.process();
+    request.commit();
+  }
+
+  @CheckForNull
+  private JBasicBlock getNextPrimary(@Nonnull JBasicBlock block) {
+    if (block instanceof JSimpleBasicBlock) {
+      return ((JSimpleBasicBlock) block).getPrimarySuccessor();
+    } else if (block instanceof JThrowingExpressionBasicBlock) {
+      return ((JThrowingExpressionBasicBlock) block).getPrimarySuccessor();
+    } else if (block instanceof JReturnBasicBlock) {
+      return ((JReturnBasicBlock) block).getPrimarySuccessor();
+    } else {
+      return null;
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/optimizations/cfg/RemoveEmptyBasicBlocks.java b/jack/src/com/android/jack/optimizations/cfg/RemoveEmptyBasicBlocks.java
new file mode 100644
index 0000000..a9e3c0b
--- /dev/null
+++ b/jack/src/com/android/jack/optimizations/cfg/RemoveEmptyBasicBlocks.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.optimizations.cfg;
+
+import com.android.jack.ir.ast.JMethodBodyCfg;
+import com.android.jack.ir.ast.cfg.BasicBlockLiveProcessor;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.jack.ir.ast.cfg.JSimpleBasicBlock;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
+import com.android.sched.item.Description;
+import com.android.sched.schedulable.Filter;
+import com.android.sched.schedulable.RunnableSchedulable;
+import com.android.sched.schedulable.Transform;
+
+import javax.annotation.Nonnull;
+
+/** Remove all empty basic blocks */
+@Description("Remove all empty basic blocks")
+@Transform(modify = JControlFlowGraph.class)
+@Filter(TypeWithoutPrebuiltFilter.class)
+public class RemoveEmptyBasicBlocks
+    implements RunnableSchedulable<JMethodBodyCfg> {
+
+  @Override
+  public void run(@Nonnull final JMethodBodyCfg body) {
+    new BasicBlockLiveProcessor(body.getCfg(), /* stepIntoElements = */ false) {
+      @Override
+      public boolean visit(@Nonnull JSimpleBasicBlock block) {
+        if (block.getElementCount() == 1 &&
+            block.getLastElement().getSourceInfo() == SourceInfo.UNKNOWN) {
+          // Delete empty basic blocks without source information
+          block.delete();
+        }
+        return false;
+      }
+    }.process();
+  }
+}
diff --git a/jack/src/com/android/jack/optimizations/cfg/RemoveRedundantConditionalBlocks.java b/jack/src/com/android/jack/optimizations/cfg/RemoveRedundantConditionalBlocks.java
new file mode 100644
index 0000000..d1d85f7
--- /dev/null
+++ b/jack/src/com/android/jack/optimizations/cfg/RemoveRedundantConditionalBlocks.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.optimizations.cfg;
+
+import com.android.jack.ir.ast.JBooleanLiteral;
+import com.android.jack.ir.ast.JExpression;
+import com.android.jack.ir.ast.JMethodBodyCfg;
+import com.android.jack.ir.ast.cfg.BasicBlockLiveProcessor;
+import com.android.jack.ir.ast.cfg.JConditionalBasicBlock;
+import com.android.jack.ir.ast.cfg.JConditionalBlockElement;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.jack.ir.ast.cfg.JGotoBlockElement;
+import com.android.jack.ir.ast.cfg.JSimpleBasicBlock;
+import com.android.jack.ir.ast.cfg.mutations.BasicBlockBuilder;
+import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
+import com.android.sched.item.Description;
+import com.android.sched.schedulable.Filter;
+import com.android.sched.schedulable.RunnableSchedulable;
+import com.android.sched.schedulable.Transform;
+import com.android.sched.util.log.Tracer;
+import com.android.sched.util.log.TracerFactory;
+import com.android.sched.util.log.stats.Counter;
+import com.android.sched.util.log.stats.CounterImpl;
+import com.android.sched.util.log.stats.StatisticId;
+
+import javax.annotation.Nonnull;
+
+/** Remove all conditional basic with constant condition */
+@Description("Remove all conditional basic with constant condition")
+@Transform(modify = JControlFlowGraph.class)
+@Filter(TypeWithoutPrebuiltFilter.class)
+public class RemoveRedundantConditionalBlocks
+    implements RunnableSchedulable<JMethodBodyCfg> {
+
+  @Nonnull
+  public static final StatisticId<Counter> REMOVED_CONST_BRANCHES = new StatisticId<>(
+      "jack.cfg.const-branches-removed", "Removed branches with constant condition",
+      CounterImpl.class, Counter.class);
+
+  @Nonnull
+  public static final StatisticId<Counter> REMOVED_REDUNDANT_CONDITIONS = new StatisticId<>(
+      "jack.cfg.redundant-conditions-removed", "Removed redundant conditional blocks",
+      CounterImpl.class, Counter.class);
+
+  @Nonnull
+  private final Tracer tracer = TracerFactory.getTracer();
+
+  @Override
+  public void run(@Nonnull final JMethodBodyCfg body) {
+    new BasicBlockLiveProcessor(body.getCfg(), /* stepIntoElements = */ false) {
+      @Override
+      public boolean visit(@Nonnull JConditionalBasicBlock block) {
+        JConditionalBlockElement element =
+            (JConditionalBlockElement) block.getLastElement();
+        JExpression condition = element.getCondition();
+
+        if (block.getIfFalse() == block.getIfTrue()) {
+          // Conditional block is redundant, replace it with a simple basic block
+
+          // Note that goto block element created reuses source
+          // info of the original conditional block element
+          JSimpleBasicBlock simple = new BasicBlockBuilder(body.getCfg())
+              .append(block).removeLast()
+              .append(new JGotoBlockElement(
+                  element.getSourceInfo(), element.getEHContext()))
+              .createSimpleBlock(block.getIfTrue());
+
+          // Replace conditional block with newly created simple block
+          block.detach(simple);
+
+          tracer.getStatistic(REMOVED_REDUNDANT_CONDITIONS).incValue();
+
+        } else if (condition instanceof JBooleanLiteral) {
+          // Split conditional block: pre-block --> cond-block
+          // with pre-block containing all the elements except for
+          // the last (conditional) one
+          block.split(-1);
+
+          // Detach the conditional block and replace it with either
+          // if-true or if-false successor depending on the constant value
+          block.detach(((JBooleanLiteral) condition).getValue()
+              ? block.getIfTrue() : block.getIfFalse());
+
+          tracer.getStatistic(REMOVED_CONST_BRANCHES).incValue();
+        }
+        return false;
+      }
+    }.process();
+  }
+}
diff --git a/jack/src/com/android/jack/optimizations/cfg/RemoveRedundantGotoReturnEdges.java b/jack/src/com/android/jack/optimizations/cfg/RemoveRedundantGotoReturnEdges.java
new file mode 100644
index 0000000..9a1997a
--- /dev/null
+++ b/jack/src/com/android/jack/optimizations/cfg/RemoveRedundantGotoReturnEdges.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.optimizations.cfg;
+
+import com.android.jack.ir.ast.JExpression;
+import com.android.jack.ir.ast.JMethodBodyCfg;
+import com.android.jack.ir.ast.cfg.BasicBlockLiveProcessor;
+import com.android.jack.ir.ast.cfg.JBasicBlock;
+import com.android.jack.ir.ast.cfg.JBasicBlockElement;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.jack.ir.ast.cfg.JReturnBasicBlock;
+import com.android.jack.ir.ast.cfg.JReturnBlockElement;
+import com.android.jack.ir.ast.cfg.JSimpleBasicBlock;
+import com.android.jack.ir.ast.cfg.JThrowBasicBlock;
+import com.android.jack.ir.ast.cfg.JThrowBlockElement;
+import com.android.jack.ir.ast.cfg.JThrowingBasicBlock;
+import com.android.jack.ir.ast.cfg.mutations.BasicBlockBuilder;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
+import com.android.jack.util.CloneExpressionVisitor;
+import com.android.sched.item.Description;
+import com.android.sched.schedulable.Filter;
+import com.android.sched.schedulable.RunnableSchedulable;
+import com.android.sched.schedulable.Transform;
+
+import javax.annotation.Nonnull;
+
+/** Remove redundant goto edges to a simple return blocks */
+@Description("Remove redundant goto edges to a simple return blocks")
+@Transform(modify = JControlFlowGraph.class)
+@Filter(TypeWithoutPrebuiltFilter.class)
+public class RemoveRedundantGotoReturnEdges
+    implements RunnableSchedulable<JMethodBodyCfg> {
+
+  @Override
+  public void run(@Nonnull final JMethodBodyCfg body) {
+    new BasicBlockLiveProcessor(body.getCfg(), /* stepIntoElements = */ false) {
+      @Nonnull
+      private final CloneExpressionVisitor copier = new CloneExpressionVisitor();
+
+      @Override
+      public boolean visit(@Nonnull JSimpleBasicBlock block) {
+        if (block.getLastElement().getSourceInfo() != SourceInfo.UNKNOWN) {
+          // Don't handle blocks with GOTO element carrying source info
+          return false;
+        }
+
+        if (block.getElementCount() < 2) {
+          // Empty simple blocks will be optimized away by `RemoveEmptyBasicBlocks`
+          return false;
+        }
+
+        // We detect the cases when the current simple block points to a
+        // single-element return ot throw blocks, in which case we can avoid
+        // unnecessary jump instruction by replacing this block's end element
+        // with appropriate return or throw instruction.
+        JBasicBlock primary = block.getPrimarySuccessor();
+        boolean isReturnBlock = primary instanceof JReturnBasicBlock;
+        boolean isThrowBlock = primary instanceof JThrowBasicBlock;
+        if (!(isReturnBlock || isThrowBlock) || primary.getElementCount() != 1) {
+          return false;
+        }
+
+        // Move all block elements except for the trailing goto element to a new block
+        BasicBlockBuilder builder =
+            new BasicBlockBuilder(body.getCfg()).append(block).removeLast();
+        JBasicBlock newBlock;
+
+        if (isThrowBlock) {
+          // Create a new throw basic block
+          JBasicBlockElement throwElement = primary.getLastElement();
+          assert throwElement instanceof JThrowBlockElement;
+
+          JExpression expression = ((JThrowBlockElement) throwElement).getExpression();
+          builder.append(new JThrowBlockElement(
+              throwElement.getSourceInfo(), throwElement.getEHContext(),
+              copier.cloneExpression(expression)));
+
+          newBlock = builder.createThrowBlock();
+          ((JThrowingBasicBlock) newBlock).resetCatchBlocks();
+
+        } else {
+          // Create a new throw return block
+          JBasicBlockElement retElement = primary.getLastElement();
+          assert retElement instanceof JReturnBlockElement;
+
+          // Note that the expression is optional in return block element
+          JExpression expression = ((JReturnBlockElement) retElement).getExpression();
+          builder.append(new JReturnBlockElement(
+              retElement.getSourceInfo(), retElement.getEHContext(),
+              expression == null ? null : copier.cloneExpression(expression)));
+
+          newBlock = builder.createReturnBlock();
+        }
+
+        // Replace this block with a new one
+        block.detach(newBlock);
+        return false;
+      }
+    }.process();
+  }
+}
diff --git a/jack/src/com/android/jack/optimizations/cfg/RemoveUnreachableBasicBlocks.java b/jack/src/com/android/jack/optimizations/cfg/RemoveUnreachableBasicBlocks.java
new file mode 100644
index 0000000..fa6eca7
--- /dev/null
+++ b/jack/src/com/android/jack/optimizations/cfg/RemoveUnreachableBasicBlocks.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.optimizations.cfg;
+
+import com.android.jack.ir.ast.JMethodBodyCfg;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
+import com.android.sched.item.Description;
+import com.android.sched.schedulable.Filter;
+import com.android.sched.schedulable.RunnableSchedulable;
+import com.android.sched.schedulable.Transform;
+
+import javax.annotation.Nonnull;
+
+/** Remove all unreachable basic blocks, except catch blocks */
+@Description("Remove all unreachable basic blocks, except catch blocks")
+@Transform(modify = JControlFlowGraph.class)
+@Filter(TypeWithoutPrebuiltFilter.class)
+public class RemoveUnreachableBasicBlocks
+    implements RunnableSchedulable<JMethodBodyCfg> {
+
+  @Override
+  public void run(@Nonnull final JMethodBodyCfg body) {
+    new CfgBasicBlockUtils(body.getCfg()).removeUnreachableBlocks();
+  }
+}
diff --git a/jack/src/com/android/jack/optimizations/cfg/SimplifyConditionalExpressions.java b/jack/src/com/android/jack/optimizations/cfg/SimplifyConditionalExpressions.java
new file mode 100644
index 0000000..161ea84
--- /dev/null
+++ b/jack/src/com/android/jack/optimizations/cfg/SimplifyConditionalExpressions.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.optimizations.cfg;
+
+import com.android.jack.ir.ast.JBinaryOperation;
+import com.android.jack.ir.ast.JBinaryOperator;
+import com.android.jack.ir.ast.JExpression;
+import com.android.jack.ir.ast.JIntegralConstant32;
+import com.android.jack.ir.ast.JMethodBodyCfg;
+import com.android.jack.ir.ast.JRelationalOperation;
+import com.android.jack.ir.ast.JValueLiteral;
+import com.android.jack.ir.ast.UnsupportedOperatorException;
+import com.android.jack.ir.ast.cfg.BasicBlockLiveProcessor;
+import com.android.jack.ir.ast.cfg.JConditionalBasicBlock;
+import com.android.jack.ir.ast.cfg.JConditionalBlockElement;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.jack.ir.sourceinfo.SourceInfo;
+import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
+import com.android.jack.transformations.request.Replace;
+import com.android.jack.transformations.request.TransformationRequest;
+import com.android.sched.item.Description;
+import com.android.sched.schedulable.Filter;
+import com.android.sched.schedulable.RunnableSchedulable;
+import com.android.sched.schedulable.Transform;
+import com.android.sched.util.log.Tracer;
+import com.android.sched.util.log.TracerFactory;
+import com.android.sched.util.log.stats.Counter;
+import com.android.sched.util.log.stats.CounterImpl;
+import com.android.sched.util.log.stats.StatisticId;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/** Simplifies conditional expressions */
+@Description("Simplifies conditional expressions")
+@Transform(modify = JControlFlowGraph.class)
+@Filter(TypeWithoutPrebuiltFilter.class)
+public class SimplifyConditionalExpressions
+    implements RunnableSchedulable<JMethodBodyCfg> {
+
+  @Nonnull
+  public static final StatisticId<Counter> CONDITIONS_SIMPLIFIED = new StatisticId<>(
+      "jack.cfg.conditions-simplified", "Simplified condition expressions",
+      CounterImpl.class, Counter.class);
+
+  @Nonnull
+  private final Tracer tracer = TracerFactory.getTracer();
+
+  @Nonnull
+  private JExpression createZeroLiteral(@Nonnull JValueLiteral original) {
+    return original.getType().createDefaultValue(original.getSourceInfo());
+  }
+
+  @CheckForNull
+  private JBinaryOperation optimizeOperation(
+      @Nonnull SourceInfo si, @Nonnull JBinaryOperator op,
+      @Nonnull JExpression lhs, @Nonnull JIntegralConstant32 rhs) {
+
+    int value = rhs.getIntValue();
+    if (value == 1) {
+      if (op == JBinaryOperator.GTE) {
+        // expr >= 1 ---> expr > 0
+        return JBinaryOperation.create(si,
+            JBinaryOperator.GT, lhs, createZeroLiteral((JValueLiteral) rhs));
+      }
+      if (op == JBinaryOperator.LT) {
+        // expr < 1 ---> expr <= 0
+        return JBinaryOperation.create(si,
+            JBinaryOperator.LTE, lhs, createZeroLiteral((JValueLiteral) rhs));
+      }
+
+    } else if (value == -1) {
+      if (op == JBinaryOperator.LTE) {
+        // expr <= -1 ---> expr <  0
+        return JBinaryOperation.create(si,
+            JBinaryOperator.LT, lhs, createZeroLiteral((JValueLiteral) rhs));
+      }
+      if (op == JBinaryOperator.GT) {
+        // expr > -1 ---> expr >= 0
+        return JBinaryOperation.create(si,
+            JBinaryOperator.GTE, lhs, createZeroLiteral((JValueLiteral) rhs));
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public void run(@Nonnull final JMethodBodyCfg body) {
+    final TransformationRequest request = new TransformationRequest(body);
+
+    new BasicBlockLiveProcessor(body.getCfg(), /* stepIntoElements = */ false) {
+      @Override
+      public boolean visit(@Nonnull JConditionalBasicBlock block) {
+        JConditionalBlockElement element =
+            (JConditionalBlockElement) block.getLastElement();
+        JExpression condition = element.getCondition();
+
+        if (condition instanceof JRelationalOperation) {
+          JBinaryOperation relation = (JRelationalOperation) condition;
+          JBinaryOperator op = relation.getOp();
+          assert op == JBinaryOperator.LT || op == JBinaryOperator.LTE ||
+              op == JBinaryOperator.GT || op == JBinaryOperator.GTE;
+
+          JExpression lhs = relation.getLhs();
+          JExpression rhs = relation.getRhs();
+
+          JBinaryOperation newRelation = null;
+
+          if (rhs instanceof JIntegralConstant32) {
+            newRelation = optimizeOperation(
+                relation.getSourceInfo(), op, lhs, (JIntegralConstant32) rhs);
+          } else if (lhs instanceof JIntegralConstant32) {
+            try {
+              newRelation = optimizeOperation(relation.getSourceInfo(),
+                  op.getReverseOperator(), rhs, (JIntegralConstant32) lhs);
+            } catch (UnsupportedOperatorException e) {
+              throw new AssertionError();
+            }
+          }
+
+          if (newRelation != null) {
+            tracer.getStatistic(CONDITIONS_SIMPLIFIED).incValue();
+            request.append(new Replace(relation, newRelation));
+          }
+        }
+        return false;
+      }
+    }.process();
+    request.commit();
+  }
+
+}
diff --git a/jack/src/com/android/jack/optimizations/cfg/ToggleCfgSsaFlagProcessor.java b/jack/src/com/android/jack/optimizations/cfg/ToggleCfgSsaFlagProcessor.java
new file mode 100644
index 0000000..5327c9a
--- /dev/null
+++ b/jack/src/com/android/jack/optimizations/cfg/ToggleCfgSsaFlagProcessor.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.optimizations.cfg;
+
+import com.android.jack.ir.ast.JMethodBodyCfg;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
+import com.android.sched.item.Description;
+import com.android.sched.schedulable.Filter;
+import com.android.sched.schedulable.RunnableSchedulable;
+import com.android.sched.schedulable.Transform;
+
+import javax.annotation.Nonnull;
+
+/** Toggle SSA flag on CFG */
+@Description("Toggle SSA flag on CFG")
+@Transform(modify = JControlFlowGraph.class)
+@Filter(TypeWithoutPrebuiltFilter.class)
+public class ToggleCfgSsaFlagProcessor
+    implements RunnableSchedulable<JMethodBodyCfg> {
+
+  @Override
+  public void run(@Nonnull final JMethodBodyCfg body) {
+    body.getCfg().setInSsaForm(!body.getCfg().isInSsaForm());
+  }
+}
diff --git a/jack/src/com/android/jack/optimizations/cfg/VariablesScope.java b/jack/src/com/android/jack/optimizations/cfg/VariablesScope.java
new file mode 100644
index 0000000..c87ea0b
--- /dev/null
+++ b/jack/src/com/android/jack/optimizations/cfg/VariablesScope.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.optimizations.cfg;
+
+import com.android.sched.util.codec.EnumName;
+import com.android.sched.util.codec.VariableName;
+
+/** Defines the variable scope (i.e. a set of the variables) relevant to a context */
+@VariableName("scope")
+public enum VariablesScope {
+  @EnumName(name = "none", description = "does not apply to any variables")
+  NONE,
+  @EnumName(name = "synthetic", description = "only applies to synthetic variables")
+  SYNTHETIC,
+  @EnumName(name = "all", description = "applies to all variables")
+  ALL
+}
diff --git a/jack/src/com/android/jack/optimizations/common/ExpressionReplaceHelper.java b/jack/src/com/android/jack/optimizations/common/ExpressionReplaceHelper.java
deleted file mode 100644
index 2f6181d..0000000
--- a/jack/src/com/android/jack/optimizations/common/ExpressionReplaceHelper.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * 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.android.jack.optimizations.common;
-
-import com.android.jack.ir.ast.JAsgOperation;
-import com.android.jack.ir.ast.JExpression;
-import com.android.jack.ir.ast.JExpressionStatement;
-import com.android.jack.ir.ast.JLocal;
-import com.android.jack.ir.ast.JLocalRef;
-import com.android.jack.ir.ast.JStatement;
-import com.android.jack.ir.ast.JValueLiteral;
-import com.android.jack.ir.sourceinfo.SourceInfo;
-import com.android.jack.transformations.LocalVarCreator;
-import com.android.jack.transformations.request.AppendBefore;
-import com.android.jack.transformations.request.Replace;
-import com.android.jack.transformations.request.TransformationRequest;
-import com.android.sched.schedulable.Transform;
-import com.android.sched.schedulable.Use;
-
-import java.util.ArrayList;
-import javax.annotation.Nonnull;
-
-/** Helps replace expression with another expression. */
-@Transform(add = {
-    JAsgOperation.NonReusedAsg.class,
-    JExpressionStatement.class,
-    JLocalRef.class })
-@Use(LocalVarCreator.class)
-public class ExpressionReplaceHelper {
-  @Nonnull
-  private final LocalVarCreator varCreator;
-
-  public ExpressionReplaceHelper(@Nonnull LocalVarCreator varCreator) {
-    this.varCreator = varCreator;
-  }
-
-  /** Replaces the expression with literal value, adds a temporary if needed. */
-  public void replace(
-      @Nonnull JExpression expr, @Nonnull JValueLiteral value,
-      @Nonnull TransformationRequest request) {
-
-    if (expr.canThrow() || !value.canThrow()) {
-      // Simple case, don't need a local
-      request.append(new Replace(expr, value));
-      return;
-    }
-
-    // When the expression being replaced was not throwing, but the
-    // expression being inserted is throwing we introduce a temporary
-    // to ensure we don't have two throwing expressions in the same
-    // basic block.
-
-    SourceInfo si = value.getSourceInfo();
-
-    // Step outside the expression
-    JStatement stmt = expr.getParent(JStatement.class);
-
-    // Create a local with the value
-    JLocal tmp = varCreator.createTempLocal(value.getType(), si, request);
-    JAsgOperation assign = new JAsgOperation(si, tmp.makeRef(si), value);
-    JExpressionStatement stmtAssignment = new JExpressionStatement(si, assign);
-    stmtAssignment.setCatchBlocks(new ArrayList<>(stmt.getJCatchBlocks()));
-
-    request.append(new Replace(expr, tmp.makeRef(si)));
-    request.append(new AppendBefore(stmt, stmtAssignment));
-  }
-}
diff --git a/jack/src/com/android/jack/optimizations/common/JlsNullabilityChecker.java b/jack/src/com/android/jack/optimizations/common/JlsNullabilityChecker.java
deleted file mode 100644
index 00c357e..0000000
--- a/jack/src/com/android/jack/optimizations/common/JlsNullabilityChecker.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * 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.android.jack.optimizations.common;
-
-import com.android.jack.ir.ast.JBlock;
-import com.android.jack.ir.ast.JClass;
-import com.android.jack.ir.ast.JEqOperation;
-import com.android.jack.ir.ast.JExpression;
-import com.android.jack.ir.ast.JExpressionStatement;
-import com.android.jack.ir.ast.JIfStatement;
-import com.android.jack.ir.ast.JNewInstance;
-import com.android.jack.ir.ast.JNullLiteral;
-import com.android.jack.ir.ast.JPrimitiveType.JPrimitiveTypeEnum;
-import com.android.jack.ir.ast.JStatement;
-import com.android.jack.ir.ast.JThisRef;
-import com.android.jack.ir.ast.JThrowStatement;
-import com.android.jack.ir.ast.JType;
-import com.android.jack.ir.ast.MethodKind;
-import com.android.jack.ir.sourceinfo.SourceInfo;
-import com.android.jack.lookup.CommonTypes;
-import com.android.jack.lookup.JPhantomLookup;
-import com.android.jack.transformations.LocalVarCreator;
-import com.android.jack.transformations.ast.splitnew.SplitNewInstance;
-import com.android.jack.transformations.request.TransformationRequest;
-import com.android.jack.util.NamingTools;
-import com.android.sched.schedulable.Transform;
-import com.android.sched.schedulable.Use;
-
-import java.util.Collections;
-
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
-
-/**
- * Helps create nullability checks for preserving JLS-compliance
- * needed in some optimizations.
- */
-@Transform(add = { JBlock.class,
-                   JEqOperation.class,
-                   JExpressionStatement.class,
-                   JIfStatement.class,
-                   JNullLiteral.class,
-                   JThrowStatement.class },
-    remove = JNewInstance.class)
-@Use({ LocalVarCreator.class,
-       SplitNewInstance.NewExpressionSplitter.class })
-public final class JlsNullabilityChecker {
-  @Nonnull
-  private final LocalVarCreator varCreator;
-  @Nonnull
-  private final JPhantomLookup getPhantomLookup;
-
-  public JlsNullabilityChecker(
-      @Nonnull LocalVarCreator varCreator,
-      @Nonnull JPhantomLookup getPhantomLookup) {
-    this.varCreator = varCreator;
-    this.getPhantomLookup = getPhantomLookup;
-  }
-
-  /** If the expression can be null, creates null-checking 'if' statement */
-  @CheckForNull
-  public JStatement createNullCheckIfNeeded(
-      @Nonnull JExpression expr, @Nonnull TransformationRequest request) {
-    return expr instanceof JThisRef ? null : createNullCheck(expr, request);
-  }
-
-  /**
-   * Creates an 'if' statement of the following form and
-   * inserts it before the statement including the expression:
-   * <pre>
-   * if ([expr] == null) {
-   *   throw new NullPointerException();
-   * }
-   * </pre>
-   */
-  @Nonnull
-  public JStatement createNullCheck(
-      @Nonnull JExpression expr, @Nonnull TransformationRequest request) {
-    SourceInfo srcInfo = expr.getSourceInfo();
-    return new JIfStatement(
-        srcInfo,
-        new JEqOperation(srcInfo, expr, new JNullLiteral(srcInfo)),
-        createThenBlock(srcInfo, request),
-        null);
-  }
-
-  @Nonnull
-  private JBlock createThenBlock(
-      @Nonnull SourceInfo srcInfo,
-      @Nonnull TransformationRequest request) {
-
-    JClass exceptionType =
-        getPhantomLookup
-            .getClass(CommonTypes.JAVA_LANG_NULL_POINTER_EXCEPTION);
-    JNewInstance newInstance = new JNewInstance(srcInfo, exceptionType,
-        exceptionType.getOrCreateMethodId(
-            NamingTools.INIT_NAME,
-            Collections.<JType>emptyList(),
-            MethodKind.INSTANCE_NON_VIRTUAL,
-            JPrimitiveTypeEnum.VOID.getType()));
-    JExpression[] expressions = SplitNewInstance.NewExpressionSplitter
-        .splitNewInstance(newInstance, request, varCreator);
-
-    // NOTE: the code below relies on exact structure of the returned
-    //       array, it should consist of three expressions, the last of
-    //       which is the resulting exception instance we are throwing
-    assert expressions.length == 3;
-    JBlock block = new JBlock(srcInfo);
-    block.addStmt(new JExpressionStatement(srcInfo, expressions[0]));
-    block.addStmt(new JExpressionStatement(srcInfo, expressions[1]));
-    block.addStmt(new JThrowStatement(srcInfo, expressions[2]));
-    return block;
-  }
-}
diff --git a/jack/src/com/android/jack/optimizations/ssa/CopyPropagation.java b/jack/src/com/android/jack/optimizations/ssa/CopyPropagation.java
new file mode 100644
index 0000000..6b5bc75
--- /dev/null
+++ b/jack/src/com/android/jack/optimizations/ssa/CopyPropagation.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.optimizations.ssa;
+
+import com.google.common.collect.Lists;
+
+import com.android.jack.Options;
+import com.android.jack.backend.dex.rop.CodeItemBuilder;
+import com.android.jack.ir.ast.JExpression;
+import com.android.jack.ir.ast.JMethod;
+import com.android.jack.ir.ast.JMethodBodyCfg;
+import com.android.jack.ir.ast.JSsaVariableDefRef;
+import com.android.jack.ir.ast.JSsaVariableRef;
+import com.android.jack.ir.ast.JSsaVariableUseRef;
+import com.android.jack.ir.ast.cfg.JBasicBlock;
+import com.android.jack.ir.ast.cfg.JBasicBlockElement;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.jack.ir.ast.cfg.JPhiBlockElement;
+import com.android.jack.ir.ast.cfg.JRegularBasicBlock;
+import com.android.jack.ir.ast.cfg.JVariableAsgBlockElement;
+import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
+import com.android.jack.transformations.request.Replace;
+import com.android.jack.transformations.request.TransformationRequest;
+import com.android.sched.item.Description;
+import com.android.sched.item.Name;
+import com.android.sched.schedulable.Constraint;
+import com.android.sched.schedulable.Filter;
+import com.android.sched.schedulable.RunnableSchedulable;
+import com.android.sched.schedulable.Transform;
+import com.android.sched.util.config.ThreadConfig;
+
+import javax.annotation.Nonnull;
+
+/**
+ * This is a pass that performs copy propagation based on SSA values. The final output should
+ * reminds SSA valid.
+ *
+ * Based somewhat around DX's SSA rename algorithm, which performs copy propagation during
+ * construction, this pass only a sparse analysis on the Phi nodes and SSA variable references.
+ */
+@Description("Copy Propagation of Locals")
+@Name("CopyPropagation")
+@Constraint(need = {JPhiBlockElement.class, JSsaVariableRef.class})
+@Transform(modify = {JPhiBlockElement.class, JSsaVariableRef.class})
+@Filter(TypeWithoutPrebuiltFilter.class)
+public class CopyPropagation implements RunnableSchedulable<JMethodBodyCfg> {
+
+  private final boolean emitSyntheticLocalDebugInfo =
+      ThreadConfig.get(CodeItemBuilder.EMIT_SYNTHETIC_LOCAL_DEBUG_INFO).booleanValue();
+  private final boolean emitLocalDebugInfo =
+      ThreadConfig.get(Options.EMIT_LOCAL_DEBUG_INFO).booleanValue();
+
+  @Nonnull
+  private final com.android.jack.util.filter.Filter<JMethod> filter =
+      ThreadConfig.get(Options.METHOD_FILTER);
+
+  @Override
+  public void run(JMethodBodyCfg body) {
+    JMethod method = body.getMethod();
+    if (method.isNative() || method.isAbstract() || !filter.accept(this.getClass(), method)) {
+      return;
+    }
+
+    boolean changed;
+    do {
+      changed = false;
+      for (JBasicBlock bb : body.getCfg().getAllBlocksUnordered()) {
+        for (JBasicBlockElement e : Lists.newArrayList(bb.getElements(true))) {
+          if (e instanceof JVariableAsgBlockElement) {
+            changed = tryPropagateAssignment((JVariableAsgBlockElement) e, body.getCfg());
+          } else if (e instanceof JPhiBlockElement) {
+            JPhiBlockElement phi = (JPhiBlockElement) e;
+            changed = tryRemoveUselessPhi(phi);
+            if (!changed) {
+              changed = tryPropagatePhi(phi, body.getCfg());
+            }
+          }
+        }
+      }
+    } while (changed);
+  }
+
+  private boolean shouldKeepVariable(JSsaVariableRef varRef) {
+    if (varRef.getTarget().isSynthetic()) {
+      return emitSyntheticLocalDebugInfo;
+    } else {
+      return emitLocalDebugInfo;
+    }
+  }
+
+  /**
+   * If we have an assignment a = b, we try to replace all access of 'a' to 'b' when possible.
+   *
+   * @return true if an optimizations was performed.
+   */
+  private boolean tryPropagateAssignment(JVariableAsgBlockElement assign, JControlFlowGraph cfg) {
+    JExpression lhs = assign.getAssignment().getLhs();
+    JExpression rhs = assign.getAssignment().getRhs();
+    if (!(rhs instanceof JSsaVariableRef)) {
+      return false;
+    }
+
+    JSsaVariableUseRef rhsVarRef = (JSsaVariableUseRef) rhs;
+    JSsaVariableDefRef lhsVarRef = (JSsaVariableDefRef) lhs;
+
+    // Check for debug build. Make sure we are keeping locals if that's the case.
+    if (shouldKeepVariable(lhsVarRef)) {
+      return false;
+    }
+
+    // We don't propagate unreachable values.
+    if (rhsVarRef.getVersion() == 0) {
+      return false;
+    }
+
+    TransformationRequest tr = new TransformationRequest(cfg);
+    propagateVarRef(lhsVarRef, rhsVarRef, tr);
+    ((JRegularBasicBlock) assign.getBasicBlock()).removeElement(assign);
+    tr.commit();
+    return true;
+  }
+
+  private boolean tryPropagatePhi(JPhiBlockElement phi, JControlFlowGraph cfg) {
+    JSsaVariableUseRef rhsVarRef = canPropagatePhi(phi);
+    JSsaVariableDefRef lhsVarRef = phi.getLhs();
+
+    // Check for debug build. Make sure we are keeping locals if that's the case.
+    if (shouldKeepVariable(lhsVarRef)) {
+      return false;
+    }
+
+    // Not every path is the same variable.
+    if (rhsVarRef == null) {
+      return false;
+    }
+
+    TransformationRequest tr = new TransformationRequest(cfg);
+    propagateVarRef(lhsVarRef, rhsVarRef, tr);
+    tr.commit();
+    ((JRegularBasicBlock) phi.getBasicBlock()).removeElement(phi);
+    return true;
+  }
+
+  private boolean tryRemoveUselessPhi(JPhiBlockElement phi) {
+    if (!phi.getLhs().hasUses()) {
+      ((JRegularBasicBlock) phi.getBasicBlock()).removeElement(phi);
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  /**
+   * Replace all the references to the lhs JSsaVariableRef with a new rhs JSsaVariableRef.
+   */
+  private void propagateVarRef(JSsaVariableDefRef lhs, JSsaVariableUseRef rhs,
+      TransformationRequest tr) {
+    JSsaVariableDefRef def = rhs.getDef();
+    for (JSsaVariableRef oldUse : Lists.newArrayList(lhs.getUses())) {
+      JSsaVariableRef newUse = def.makeRef(oldUse.getSourceInfo());
+      newUse.addAllMarkers(oldUse.getAllMarkers());
+      tr.append(new Replace(oldUse, newUse));
+    }
+    lhs.removeUses();
+  }
+
+  /**
+   * If we have a = phi(b,b,b,b), it is ok with replace a with b.
+   *
+   * @return The right hand side values that the left hand side should be replaced with. Otherwise
+   *         null.
+   */
+  public JSsaVariableUseRef canPropagatePhi(JPhiBlockElement e) {
+    JSsaVariableUseRef first = e.getRhs(e.getBasicBlock().getPredecessors().get(0));
+    assert first != null;
+    for (JSsaVariableUseRef operand : e.getRhs()) {
+      if (first.getDef() != operand.getDef()) {
+        return null;
+      }
+    }
+    return first;
+  }
+}
diff --git a/jack/src/com/android/jack/optimizations/valuepropagation/argument/AvpPropagateArgumentValues.java b/jack/src/com/android/jack/optimizations/valuepropagation/argument/AvpPropagateArgumentValues.java
index e5c0f5d..d6e0279 100644
--- a/jack/src/com/android/jack/optimizations/valuepropagation/argument/AvpPropagateArgumentValues.java
+++ b/jack/src/com/android/jack/optimizations/valuepropagation/argument/AvpPropagateArgumentValues.java
@@ -19,17 +19,25 @@
 import com.android.jack.Jack;
 import com.android.jack.annotations.DisableArgumentValuePropagationOptimization;
 import com.android.jack.ir.ast.JAnnotationType;
-import com.android.jack.ir.ast.JAsgOperation;
+import com.android.jack.ir.ast.JLocalRef;
 import com.android.jack.ir.ast.JMethod;
-import com.android.jack.ir.ast.JNode;
+import com.android.jack.ir.ast.JMethodBodyCfg;
 import com.android.jack.ir.ast.JParameter;
 import com.android.jack.ir.ast.JParameterRef;
 import com.android.jack.ir.ast.JValueLiteral;
+import com.android.jack.ir.ast.JVariable;
 import com.android.jack.ir.ast.JVisitor;
-import com.android.jack.optimizations.common.ExpressionReplaceHelper;
+import com.android.jack.ir.ast.cfg.JBasicBlock;
+import com.android.jack.ir.ast.cfg.JBasicBlockElement;
+import com.android.jack.ir.ast.cfg.JSimpleBasicBlock;
+import com.android.jack.ir.ast.cfg.JThrowingExpressionBasicBlock;
+import com.android.jack.ir.ast.cfg.JVariableAsgBlockElement;
+import com.android.jack.ir.ast.cfg.mutations.BasicBlockBuilder;
+import com.android.jack.optimizations.cfg.CfgVarUtils;
 import com.android.jack.optimizations.common.LiteralValueListTracker;
 import com.android.jack.optimizations.common.OptimizerUtils;
 import com.android.jack.transformations.LocalVarCreator;
+import com.android.jack.transformations.request.Replace;
 import com.android.jack.transformations.request.TransformationRequest;
 import com.android.jack.util.NamingTools;
 import com.android.sched.item.Description;
@@ -52,10 +60,10 @@
                      AvpSchedulable.TaintedMethodMarker.class })
 @Transform(remove = { MethodCallArgumentsMarker.class,
                       AvpSchedulable.TaintedMethodMarker.class })
-@Use(ExpressionReplaceHelper.class)
+@Use(CfgVarUtils.ReplaceWithLocal.class)
 @Name("ArgumentValuePropagation: PropagateArgumentValues")
 public class AvpPropagateArgumentValues extends AvpSchedulable
-    implements RunnableSchedulable<JMethod> {
+    implements RunnableSchedulable<JMethodBodyCfg> {
 
   @Nonnull
   public final JAnnotationType disablingAnnotationType =
@@ -67,7 +75,9 @@
   private final Tracer tracer = TracerFactory.getTracer();
 
   @Override
-  public void run(@Nonnull final JMethod method) {
+  public void run(@Nonnull final JMethodBodyCfg body) {
+    final JMethod method = body.getMethod();
+
     LiteralValueListTracker tracker =
         MethodCallArgumentsMarker.getTrackerAndRemoveMarker(method);
     boolean isTainted = TaintedMethodMarker.checkIfTaintedAndRemoveMarker(method);
@@ -93,17 +103,16 @@
 
     // Detect parameters not being assigned to
     JVisitor asgAnalyzer = new JVisitor() {
-      @Override public void endVisit(@Nonnull JParameterRef x) {
-        JNode parent = x.getParent();
-        if (parent instanceof JAsgOperation &&
-            ((JAsgOperation) parent).getLhs() == x) {
-          if (paramValues.remove(x.getParameter()) != null) {
+      @Override public void endVisit(@Nonnull JVariableAsgBlockElement x) {
+        JVariable variable = x.getVariable();
+        if (variable instanceof JParameter) {
+          if (paramValues.remove(variable) != null) {
             tracer.getStatistic(PARAMETER_IS_WRITTEN_TO).incValue();
           }
         }
       }
     };
-    asgAnalyzer.accept(method);
+    asgAnalyzer.accept(body.getCfg());
 
     if (paramValues.size() == 0) {
       // All eligible parameters are mutated in the method
@@ -113,19 +122,47 @@
     // Substitute parameter with value
     class Processor extends JVisitor {
       @Nonnull
-      private TransformationRequest request =
-          new TransformationRequest(method);
+      private final TransformationRequest request = new TransformationRequest(method);
       @Nonnull
-      private ExpressionReplaceHelper helper =
-          new ExpressionReplaceHelper(new LocalVarCreator(method, "avp"));
+      private final CfgVarUtils helper = new CfgVarUtils();
+      @Nonnull
+      private final LocalVarCreator varCreator = new LocalVarCreator(method, "avp");
 
-      @Override public void endVisit(@Nonnull JParameterRef x) {
+      @Override
+      public void endVisit(@Nonnull JParameterRef x) {
+        assert !x.canThrow();
         JParameter parameter = x.getParameter();
         JValueLiteral literal = paramValues.get(parameter);
+
         if (literal != null && parameter.getAnnotations(disablingAnnotationType).isEmpty()) {
           literal = OptimizerUtils.cloneExpression(literal);
           literal.setSourceInfo(x.getSourceInfo());
-          helper.replace(x, literal, request);
+
+          if (literal.canThrow()) {
+            // If the literal can throw, we are replacing non-throwing expression
+            // with throwing one. Thus we need to split the basic block and make it
+            // throwing
+
+            // First we replace the param reference with a temp local
+            JLocalRef tmpRef = helper.replaceWithLocal(varCreator, x);
+
+            // The local initialization is inserted before the element where
+            // the local is being used. We split the basic block such that
+            // The beginning of the block up to the initialization becomes a
+            // new simple basic block
+            JBasicBlockElement element = tmpRef.getParent(JBasicBlockElement.class);
+            JBasicBlock basicBlock = element.getBasicBlock();
+            JSimpleBasicBlock preBlock = basicBlock.split(basicBlock.indexOf(element));
+
+            // Create a new throwing expression basic block and new JThrowingEx
+            JThrowingExpressionBasicBlock newBlock =
+                new BasicBlockBuilder(body.getCfg()).append(preBlock).removeLast()
+                    .createThrowingExprBlock(preBlock.getPrimarySuccessor());
+            preBlock.detach(newBlock);
+          }
+
+          // Finally, replace the parameter reference with the literal
+          request.append(new Replace(x, literal));
           tracer.getStatistic(ARGUMENT_VALUES_PROPAGATED).incValue();
         }
       }
diff --git a/jack/src/com/android/jack/optimizations/valuepropagation/field/FvpPropagateFieldValues.java b/jack/src/com/android/jack/optimizations/valuepropagation/field/FvpPropagateFieldValues.java
index 2f51787..8e01b10 100644
--- a/jack/src/com/android/jack/optimizations/valuepropagation/field/FvpPropagateFieldValues.java
+++ b/jack/src/com/android/jack/optimizations/valuepropagation/field/FvpPropagateFieldValues.java
@@ -24,17 +24,24 @@
 import com.android.jack.ir.ast.JField;
 import com.android.jack.ir.ast.JFieldRef;
 import com.android.jack.ir.ast.JMethod;
-import com.android.jack.ir.ast.JStatement;
+import com.android.jack.ir.ast.JMethodBodyCfg;
 import com.android.jack.ir.ast.JThisRef;
 import com.android.jack.ir.ast.JValueLiteral;
-import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.ast.cfg.BasicBlockLiveProcessor;
+import com.android.jack.ir.ast.cfg.JBasicBlockElement;
+import com.android.jack.ir.ast.cfg.JGotoBlockElement;
+import com.android.jack.ir.ast.cfg.JSimpleBasicBlock;
+import com.android.jack.ir.ast.cfg.JThrowingExpressionBasicBlock;
+import com.android.jack.ir.ast.cfg.JVariableAsgBlockElement;
+import com.android.jack.ir.ast.cfg.mutations.BasicBlockBuilder;
+import com.android.jack.ir.ast.cfg.mutations.CfgFragment;
+import com.android.jack.ir.sourceinfo.SourceInfo;
 import com.android.jack.lookup.JPhantomLookup;
 import com.android.jack.optimizations.Optimizations;
-import com.android.jack.optimizations.common.ExpressionReplaceHelper;
-import com.android.jack.optimizations.common.JlsNullabilityChecker;
+import com.android.jack.optimizations.cfg.CfgJlsNullabilityChecker;
 import com.android.jack.optimizations.common.OptimizerUtils;
 import com.android.jack.transformations.LocalVarCreator;
-import com.android.jack.transformations.request.AppendBefore;
+import com.android.jack.transformations.request.Replace;
 import com.android.jack.transformations.request.TransformationRequest;
 import com.android.jack.transformations.threeaddresscode.ThreeAddressCodeForm;
 import com.android.jack.util.NamingTools;
@@ -48,7 +55,6 @@
 import com.android.sched.util.log.Tracer;
 import com.android.sched.util.log.TracerFactory;
 
-import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 
 /**
@@ -59,11 +65,10 @@
 @Constraint(need = { FieldSingleValueMarker.class,
                      ThreeAddressCodeForm.class })
 @Transform(add = JValueLiteral.class)
-@Use({ ExpressionReplaceHelper.class,
-       JlsNullabilityChecker.class })
+@Use(CfgJlsNullabilityChecker.class)
 @Name("FieldValuePropagation: PropagateFieldValues")
 public class FvpPropagateFieldValues extends FvpSchedulable
-    implements RunnableSchedulable<JMethod> {
+    implements RunnableSchedulable<JMethodBodyCfg> {
 
   @Nonnull
   public final JAnnotationType disablingAnnotationType =
@@ -78,118 +83,122 @@
 
   private final boolean preserveNullChecks = ThreadConfig.get(
       Optimizations.FieldValuePropagation.PRESERVE_NULL_CHECKS).booleanValue();
-
   private final boolean ensureTypeInitializers = ThreadConfig.get(
       Optimizations.FieldValuePropagation.ENSURE_TYPE_INITIALIZERS).booleanValue();
 
-  private class Visitor extends JVisitor {
-    @Nonnull
-    private final JMethod method;
-    private final boolean insideConstructor;
+  @Override
+  public void run(@Nonnull final JMethodBodyCfg body) {
+    final JMethod method = body.getMethod();
+    final boolean insideConstructor = OptimizerUtils.isConstructor(method);
+    final TransformationRequest request = new TransformationRequest(method);
+    final CfgJlsNullabilityChecker nullChecker = preserveNullChecks ?
+        new CfgJlsNullabilityChecker(body.getCfg(),
+            new LocalVarCreator(method, "fvp"), phantomLookup) : null;
 
-    @CheckForNull
-    private final JlsNullabilityChecker jlsNullabilityHelper;
-    @Nonnull
-    private final ExpressionReplaceHelper replaceHelper;
-    @Nonnull
-    public final TransformationRequest request;
-
-    Visitor(@Nonnull JMethod method, boolean addNullChecks) {
-      this.method = method;
-      this.insideConstructor = OptimizerUtils.isConstructor(method);
-      this.request = new TransformationRequest(method);
-
-      LocalVarCreator fvp = new LocalVarCreator(method, "fvp");
-      this.replaceHelper = new ExpressionReplaceHelper(fvp);
-      this.jlsNullabilityHelper = addNullChecks ?
-          new JlsNullabilityChecker(fvp, phantomLookup) : null;
-    }
-
-    @Override
-    public void endVisit(@Nonnull JFieldRef ref) {
-      replaceFieldWithValue(ref);
-      super.endVisit(ref);
-    }
-
-    private void replaceFieldWithValue(@Nonnull JFieldRef ref) {
-      if (OptimizerUtils.isAssigned(ref)) {
-        return;
+    new BasicBlockLiveProcessor(body.getCfg(), /* stepIntoElements: */ false) {
+      @Override
+      public boolean visit(@Nonnull JThrowingExpressionBasicBlock block) {
+        JBasicBlockElement element = block.getLastElement();
+        if (element instanceof JVariableAsgBlockElement &&
+            ((JVariableAsgBlockElement) element).isFieldLoad()) {
+          handle((JVariableAsgBlockElement) element);
+        }
+        return false;
       }
 
-      JField field = ref.getFieldId().getField();
-      if (field == null ||
-          !field.getAnnotations(disablingAnnotationType).isEmpty() ||
-          !field.getEnclosingType().getAnnotations(disablingAnnotationType).isEmpty()) {
-        return;
-      }
-
-      // Only process fields of types to be emitted
-      JDefinedClassOrInterface type = field.getEnclosingType();
-      if (!type.isToEmit()) {
-        return;
-      }
-
-      // Only process tracked fields. Note: there may be valid reasons why a tracked field
-      // might not have a marker yet, for example not initialized static fields if there
-      // is no any assignment to this field and type static initializer is also missing.
-      FieldSingleValueMarker marker = FieldSingleValueMarker.getOrCreate(field);
-      if (marker == null || marker.isMultipleOrNonLiteralValue()) {
-        return;
-      }
-
-      // Do not substitute field reads inside correspondent type
-      // initializers where this field is being initialized
-      if (insideConstructor &&
-          type == method.getEnclosingType() &&
-          method.isStatic() == field.isStatic()) {
-        if (method.isStatic() || ref.getInstance() instanceof JThisRef) {
-          // Field either is static or is instance and implicitly
-          // or explicitly referenced via 'this' reference
+      private void handle(@Nonnull JVariableAsgBlockElement element) {
+        JFieldRef ref = (JFieldRef) element.getValue();
+        if (OptimizerUtils.isAssigned(ref)) {
           return;
         }
-      }
 
-      // In case this is a static field and the field is accessed not from the same
-      // type, we don't propagate value if 'ensure-type-initializers' is true.
-      if (field.isStatic() && ensureTypeInitializers &&
-          field.getEnclosingType() != method.getEnclosingType()) {
-        return;
-      }
+        JField field = ref.getFieldId().getField();
+        if (field == null ||
+            !field.getAnnotations(disablingAnnotationType).isEmpty() ||
+            !field.getEnclosingType().getAnnotations(disablingAnnotationType).isEmpty()) {
+          return;
+        }
 
-      JValueLiteral value = marker.getConsolidatedValue();
-      if (value == null) {
-        value = createDefaultValue(field); // Assume default value.
-      } else {
-        value = OptimizerUtils.cloneExpression(value);
-        value.setSourceInfo(ref.getSourceInfo());
-      }
+        // Only process fields of types to be emitted
+        JDefinedClassOrInterface type = field.getEnclosingType();
+        if (!type.isToEmit()) {
+          return;
+        }
 
-      replaceHelper.replace(ref, value, request);
-      tracer.getStatistic(FIELD_VALUES_PROPAGATED).incValue();
+        // Only process tracked fields. Note: there may be valid reasons why a tracked field
+        // might not have a marker yet, for example not initialized static fields if there
+        // is no any assignment to this field and type static initializer is also missing.
+        FieldSingleValueMarker marker = FieldSingleValueMarker.getOrCreate(field);
+        if (marker == null || marker.isMultipleOrNonLiteralValue()) {
+          return;
+        }
 
-      if (jlsNullabilityHelper != null) {
-        if (!field.isStatic()) {
-          JExpression instance = ref.getInstance();
-          assert instance != null;
-          JStatement nullCheck = jlsNullabilityHelper
-              .createNullCheckIfNeeded(instance, request);
-          if (nullCheck != null) {
-            JStatement stmt = instance.getParent(JStatement.class);
-            request.append(new AppendBefore(stmt, nullCheck));
+        // Do not substitute field reads inside correspondent type
+        // initializers where this field is being initialized
+        if (insideConstructor &&
+            type == method.getEnclosingType() &&
+            method.isStatic() == field.isStatic()) {
+          if (method.isStatic() || ref.getInstance() instanceof JThisRef) {
+            // Field either is static or is instance and implicitly
+            // or explicitly referenced via 'this' reference
+            return;
           }
         }
+
+        // In case this is a static field and the field is accessed not from the same
+        // type, we don't propagate value if 'ensure-type-initializers' is true.
+        if (field.isStatic() && ensureTypeInitializers &&
+            field.getEnclosingType() != method.getEnclosingType()) {
+          return;
+        }
+
+        JValueLiteral value = marker.getConsolidatedValue();
+        if (value == null) {
+          value = createDefaultValue(field); // Assume default value.
+        } else {
+          value = OptimizerUtils.cloneExpression(value);
+          value.setSourceInfo(ref.getSourceInfo());
+        }
+
+        // #1: Split the block to move all but the last element
+        //     into a separate simple block.
+        JThrowingExpressionBasicBlock secondBlock =
+            (JThrowingExpressionBasicBlock) element.getBasicBlock();
+        JSimpleBasicBlock firstBlock = secondBlock.split(-1);
+
+        // #2: Insert null-checks if needed.
+        if (nullChecker != null && !field.isStatic()) {
+          // The null-check is needed, we insert it in between the two blocks
+          JExpression instance = ref.getInstance();
+          assert instance != null;
+          assert !instance.canThrow();
+
+          CfgFragment nullCheckFragment =
+              nullChecker.createNullCheck(element.getEHContext(), instance, request);
+
+          nullCheckFragment.insert(firstBlock, secondBlock);
+        }
+
+        // #3: Schedule value replace request
+        request.append(new Replace(ref, value));
+        tracer.getStatistic(FIELD_VALUES_PROPAGATED).incValue();
+
+        // #4: Turn the second block into a simple block in case it does not throw
+        if (!value.canThrow()) {
+          JSimpleBasicBlock newSecondBlock =
+              new BasicBlockBuilder(body.getCfg())
+                  .append(secondBlock)
+                  .append(new JGotoBlockElement(
+                      SourceInfo.UNKNOWN, secondBlock.getLastElement().getEHContext()))
+                  .createSimpleBlock(secondBlock.getPrimarySuccessor());
+          secondBlock.detach(newSecondBlock);
+        }
+
+        // #5: Merge the first block into its primary successor
+        firstBlock.mergeIntoSuccessor();
       }
-    }
-  }
+    }.process();
 
-  @Override
-  public void run(@Nonnull JMethod method) {
-    if (method.isNative() || method.isAbstract()) {
-      return;
-    }
-
-    Visitor visitor = new Visitor(method, preserveNullChecks);
-    visitor.accept(method);
-    visitor.request.commit();
+    request.commit();
   }
 }
diff --git a/jack/src/com/android/jack/optimizations/wofr/WofrRemoveFieldWrites.java b/jack/src/com/android/jack/optimizations/wofr/WofrRemoveFieldWrites.java
index 186ef94..3ab472a 100644
--- a/jack/src/com/android/jack/optimizations/wofr/WofrRemoveFieldWrites.java
+++ b/jack/src/com/android/jack/optimizations/wofr/WofrRemoveFieldWrites.java
@@ -17,27 +17,24 @@
 package com.android.jack.optimizations.wofr;
 
 import com.android.jack.Jack;
-import com.android.jack.cfg.BasicBlock;
-import com.android.jack.cfg.ControlFlowGraph;
-import com.android.jack.ir.ast.JAsgOperation;
 import com.android.jack.ir.ast.JDefinedClassOrInterface;
 import com.android.jack.ir.ast.JExpression;
-import com.android.jack.ir.ast.JExpressionStatement;
 import com.android.jack.ir.ast.JField;
 import com.android.jack.ir.ast.JFieldRef;
-import com.android.jack.ir.ast.JLocal;
 import com.android.jack.ir.ast.JMethod;
-import com.android.jack.ir.ast.JMethodCall;
+import com.android.jack.ir.ast.JMethodBodyCfg;
 import com.android.jack.ir.ast.JPrimitiveType;
-import com.android.jack.ir.ast.JStatement;
 import com.android.jack.ir.ast.JThisRef;
+import com.android.jack.ir.ast.cfg.BasicBlockLiveProcessor;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.jack.ir.ast.cfg.JSimpleBasicBlock;
+import com.android.jack.ir.ast.cfg.JStoreBlockElement;
+import com.android.jack.ir.ast.cfg.JThrowingExpressionBasicBlock;
+import com.android.jack.ir.ast.cfg.mutations.CfgFragment;
 import com.android.jack.lookup.JPhantomLookup;
 import com.android.jack.optimizations.Optimizations;
-import com.android.jack.optimizations.common.JlsNullabilityChecker;
+import com.android.jack.optimizations.cfg.CfgJlsNullabilityChecker;
 import com.android.jack.transformations.LocalVarCreator;
-import com.android.jack.transformations.request.AppendBefore;
-import com.android.jack.transformations.request.PrependAfter;
-import com.android.jack.transformations.request.Replace;
 import com.android.jack.transformations.request.TransformationRequest;
 import com.android.sched.item.Description;
 import com.android.sched.item.Name;
@@ -49,21 +46,16 @@
 import com.android.sched.util.log.Tracer;
 import com.android.sched.util.log.TracerFactory;
 
-import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 
 /** Write-only field removal, second phase: remove field assignments */
 @Description("Write-only field removal, field writes removal")
-@Constraint(need = { ControlFlowGraph.class,
-                     JFieldRef.class,
-                     FieldReadWriteCountsMarker.class })
-@Transform(modify = FieldReadWriteCountsMarker.class,
-    add = JExpressionStatement.class)
+@Constraint(need = { FieldReadWriteCountsMarker.class, JFieldRef.class })
+@Transform(modify = { FieldReadWriteCountsMarker.class, JControlFlowGraph.class })
 @Name("WriteOnlyFieldRemoval: RemoveFieldWrites")
-@Use({ JlsNullabilityChecker.class,
-       LocalVarCreator.class })
+@Use({ CfgJlsNullabilityChecker.class, LocalVarCreator.class })
 public class WofrRemoveFieldWrites extends WofrSchedulable
-    implements RunnableSchedulable<JMethod> {
+    implements RunnableSchedulable<JMethodBodyCfg> {
 
   private final boolean preserveObjectLifetime =
       ThreadConfig.get(Optimizations.WriteOnlyFieldRemoval.PRESERVE_OBJECT_LIFETIME).booleanValue();
@@ -84,17 +76,14 @@
   private enum Action {
     /** Field is either not eligible for optimization or write cannot be removed */
     None,
-    /** Field write can be replaced with just expression */
-    Expression,
-    /** Field write can be replaced with receiver and expression */
-    ReceiverAndExpression,
-    /** Field write can be replaced with receiver, expression and null-check */
-    ReceiverExpressionAndNullCheck
+    /** Field write can be removed */
+    Remove,
+    /** Field write can be removed, but a null-check need to be added */
+    RemoveAndAddNullCheck
   }
 
   /** Classify field assignment */
   private Action classify(@Nonnull JMethod method, @Nonnull JFieldRef ref) {
-
     JField field = ref.getFieldId().getField();
     if (field == null ||
         !field.getAnnotations(disablingAnnotationType).isEmpty() ||
@@ -107,7 +96,7 @@
     }
 
     if (FieldReadWriteCountsMarker.hasReads(field)) {
-      return Action.None; // The field is read
+      return Action.None; // The field has reads
     }
 
     JDefinedClassOrInterface fieldOwningType = field.getEnclosingType();
@@ -135,120 +124,81 @@
     if (field.isStatic() || ref.getInstance() instanceof JThisRef) {
       // The field is static OR the field is an instance field accessed
       // via this reference: no side-effects while calculating receiver
-      return Action.Expression;
+      return Action.Remove;
     }
 
     assert !field.isStatic();
-
-    return preserveNullChecks
-        ? Action.ReceiverExpressionAndNullCheck
-        : Action.ReceiverAndExpression;
+    return preserveNullChecks ? Action.RemoveAndAddNullCheck : Action.Remove;
   }
 
   @Override
-  public void run(@Nonnull final JMethod method) {
-    if (method.isAbstract() || method.isNative()) {
-      return;
-    }
+  public void run(@Nonnull final JMethodBodyCfg body) {
+    final TransformationRequest request = new TransformationRequest(body);
+    final CfgJlsNullabilityChecker nullChecker =
+        new CfgJlsNullabilityChecker(body.getCfg(),
+            new LocalVarCreator(body.getMethod(), "wofr"), phantomLookup);
 
-    final TransformationRequest request = new TransformationRequest(method);
-    final LocalVarCreator varCreator = new LocalVarCreator(method, "wofr");
-    final JlsNullabilityChecker nullChecker = new JlsNullabilityChecker(varCreator, phantomLookup);
+    // Create a processor to walk the cfg and remove the writes, note that
+    // even though we insert the new basic blocks, we don't need to update
+    // the processor to know about these newly created blocks, since they
+    // don't have any field writes we might want to process.
 
-    class Processor {
-      private void handleExprStmt(@Nonnull JExpressionStatement stmt) {
-        JExpression expr = stmt.getExpr();
-        if (expr instanceof JAsgOperation) {
-          JAsgOperation asg = (JAsgOperation) expr;
-          JExpression lhs = asg.getLhs();
-          if (lhs instanceof JFieldRef) {
-            JFieldRef ref = (JFieldRef) lhs;
-
-            switch (classify(method, ref)) {
-              case None:
-                // Cannot remove field assignment
-                return;
-
-              case ReceiverExpressionAndNullCheck:
-                // Replace received with temp local
-                JLocal local = handleFieldReceiver(asg, true);
-                assert local != null;
-                // Replace assignment expression with rhs
-                handleAssignmentRhs(asg);
-                // Append null-check
-                JStatement nullCheck =
-                    nullChecker.createNullCheck(
-                        local.makeRef(local.getSourceInfo()), request);
-                request.append(new PrependAfter(stmt, nullCheck));
-                break;
-
-              case ReceiverAndExpression:
-                // Prepend statement with expression statement representing <receiver>
-                handleFieldReceiver(asg, false);
-                // Replace assignment expression with rhs
-                handleAssignmentRhs(asg);
-                break;
-
-              case Expression:
-                // Replace assignment expression with rhs
-                handleAssignmentRhs(asg);
-                break;
-            }
-
-            JField field = ref.getFieldId().getField();
-            assert field != null;
-            FieldReadWriteCountsMarker.unmarkWrite(field);
-            tracer.getStatistic(FIELD_WRITES_REMOVED).incValue();
-          }
+    new BasicBlockLiveProcessor(body.getCfg(), /* stepIntoElements = */ true) {
+      @Override
+      public boolean visit(@Nonnull JStoreBlockElement element) {
+        JFieldRef ref = element.getLhsAsFieldRef();
+        if (ref == null) {
+          return false;
         }
-      }
+        JExpression value = element.getValueExpression();
+        assert !value.canThrow();
 
-      private void handleAssignmentRhs(@Nonnull JAsgOperation asg) {
-        JExpression rhs = asg.getRhs();
-        if (rhs instanceof JMethodCall) {
-          request.append(new Replace(asg, rhs));
-        } else {
-          JExpression lhs = asg.getLhs();
-          JLocal local =
-              varCreator.createTempLocal(
-                  lhs.getType(), lhs.getSourceInfo(), request);
-          request.append(new Replace(lhs, local.makeRef(local.getSourceInfo())));
-        }
-      }
-
-      @CheckForNull
-      private JLocal handleFieldReceiver(@Nonnull JAsgOperation asg, boolean forceLocal) {
-        JExpression receiver = ((JFieldRef) asg.getLhs()).getInstance();
-        assert receiver != null;
-
-        JLocal local = null;
-
-        if (!(receiver instanceof JMethodCall) || forceLocal) {
-          // Need a temp local
-          local = varCreator.createTempLocal(
-              receiver.getType(), receiver.getSourceInfo(), request);
-          receiver = new JAsgOperation(
-              receiver.getSourceInfo(), local.makeRef(receiver.getSourceInfo()), receiver);
+        Action action = classify(body.getMethod(), ref);
+        if (action == Action.None) {
+          // Cannot remove field assignment
+          return false;
         }
 
-        JExpressionStatement stmt =
-            new JExpressionStatement(asg.getSourceInfo(), receiver);
-        request.append(new AppendBefore(asg.getParent(), stmt));
-        return local;
-      }
-    }
+        // Field store operation must be the LAST element of
+        // the throwing expression basic block.
+        JThrowingExpressionBasicBlock block = element.getBasicBlock();
+        assert block.getLastElement() == element;
 
-    Processor processor = new Processor();
-    ControlFlowGraph cfg = method.getMarker(ControlFlowGraph.class);
-    assert cfg != null;
+        // We first split the basic block into two parts:
+        //
+        //        block { e0, e1, ...,  eLast (== element) }
+        //                      |  |  |
+        //                      V  V  V
+        //    simple { e0, e1, ..., goto }  -->  block { eLast }
+        //
+        JSimpleBasicBlock simple = block.split(-1);
 
-    for (BasicBlock bb : cfg.getNodes()) {
-      for (JStatement stmt : bb.getStatements()) {
-        if (stmt instanceof JExpressionStatement) {
-          processor.handleExprStmt((JExpressionStatement) stmt);
+        if (action == Action.RemoveAndAddNullCheck) {
+          // The null-check is needed, we insert it in between the two blocks
+          JExpression instance = ref.getInstance();
+          assert instance != null;
+          assert !instance.canThrow();
+
+          CfgFragment nullCheckFragment =
+              nullChecker.createNullCheck(element.getEHContext(), instance, request);
+
+          nullCheckFragment.insert(simple, block);
         }
+
+        // Delete the block, since we remove the write
+        assert block.getElementCount() == 1;
+        assert block.getLastElement() == element;
+        block.delete();
+
+        // Unmark the write
+        JField field = ref.getFieldId().getField();
+        assert field != null;
+        FieldReadWriteCountsMarker.unmarkWrite(field);
+        tracer.getStatistic(FIELD_WRITES_REMOVED).incValue();
+
+        return false;
       }
-    }
+    }.process();
 
     request.commit();
   }
diff --git a/jack/src/com/android/jack/scheduling/adapter/JAllBasicBlockAdapter.java b/jack/src/com/android/jack/scheduling/adapter/JAllBasicBlockAdapter.java
new file mode 100644
index 0000000..e1b853b
--- /dev/null
+++ b/jack/src/com/android/jack/scheduling/adapter/JAllBasicBlockAdapter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.scheduling.adapter;
+
+import com.android.jack.ir.ast.cfg.JBasicBlock;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.sched.item.Description;
+import com.android.sched.schedulable.AdapterSchedulable;
+
+import java.util.Iterator;
+import javax.annotation.Nonnull;
+
+/**
+ * Adapts a process on {@code JControlFlowGraph} onto one or several processes
+ * on each {@code JBasicBlock} of this type control flow graph.
+ */
+@Description("Adapts process on JControlFlowGraph to one "
+    + "or several processes on each of its basic blocks")
+public class JAllBasicBlockAdapter
+    implements AdapterSchedulable<JControlFlowGraph, JBasicBlock> {
+
+  /** Returns every {@code JBasicBlock} of the given {@code JControlFlowGraph}. */
+  @Override
+  @Nonnull
+  public Iterator<JBasicBlock> adapt(@Nonnull JControlFlowGraph cfg) {
+    return cfg.getAllBlocksUnordered().iterator();
+  }
+}
diff --git a/jack/src/com/android/jack/scheduling/adapter/JMethodBodyCfgAdapter.java b/jack/src/com/android/jack/scheduling/adapter/JMethodBodyCfgAdapter.java
new file mode 100644
index 0000000..4c3d7d1
--- /dev/null
+++ b/jack/src/com/android/jack/scheduling/adapter/JMethodBodyCfgAdapter.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.scheduling.adapter;
+
+import com.android.jack.ir.ast.JAbstractMethodBody;
+import com.android.jack.ir.ast.JDefinedClassOrInterface;
+import com.android.jack.ir.ast.JMethod;
+import com.android.jack.ir.ast.JMethodBodyCfg;
+import com.android.sched.item.Description;
+import com.android.sched.schedulable.AdapterSchedulable;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import javax.annotation.Nonnull;
+
+/**
+ * Adapts a process on {@code JDefinedClassOrInterface} onto one or several processes on
+ * each {@code JMethodBodyCfg} of all the methods declared by this type.
+ */
+@Description("Adapts process on JDefinedClassOrInterface to one or several processes "
+    + "on each cfg method body of all the methods declared by this type")
+public class JMethodBodyCfgAdapter
+    implements AdapterSchedulable<JDefinedClassOrInterface, JMethodBodyCfg> {
+
+  /**
+   * Returns every {@code JMethodBodyCfg} of the methods declared
+   * in the given {@code JDefinedClassOrInterface}.
+   */
+  @Override
+  @Nonnull
+  public Iterator<JMethodBodyCfg> adapt(@Nonnull JDefinedClassOrInterface declaredType) {
+    ArrayList<JMethodBodyCfg> cfgList = new ArrayList<>();
+    for (JMethod method : declaredType.getMethods()) {
+      JAbstractMethodBody body = method.getBody();
+      if (body instanceof JMethodBodyCfg) {
+        cfgList.add((JMethodBodyCfg) body);
+      }
+    }
+    return cfgList.iterator();
+  }
+}
diff --git a/jack/src/com/android/jack/transformations/LocalVarCreator.java b/jack/src/com/android/jack/transformations/LocalVarCreator.java
index 42f9859..3ec3822f 100644
--- a/jack/src/com/android/jack/transformations/LocalVarCreator.java
+++ b/jack/src/com/android/jack/transformations/LocalVarCreator.java
@@ -17,9 +17,9 @@
 package com.android.jack.transformations;
 
 import com.android.jack.ir.ast.JAbstractMethodBody;
+import com.android.jack.ir.ast.JConcreteMethodBody;
 import com.android.jack.ir.ast.JLocal;
 import com.android.jack.ir.ast.JMethod;
-import com.android.jack.ir.ast.JMethodBody;
 import com.android.jack.ir.ast.JModifier;
 import com.android.jack.ir.ast.JType;
 import com.android.jack.ir.sourceinfo.SourceInfo;
@@ -38,7 +38,7 @@
 public class LocalVarCreator {
 
   @Nonnull
-  private final JMethodBody currentMethodBody;
+  private final JConcreteMethodBody currentMethodBody;
   @Nonnull
   private final String tmpLocalVarPrefix;
   @Nonnegative
@@ -52,8 +52,8 @@
   public LocalVarCreator(@Nonnull JMethod method, @Nonnull String prefix) {
     JAbstractMethodBody body = method.getBody();
     assert body != null;
-    assert body instanceof JMethodBody;
-    currentMethodBody = (JMethodBody) body;
+    assert body instanceof JConcreteMethodBody;
+    currentMethodBody = (JConcreteMethodBody) body;
     tmpLocalVarPrefix = prefix;
   }
 
@@ -72,4 +72,4 @@
 
     return local;
   }
-}
\ No newline at end of file
+}
diff --git a/jack/src/com/android/jack/transformations/request/AddJLocalInMethodBody.java b/jack/src/com/android/jack/transformations/request/AddJLocalInMethodBody.java
index df63820..ca3e00f 100644
--- a/jack/src/com/android/jack/transformations/request/AddJLocalInMethodBody.java
+++ b/jack/src/com/android/jack/transformations/request/AddJLocalInMethodBody.java
@@ -16,6 +16,7 @@
 
 package com.android.jack.transformations.request;
 
+import com.android.jack.ir.ast.JConcreteMethodBody;
 import com.android.jack.ir.ast.JLocal;
 import com.android.jack.ir.ast.JMethodBody;
 import com.android.sched.transform.TransformStep;
@@ -31,7 +32,7 @@
   @Nonnull
   private final JLocal local;
   @Nonnull
-  private final JMethodBody methodBody;
+  private final JConcreteMethodBody methodBody;
 
   /**
    * Constructor specifying the {@link JLocal} to add into the {@link JMethodBody}.
@@ -39,7 +40,7 @@
    * @param local the local variable to add to the method body
    * @param methodBody the body of the method to update
    */
-  public AddJLocalInMethodBody(@Nonnull JLocal local, @Nonnull JMethodBody methodBody) {
+  public AddJLocalInMethodBody(@Nonnull JLocal local, @Nonnull JConcreteMethodBody methodBody) {
     this.local = local;
     this.methodBody = methodBody;
   }
diff --git a/jack/src/com/android/jack/transformations/ssa/CfgNodeIdAssignment.java b/jack/src/com/android/jack/transformations/ssa/CfgNodeIdAssignment.java
new file mode 100644
index 0000000..cd248af
--- /dev/null
+++ b/jack/src/com/android/jack/transformations/ssa/CfgNodeIdAssignment.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.transformations.ssa;
+
+import com.android.jack.ir.ast.JMethodBodyCfg;
+import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
+import com.android.jack.util.graph.NodeIdMarker;
+import com.android.sched.item.Description;
+import com.android.sched.item.Name;
+import com.android.sched.schedulable.Constraint;
+import com.android.sched.schedulable.Filter;
+import com.android.sched.schedulable.RunnableSchedulable;
+import com.android.sched.schedulable.Transform;
+
+/**
+ * Assign Node ID from the control flow graph.
+ */
+@Description("Insert Node ID to the CFG for SSA conversion")
+@Name("CfgNodeIdAssignment")
+@Constraint(need = {SsaBasicBlockSplitterMarker.class})
+@Transform(add = {NodeIdMarker.class})
+@Filter(TypeWithoutPrebuiltFilter.class)
+public class CfgNodeIdAssignment implements RunnableSchedulable<JMethodBodyCfg> {
+  @Override
+  public void run(JMethodBodyCfg t) {
+    NodeIdMarker.assignIds(t.getCfg());
+  }
+}
diff --git a/jack/src/com/android/jack/transformations/ssa/CfgNodeIdRemoval.java b/jack/src/com/android/jack/transformations/ssa/CfgNodeIdRemoval.java
new file mode 100644
index 0000000..d030387
--- /dev/null
+++ b/jack/src/com/android/jack/transformations/ssa/CfgNodeIdRemoval.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.transformations.ssa;
+
+import com.android.jack.ir.ast.JMethodBodyCfg;
+import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
+import com.android.jack.util.graph.NodeIdMarker;
+import com.android.sched.item.Description;
+import com.android.sched.item.Name;
+import com.android.sched.schedulable.Constraint;
+import com.android.sched.schedulable.Filter;
+import com.android.sched.schedulable.RunnableSchedulable;
+import com.android.sched.schedulable.Transform;
+
+/**
+ * Removes Node ID to the control flow graph.
+ */
+@Description("Removes Node ID to the CFG for SSA conversion")
+@Name("CfgNodeIdRemoval")
+@Constraint(need = {NodeIdMarker.class})
+@Transform(remove = {NodeIdMarker.class})
+@Filter(TypeWithoutPrebuiltFilter.class)
+public class CfgNodeIdRemoval implements RunnableSchedulable<JMethodBodyCfg> {
+  @Override
+  public void run(JMethodBodyCfg body) {
+    NodeIdMarker.removeIds(body.getCfg());
+  }
+}
diff --git a/jack/src/com/android/jack/transformations/ssa/CfgNodeListAssignment.java b/jack/src/com/android/jack/transformations/ssa/CfgNodeListAssignment.java
new file mode 100644
index 0000000..b85f04a
--- /dev/null
+++ b/jack/src/com/android/jack/transformations/ssa/CfgNodeListAssignment.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.transformations.ssa;
+
+import com.android.jack.ir.ast.JMethodBodyCfg;
+import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
+import com.android.jack.util.graph.NodeListMarker;
+import com.android.sched.item.Description;
+import com.android.sched.item.Name;
+import com.android.sched.schedulable.Constraint;
+import com.android.sched.schedulable.Filter;
+import com.android.sched.schedulable.RunnableSchedulable;
+import com.android.sched.schedulable.Transform;
+
+
+/**
+ * Removes Node ID from the control flow graph.
+ */
+@Description("Insert Node list to the CFG for SSA conversion")
+@Name("CfgNodeListAssignment")
+@Constraint(need = {SsaBasicBlockSplitterMarker.class})
+@Transform(add = {NodeListMarker.class})
+@Filter(TypeWithoutPrebuiltFilter.class)
+public class CfgNodeListAssignment implements RunnableSchedulable<JMethodBodyCfg> {
+  @Override
+  public void run(JMethodBodyCfg body) {
+    NodeListMarker.assignNodeList(body.getCfg());
+  }
+}
diff --git a/jack/src/com/android/jack/transformations/ssa/CfgNodeListRemoval.java b/jack/src/com/android/jack/transformations/ssa/CfgNodeListRemoval.java
new file mode 100644
index 0000000..94eb169
--- /dev/null
+++ b/jack/src/com/android/jack/transformations/ssa/CfgNodeListRemoval.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.transformations.ssa;
+
+import com.android.jack.ir.ast.JMethodBodyCfg;
+import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
+import com.android.jack.util.graph.NodeListMarker;
+import com.android.sched.item.Description;
+import com.android.sched.item.Name;
+import com.android.sched.schedulable.Constraint;
+import com.android.sched.schedulable.Filter;
+import com.android.sched.schedulable.RunnableSchedulable;
+import com.android.sched.schedulable.Transform;
+
+/**
+ * Removes Node List from the CFG for SSA conversion.
+ */
+@Description("Removes Node List from the CFG for SSA conversion")
+@Name("CfgNodeListRemoval")
+@Constraint(need = {NodeListMarker.class})
+@Transform(remove = {NodeListMarker.class})
+@Filter(TypeWithoutPrebuiltFilter.class)
+public class CfgNodeListRemoval implements RunnableSchedulable<JMethodBodyCfg> {
+  @Override
+  public void run(JMethodBodyCfg body) {
+    NodeListMarker.removeNodeList(body.getCfg());
+  }
+}
diff --git a/jack/src/com/android/jack/transformations/ssa/DominanceFrontierAssignment.java b/jack/src/com/android/jack/transformations/ssa/DominanceFrontierAssignment.java
new file mode 100644
index 0000000..05f0f2a
--- /dev/null
+++ b/jack/src/com/android/jack/transformations/ssa/DominanceFrontierAssignment.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.transformations.ssa;
+
+import com.android.jack.ir.ast.JMethodBodyCfg;
+import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
+import com.android.jack.util.graph.DominanceFrontier;
+import com.android.sched.item.Description;
+import com.android.sched.item.Name;
+import com.android.sched.schedulable.Constraint;
+import com.android.sched.schedulable.Filter;
+import com.android.sched.schedulable.RunnableSchedulable;
+import com.android.sched.schedulable.Use;
+
+/**
+ *
+ */
+@Description("Computes dominance frontier in the CFG.")
+@Name("DominanceFrontierAssignment")
+@Constraint(need = {SsaBasicBlockSplitterMarker.class})
+@Use(DominanceFrontier.class)
+@Filter(TypeWithoutPrebuiltFilter.class)
+public class DominanceFrontierAssignment implements RunnableSchedulable<JMethodBodyCfg> {
+  @Override
+  public void run(JMethodBodyCfg body) {
+    new DominanceFrontier<>(body.getCfg()).run();
+  }
+}
diff --git a/jack/src/com/android/jack/transformations/ssa/DominanceFrontierRemoval.java b/jack/src/com/android/jack/transformations/ssa/DominanceFrontierRemoval.java
new file mode 100644
index 0000000..97495d3
--- /dev/null
+++ b/jack/src/com/android/jack/transformations/ssa/DominanceFrontierRemoval.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.transformations.ssa;
+
+import com.android.jack.ir.ast.JMethodBodyCfg;
+import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
+import com.android.jack.util.graph.DominanceFrontierInfoMarker;
+import com.android.jack.util.graph.DominatorTreeMarker;
+import com.android.sched.item.Description;
+import com.android.sched.item.Name;
+import com.android.sched.schedulable.Constraint;
+import com.android.sched.schedulable.Filter;
+import com.android.sched.schedulable.RunnableSchedulable;
+import com.android.sched.schedulable.Transform;
+
+/**
+ * Removes dominance frontier related marker from control flow graph.
+ */
+@Description("Removes dominance frontier related marker from control flow graph")
+@Name("DominanceFrontierRemoval")
+@Constraint(need = {DominanceFrontierInfoMarker.class})
+@Transform(remove = {DominanceFrontierInfoMarker.class})
+@Filter(TypeWithoutPrebuiltFilter.class)
+public class DominanceFrontierRemoval implements RunnableSchedulable<JMethodBodyCfg> {
+  @Override
+  public void run(JMethodBodyCfg body) {
+    DominanceFrontierInfoMarker.clearMarkers(body.getCfg());
+    DominatorTreeMarker.clearMarkers(body.getCfg());
+  }
+}
diff --git a/jack/src/com/android/jack/transformations/ssa/JPhiElementInsertion.java b/jack/src/com/android/jack/transformations/ssa/JPhiElementInsertion.java
new file mode 100644
index 0000000..ac74326
--- /dev/null
+++ b/jack/src/com/android/jack/transformations/ssa/JPhiElementInsertion.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.transformations.ssa;
+
+import com.android.jack.dx.util.IntIterator;
+import com.android.jack.ir.ast.JMethodBodyCfg;
+import com.android.jack.ir.ast.JVariable;
+import com.android.jack.ir.ast.JVariableRef;
+import com.android.jack.ir.ast.cfg.JBasicBlock;
+import com.android.jack.ir.ast.cfg.JBasicBlockElement;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.jack.ir.ast.cfg.JPhiBlockElement;
+import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
+import com.android.jack.util.graph.DominanceFrontierInfoMarker;
+import com.android.jack.util.graph.NodeIdMarker;
+import com.android.jack.util.graph.NodeListMarker;
+import com.android.sched.item.Description;
+import com.android.sched.item.Name;
+import com.android.sched.schedulable.Constraint;
+import com.android.sched.schedulable.Filter;
+import com.android.sched.schedulable.RunnableSchedulable;
+import com.android.sched.schedulable.Transform;
+
+import java.util.BitSet;
+import java.util.List;
+
+/**
+ * Split basic blocks for Phi element insertion.
+ */
+@Description("Insert Phi elements into the CFG.")
+@Name("JPhiElementInsertion")
+@Transform(add = {JPhiBlockElement.class},
+    remove = {SsaBasicBlockSplitterMarker.class})
+@Constraint(need = {DominanceFrontierInfoMarker.class})
+@Filter(TypeWithoutPrebuiltFilter.class)
+public class JPhiElementInsertion implements RunnableSchedulable<JMethodBodyCfg> {
+
+  @Override
+  public void run(JMethodBodyCfg body) {
+    // Invalidates the block split marker.
+    SsaBasicBlockSplitterMarker marker =
+        body.getCfg().removeMarker(SsaBasicBlockSplitterMarker.class);
+    assert marker != null;
+    placePhiFunctions(body.getCfg());
+  }
+
+  /**
+   * See Appel algorithm 19.6:
+   *
+   * Place Phi functions in appropriate locations.
+   *
+   */
+  private void placePhiFunctions(JControlFlowGraph cfg) {
+    final int numLocals = SsaUtil.getTotalNumberOfLocals(cfg);
+    int regCount;
+    int blockCount;
+
+    final List<JBasicBlock> bbMap = NodeListMarker.getNodeList(cfg);
+
+    blockCount = bbMap.size();
+    regCount = numLocals;
+
+
+    // Bit set of registers vs block index "definition sites"
+    BitSet[] defsites = new BitSet[regCount];
+
+    // Bit set of registers vs block index "phi placement sites"
+    BitSet[] phisites = new BitSet[regCount];
+
+    for (int i = 0; i < regCount; i++) {
+      defsites[i] = new BitSet(blockCount);
+      phisites[i] = new BitSet(blockCount);
+    }
+
+    /*
+     * For each register, build a set of all basic blocks where containing an assignment to that
+     * register.
+     */
+    for (JBasicBlock b : bbMap) {
+      for (JBasicBlockElement stmt : b.getElements(true)) {
+        JVariableRef dv = stmt.getDefinedVariable();
+        if (dv != null) {
+          JVariable rs = dv.getTarget();
+          // We don't need to check for JThis because JThis will never be defined.
+          int index = SsaUtil.getLocalIndex(cfg, rs);
+          defsites[index].set(NodeIdMarker.getId(b));
+        }
+      }
+    }
+
+    BitSet worklist;
+    /*
+     * For each register, compute all locations for phi placement based on dominance-frontier
+     * algorithm.
+     */
+    for (int reg = 0, s = regCount; reg < s; reg++) {
+      int workBlockIndex;
+
+      /* Worklist set starts out with each node where reg is assigned. */
+      worklist = (BitSet) (defsites[reg].clone());
+
+      while (0 <= (workBlockIndex = worklist.nextSetBit(0))) {
+        worklist.clear(workBlockIndex);
+        DominanceFrontierInfoMarker domInfo =
+            DominanceFrontierInfoMarker.getDomInfo(bbMap.get(workBlockIndex));
+        IntIterator dfIterator = domInfo.dominanceFrontiers.iterator();
+
+        while (dfIterator.hasNext()) {
+          int dfBlockIndex = dfIterator.next();
+          JBasicBlock dfBlock = bbMap.get(dfBlockIndex);
+
+          if (!phisites[reg].get(dfBlockIndex) && dfBlock != cfg.getExitBlock()) {
+            phisites[reg].set(dfBlockIndex);
+
+            JVariable target = SsaUtil.getVariableByIndex(cfg, reg);
+
+            JPhiBlockElement phi =
+                new JPhiBlockElement(target, dfBlock.getPredecessors(), dfBlock.getSourceInfo());
+            dfBlock.insertElement(0, phi);
+
+            if (!defsites[reg].get(dfBlockIndex)) {
+              worklist.set(dfBlockIndex);
+            }
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/transformations/ssa/SsaBasicBlockSplitter.java b/jack/src/com/android/jack/transformations/ssa/SsaBasicBlockSplitter.java
new file mode 100644
index 0000000..32c6c5d
--- /dev/null
+++ b/jack/src/com/android/jack/transformations/ssa/SsaBasicBlockSplitter.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.transformations.ssa;
+
+import com.google.common.collect.Lists;
+
+import com.android.jack.ir.ast.JMethodBodyCfg;
+import com.android.jack.ir.ast.cfg.ExceptionHandlingContext;
+import com.android.jack.ir.ast.cfg.JBasicBlock;
+import com.android.jack.ir.ast.cfg.JBasicBlockElement;
+import com.android.jack.ir.ast.cfg.JCaseBasicBlock;
+import com.android.jack.ir.ast.cfg.JCatchBasicBlock;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.jack.ir.ast.cfg.JEntryBasicBlock;
+import com.android.jack.ir.ast.cfg.JExitBasicBlock;
+import com.android.jack.ir.ast.cfg.JGotoBlockElement;
+import com.android.jack.ir.ast.cfg.JSimpleBasicBlock;
+import com.android.jack.optimizations.cfg.CfgBasicBlockUtils;
+import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
+import com.android.sched.item.Description;
+import com.android.sched.item.Name;
+import com.android.sched.schedulable.Filter;
+import com.android.sched.schedulable.RunnableSchedulable;
+import com.android.sched.schedulable.Transform;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Split basic blocks for Phi element insertion.
+ */
+@Description("Split basic blocks for Phi element insertion.")
+@Name("SsaBasicBlockSplitter")
+@Transform(add = {SsaBasicBlockSplitterMarker.class},
+    modify = {JControlFlowGraph.class})
+@Filter(TypeWithoutPrebuiltFilter.class)
+public class SsaBasicBlockSplitter implements RunnableSchedulable<JMethodBodyCfg> {
+
+  // This appears to never be an issue in Jack nor DX as far as we can tell.
+  public static final boolean NEED_EDGE_SPLIT_PREDECESSOR = false;
+
+  // This *IS* an issue in code gen. However, we have "delayed" it and do the splitting right
+  // before ROP is built.
+  public static final boolean NEED_EDGE_SPLIT_MV_EXCEPTION = false;
+
+  // This is like NEED_EDGE_SPLIT_MV_EXCEPTION and can be handled later.
+  public static final boolean NEED_EDGE_SPLIT_CATCH_BASIC_BLOCK = false;
+
+  public static final boolean NEED_EDGE_SPLIT_CASE_BASIC_BLOCK = false;
+
+  @Override
+  public void run(@Nonnull JMethodBodyCfg body) {
+    assert body.getCfg().getMarker(SsaBasicBlockSplitterMarker.class) == null;
+    removeExceptionHandlingContext(body.getCfg());
+    edgeSplit(body.getCfg());
+    body.getCfg().addMarker(SsaBasicBlockSplitterMarker.INSTANCE);
+  }
+
+  /**
+   * Extracts control flow graph from method and perform edge split in neccessary.
+   */
+  private void edgeSplit(@Nonnull JControlFlowGraph cfg) {
+    if (NEED_EDGE_SPLIT_PREDECESSOR) {
+      edgeSplitPredecessors(cfg);
+    }
+    if (NEED_EDGE_SPLIT_MV_EXCEPTION) {
+      edgeSplitMoveExceptionsAndResults(cfg);
+    }
+    edgeSplitSuccessors(cfg);
+  }
+
+  /**
+   * Inserts Z nodes as new predecessors for every node that has multiple successors and multiple
+   * predecessors.
+   */
+  private void edgeSplitPredecessors(JControlFlowGraph cfg) {
+    for (JBasicBlock block : cfg.getReachableBlocksDepthFirst()) {
+      if (nodeNeedsUniquePredecessor(block)) {
+        block.split(0);
+      }
+    }
+  }
+
+  /**
+   * @param block {@code non-null;} block in question
+   * @return {@code true} if this node needs to have a unique predecessor created for it
+   */
+  private static boolean nodeNeedsUniquePredecessor(JBasicBlock block) {
+    if (block instanceof JExitBasicBlock) {
+      return false;
+    }
+    /*
+     * Any block with that has both multiple successors and multiple predecessors needs a new
+     * predecessor node.
+     */
+    int countPredecessors = block.getPredecessorCount();
+    int countSuccessors = block.getSuccessors().size();
+    boolean needsUniquePredecessor = countPredecessors > 1 && countSuccessors > 1;
+    assert !needsUniquePredecessor
+        || !(block instanceof JCaseBasicBlock || block instanceof JCatchBasicBlock);
+    return needsUniquePredecessor;
+  }
+
+  private static void edgeSplitMoveExceptionsAndResults(JControlFlowGraph cfg) {
+    /*
+     * New blocks are added to the end of the block list during this iteration.
+     */
+    for (JBasicBlock block : cfg.getReachableBlocksDepthFirst()) {
+      /*
+       * Any block that starts with a move-exception and has more than one predecessor...
+       */
+      if (!(block instanceof JExitBasicBlock) && block.getPredecessorCount() > 1
+          && (block instanceof JCatchBasicBlock)) {
+        for (JBasicBlock predecessor : Lists.newArrayList(block.getPredecessors())) {
+          insertNewSimpleSuccessor(predecessor, block);
+        }
+      }
+    }
+  }
+
+  /**
+   * Inserts Z nodes for every node that needs a new successor.
+   *
+   */
+  private static void edgeSplitSuccessors(JControlFlowGraph cfg) {
+    /*
+     * New blocks are added to the end of the block list during this iteration.
+     */
+    for (JBasicBlock block : cfg.getReachableBlocksDepthFirst()) {
+      // Successors list is modified in loop below.
+      for (JBasicBlock succ : block.getSuccessors()) {
+        if (needsNewSuccessor(block, succ)) {
+          // These two type of basic block requires special case handling. Otherwise, we might
+          // up with an IR that is very difficult to understand. Please refer to the design for
+          // detail information.
+          if (succ instanceof JCatchBasicBlock) {
+            if (NEED_EDGE_SPLIT_CATCH_BASIC_BLOCK) {
+              block.split(-1);
+            }
+          } else if (succ instanceof JCaseBasicBlock) {
+            if (NEED_EDGE_SPLIT_CASE_BASIC_BLOCK) {
+              block.split(-1);
+            }
+          } else {
+            insertNewSimpleSuccessor(block, succ);
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns {@code true} if block and successor need a Z-node between them. Presently, this is
+   * {@code true} if the final instruction has any sources or results and the current successor
+   * block has more than one predecessor.
+   *
+   * @param block predecessor node
+   * @param succ successor node
+   * @return {@code true} if a Z node is needed
+   */
+  private static boolean needsNewSuccessor(JBasicBlock block, JBasicBlock succ) {
+    if (block instanceof JEntryBasicBlock) {
+      return false;
+    }
+    if (block.getElementCount() == 0) {
+      return false;
+    }
+    if (succ instanceof JExitBasicBlock) {
+      return false;
+    }
+    JBasicBlockElement lastInsn = block.getLastElement();
+    int uvCount = lastInsn.getUsedVariables().size();
+    int dvCount = lastInsn.getDefinedVariable() == null ? 0 : 1;
+    return (uvCount + dvCount > 0) && succ.getPredecessorCount() > 1;
+  }
+
+  /**
+   * Insert a new successor between a block and one of its successor.
+   */
+  private static void insertNewSimpleSuccessor(JBasicBlock block, JBasicBlock other) {
+    JSimpleBasicBlock newSucc = new JSimpleBasicBlock(block.getCfg(), other);
+    // The goto itself shouldn't throw, therefore, it is ok with empty EHC.
+    JGotoBlockElement jGoto =
+        new JGotoBlockElement(other.getSourceInfo(), ExceptionHandlingContext.EMPTY);
+    newSucc.insertElement(0, jGoto);
+    block.replaceAllSuccessors(other, newSucc);
+  }
+
+  private void removeExceptionHandlingContext(@Nonnull JControlFlowGraph cfg) {
+    for (JBasicBlock block : cfg.getInternalBlocksUnordered()) {
+      for (JBasicBlockElement element : block.getElements(true)) {
+        element.resetEHContext(ExceptionHandlingContext.EMPTY);
+      }
+    }
+    new CfgBasicBlockUtils(cfg).removeUnreachableBlocks();
+  }
+}
diff --git a/jack/src/com/android/jack/transformations/ssa/SsaBasicBlockSplitterMarker.java b/jack/src/com/android/jack/transformations/ssa/SsaBasicBlockSplitterMarker.java
new file mode 100644
index 0000000..45b29ca
--- /dev/null
+++ b/jack/src/com/android/jack/transformations/ssa/SsaBasicBlockSplitterMarker.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.transformations.ssa;
+
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.sched.item.Description;
+import com.android.sched.marker.Marker;
+import com.android.sched.marker.ValidOn;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Marks a Method as block splitted and ready for Phi instructino insertion.
+ */
+@Description("Marks a CFG that has been block splitted and ready for SSA construction.")
+@ValidOn(JControlFlowGraph.class)
+public final class SsaBasicBlockSplitterMarker implements Marker {
+  @Nonnull
+  public static final SsaBasicBlockSplitterMarker INSTANCE = new SsaBasicBlockSplitterMarker();
+
+  private SsaBasicBlockSplitterMarker() {}
+
+  @Override
+  @Nonnull
+  public Marker cloneIfNeeded() {
+    return this;
+  }
+}
diff --git a/jack/src/com/android/jack/transformations/ssa/SsaMethodMarker.java b/jack/src/com/android/jack/transformations/ssa/SsaMethodMarker.java
new file mode 100644
index 0000000..2f6c0dd
--- /dev/null
+++ b/jack/src/com/android/jack/transformations/ssa/SsaMethodMarker.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.transformations.ssa;
+
+import com.android.jack.ir.ast.JMethod;
+import com.android.sched.item.Description;
+import com.android.sched.marker.Marker;
+import com.android.sched.marker.ValidOn;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Implies this method is SSA compatible.
+ */
+@Description("SsaMethodMarker")
+@ValidOn(value = {JMethod.class})
+public class SsaMethodMarker implements Marker {
+  @Override
+  @Nonnull
+  public Marker cloneIfNeeded() {
+    return this;
+  }
+}
diff --git a/jack/src/com/android/jack/transformations/ssa/SsaRenamer.java b/jack/src/com/android/jack/transformations/ssa/SsaRenamer.java
new file mode 100644
index 0000000..58b1380
--- /dev/null
+++ b/jack/src/com/android/jack/transformations/ssa/SsaRenamer.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.transformations.ssa;
+
+import com.android.jack.ir.ast.JMethod;
+import com.android.jack.ir.ast.JMethodBodyCfg;
+import com.android.jack.ir.ast.JParameter;
+import com.android.jack.ir.ast.JSsaVariableDefRef;
+import com.android.jack.ir.ast.JSsaVariableDefRefPlaceHolder;
+import com.android.jack.ir.ast.JSsaVariableRef;
+import com.android.jack.ir.ast.JSsaVariableUseRef;
+import com.android.jack.ir.ast.JSsaVariableUseRefPlaceHolder;
+import com.android.jack.ir.ast.JThis;
+import com.android.jack.ir.ast.JThisRef;
+import com.android.jack.ir.ast.JVariable;
+import com.android.jack.ir.ast.JVariableRef;
+import com.android.jack.ir.ast.cfg.JBasicBlock;
+import com.android.jack.ir.ast.cfg.JBasicBlockElement;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.jack.ir.ast.cfg.JPhiBlockElement;
+import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
+import com.android.jack.transformations.request.Replace;
+import com.android.jack.transformations.request.TransformationRequest;
+import com.android.jack.util.graph.DominatorTreeMarker;
+import com.android.jack.util.graph.NodeIdMarker;
+import com.android.jack.util.graph.NodeListMarker;
+import com.android.sched.item.Description;
+import com.android.sched.item.Name;
+import com.android.sched.schedulable.Constraint;
+import com.android.sched.schedulable.Filter;
+import com.android.sched.schedulable.RunnableSchedulable;
+import com.android.sched.schedulable.Transform;
+
+import java.util.BitSet;
+import java.util.List;
+import java.util.Stack;
+
+/**
+ * Complete transformation to SSA form by renaming all registers accessed.
+ * <p>
+ *
+ * See Appel algorithm 19.7
+ * <p>
+ *
+ * Unlike the original algorithm presented in Appel, this renamer converts to a new flat
+ * (versionless) register space. The "version 0" registers, which represent the initial state of the
+ * Rop registers and should never actually be meaningfully accessed in a legal program, are
+ * represented as the first N registers in the SSA namespace. Subsequent assignments are assigned
+ * new unique names. Note that the incoming Rop representation has a concept of register widths,
+ * where 64-bit values are stored into two adjoining Rop registers. This adjoining register
+ * representation is ignored in SSA form conversion and while in SSA form, each register can be e
+ * either 32 or 64 bits wide depending on use. The adjoining-register represention is re-created
+ * later when converting back to Rop form.
+ * <p>
+ *
+ * But, please note, the SSA Renamer's ignoring of the adjoining-register ROP representation means
+ * that unaligned accesses to 64-bit registers are not supported. For example, you cannot do a
+ * 32-bit operation on a portion of a 64-bit register. This will never be observed to happen when
+ * coming from Java code, of course.
+ * <p>
+ *
+ * The implementation here, rather than keeping a single register version stack for the entire
+ * method as the dom tree is walked, instead keeps a mapping table for the current block being
+ * processed. Once the current block has been processed, this mapping table is then copied and used
+ * as the initial state for child blocks.
+ * <p>
+ */
+@Description("Rename variables in the CFG for SSA properties.")
+@Name("SsaRenamer")
+@Constraint(need = {JPhiBlockElement.class})
+@Transform(add = JSsaVariableRef.class)
+@Filter(TypeWithoutPrebuiltFilter.class)
+public class SsaRenamer implements RunnableSchedulable<JMethodBodyCfg> {
+
+  private static class GraphRenamer {
+    private final JControlFlowGraph cfg;
+    private final JMethod method;
+
+    /** the number of original rop registers */
+    private final int ropRegCount;
+    /** next available SSA register */
+    private final int[] nextSsaReg;
+
+    private final List<JBasicBlock> bbMap;
+
+    /**
+     * indexed by block index; register version state for each block start. This list is updated by
+     * each dom parent for its children. The only sub-arrays that exist at any one time are the
+     * start states for blocks yet to be processed by a {@code BlockRenamer} instance.
+     */
+    private final JSsaVariableDefRef[][] startsForBlocks;
+
+    private GraphRenamer(JControlFlowGraph cfg) {
+      this.cfg = cfg;
+      this.method = cfg.getMethod();
+      bbMap = NodeListMarker.getNodeList(cfg);
+      ropRegCount = SsaUtil.getTotalNumberOfLocals(cfg);
+
+      /*
+       * Reserve the first N registers in the SSA register space for "version 0" registers.
+       */
+      nextSsaReg = new int[ropRegCount];
+      startsForBlocks = new JSsaVariableDefRef[bbMap.size()][];
+
+      /*
+       * Appel 19.7
+       *
+       * Initialization: for each variable a // register i Count[a] <- 0 // nextSsaReg, flattened
+       * Stack[a] <- 0 // versionStack push 0 onto Stack[a]
+       *
+       */
+
+      // top entry for the version stack is version 0
+      JSsaVariableDefRef[] initialRegMapping = new JSsaVariableDefRef[ropRegCount];
+      for (int i = 0; i < ropRegCount; i++) {
+        JVariable target = SsaUtil.getVariableByIndex(cfg, i);
+        initialRegMapping[i] = new JSsaVariableDefRef(method.getSourceInfo(), target, 0);
+        if (target instanceof JParameter) {
+          cfg.getMethodBody().addSsaParamDef(initialRegMapping[i]);
+        }
+      }
+      // Initial state for entry block
+      int entryId = NodeIdMarker.getId(cfg.getEntryBlock());
+      startsForBlocks[entryId] = initialRegMapping;
+    }
+
+    private void performRename() {
+      // Rename each block in dom-tree DFS order.
+      forEachBlockDepthFirstDom(cfg, new Visitor() {
+        @Override
+        public void visitBlock(JBasicBlock block, JBasicBlock unused) {
+          new BlockRenamer(block).process();
+        }
+      });
+    }
+
+    public void forEachBlockDepthFirstDom(JControlFlowGraph cfg, Visitor v) {
+      BitSet visited = new BitSet(bbMap.size() - 1);
+      Stack<JBasicBlock> stack = new Stack<JBasicBlock>();
+
+      stack.add(cfg.getEntryNode());
+
+      while (stack.size() > 0) {
+        JBasicBlock cur = stack.pop();
+        List<JBasicBlock> curDomChildren = DominatorTreeMarker.getDomChild(cur);
+
+        if (!visited.get(NodeIdMarker.getId(cur))) {
+          // We walk the tree this way for historical reasons...
+          for (int i = curDomChildren.size() - 1; i >= 0; i--) {
+            JBasicBlock child = curDomChildren.get(i);
+            stack.add(child);
+          }
+          visited.set(NodeIdMarker.getId(cur));
+          v.visitBlock(cur, null);
+        }
+      }
+    }
+
+    /**
+     * Processes all insns in a block and renames their registers as appropriate.
+     */
+    private class BlockRenamer {
+      /** {@code non-null;} block we're processing. */
+      private final JBasicBlock block;
+
+      /**
+       * {@code non-null;} indexed by old register name. The current top of the version stack as
+       * seen by this block. It's initialized from the ending state of its dom parent, updated as
+       * the block's instructions are processed, and then copied to each one of its dom children.
+       */
+      private final JSsaVariableDefRef[] currentMapping;
+
+      /**
+       * Constructs a block renamer instance. Call {@code process} to process.
+       *
+       * @param block {@code non-null;} block to process
+       */
+      BlockRenamer(final JBasicBlock block) {
+        this.block = block;
+        currentMapping = startsForBlocks[NodeIdMarker.getId(block)];
+        // We don't need our own start state anymore
+        startsForBlocks[NodeIdMarker.getId(block)] = null;
+      }
+
+      /**
+       * Renames all the variables in this block and inserts appriopriate phis in successor blocks.
+       */
+      public void process() {
+        /*
+         * From Appel:
+         *
+         * Rename(n) = for each statement S in block n // 'statement' in 'block'
+         */
+        for (JBasicBlockElement stmt : block.getElements(true)) {
+          if (stmt instanceof JPhiBlockElement) {
+            processPhiStmt((JPhiBlockElement) stmt);
+          } else {
+            processSourceReg(stmt);
+            processResultReg(stmt);
+          }
+        }
+
+        updateSuccessorPhis();
+
+        // Store the start states for our dom children.
+        boolean first = true;
+        List<JBasicBlock> domChildren = DominatorTreeMarker.getDomChild(block);
+        for (JBasicBlock child : domChildren) {
+          if (child != block) {
+            // Don't bother duplicating the array for the first child.
+            JSsaVariableDefRef[] childStart = first ? currentMapping : dupArray(currentMapping);
+            startsForBlocks[NodeIdMarker.getId(child)] = childStart;
+            first = false;
+          }
+        }
+
+        // currentMapping is owned by a child now.
+      }
+
+      /**
+       * Enforces a few contraints when a register mapping is added.
+       *
+       * <ol>
+       * <li>Ensures that all new SSA registers specs in the mapping table with the same register
+       * number are identical. In effect, once an SSA register spec has received or lost a local
+       * variable name, then every old-namespace register that maps to it should gain or lose its
+       * local variable name as well.
+       * <li>Records the local name associated with the register so that a register is never
+       * associated with more than one local.
+       * <li>ensures that only one SSA register at a time is considered to be associated with a
+       * local variable. When {@code currentMapping} is updated and the newly added element is
+       * named, strip that name from any other SSA registers.
+       * </ol>
+       *
+       * @param ropReg {@code >= 0;} rop register number
+       * @param ssaReg {@code non-null;} an SSA register that has just been added to
+       *        {@code currentMapping}
+       */
+      private void addMapping(int ropReg, JSsaVariableDefRef ssaReg) {
+        currentMapping[ropReg] = ssaReg;
+      }
+
+      /**
+       *
+       * Phi insns have their result registers renamed.
+       */
+      public void processPhiStmt(JPhiBlockElement phi) {
+        JVariable target = phi.getTarget();
+        if (target instanceof JThis) {
+          return; // I don't think we ever run into this.
+        }
+        int index = SsaUtil.getLocalIndex(cfg, target);
+
+        /* don't process sources for phi's except to replace place holders. */
+        for (JSsaVariableUseRef use : phi.getRhs()) {
+          if (isVersionZeroRegister(use)) {
+            TransformationRequest tr2 = new TransformationRequest(phi);
+            JSsaVariableUseRef ref = currentMapping[index].makeRef(use.getSourceInfo());
+            ref.addAllMarkers(use.getAllMarkers());
+            tr2.append(new Replace(use, ref));
+            tr2.commit();
+          }
+        }
+
+        nextSsaReg[index]++;
+        // It is probably ok to not have any debug marker here.
+        JSsaVariableDefRef lhs =
+            new JSsaVariableDefRef(phi.getSourceInfo(), target, nextSsaReg[index]);
+        TransformationRequest tr = new TransformationRequest(phi);
+        tr.append(new Replace(phi.getLhs(), lhs));
+        addMapping(index, lhs);
+        tr.commit();
+      }
+
+      /**
+       * Renames the result register of this insn and updates the current register mapping. Does
+       * nothing if this insn has no result. Applied to all non-move insns.
+       *
+       * @param insn insn to process.
+       */
+      void processResultReg(JBasicBlockElement insn) {
+        JVariableRef dv = insn.getDefinedVariable();
+        if (dv == null) {
+          return;
+        }
+
+        JVariable ropReg = dv.getTarget();
+        int index = SsaUtil.getLocalIndex(cfg, ropReg);
+
+        nextSsaReg[index]++;
+        JSsaVariableDefRef ref =
+            new JSsaVariableDefRef(dv.getSourceInfo(), ropReg, nextSsaReg[index]);
+        ref.addAllMarkers(dv.getAllMarkers());
+        TransformationRequest tr = new TransformationRequest(method);
+        tr.append(new Replace(dv, ref));
+        tr.commit();
+        addMapping(index, ref);
+      }
+
+      void processSourceReg(JBasicBlockElement insn) {
+        List<JVariableRef> uv = insn.getUsedVariables();
+        if (uv.isEmpty()) {
+          return;
+        }
+
+        TransformationRequest tr = new TransformationRequest(method);
+        for (JVariableRef varRef : uv) {
+          if (varRef instanceof JThisRef) {
+            continue;
+          }
+          int index = SsaUtil.getLocalIndex(cfg, varRef.getTarget());
+          assert index != -1;
+          JSsaVariableUseRef ref = currentMapping[index].makeRef(varRef.getSourceInfo());
+          ref.addAllMarkers(varRef.getAllMarkers());
+          tr.append(new Replace(varRef, ref));
+        }
+        tr.commit();
+      }
+
+      /**
+       * Updates the phi insns in successor blocks with operands based on the current mapping of the
+       * rop register the phis represent.
+       */
+      private void updateSuccessorPhis() {
+        for (JBasicBlock successor : block.getSuccessors()) {
+          for (JBasicBlockElement stmt : successor.getElements(true)) {
+            if (stmt instanceof JPhiBlockElement) {
+              JPhiBlockElement phi = (JPhiBlockElement) stmt;
+              int ropReg = -1;
+              JVariable target = phi.getTarget();
+              if (target instanceof JThis) {
+                continue;
+              }
+              ropReg = SsaUtil.getLocalIndex(cfg, target);
+              /*
+               * Never add a version 0 register as a phi operand. Version 0 registers represent the
+               * initial register state, and thus are never significant. Furthermore, the register
+               * liveness algorithm doesn't properly count them as "live in" at the beginning of the
+               * method.
+               */
+              JSsaVariableDefRef stackTop = currentMapping[ropReg];
+              if (!isVersionZeroRegister(stackTop)) {
+                TransformationRequest tr = new TransformationRequest(phi);
+                JSsaVariableUseRef rhs = stackTop.makeRef(phi.getSourceInfo());
+                rhs.addAllMarkers(phi.getRhs(block).getAllMarkers());
+                JSsaVariableUseRef oldRhs = phi.getRhs(block);
+                tr.append(new Replace(oldRhs, rhs));
+                if (!(oldRhs instanceof JSsaVariableUseRefPlaceHolder)) {
+                  oldRhs.deleteUseFromDef();
+                }
+                tr.commit();
+              }
+            }
+          }
+        }
+      }
+    }
+
+    /**
+     * Returns true if this SSA register is a "version 0" register. All version 0 registers are
+     * assigned the first N register numbers, where N is the count of original rop registers.
+     *
+     * @param ssaReg the SSA register in question
+     * @return true if it is a version 0 register.
+     */
+    private static boolean isVersionZeroRegister(JSsaVariableDefRef ssaReg) {
+      return ssaReg.getVersion() == 0 && ssaReg instanceof JSsaVariableDefRefPlaceHolder;
+    }
+
+    private static boolean isVersionZeroRegister(JSsaVariableUseRef ssaReg) {
+      return ssaReg.getVersion() == 0 && ssaReg instanceof JSsaVariableUseRefPlaceHolder;
+    }
+  }
+
+  @Override
+  public void run(JMethodBodyCfg body) {
+    new GraphRenamer(body.getCfg()).performRename();
+  }
+
+  private static JSsaVariableDefRef[] dupArray(JSsaVariableDefRef[] orig) {
+    JSsaVariableDefRef[] copy = new JSsaVariableDefRef[orig.length];
+    System.arraycopy(orig, 0, copy, 0, orig.length);
+    return copy;
+  }
+
+  /**
+   * Visitor interface for basic blocks.
+   */
+  public static interface Visitor {
+    /**
+     * Indicates a block has been visited by an iterator method.
+     *
+     * @param v {@code non-null;} block visited
+     * @param parent {@code null-ok;} parent node if applicable
+     */
+    void visitBlock(JBasicBlock v, JBasicBlock parent);
+  }
+}
diff --git a/jack/src/com/android/jack/transformations/ssa/SsaUtil.java b/jack/src/com/android/jack/transformations/ssa/SsaUtil.java
new file mode 100644
index 0000000..707f700
--- /dev/null
+++ b/jack/src/com/android/jack/transformations/ssa/SsaUtil.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.transformations.ssa;
+
+import com.android.jack.ir.ast.JLocal;
+import com.android.jack.ir.ast.JMethodBodyCfg;
+import com.android.jack.ir.ast.JParameter;
+import com.android.jack.ir.ast.JVariable;
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Not exactly a utilities but more of a set of things that are missing in the current API and we
+ * have to brute force it here.
+ */
+public class SsaUtil {
+
+  public static int getLocalIndex(@Nonnull JControlFlowGraph cfg, JVariable var) {
+    JMethodBodyCfg body = cfg.getMethodBody();
+    int numParam = body.getMethod().getParams().size();
+    if (var instanceof JParameter) {
+      int paramIdx = body.getMethod().getParams().indexOf(var);
+      assert paramIdx != -1;
+      return paramIdx;
+    }
+
+    int numLocal = body.getLocals().size();
+    if (var instanceof JLocal) {
+      // We either have a defined local or catch param.
+      List<JLocal> catchParams = body.getCatchLocals();
+      int index = catchParams.indexOf(var);
+      if (index != -1) {
+        return numLocal + numParam + index;
+      } else {
+        int localIdx = body.getLocals().indexOf(var);
+        assert localIdx != -1;
+        return numParam + localIdx;
+      }
+    }
+
+    return -1;
+  }
+
+  /**
+   * TODO(acleung): Investigate if it is worth caching this.
+   */
+  public static JVariable getVariableByIndex(@Nonnull JControlFlowGraph cfg, int index) {
+    JMethodBodyCfg body = cfg.getMethodBody();
+    assert body != null;
+    int numLocal = body.getLocals().size();
+    int numParam = body.getMethod().getParams().size();
+
+    if (index < numParam) {
+      return body.getMethod().getParams().get(index);
+    }
+
+    if (index < numParam + numLocal) {
+      return body.getLocals().get(index - numParam);
+    }
+
+    List<JLocal> catchParams = body.getCatchLocals();
+    return catchParams.get(index - numParam - numLocal);
+  }
+
+  public static int getTotalNumberOfLocals(@Nonnull JControlFlowGraph cfg) {
+    JMethodBodyCfg body = cfg.getMethodBody();
+    assert body != null;
+    int numParam = body.getMethod().getParams().size();
+    assert body.getLocals() != null;
+    int numLocal = body.getLocals().size();
+    int numCatch = body.getNumCatchLocals();
+    return numLocal + numParam + numCatch;
+  }
+}
diff --git a/jack/src/com/android/jack/transformations/uselessif/UselessIfRemover.java b/jack/src/com/android/jack/transformations/uselessif/UselessIfRemover.java
deleted file mode 100644
index 7a4baed..0000000
--- a/jack/src/com/android/jack/transformations/uselessif/UselessIfRemover.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.jack.transformations.uselessif;
-
-import com.android.jack.Options;
-import com.android.jack.ir.ast.JBooleanLiteral;
-import com.android.jack.ir.ast.JIfStatement;
-import com.android.jack.ir.ast.JMethod;
-import com.android.jack.ir.ast.JStatement;
-import com.android.jack.ir.ast.JVisitor;
-import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
-import com.android.jack.transformations.request.Remove;
-import com.android.jack.transformations.request.Replace;
-import com.android.jack.transformations.request.TransformationRequest;
-import com.android.sched.item.Description;
-import com.android.sched.schedulable.Constraint;
-import com.android.sched.schedulable.Filter;
-import com.android.sched.schedulable.RunnableSchedulable;
-import com.android.sched.util.config.ThreadConfig;
-import com.android.sched.util.log.Tracer;
-import com.android.sched.util.log.TracerFactory;
-import com.android.sched.util.log.stats.Counter;
-import com.android.sched.util.log.stats.CounterImpl;
-import com.android.sched.util.log.stats.StatisticId;
-
-import javax.annotation.Nonnull;
-
-/**
- * This visitor removes the if statement when the condition is a boolean literal
- */
-@Description("Removes useless if statement")
-@Constraint(need = {JIfStatement.class})
-@Filter(TypeWithoutPrebuiltFilter.class)
-public class UselessIfRemover implements RunnableSchedulable<JMethod> {
-
-  @Nonnull
-  public static final StatisticId<Counter> REMOVED_IF = new StatisticId<Counter>(
-      "jack.statement.if.removed", "Removed 'if' statement",
-      CounterImpl.class, Counter.class);
-
-  @Nonnull
-  private final com.android.jack.util.filter.Filter<JMethod> filter =
-      ThreadConfig.get(Options.METHOD_FILTER);
-
-  @Nonnull
-  private final Tracer tracer = TracerFactory.getTracer();
-
-  private class UselessIfRemoverVisitor extends JVisitor {
-
-    @Nonnull
-    private final TransformationRequest request;
-
-    private UselessIfRemoverVisitor(@Nonnull TransformationRequest request) {
-      this.request = request;
-    }
-
-    @Override
-    public boolean visit(@Nonnull JIfStatement ifStmt) {
-      if (ifStmt.getIfExpr() instanceof JBooleanLiteral) {
-        JBooleanLiteral cond = (JBooleanLiteral) ifStmt.getIfExpr();
-        tracer.getStatistic(REMOVED_IF).incValue();
-        if (cond.getValue()) {
-          JStatement thenStmt = ifStmt.getThenStmt();
-          // if (true) A else B => A
-          request.append(new Replace(ifStmt, thenStmt));
-        } else {
-          JStatement elseStmt = ifStmt.getElseStmt();
-          if (elseStmt != null) {
-            // if (false) A else B => B
-            request.append(new Replace(ifStmt, elseStmt));
-          } else {
-            // if (false) then B => []
-            request.append(new Remove(ifStmt));
-          }
-        }
-      }
-
-      return super.visit(ifStmt);
-    }
-  }
-
-  @Override
-  public void run(@Nonnull JMethod method) {
-    if (method.isNative() || method.isAbstract() || !filter.accept(this.getClass(), method)) {
-      return;
-    }
-
-    TransformationRequest request = new TransformationRequest(method);
-    UselessIfRemoverVisitor visitor = new UselessIfRemoverVisitor(request);
-    visitor.accept(method);
-    request.commit();
-  }
-
-}
\ No newline at end of file
diff --git a/jack/src/com/android/jack/util/graph/DepthFirstTraversal.java b/jack/src/com/android/jack/util/graph/DepthFirstTraversal.java
new file mode 100644
index 0000000..8dca8d8
--- /dev/null
+++ b/jack/src/com/android/jack/util/graph/DepthFirstTraversal.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.util.graph;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.BitSet;
+import java.util.Stack;
+
+import javax.annotation.Nonnull;
+
+/**
+ * A basic depth first traversal of a directed graph.
+ */
+public class DepthFirstTraversal<N extends IGraphNode<N>> {
+  @Nonnull
+  private final IGraph<N> graph;
+
+  @Nonnull
+  private final GraphNodeVisitor<N> visitor;
+
+  private final boolean reverse;
+
+  @Nonnull
+  private final ImmutableList<N> nodes;
+
+  /**
+   * Performance a Depth First Traversal on a graph using a provided visitor starting from the root
+   * node.
+   *
+   * @param graph Give Graph.
+   * @param reverse If true, reverse the direction of all the edges and start from the end node.
+   * @param visitor
+   */
+  public static <N extends IGraphNode<N>> void run(@Nonnull IGraph<N> graph, boolean reverse,
+      @Nonnull GraphNodeVisitor<N> visitor) {
+    DepthFirstTraversal<N> dft = new DepthFirstTraversal<N>(graph, reverse, visitor);
+    dft.run();
+  }
+
+  private DepthFirstTraversal(@Nonnull IGraph<N> graph, boolean reverse,
+      @Nonnull GraphNodeVisitor<N> visitor) {
+    this.graph = graph;
+    this.reverse = reverse;
+    this.visitor = visitor;
+    this.nodes = NodeListMarker.getNodeList(graph);
+  }
+
+  private void run() {
+    BitSet visited = new BitSet(nodes.size());
+
+    // We push the parent first, then the child on the stack.
+    Stack<N> stack = new Stack<N>();
+
+    N rootBlock = reverse ? graph.getExitNode() : graph.getEntryNode();
+
+    stack.add(null); // Start with null parent.
+    stack.add(rootBlock);
+
+    while (stack.size() > 0) {
+      N cur = stack.pop();
+      N parent = stack.pop();
+
+      if (!visited.get(NodeIdMarker.getId(cur))) {
+        for (N child : reverse ? cur.getPredecessorsIterable() : cur.getSuccessorsIterable()) {
+          stack.add(cur);
+          stack.add(child);
+        }
+        visited.set(NodeIdMarker.getId(cur));
+        visitor.visitNode(cur, parent);
+      }
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/util/graph/DominanceFrontier.java b/jack/src/com/android/jack/util/graph/DominanceFrontier.java
new file mode 100644
index 0000000..433f6ba
--- /dev/null
+++ b/jack/src/com/android/jack/util/graph/DominanceFrontier.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.util.graph;
+
+import com.google.common.collect.ImmutableList;
+
+import com.android.sched.schedulable.Constraint;
+import com.android.sched.schedulable.Transform;
+import com.android.sched.schedulable.Use;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Calculates the dominance-frontiers of a method's basic blocks. Algorithm from "A Simple, Fast
+ * Dominance Algorithm" by Cooper, Harvey, and Kennedy; transliterated to Java.
+ */
+@Transform(add = {DominanceFrontierInfoMarker.class, DominatorTreeMarker.class})
+@Constraint(need = {NodeListMarker.class})
+@Use(Dominators.class)
+public class DominanceFrontier<N extends IGraphNode<N>> {
+
+  @Nonnull
+  private final IGraph<N> graph;
+
+  @Nonnull
+  private final ImmutableList<N> nodes;
+
+  /**
+   * Constructs instance. Call {@link DominanceFrontier#run} to process.
+   *
+   * @param graph {@code non-null;} Control flow graph to process
+   */
+  public DominanceFrontier(@Nonnull IGraph<N> graph) {
+    this.graph = graph;
+    this.nodes = NodeListMarker.getNodeList(graph);
+    int szNodes = nodes.size();
+    for (N bb : nodes) {
+      DominanceFrontierInfoMarker.setDomInfo(bb, szNodes);
+    }
+  }
+
+  /**
+   * Calculates the dominance frontier information for the method.
+   */
+  @Nonnull
+  public void run() {
+    Dominators.make(graph, false);
+    buildDomTree();
+    calcDomFronts();
+  }
+
+  /**
+   * The dominators algorithm leaves us knowing who the immediate dominator is for each node. This
+   * sweeps the node list and builds the proper dominance tree.
+   */
+  private void buildDomTree() {
+    for (N node : nodes) {
+      N idom = DominanceFrontierInfoMarker.getIDom(node);
+      if (idom == null) {
+        continue;
+      }
+      DominatorTreeMarker.addDomChild(idom, node);
+    }
+  }
+
+  /**
+   * Calculates the dominance-frontier set. from "A Simple, Fast Dominance Algorithm" by Cooper,
+   * Harvey, and Kennedy; transliterated to Java.
+   */
+  private void calcDomFronts() {
+    for (N nb : nodes) {
+      //final DomInfo nbInfo = domInfos[getIdByNode(nb)];
+      final N nbIdom = DominanceFrontierInfoMarker.getIDom(nb);
+      int numPreds = 0;
+      for (@SuppressWarnings("unused") N pred : nb.getPredecessorsIterable()) {
+        numPreds++;
+      }
+
+      if (numPreds > 1) {
+        for (N pred : nb.getPredecessorsIterable()) {
+          for (N runner = pred; runner != nbIdom; /* empty */) {
+            /*
+             * We can stop if we hit a block we already added label to, since we must be at a part
+             * of the dom tree we have seen before.
+             */
+            if (runner == null) {
+              break;
+            }
+
+            if (DominanceFrontierInfoMarker.isInDominanceFrontier(runner, nb)) {
+              break;
+            }
+
+            // Add b to runner's dominance frontier set.
+            DominanceFrontierInfoMarker.addDominanceFrontier(runner, nb);
+            runner = DominanceFrontierInfoMarker.getIDom(runner);
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/util/graph/DominanceFrontierInfoMarker.java b/jack/src/com/android/jack/util/graph/DominanceFrontierInfoMarker.java
new file mode 100644
index 0000000..5ead823
--- /dev/null
+++ b/jack/src/com/android/jack/util/graph/DominanceFrontierInfoMarker.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.util.graph;
+
+import com.android.jack.dx.util.BitIntSet;
+import com.android.jack.dx.util.IntSet;
+import com.android.jack.dx.util.ListIntSet;
+import com.android.jack.ir.ast.cfg.JBasicBlock;
+import com.android.sched.item.Description;
+import com.android.sched.marker.Marker;
+import com.android.sched.marker.ValidOn;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/**
+ * A marker to store information computed by {@Link DominanceFrontier}.
+ */
+@Description("Dominance Frontier information for a node in a CFG.")
+@ValidOn(JBasicBlock.class)
+public class DominanceFrontierInfoMarker implements Marker {
+  /**
+   * BitIntSet/ListIntSet threshold for dominance frontier sets. These sets are kept per basic block
+   * until phi placement and tend to be, like the CFG itself, very sparse at large sizes.
+   *
+   * A value of 3072 here is somewhere around 1.125mb of total bitset size.
+   */
+  private static final int DOMFRONT_SET_THRESHOLD_SIZE = 3072;
+
+  @Nonnull
+  public final IntSet dominanceFrontiers;
+
+  /** {@code >= 0 after run();} the index of the immediate dominator */
+  @CheckForNull
+  public IGraphNode<?> idom = null;
+
+  private DominanceFrontierInfoMarker(@Nonnull int szBlocks) {
+    dominanceFrontiers =
+        szBlocks <= DOMFRONT_SET_THRESHOLD_SIZE ? new BitIntSet(szBlocks) : new ListIntSet();
+  }
+
+  @Nonnull
+  public static DominanceFrontierInfoMarker getDomInfo(JBasicBlock bb) {
+    DominanceFrontierInfoMarker marker = bb.getMarker(DominanceFrontierInfoMarker.class);
+    assert marker != null;
+    return marker;
+  }
+
+  public static <N extends IGraphNode<N>> void setDomInfo(N node, int size) {
+    DominanceFrontierInfoMarker marker = new DominanceFrontierInfoMarker(size);
+    node.addMarker(marker);
+  }
+
+  public static <N extends IGraphNode<N>> void setIDom(N parent, N idom) {
+    DominanceFrontierInfoMarker marker = parent.getMarker(DominanceFrontierInfoMarker.class);
+    assert marker != null;
+    marker.idom = idom;
+  }
+
+  @SuppressWarnings("unchecked")
+  public static <N extends IGraphNode<N>> N getIDom(N parent) {
+    DominanceFrontierInfoMarker marker = parent.getMarker(DominanceFrontierInfoMarker.class);
+    assert marker != null;
+    return (N) marker.idom;
+  }
+
+  public static <N extends IGraphNode<N>> boolean isInDominanceFrontier(N parent, N target) {
+    DominanceFrontierInfoMarker df = parent.getMarker(DominanceFrontierInfoMarker.class);
+    assert df != null;
+    int targetIdx = NodeIdMarker.getId(target);
+    assert df.dominanceFrontiers != null;
+    return df.dominanceFrontiers.has(targetIdx);
+  }
+
+  public static <N extends IGraphNode<N>> void addDominanceFrontier(N parent, N target) {
+    DominanceFrontierInfoMarker df = parent.getMarker(DominanceFrontierInfoMarker.class);
+    assert df != null;
+    int targetIdx = NodeIdMarker.getId(target);
+    assert df.dominanceFrontiers != null;
+    df.dominanceFrontiers.add(targetIdx);
+  }
+
+  @Override
+  public Marker cloneIfNeeded() {
+    throw new AssertionError("It is not valid to use cloneIfNeeded, create a new marker.");
+  }
+
+  /**
+   * Removes all dominance frontier info marker from a graph.
+   */
+  public static void clearMarkers(@Nonnull IGraph<?> graph) {
+    for (IGraphNode<?> n : graph.getNodes()) {
+      n.removeMarker(DominanceFrontierInfoMarker.class);
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/util/graph/DominatorDepthFirstTraversal.java b/jack/src/com/android/jack/util/graph/DominatorDepthFirstTraversal.java
new file mode 100644
index 0000000..848126d
--- /dev/null
+++ b/jack/src/com/android/jack/util/graph/DominatorDepthFirstTraversal.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.util.graph;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.BitSet;
+import java.util.List;
+import java.util.Stack;
+
+import javax.annotation.Nonnull;
+
+/**
+ * A traversal that visits a directed graph by dominator order in a depth first manner.
+ */
+public class DominatorDepthFirstTraversal<N extends IGraphNode<N>> {
+
+  @Nonnull
+  private final IGraph<N> graph;
+
+  @Nonnull
+  private final GraphNodeVisitor<N> visitor;
+
+  @Nonnull
+  private final ImmutableList<N> nodes;
+
+  /**
+   * Visits each reachable node of the graph in a dominator order and invoke the visitor's visit
+   * method on it. Each visit call will have null as parent.
+   */
+  public static <N extends IGraphNode<N>> void run(@Nonnull IGraph<N> graph,
+      @Nonnull GraphNodeVisitor<N> visitor) {
+    DominatorDepthFirstTraversal<N> ddft = new DominatorDepthFirstTraversal<N>(graph, visitor);
+    ddft.run();
+  }
+
+  private DominatorDepthFirstTraversal(@Nonnull IGraph<N> graph,
+      @Nonnull GraphNodeVisitor<N> visitor) {
+    this.graph = graph;
+    this.visitor = visitor;
+    this.nodes = NodeListMarker.getNodeList(graph);
+  }
+
+  private void run() {
+    BitSet visited = new BitSet(nodes.size());
+    Stack<N> stack = new Stack<N>();
+
+    stack.add(graph.getEntryNode());
+
+    while (stack.size() > 0) {
+      N cur = stack.pop();
+
+      List<N> curDomChildren = DominatorTreeMarker.getDomChild(cur);
+
+      int curId = NodeIdMarker.getId(cur);
+      if (!visited.get(curId)) {
+        // We walk the tree this way for historical reasons...
+        for (int i = curDomChildren.size() - 1; i >= 0; i--) {
+          N child = curDomChildren.get(i);
+          stack.add(child);
+        }
+        visited.set(curId);
+        visitor.visitNode(cur, /* parent = */ null);
+      }
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/util/graph/DominatorTreeMarker.java b/jack/src/com/android/jack/util/graph/DominatorTreeMarker.java
new file mode 100644
index 0000000..5416039
--- /dev/null
+++ b/jack/src/com/android/jack/util/graph/DominatorTreeMarker.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.util.graph;
+
+import com.android.sched.item.Description;
+import com.android.sched.marker.Marker;
+import com.android.sched.marker.ValidOn;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+/**
+ * A marker that represent a dominator tree in a graph.
+ */
+@Description("Marks the dominator parent / child relation of a CFG.")
+@ValidOn(IGraphNode.class)
+public class DominatorTreeMarker<N extends IGraphNode<N>> implements Marker {
+  private final List<N> domChildren = new ArrayList<>();
+
+  public static <N extends IGraphNode<N>> void addDomChild(N node, N domChild) {
+    @SuppressWarnings({"cast", "unchecked"})
+    DominatorTreeMarker<N> marker =
+        (DominatorTreeMarker<N>) node.getMarker(DominatorTreeMarker.class);
+    if (marker == null) {
+      marker = new DominatorTreeMarker<N>();
+      node.addMarker(marker);
+    }
+    marker.domChildren.add(domChild);
+  }
+
+  @SuppressWarnings({"rawtypes", "unchecked"})
+  public static <N extends IGraphNode<N>> List<N> getDomChild(IGraphNode node) {
+    DominatorTreeMarker marker = node.getMarker(DominatorTreeMarker.class);
+    if (marker == null) {
+      return Collections.EMPTY_LIST;
+    } else {
+      return marker.domChildren;
+    }
+  }
+
+  @Override
+  public Marker cloneIfNeeded() {
+    throw new AssertionError("It is not valid to use cloneIfNeeded, create a new marker.");
+  }
+
+  /**
+   * Removes all dominance tree marker from a graph.
+   */
+  public static <N extends IGraphNode<N>> void clearMarkers(@Nonnull IGraph<N> graph) {
+    for (IGraphNode<?> n : graph.getNodes()) {
+      n.removeMarker(DominatorTreeMarker.class);
+    }
+  }
+}
diff --git a/jack/src/com/android/jack/util/graph/Dominators.java b/jack/src/com/android/jack/util/graph/Dominators.java
new file mode 100644
index 0000000..00bb88e
--- /dev/null
+++ b/jack/src/com/android/jack/util/graph/Dominators.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.util.graph;
+
+import com.google.common.collect.ImmutableList;
+
+import com.android.sched.schedulable.Constraint;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+
+/**
+ * This class computes dominator and post-dominator information using the Lengauer-Tarjan method.
+ *
+ * See A Fast Algorithm for Finding Dominators in a Flowgraph T. Lengauer & R. Tarjan, ACM TOPLAS
+ * July 1979, pgs 121-141.
+ *
+ * This implementation runs in time O(n log n). The time bound could be changed to O(n * ack(n))
+ * with a small change to the link and eval, and an addition of a child field to the DFS info. In
+ * reality, the constant overheads are high enough that the current method is faster in all but the
+ * strangest artificially constructed examples.
+ *
+ * The basic idea behind this algorithm is to perform a DFS walk, keeping track of various info
+ * about parents. We then use this info to calculate the dominators, using union-find structures to
+ * link together the DFS info, then finally evaluate the union-find results to get the dominators.
+ * This implementation is m log n because it does not perform union by rank to keep the union-find
+ * tree balanced.
+ */
+@Constraint(need = {NodeIdMarker.class, NodeListMarker.class})
+public final class Dominators<N extends IGraphNode<N>> {
+  /* postdom is true if we want post dominators */
+  private final boolean postdom;
+
+  private final IGraph<N> graph;
+
+  /* Method's basic blocks. */
+  @Nonnull
+  private final ImmutableList<N> nodes;
+
+  /** indexed by basic block index */
+  @Nonnull
+  private final DFSInfo<N>[] info;
+
+  @Nonnull
+  private final List<N> vertex;
+
+  /**
+   * Constructs an instance.
+   *
+   * @param graph {@code non-null;} Graph to process
+   * @param postdom true for postdom information, false for normal dom info
+   */
+  @SuppressWarnings("unchecked")
+  private Dominators(@Nonnull IGraph<N> graph, boolean postdom) {
+    this.graph = graph;
+    this.postdom = postdom;
+    this.nodes = NodeListMarker.getNodeList(graph);
+    this.info = new DFSInfo[nodes.size()]; // Plus implicit entry + exit.
+    this.vertex = new ArrayList<N>();
+  }
+
+  /**
+   * Constructs a fully-initialized instance. (This method exists so as to avoid calling a large
+   * amount of code in the constructor.)
+   *
+   * @param graph {@code non-null;} Control flow graph to process
+   * @param postdom true for postdom information, false for normal dom info
+   */
+  public static <N extends IGraphNode<N>> Dominators<N> make(@Nonnull IGraph<N> graph,
+      boolean postdom) {
+    Dominators<N> result = new Dominators<N>(graph, postdom);
+    result.run();
+    return result;
+  }
+
+  @Nonnull
+  private Iterable<N> getPreds(@Nonnull N block) {
+    if (postdom) {
+      return block.getSuccessorsIterable();
+    } else {
+      return block.getPredecessorsIterable();
+    }
+  }
+
+  @Nonnegative
+  private int getIdByNode(@Nonnull N n) {
+    return NodeIdMarker.getId(n);
+  }
+
+  /**
+   * Performs path compress on the DFS info.
+   *
+   * @param in Basic block whose DFS info we are path compressing.
+   */
+  private void compress(@Nonnull N in) {
+    DFSInfo<N> bbInfo = info[getIdByNode(in)];
+
+    assert bbInfo.ancestor != null;
+    DFSInfo<N> ancestorbbInfo = info[getIdByNode(bbInfo.ancestor)];
+
+    if (ancestorbbInfo.ancestor != null) {
+      ArrayList<N> worklist = new ArrayList<N>();
+      HashSet<N> visited = new HashSet<N>();
+      worklist.add(in);
+
+      while (!worklist.isEmpty()) {
+        int wsize = worklist.size();
+        N v = worklist.get(wsize - 1);
+        DFSInfo<N> vbbInfo = info[getIdByNode(v)];
+        N vAncestor = vbbInfo.ancestor;
+        assert vbbInfo.ancestor != null;
+        DFSInfo<N> vabbInfo = info[getIdByNode(vAncestor)];
+
+        // Make sure we process our ancestor before ourselves.
+        if (visited.add(vAncestor) && vabbInfo.ancestor != null) {
+          worklist.add(vAncestor);
+          continue;
+        }
+        worklist.remove(wsize - 1);
+
+        // Update based on ancestor info.
+        if (vabbInfo.ancestor == null) {
+          continue;
+        }
+        N vAncestorRep = vabbInfo.rep;
+        N vRep = vbbInfo.rep;
+        assert vRep != null;
+        if (info[getIdByNode(vAncestorRep)].semidom < info[getIdByNode(vRep)].semidom) {
+          vbbInfo.rep = vAncestorRep;
+        }
+        vbbInfo.ancestor = vabbInfo.ancestor;
+      }
+    }
+  }
+
+  @Nonnull
+  private N eval(@Nonnull N v) {
+    DFSInfo<N> bbInfo = info[getIdByNode(v)];
+
+    if (bbInfo.ancestor == null) {
+      return v;
+    }
+
+    compress(v);
+    assert bbInfo.rep != null;
+    return bbInfo.rep;
+  }
+
+  /**
+   * Performs dominator/post-dominator calculation for the control flow graph.
+   */
+  private void run() {
+    N root = postdom ? graph.getExitNode() : graph.getEntryNode();
+
+    vertex.add(root);
+    DominanceFrontierInfoMarker.setIDom(root, root);
+
+    /*
+     * First we perform a DFS numbering of the blocks, by numbering the dfs tree roots.
+     */
+    DepthFirstTraversal.<N>run(graph, postdom, new DfsWalker());
+
+    // the largest semidom number assigned
+    int dfsMax = vertex.size() - 1;
+
+    // Now calculate semidominators.
+    for (int i = dfsMax; i >= 2; --i) {
+      N w = vertex.get(i);
+      DFSInfo<N> wInfo = info[NodeIdMarker.getId(w)];
+
+      for (N predBlock : getPreds(w)) {
+        DFSInfo<N> predInfo = info[NodeIdMarker.getId(predBlock)];
+
+        /*
+         * PredInfo may not exist in case the predecessor is not reachable.
+         */
+        if (predInfo != null) {
+          int predSemidom = info[NodeIdMarker.getId(eval(predBlock))].semidom;
+          if (predSemidom < wInfo.semidom) {
+            wInfo.semidom = predSemidom;
+          }
+        }
+      }
+      info[NodeIdMarker.getId(vertex.get(wInfo.semidom))].bucket.add(w);
+
+      /*
+       * Normally we would call link here, but in our O(m log n) implementation this is equivalent
+       * to the following single line.
+       */
+      wInfo.ancestor = wInfo.parent;
+
+      // Implicity define idom for each vertex.
+      ArrayList<N> wParentBucket;
+      assert wInfo.parent != null;
+      wParentBucket = info[NodeIdMarker.getId(wInfo.parent)].bucket;
+
+      while (!wParentBucket.isEmpty()) {
+        int lastItem = wParentBucket.size() - 1;
+        N last = wParentBucket.remove(lastItem);
+        N u = eval(last);
+        DFSInfo<N> uDomInfo = info[NodeIdMarker.getId(u)];
+        DFSInfo<N> lastDomInfo = info[NodeIdMarker.getId(last)];
+        if (uDomInfo.semidom < lastDomInfo.semidom) {
+          DominanceFrontierInfoMarker.setIDom(last, u);
+        } else {
+          DominanceFrontierInfoMarker.setIDom(last, wInfo.parent);
+        }
+      }
+    }
+
+    // Now explicitly define the immediate dominator of each vertex
+    for (int i = 2; i <= dfsMax; ++i) {
+      N w = vertex.get(i);
+      if (DominanceFrontierInfoMarker.getIDom(w) != vertex
+          .get(info[NodeIdMarker.getId(w)].semidom)) {
+        DominanceFrontierInfoMarker.setIDom(w,
+            DominanceFrontierInfoMarker.getIDom(DominanceFrontierInfoMarker.getIDom(w)));
+      }
+    }
+  }
+
+  /**
+   * Callback for depth-first walk through control flow graph (either from the entry block or the
+   * exit block). Records the traversal order in the {@code info}list.
+   */
+  private class DfsWalker extends GraphNodeVisitor<N> {
+    @Nonnegative
+    private int dfsNum = 0;
+
+    @Override
+    public void visitNode(@Nonnull N v, @CheckForNull N parent) {
+      if (NodeIdMarker.getId(v) == Integer.MAX_VALUE) {
+        return;
+      }
+      DFSInfo<N> bbInfo = new DFSInfo<N>();
+      bbInfo.semidom = ++dfsNum;
+      bbInfo.rep = v;
+      bbInfo.parent = parent;
+      vertex.add(v);
+      info[NodeIdMarker.getId(v)] = bbInfo;
+    }
+  }
+
+  private static final class DFSInfo<N extends IGraphNode<N>> {
+    @Nonnegative
+    public int semidom;
+    @CheckForNull
+    public N parent;
+
+    /**
+     * rep(resentative) is known as "label" in the paper. It is the node that our block's DFS info
+     * has been unioned to.
+     */
+    @CheckForNull
+    public N rep;
+
+    @CheckForNull
+    public N ancestor;
+
+    @Nonnull
+    public final ArrayList<N> bucket = new ArrayList<N>();
+  }
+}
diff --git a/jack/src/com/android/jack/util/graph/Graph.java b/jack/src/com/android/jack/util/graph/Graph.java
index c1dc2d7..b3ef512 100644
--- a/jack/src/com/android/jack/util/graph/Graph.java
+++ b/jack/src/com/android/jack/util/graph/Graph.java
@@ -17,6 +17,7 @@
 package com.android.jack.util.graph;
 
 import com.android.jack.Jack;
+import com.android.sched.marker.LocalMarkerManager;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -28,7 +29,7 @@
  *
  * @param <N> The type of node contained by the graph.
  */
-public class Graph<N extends GraphNode<N>> {
+public abstract class Graph<N extends GraphNode<N>> extends LocalMarkerManager implements IGraph<N>{
 
   @Nonnull
   private final List<N> nodes;
@@ -49,6 +50,7 @@
   }
 
   @Nonnull
+  @Override
   public List<N> getNodes() {
     return nodes;
   }
@@ -57,6 +59,7 @@
    * @return the entry
    */
   @Nonnull
+  @Override
   public N getEntryNode() {
     return entry;
   }
@@ -65,6 +68,7 @@
    * @return the exit
    */
   @Nonnull
+  @Override
   public N getExitNode() {
     return exit;
   }
diff --git a/jack/src/com/android/jack/util/graph/GraphNode.java b/jack/src/com/android/jack/util/graph/GraphNode.java
index fe0f094..0a928c6 100644
--- a/jack/src/com/android/jack/util/graph/GraphNode.java
+++ b/jack/src/com/android/jack/util/graph/GraphNode.java
@@ -30,7 +30,8 @@
  *
  * @param <N> The type of graph node.
  */
-public abstract class GraphNode<N extends GraphNode<N>> extends LocalMarkerManager {
+public abstract class GraphNode<N extends GraphNode<N>> extends LocalMarkerManager
+    implements IGraphNode<N> {
 
   // TODO(mikaelpeltier) Think about new implementation of sparse list due to index usage and
   // append usage to fill successors.
@@ -89,4 +90,16 @@
   public boolean removePredecessor(@Nonnull N predecessor) {
     return (predecessors.remove(predecessor));
   }
+
+  @Override
+  @Nonnull
+  public Iterable<N> getSuccessorsIterable() {
+    return getSuccessors();
+  }
+
+  @Override
+  @Nonnull
+  public Iterable<N> getPredecessorsIterable() {
+    return getPredecessors();
+  }
 }
diff --git a/jack/src/com/android/jack/util/graph/GraphNodeVisitor.java b/jack/src/com/android/jack/util/graph/GraphNodeVisitor.java
new file mode 100644
index 0000000..95fcc92
--- /dev/null
+++ b/jack/src/com/android/jack/util/graph/GraphNodeVisitor.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.util.graph;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/**
+ * Visitor abstract class for graph nodes.
+ */
+public abstract class GraphNodeVisitor<N extends IGraphNode<N>> {
+
+  /**
+   * Visits the current node with a possible parent node.
+   *
+   * @param v {@code non-null;} graph node visited
+   * @param parent {@code null-ok;} parent node if applicable
+   */
+   public abstract void visitNode(@Nonnull N v, @CheckForNull N parent);
+}
diff --git a/jack/src/com/android/jack/util/graph/IGraph.java b/jack/src/com/android/jack/util/graph/IGraph.java
new file mode 100644
index 0000000..8dcad49
--- /dev/null
+++ b/jack/src/com/android/jack/util/graph/IGraph.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.util.graph;
+
+import com.android.sched.marker.MarkerManager;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+/**
+ * A interface for all abstract graph representation.
+ */
+public interface IGraph<N extends IGraphNode<N>> extends MarkerManager {
+
+  @Nonnull
+  public List<N> getNodes();
+
+  /**
+   * @return the entry
+   */
+  @Nonnull
+  public N getEntryNode();
+
+  /**
+   * @return the exit
+   */
+  @Nonnull
+  public N getExitNode();
+}
diff --git a/jack/src/com/android/jack/util/graph/IGraphNode.java b/jack/src/com/android/jack/util/graph/IGraphNode.java
new file mode 100644
index 0000000..77e3545
--- /dev/null
+++ b/jack/src/com/android/jack/util/graph/IGraphNode.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.util.graph;
+
+import com.android.sched.marker.MarkerManager;
+
+import javax.annotation.Nonnull;
+
+/**
+ *
+ */
+public interface IGraphNode<N extends IGraphNode<N>> extends MarkerManager {
+
+  @Nonnull
+  public abstract Iterable<N> getSuccessorsIterable();
+
+  @Nonnull
+  public abstract Iterable<N> getPredecessorsIterable();
+}
diff --git a/jack/src/com/android/jack/util/graph/NodeIdMarker.java b/jack/src/com/android/jack/util/graph/NodeIdMarker.java
new file mode 100644
index 0000000..61946c6
--- /dev/null
+++ b/jack/src/com/android/jack/util/graph/NodeIdMarker.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.util.graph;
+
+import com.android.jack.ir.ast.cfg.JBasicBlock;
+import com.android.sched.item.Description;
+import com.android.sched.marker.Marker;
+import com.android.sched.marker.MarkerManager;
+import com.android.sched.marker.ValidOn;
+
+import java.util.BitSet;
+
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+
+/**
+ * A {@link Marker} to represent a numeric ID for a basic block.
+ *
+ * The goal is to allow sets of blocks to be represented by {@link BitSet} by assigning a number to
+ * each basic block.
+ */
+@Description("A numeric ID for a node in a graph.")
+@ValidOn(JBasicBlock.class)
+public class NodeIdMarker implements Marker {
+  @Nonnegative
+  private final int id;
+
+  public NodeIdMarker(@Nonnegative int id) {
+    this.id = id;
+  }
+
+  @Nonnegative
+  public static int getId(@Nonnull MarkerManager node) {
+    NodeIdMarker marker = node.getMarker(NodeIdMarker.class);
+    assert marker != null;
+    return marker.id;
+  }
+
+  public static void setId(@Nonnull MarkerManager node, @Nonnegative int id) {
+    assert node.getMarker(NodeIdMarker.class) == null;
+    node.addMarker(new NodeIdMarker(id));
+  }
+
+  @Override
+  @Nonnull
+  public Marker cloneIfNeeded() {
+    return this;
+  }
+
+  /**
+   * Assign numberic IDs to a graph.
+   */
+  public static void assignIds(@Nonnull IGraph<?> graph) {
+    int index = 0;
+    for (IGraphNode<?> n : graph.getNodes()) {
+      NodeIdMarker.setId(n, index++);
+    }
+  }
+
+  /**
+   * Removes all numberic IDs from a graph.
+   */
+  public static void removeIds(@Nonnull IGraph<?> graph) {
+    for (IGraphNode<?> n : graph.getNodes()) {
+      n.removeMarker(NodeIdMarker.class);
+    }
+  }
+}
\ No newline at end of file
diff --git a/jack/src/com/android/jack/util/graph/NodeListMarker.java b/jack/src/com/android/jack/util/graph/NodeListMarker.java
new file mode 100644
index 0000000..3770b67
--- /dev/null
+++ b/jack/src/com/android/jack/util/graph/NodeListMarker.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.util.graph;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+
+import com.android.jack.ir.ast.cfg.JControlFlowGraph;
+import com.android.sched.item.Description;
+import com.android.sched.marker.Marker;
+import com.android.sched.marker.ValidOn;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+/**
+ *
+ */
+@Description("Assign a number to block map to a CFG.")
+@ValidOn(JControlFlowGraph.class)
+public class NodeListMarker implements Marker {
+
+  @SuppressWarnings("rawtypes")
+  private final ImmutableList<IGraphNode> orderedList;
+
+  public NodeListMarker(@SuppressWarnings("rawtypes") List<IGraphNode> orderedList) {
+    this.orderedList = ImmutableList.copyOf(orderedList);
+  }
+
+  @SuppressWarnings({"rawtypes", "unchecked"})
+  @Nonnull
+  public static <N extends IGraphNode<N>> ImmutableList<N> getNodeList(@Nonnull IGraph<N> graph) {
+    NodeListMarker marker = graph.getMarker(NodeListMarker.class);
+    assert marker != null;
+    Builder<N> builder = ImmutableList.<N>builder();
+    for (IGraphNode node : marker.orderedList) {
+      builder.add((N) node);
+    }
+    return builder.build();
+  }
+
+  @Override
+  public Marker cloneIfNeeded() {
+    throw new AssertionError("It is not valid to use cloneIfNeeded, create a new marker.");
+  }
+
+  @SuppressWarnings("unchecked")
+  public static void assignNodeList(@SuppressWarnings("rawtypes") @Nonnull IGraph graph) {
+    assert graph.getMarker(NodeListMarker.class) == null;
+    graph.addMarker(new NodeListMarker(graph.getNodes()));
+  }
+
+  public static void removeNodeList(@SuppressWarnings("rawtypes") @Nonnull IGraph graph) {
+    graph.removeMarker(NodeListMarker.class);
+  }
+}
diff --git a/jack/tests/com/android/jack/UnaryTest.java b/jack/tests/com/android/jack/UnaryTest.java
index a70e61f..669648a 100644
--- a/jack/tests/com/android/jack/UnaryTest.java
+++ b/jack/tests/com/android/jack/UnaryTest.java
@@ -16,9 +16,9 @@
 
 package com.android.jack;
 
-import com.android.jack.ir.ast.JIfStatement;
 import com.android.jack.ir.ast.JMethod;
 import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.ast.cfg.JConditionalBlockElement;
 import com.android.jack.optimizations.Optimizations;
 import com.android.sched.util.config.ThreadConfig;
 
@@ -84,7 +84,7 @@
     private int countIf = 0;
 
     @Override
-    public boolean visit(@Nonnull JIfStatement x) {
+    public boolean visit(@Nonnull JConditionalBlockElement x) {
       countIf++;
       return super.visit(x);
     }
diff --git a/jack/tests/com/android/jack/cfg/BaseGraphTestCase.java b/jack/tests/com/android/jack/cfg/BaseGraphTestCase.java
new file mode 100644
index 0000000..c4aed15
--- /dev/null
+++ b/jack/tests/com/android/jack/cfg/BaseGraphTestCase.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.cfg;
+
+import com.google.common.collect.Lists;
+
+import com.android.jack.Options;
+import com.android.jack.util.graph.IGraph;
+import com.android.jack.util.graph.IGraphNode;
+import com.android.jack.util.graph.NodeIdMarker;
+import com.android.jack.util.graph.NodeListMarker;
+import com.android.sched.marker.LocalMarkerManager;
+import com.android.sched.marker.Marker;
+import com.android.sched.util.RunnableHooks;
+import com.android.sched.util.config.Config;
+import com.android.sched.util.config.ThreadConfig;
+
+import junit.framework.Assert;
+
+import org.junit.After;
+import org.junit.Before;
+
+import java.util.List;
+
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+
+/**
+ * Utilities to conduct some unit tests on a simple graph.
+ *
+ */
+public abstract class BaseGraphTestCase {
+
+  /**
+   * Creates a graph for testing purposes.
+   */
+  protected static TestGraph makeGraph(@Nonnegative int nNodes) {
+    List<TestNode> nodes = Lists.newArrayList();
+    for (int i = 0; i < nNodes; i++) {
+      nodes.add(new TestNode(i));
+    }
+    TestNode entry = new TestNode(nNodes);
+    TestNode exit = new TestNode(nNodes + 1);
+    nodes.add(entry);
+    nodes.add(exit);
+    TestGraph graph = new TestGraph(entry, exit, nodes);
+    NodeIdMarker.assignIds(graph);
+    NodeListMarker.assignNodeList(graph);
+    return graph;
+  }
+
+  /**
+   * Simple node type that represents a number which is same as the node's label.
+   *
+   * Note that this label is purely for testing purpose and it is totally independent on any sort
+   * of "ID" a graph or graph algorithm use for traversal and other computations.
+   */
+  protected static class TestNode extends LocalMarkerManager implements IGraphNode<TestNode> {
+    private final int label;
+    private final List<TestNode> predecessors = Lists.newArrayList();
+    private final List<TestNode> successors = Lists.newArrayList();
+
+    public TestNode(@Nonnegative int label) {
+      this.label = label;
+    }
+
+    /**
+     * For testing purposing, everything is a valid marker.
+     */
+    @Override
+    protected boolean isValidMarker(@Nonnull Marker marker) {
+      return true;
+    }
+
+    @Override
+    public String toString() {
+      return "" + label;
+    }
+
+    @Override
+    public Iterable<TestNode> getSuccessorsIterable() {
+      return successors;
+    }
+
+    @Override
+    @Nonnull
+    public Iterable<TestNode> getPredecessorsIterable() {
+      return predecessors;
+    }
+  }
+
+  /**
+   * Simple graph data structure for testing.
+   */
+  protected static class TestGraph extends LocalMarkerManager implements IGraph<TestNode> {
+    private final List<TestNode> nodes;
+    private final @Nonnull TestNode entry;
+    private final @Nonnull TestNode exit;
+    protected TestGraph(@Nonnull TestNode entry, @Nonnull TestNode exit,
+        @Nonnull List<TestNode> nodes) {
+      this.nodes = nodes;
+      this.entry = entry;
+      this.exit = exit;
+    }
+
+    /**
+     * For testing purposing, everything is a valid marker.
+     */
+    @Override
+    protected boolean isValidMarker(@Nonnull Marker marker) {
+      return true;
+    }
+
+    @Nonnull
+    protected TestNode getNode(@Nonnegative int index) {
+      return nodes.get(index);
+    }
+
+    protected void connect(@Nonnegative int from, @Nonnegative int to) {
+      TestNode fromNode = getNode(from);
+      TestNode toNode = getNode(to);
+      connect(fromNode, toNode);
+    }
+
+    protected void connect(@Nonnegative TestNode fromNode, @Nonnegative TestNode toNode) {
+      fromNode.successors.add(toNode);
+      toNode.predecessors.add(fromNode);
+    }
+
+    protected void assertConnected(@Nonnegative int from, @Nonnegative int to) {
+      TestNode fromNode = getNode(from);
+      TestNode toNode = getNode(to);
+      Assert.assertTrue(fromNode.successors.contains(toNode));
+      Assert.assertTrue(toNode.predecessors.contains(fromNode));
+    }
+
+    protected void assertConnected(@Nonnull TestNode fromNode, @Nonnull TestNode toNode) {
+      Assert.assertTrue(fromNode.successors.contains(toNode));
+      Assert.assertTrue(toNode.predecessors.contains(fromNode));
+    }
+
+    protected void assertNotConnected(@Nonnegative int from, @Nonnegative int to) {
+      TestNode fromNode = getNode(from);
+      TestNode toNode = getNode(to);
+      Assert.assertFalse(fromNode.successors.contains(toNode));
+      Assert.assertFalse(toNode.predecessors.contains(fromNode));
+    }
+
+    @Override
+    @Nonnull
+    public List<TestNode> getNodes() {
+      return nodes;
+    }
+
+    @Override
+    @Nonnull
+    public TestNode getEntryNode() {
+      return entry;
+    }
+
+    @Override
+    @Nonnull
+    public TestNode getExitNode() {
+      return exit;
+    }
+  }
+
+  @Before
+  public void setUp() throws Exception {
+    Options options = new Options();
+    RunnableHooks hooks = new RunnableHooks();
+    options.checkValidity(hooks);
+    Config config = options.getConfig();
+    ThreadConfig.setConfig(config);
+  }
+
+  @After
+  public void tearDown() {
+    ThreadConfig.unsetConfig();
+  }
+}
\ No newline at end of file
diff --git a/jack/tests/com/android/jack/cfg/DepthFirstTraversalTest.java b/jack/tests/com/android/jack/cfg/DepthFirstTraversalTest.java
new file mode 100644
index 0000000..9d00071
--- /dev/null
+++ b/jack/tests/com/android/jack/cfg/DepthFirstTraversalTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.cfg;
+
+import com.android.jack.util.graph.DepthFirstTraversal;
+import com.android.jack.util.graph.GraphNodeVisitor;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/**
+ * Tests for basic depth first traversal.
+ */
+public class DepthFirstTraversalTest extends BaseGraphTestCase {
+
+  @Test
+  public void dft1() throws Exception {
+    //   0
+    // /   \
+    // 1    2
+    // |    |
+    // 3   /
+    // | /
+    // 4
+    //
+    // 1 is the idom of 3
+    // 4 is in the dominance frontier of 4
+    TestGraph graph = makeGraph(6);
+    graph.connect(graph.getEntryNode(), graph.getNodes().get(0));
+    graph.connect(0, 1);
+    graph.connect(0, 2);
+    graph.connect(1, 3);
+    graph.connect(3, 4);
+    graph.connect(2, 4);
+    graph.connect(graph.getNodes().get(4), graph.getExitNode());
+    Assert.assertEquals("02413", doDepthFirstTraversal(graph, false));
+  }
+
+  @Test
+  public void dftReverse1() throws Exception {
+    //   0
+    // /   \
+    // 1    2
+    // |    |
+    // 3   /
+    // | /
+    // 4
+    //
+    TestGraph graph = makeGraph(5);
+    graph.connect(graph.getEntryNode(), graph.getNodes().get(0));
+    graph.connect(0, 1);
+    graph.connect(0, 2);
+    graph.connect(1, 3);
+    graph.connect(3, 4);
+    graph.connect(2, 4);
+    graph.connect(graph.getNodes().get(4), graph.getExitNode());
+
+    Assert.assertEquals("42031", doDepthFirstTraversal(graph, true));
+  }
+
+  @Test
+  public void dftReverse2() throws Exception {
+    //   0
+    // /   \
+    // 1    2
+    // |
+    // 3
+    // |
+    // 4
+    TestGraph graph = makeGraph(5);
+    graph.connect(graph.getEntryNode(), graph.getNodes().get(0));
+    graph.connect(0, 1);
+    graph.connect(0, 2);
+    graph.connect(1, 3);
+    graph.connect(3, 4);
+    graph.connect(graph.getNodes().get(4), graph.getExitNode());
+    graph.connect(graph.getNodes().get(2), graph.getExitNode());
+    Assert.assertEquals("20431", doDepthFirstTraversal(graph, true));
+  }
+
+  @Nonnull
+  private static String doDepthFirstTraversal(final TestGraph graph, boolean reverse) {
+    final StringBuilder sb = new StringBuilder();
+    DepthFirstTraversal.run(graph, reverse, new GraphNodeVisitor<TestNode>() {
+      @Override
+      public void visitNode(@Nonnull TestNode v, @CheckForNull TestNode parent) {
+        if (v != graph.getEntryNode() && v != graph.getExitNode()) {
+          sb.append(v);
+        }
+      }
+    });
+    return sb.toString();
+  }
+}
\ No newline at end of file
diff --git a/jack/tests/com/android/jack/cfg/DominaceFrontierTest.java b/jack/tests/com/android/jack/cfg/DominaceFrontierTest.java
new file mode 100644
index 0000000..7820003
--- /dev/null
+++ b/jack/tests/com/android/jack/cfg/DominaceFrontierTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.cfg;
+
+import com.android.jack.util.graph.DominanceFrontier;
+import com.android.jack.util.graph.DominanceFrontierInfoMarker;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+
+/**
+ * Some basic tests for {@link DominanceFrontier}
+ */
+public class DominaceFrontierTest extends BaseGraphTestCase {
+  private TestGraph graph = null;
+
+  @Test
+  public void df1()throws Exception {
+    //    0
+    //   / \
+    //  1   2
+    //  |   |
+    //  3  /
+    //  | /
+    //  4
+    //
+    // 1 is the idom of 3
+    // 4 is in the dominance frontier of 4
+    graph = makeGraph(5);
+    graph.connect(graph.getEntryNode(), graph.getNodes().get(0));
+    graph.connect(0, 1);
+    graph.connect(0, 2);
+    graph.connect(1, 3);
+    graph.connect(3, 4);
+    graph.connect(2, 4);
+    graph.connect(graph.getNodes().get(4), graph.getExitNode());
+    new DominanceFrontier<TestNode>(graph).run();
+
+    // The only DF set that exists. Everything else should be empty.
+    Assert.assertTrue(isInDominanceFrontier(1, 4));
+    for (int i = 0; i < 5; i++) {
+      for (int j = 0; j < 5; j++) {
+        if (i != 1 && j != 4) {
+          Assert.assertFalse(isInDominanceFrontier(i, j));
+        }
+      }
+    }
+  }
+
+  @Test
+  public void df2() throws Exception {
+    //    __0__
+    //   /  |   \
+    //  1   2   3
+    //  | \     |
+    //  4   5   6
+    //  |   |  /
+    //  | / | /
+    //  7   8
+    //
+    graph = makeGraph(9);
+    graph.connect(graph.getEntryNode(), graph.getNodes().get(0));
+
+    // level 1
+    graph.connect(0, 1);
+    graph.connect(0, 2);
+    graph.connect(0, 3);
+
+    // level 2
+    graph.connect(1, 4);
+    graph.connect(1, 5);
+    graph.connect(3, 6);
+
+    // level 3
+    graph.connect(4, 7);
+    graph.connect(5, 7);
+    graph.connect(5, 8);
+    graph.connect(6, 8);
+
+    graph.connect(graph.getNodes().get(7), graph.getExitNode());
+    graph.connect(graph.getNodes().get(8), graph.getExitNode());
+
+    new DominanceFrontier<TestNode>(graph).run();
+
+    // 8 is in the DF of 1 but not 7 because 1 is still dominating 7.
+    Assert.assertTrue(isInDominanceFrontier(1, 8));
+    Assert.assertFalse(isInDominanceFrontier(1, 7));
+  }
+
+  private boolean isInDominanceFrontier(int parent, int target) {
+    TestNode parentNode = graph.getNodes().get(parent);
+    TestNode targetNode = graph.getNodes().get(target);
+    return DominanceFrontierInfoMarker.isInDominanceFrontier(parentNode, targetNode);
+  }
+}
diff --git a/jack/tests/com/android/jack/cfg/DominatorDepthFirstTraversalTest.java b/jack/tests/com/android/jack/cfg/DominatorDepthFirstTraversalTest.java
new file mode 100644
index 0000000..9cf7811
--- /dev/null
+++ b/jack/tests/com/android/jack/cfg/DominatorDepthFirstTraversalTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.android.jack.cfg;
+
+import com.android.jack.util.graph.DominanceFrontier;
+import com.android.jack.util.graph.DominatorDepthFirstTraversal;
+import com.android.jack.util.graph.GraphNodeVisitor;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/**
+ * Tests for {@link DominatorDepthFirstTraversal}.
+ */
+public class DominatorDepthFirstTraversalTest extends BaseGraphTestCase {
+  @Test
+  public void ddft1() throws Exception {
+    //  0
+    // / \
+    // 1  2
+    // |  |
+    // 3  /
+    // | /
+    // 4
+    //
+    TestGraph graph = makeGraph(5);
+    graph.connect(graph.getEntryNode(), graph.getNodes().get(0));
+    graph.connect(0, 1);
+    graph.connect(0, 2);
+    graph.connect(1, 3);
+    graph.connect(3, 4);
+    graph.connect(2, 4);
+    graph.connect(graph.getNodes().get(4), graph.getExitNode());
+    Assert.assertEquals("01324", doDominatorDepthFirstTraversal(graph));
+  }
+
+  @Test
+  public void ddft2() throws Exception {
+    //  0
+    // /
+    // 1
+    // | \
+    // 2  3
+    // |   \
+    // 4   5
+    // |  /
+    // | /
+    // 6
+    TestGraph graph = makeGraph(7);
+    graph.connect(graph.getEntryNode(), graph.getNodes().get(0));
+    graph.connect(0, 1);
+    graph.connect(1, 2);
+    graph.connect(1, 3);
+    graph.connect(2, 4);
+    graph.connect(3, 5);
+    graph.connect(4, 6);
+    graph.connect(5, 6);
+    graph.connect(graph.getNodes().get(6), graph.getExitNode());
+    Assert.assertEquals("0124356", doDominatorDepthFirstTraversal(graph));
+  }
+
+  @Test
+  public void ddft3() throws Exception {
+    // This creates a loop.
+    // 0 -> 1 -> 2 -> 1.
+    TestGraph graph = makeGraph(7);
+    graph.connect(graph.getEntryNode(), graph.getNodes().get(0));
+    graph.connect(0, 1);
+    graph.connect(1, 2);
+    graph.connect(2, 1);
+    Assert.assertEquals("012", doDominatorDepthFirstTraversal(graph));
+  }
+
+  @Nonnull
+  private static String doDominatorDepthFirstTraversal(final TestGraph graph) {
+    new DominanceFrontier<>(graph).run();
+    final StringBuilder sb = new StringBuilder();
+    DominatorDepthFirstTraversal.run(graph, new GraphNodeVisitor<TestNode>() {
+      @Override
+      public void visitNode(@Nonnull TestNode v, @CheckForNull TestNode parent) {
+        // Parent is always null since it is meaning less in this context.
+        Assert.assertNull(parent);
+        if (v != graph.getEntryNode() && v != graph.getExitNode()) {
+          sb.append(v);
+        }
+      }
+    });
+    return sb.toString();
+  }
+}
\ No newline at end of file
diff --git a/jack/tests/com/android/jack/optimizations/tailrecursion/TailRecursionTest.java b/jack/tests/com/android/jack/optimizations/tailrecursion/TailRecursionTest.java
index 04061e9..0b05576 100644
--- a/jack/tests/com/android/jack/optimizations/tailrecursion/TailRecursionTest.java
+++ b/jack/tests/com/android/jack/optimizations/tailrecursion/TailRecursionTest.java
@@ -18,11 +18,11 @@
 
 import com.android.jack.Options;
 import com.android.jack.TestTools;
-import com.android.jack.ir.ast.JGoto;
 import com.android.jack.ir.ast.JMethod;
-import com.android.jack.ir.ast.JReturnStatement;
-import com.android.jack.ir.ast.JStatement;
 import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.ast.cfg.JBasicBlockElement;
+import com.android.jack.ir.ast.cfg.JGotoBlockElement;
+import com.android.jack.ir.ast.cfg.JReturnBlockElement;
 import com.android.jack.util.filter.SignatureMethodFilter;
 
 import junit.framework.Assert;
@@ -55,31 +55,31 @@
     JMethod m =
         getJMethodAfterTailRecursionOptimization(TestTools.getJackTestFromBinaryName(classBinaryName), "L"
             + classBinaryName + ";", methodSignature);
-    StatementsCounter counter1 = new StatementsCounter(JReturnStatement.class);
+    Counter counter1 = new Counter(JReturnBlockElement.class);
     counter1.accept(m);
     Assert.assertEquals(1, counter1.getCounter());
-    StatementsCounter counter2 = new StatementsCounter(JGoto.class);
+    Counter counter2 = new Counter(JGotoBlockElement.class);
     counter2.accept(m);
     Assert.assertEquals(1, counter2.getCounter());
   }
 
-  private static class StatementsCounter extends JVisitor {
+  private static class Counter extends JVisitor {
 
     private int counter = 0;
 
     @Nonnull
-    private final Class<? extends JStatement> clazz;
+    private final Class<? extends JBasicBlockElement> clazz;
 
-    public StatementsCounter(Class<? extends JStatement> clazz) {
+    public Counter(@Nonnull Class<? extends JBasicBlockElement> clazz) {
       this.clazz = clazz;
     }
 
     @Override
-    public boolean visit(@Nonnull JStatement stmt) {
-      if (clazz.isInstance(stmt)) {
+    public boolean visit(@Nonnull JBasicBlockElement element) {
+      if (clazz.isInstance(element)) {
         counter++;
       }
-      return super.visit(stmt);
+      return super.visit(element);
     }
 
     public int getCounter() {
diff --git a/jack/tests/com/android/jack/util/GraphUtilsTests.java b/jack/tests/com/android/jack/util/GraphUtilsTests.java
index 3ee7e11..fb2e238 100644
--- a/jack/tests/com/android/jack/util/GraphUtilsTests.java
+++ b/jack/tests/com/android/jack/util/GraphUtilsTests.java
@@ -339,7 +339,7 @@
     assert exitNode != null;
 
     return new Graph<GraphUtilsTests.TestNode>(entryNode, exitNode,
-        new ArrayList<>(idToNode.values()));
+        new ArrayList<>(idToNode.values())) { /* anonymous */ };
   }
 
   private static class TestNode extends GraphNode<TestNode> {
diff --git a/server/jack-server/etc/jack b/server/jack-server/etc/jack
index 75aed94..99f6c18 100755
--- a/server/jack-server/etc/jack
+++ b/server/jack-server/etc/jack
@@ -22,7 +22,7 @@
 #
 # Settings
 #
-JACK_VERSION=${JACK_VERSION:=4.0.ENGINEERING}
+JACK_VERSION=${JACK_VERSION:=-2.0.ENGINEERING}
 JACK_HOME="${JACK_HOME:=$HOME/.jack-server}"
 JACK_CLIENT_SETTING="${JACK_CLIENT_SETTING:=$HOME/.jack-settings}"
 TMPDIR=${TMPDIR:=/tmp}
diff --git a/version.properties b/version.properties
index 12380b0..13a3c2a 100644
--- a/version.properties
+++ b/version.properties
@@ -15,9 +15,9 @@
 #
 version-file.version.code=2
 
-version=1.3
-version.release.name=Douarn
-version.release.code=4
+version=-2.0
+version.release.name=Douarn 1.3-b6 + OptimDev
+version.release.code=-2
 version.sub-release.kind=ENGINEERING
 version.sub-release.code=0