Merge "Handle default methods properly during tree shaking."
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
new file mode 100644
index 0000000..2207e6f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.cf.code;
+
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class CfGoto extends CfInstruction {
+
+  private final CfLabel target;
+
+  public CfGoto(CfLabel target) {
+    this.target = target;
+  }
+
+  @Override
+  public void write(MethodVisitor visitor) {
+    visitor.visitJumpInsn(Opcodes.GOTO, target.getLabel());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIf.java b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
new file mode 100644
index 0000000..b8397e5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.cf.code;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.ValueType;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class CfIf extends CfInstruction {
+
+  private final If.Type kind;
+  private final ValueType type;
+  private final CfLabel target;
+
+  public CfIf(If.Type kind, ValueType type, CfLabel target) {
+    this.kind = kind;
+    this.type = type;
+    this.target = target;
+  }
+
+  private int getOpcode() {
+    switch (kind) {
+      case EQ:
+        return type.isObject() ? Opcodes.IFNULL : Opcodes.IFEQ;
+      case GE:
+        return Opcodes.IFGE;
+      case GT:
+        return Opcodes.IFGT;
+      case LE:
+        return Opcodes.IFLE;
+      case LT:
+        return Opcodes.IFLT;
+      case NE:
+        return type.isObject() ? Opcodes.IFNONNULL : Opcodes.IFNE;
+      default:
+        throw new Unreachable("Unexpected type " + type);
+    }
+  }
+
+  @Override
+  public void write(MethodVisitor visitor) {
+    visitor.visitJumpInsn(getOpcode(), target.getLabel());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
new file mode 100644
index 0000000..9c6560d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.cf.code;
+
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+
+public class CfLabel extends CfInstruction {
+
+  private Label label = null;
+
+  public Label getLabel() {
+    if (label == null) {
+      label = new Label();
+    }
+    return label;
+  }
+
+  @Override
+  public void write(MethodVisitor visitor) {
+    visitor.visitLabel(getLabel());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java b/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
new file mode 100644
index 0000000..a0d1f6a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.cf.code;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+import java.util.ArrayList;
+import java.util.List;
+
+public class CfTryCatch {
+  public final CfLabel start;
+  public final CfLabel end;
+  public final List<DexType> guards;
+  public final List<CfLabel> targets;
+
+  public CfTryCatch(
+      CfLabel start,
+      CfLabel end,
+      CatchHandlers<BasicBlock> handlers,
+      CfBuilder builder) {
+    this.start = start;
+    this.end = end;
+    guards = handlers.getGuards();
+    targets = new ArrayList<>(handlers.getAllTargets().size());
+    for (BasicBlock block : handlers.getAllTargets()) {
+      targets.add(builder.getLabel(block));
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index e1f9c63..7c76969 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -5,23 +5,32 @@
 
 import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfTryCatch;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.List;
+import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Type;
 
 public class CfCode extends Code {
 
   private final int maxStack;
   private final int maxLocals;
   private final List<CfInstruction> instructions;
+  private final List<CfTryCatch> tryCatchRanges;
 
-  public CfCode(int maxStack, int maxLocals, List<CfInstruction> instructions) {
+  public CfCode(
+      int maxStack,
+      int maxLocals,
+      List<CfInstruction> instructions,
+      List<CfTryCatch> tryCatchRanges) {
     this.maxStack = maxStack;
     this.maxLocals = maxLocals;
     this.instructions = instructions;
+    this.tryCatchRanges = tryCatchRanges;
   }
 
   @Override
@@ -40,6 +49,21 @@
     }
     visitor.visitEnd();
     visitor.visitMaxs(maxStack, maxLocals);
+    for (CfTryCatch tryCatch : tryCatchRanges) {
+      Label start = tryCatch.start.getLabel();
+      Label end = tryCatch.end.getLabel();
+      for (int i = 0; i < tryCatch.guards.size(); i++) {
+        DexType guard = tryCatch.guards.get(i);
+        Label target = tryCatch.targets.get(i).getLabel();
+        visitor.visitTryCatchBlock(
+            start,
+            end,
+            target,
+            guard == DexItemFactory.catchAllType
+                ? null
+                : Type.getType(guard.toDescriptorString()).getInternalName());
+      }
+    }
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Goto.java b/src/main/java/com/android/tools/r8/ir/code/Goto.java
index eef666b..4b88706 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Goto.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Goto.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.code.CfGoto;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.utils.CfgPrinter;
 import java.util.List;
@@ -94,4 +97,14 @@
   public Goto asGoto() {
     return this;
   }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+    // Nothing to do.
+  }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    builder.add(new CfGoto(builder.getLabel(getTarget())));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 95dd4ba..934d3db 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -402,7 +402,8 @@
             assert instruction.isDebugInstruction()
                 || instruction.isJumpInstruction()
                 || instruction.isConstInstruction()
-                || instruction.isNewArrayFilledData();
+                || instruction.isNewArrayFilledData()
+                || instruction.isStore();
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/code/If.java b/src/main/java/com/android/tools/r8/ir/code/If.java
index d2e16f6..8cdf209 100644
--- a/src/main/java/com/android/tools/r8/ir/code/If.java
+++ b/src/main/java/com/android/tools/r8/ir/code/If.java
@@ -6,7 +6,10 @@
 import static com.android.tools.r8.dex.Constants.U4BIT_MAX;
 import static com.android.tools.r8.dex.Constants.U8BIT_MAX;
 
+import com.android.tools.r8.cf.code.CfIf;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.utils.CfgPrinter;
 import java.util.List;
@@ -186,4 +189,15 @@
   public If asIf() {
     return this;
   }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+    stack.loadInValues(this, it);
+  }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    assert inValues.size() == 1;
+    builder.add(new CfIf(type, inValues.get(0).type, builder.getLabel(getTrueTarget())));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index cdcf070..eeee6f8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -938,6 +938,22 @@
     return null;
   }
 
+  public boolean isStore() {
+    return false;
+  }
+
+  public Store asStore() {
+    return null;
+  }
+
+  public boolean isLoad() {
+    return false;
+  }
+
+  public Load asLoad() {
+    return null;
+  }
+
   public boolean canBeFolded() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Load.java b/src/main/java/com/android/tools/r8/ir/code/Load.java
index 734b2dd..401a880 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Load.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Load.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 
 public class Load extends Instruction {
@@ -17,6 +18,16 @@
   }
 
   @Override
+  public boolean isLoad() {
+    return true;
+  }
+
+  @Override
+  public Load asLoad() {
+    return this;
+  }
+
+  @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
     return true;
   }
@@ -46,4 +57,9 @@
     Value value = inValues.get(0);
     builder.add(new CfLoad(value.outType(), builder.getLocalRegister(value)));
   }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+    // Nothing to do. This is only hit because loads and stores are insert for phis.
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 0f72182..2b419fd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -6,6 +6,8 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.utils.InternalOptions;
@@ -61,7 +63,7 @@
 
   @Override
   public boolean canBeDeadCode(IRCode code, InternalOptions options) {
-    return !options.debug;
+    return !options.debug && !options.outputClassFiles;
   }
 
   @Override
@@ -69,4 +71,18 @@
     // TODO(64432527): Revisit this constraint.
     return Constraint.NEVER;
   }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+    if (outValue.isUsed()) {
+      stack.storeOutValue(this, it);
+    } else {
+      stack.popOutValue(outValue.type, this, it);
+    }
+  }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    // Nothing to do. The exception is implicitly pushed on the stack.
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Pop.java b/src/main/java/com/android/tools/r8/ir/code/Pop.java
index 33d5557..7dd62ae 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Pop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Pop.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.utils.InternalOptions;
 
 public class Pop extends Instruction {
 
@@ -45,4 +46,10 @@
   public void buildCf(CfBuilder builder) {
     builder.add(new CfPop(inValues.get(0).type));
   }
+
+  @Override
+  public boolean canBeDeadCode(IRCode code, InternalOptions options) {
+    // Pop cannot be dead code as it modifies the stack height.
+    return false;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StackValue.java b/src/main/java/com/android/tools/r8/ir/code/StackValue.java
index f670621..3bf1417 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StackValue.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StackValue.java
@@ -5,12 +5,16 @@
 
 public class StackValue extends Value {
 
-  public StackValue(ValueType type) {
+  private final int height;
+
+  public StackValue(ValueType type, int height) {
     super(Value.UNDEFINED_NUMBER, type, null);
+    this.height = height;
+    assert height >= 0;
   }
 
   @Override
   public String toString() {
-    return "s";
+    return "s" + height;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Store.java b/src/main/java/com/android/tools/r8/ir/code/Store.java
index ddc4f50..bc1e419 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Store.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Store.java
@@ -8,7 +8,10 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.CfBuilder.FixedLocal;
+import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.utils.InternalOptions;
 
 public class Store extends Instruction {
 
@@ -17,6 +20,16 @@
   }
 
   @Override
+  public boolean isStore() {
+    return true;
+  }
+
+  @Override
+  public Store asStore() {
+    return this;
+  }
+
+  @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
     return true;
   }
@@ -45,4 +58,14 @@
   public void buildCf(CfBuilder builder) {
     builder.add(new CfStore(outType(), builder.getLocalRegister(outValue)));
   }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+    // Nothing to do. This is only hit because loads and stores are insert for phis.
+  }
+
+  @Override
+  public boolean canBeDeadCode(IRCode code, InternalOptions options) {
+    return !(outValue instanceof FixedLocal);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index aed20acb..f07b61e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -4,6 +4,8 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfTryCatch;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.CfCode;
@@ -11,6 +13,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.ConstInstruction;
 import com.android.tools.r8.ir.code.ConstNumber;
@@ -20,7 +23,9 @@
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Load;
+import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Pop;
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.StackValue;
 import com.android.tools.r8.ir.code.Store;
 import com.android.tools.r8.ir.code.Value;
@@ -28,43 +33,64 @@
 import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover;
 import com.android.tools.r8.utils.InternalOptions;
-import it.unimi.dsi.fastutil.objects.Reference2IntArrayMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
 
 public class CfBuilder {
 
   private final DexEncodedMethod method;
   private final IRCode code;
+
+  private int maxLocals = -1;
+  private int maxStack = 0;
+  private int currentStack = 0;
   private List<CfInstruction> instructions;
-  private Reference2IntMap<Value> argumentRegisters;
-  private int maxLocals;
+  private Reference2IntMap<Value> registers;
+  private Map<BasicBlock, CfLabel> labels;
+
+  /**
+   * Value that represents a shared physical location defined by the phi value.
+   *
+   * This value is introduced to represent the store instructions used to unify the location of
+   * in-flowing values to phi's. After introducing this fixed location the graph is no longer in
+   * SSA since the fixed location signifies a place that can be written to from multiple places.
+   */
+  public static class FixedLocal extends Value {
+
+    private final Phi phi;
+
+    public FixedLocal(Phi phi) {
+      super(phi.getNumber(), phi.outType(), phi.getLocalInfo());
+      this.phi = phi;
+    }
+
+    @Override
+    public boolean isConstant() {
+      return false;
+    }
+
+    @Override
+    public String toString() {
+      return "fixed:v" + phi.getNumber();
+    }
+  }
 
   public static class StackHelper {
 
+    private int currentStackHeight = 0;
+
     public void loadInValues(Instruction instruction, InstructionListIterator it) {
+      int topOfStack = currentStackHeight;
       it.previous();
-      for (int i = 0; i < instruction.inValues().size(); i++) {
-        Value value = instruction.inValues().get(i);
-        StackValue stackValue = new StackValue(value.outType());
-        Instruction load;
-        if (value.isConstant()) {
-          ConstInstruction constant = value.getConstInstruction();
-          if (constant.isConstNumber()) {
-            load = new ConstNumber(stackValue, constant.asConstNumber().getRawValue());
-          } else if (constant.isConstString()) {
-            load = new ConstString(stackValue, constant.asConstString().getValue());
-          } else if (constant.isConstClass()) {
-            load = new ConstClass(stackValue, constant.asConstClass().getValue());
-          } else {
-            throw new Unreachable("Unexpected constant value: " + value);
-          }
-        } else {
-          load = new Load(stackValue, value);
-        }
-        add(load, instruction, it);
+      for (Value value : instruction.inValues()) {
+        StackValue stackValue = new StackValue(value.outType(), topOfStack++);
+        add(load(stackValue, value), instruction, it);
         value.removeUser(instruction);
         instruction.replaceValue(value, stackValue);
       }
@@ -72,26 +98,61 @@
     }
 
     public void storeOutValue(Instruction instruction, InstructionListIterator it) {
-      if (instruction.isOutConstant()) {
+      if (instruction.outValue() instanceof StackValue) {
+        assert instruction.isConstInstruction();
         return;
       }
-      StackValue newOutValue = new StackValue(instruction.outType());
+      StackValue newOutValue = new StackValue(instruction.outType(), currentStackHeight);
       Value oldOutValue = instruction.swapOutValue(newOutValue);
       add(new Store(oldOutValue, newOutValue), instruction, it);
     }
 
     public void popOutValue(ValueType type, Instruction instruction, InstructionListIterator it) {
-      StackValue newOutValue = new StackValue(type);
+      StackValue newOutValue = new StackValue(type, currentStackHeight);
       instruction.swapOutValue(newOutValue);
       add(new Pop(newOutValue), instruction, it);
     }
 
+    public void movePhi(Phi phi, Value value, InstructionListIterator it) {
+      StackValue tmp = new StackValue(phi.outType(), currentStackHeight);
+      FixedLocal out = new FixedLocal(phi);
+      add(load(tmp, value), phi.getBlock(), Position.none(), it);
+      add(new Store(out, tmp), phi.getBlock(), Position.none(), it);
+      value.removePhiUser(phi);
+      phi.replaceUsers(out);
+    }
+
+    private Instruction load(StackValue stackValue, Value value) {
+      if (value.isConstant()) {
+        ConstInstruction constant = value.getConstInstruction();
+        if (constant.isConstNumber()) {
+          return new ConstNumber(stackValue, constant.asConstNumber().getRawValue());
+        } else if (constant.isConstString()) {
+          return new ConstString(stackValue, constant.asConstString().getValue());
+        } else if (constant.isConstClass()) {
+          return new ConstClass(stackValue, constant.asConstClass().getValue());
+        } else {
+          throw new Unreachable("Unexpected constant value: " + value);
+        }
+      }
+      return new Load(stackValue, value);
+    }
+
     private static void add(
         Instruction newInstruction, Instruction existingInstruction, InstructionListIterator it) {
-      newInstruction.setBlock(existingInstruction.getBlock());
-      newInstruction.setPosition(existingInstruction.getPosition());
+      add(newInstruction, existingInstruction.getBlock(), existingInstruction.getPosition(), it);
+    }
+
+    private static void add(
+        Instruction newInstruction,
+        BasicBlock block,
+        Position position,
+        InstructionListIterator it) {
+      newInstruction.setBlock(block);
+      newInstruction.setPosition(position);
       it.add(newInstruction);
     }
+
   }
 
   public CfBuilder(DexEncodedMethod method, IRCode code) {
@@ -101,6 +162,7 @@
 
   public Code build(CodeRewriter rewriter, InternalOptions options) {
     try {
+      splitExceptionalBlocks();
       loadStoreInsertion();
       DeadCodeRemover.removeDeadCode(code, rewriter, options);
       removeUnneededLoadsAndStores();
@@ -113,8 +175,55 @@
     }
   }
 
+  // Split all blocks with throwing instructions and exceptional edges such that any non-throwing
+  // instructions that might define values prior to the throwing exception are excluded from the
+  // try-catch range. Failure to do so will result in code that does not verify on the JVM.
+  private void splitExceptionalBlocks() {
+    ListIterator<BasicBlock> it = code.listIterator();
+    while (it.hasNext()) {
+      BasicBlock block = it.next();
+      if (!block.hasCatchHandlers()) {
+        continue;
+      }
+      int size = block.getInstructions().size();
+      boolean isThrow = block.exit().isThrow();
+      if ((isThrow && size == 1) || (!isThrow && size == 2)) {
+        // Fast-path to avoid processing blocks with just a single throwing instruction.
+        continue;
+      }
+      InstructionListIterator instructions = block.listIterator();
+      boolean hasOutValues = false;
+      while (instructions.hasNext()) {
+        Instruction instruction = instructions.next();
+        if (instruction.instructionTypeCanThrow()) {
+          break;
+        }
+        hasOutValues |= instruction.outValue() != null;
+      }
+      if (hasOutValues) {
+        instructions.previous();
+        instructions.split(code, it);
+      }
+    }
+  }
+
   private void loadStoreInsertion() {
     StackHelper stack = new StackHelper();
+    // Insert phi stores in all predecessors.
+    for (BasicBlock block : code.blocks) {
+      if (!block.getPhis().isEmpty()) {
+        for (int predIndex = 0; predIndex < block.getPredecessors().size(); predIndex++) {
+          BasicBlock pred = block.getPredecessors().get(predIndex);
+          for (Phi phi : block.getPhis()) {
+            Value value = phi.getOperand(predIndex);
+            InstructionListIterator it = pred.listIterator(pred.getInstructions().size());
+            it.previous();
+            stack.movePhi(phi, value, it);
+          }
+        }
+      }
+    }
+    // Insert per-instruction loads and stores.
     for (BasicBlock block : code.blocks) {
       InstructionListIterator it = block.listIterator();
       while (it.hasNext()) {
@@ -131,6 +240,10 @@
       InstructionListIterator it = block.listIterator();
       while (it.hasNext()) {
         Instruction store = it.next();
+        // Eliminate unneeded loads of stores:
+        //  v <- store si
+        //  si <- load v
+        // where |users(v)| == 1 (ie, the load is the only user)
         if (store instanceof Store && store.outValue().numberOfAllUsers() == 1) {
           Instruction load = it.peekNext();
           if (load instanceof Load && load.inValues().get(0) == store.outValue()) {
@@ -154,60 +267,115 @@
   private void allocateLocalRegisters() {
     // TODO(zerny): Allocate locals based on live ranges.
     InstructionIterator it = code.instructionIterator();
-    argumentRegisters = new Reference2IntArrayMap<>(
-        method.method.proto.parameters.values.length + (method.accessFlags.isStrict() ? 0 : 1));
-    int argumentRegister = 0;
-    int maxRegister = -1;
+    registers = new Reference2IntOpenHashMap<>();
+    int nextFreeRegister = 0;
     while (it.hasNext()) {
       Instruction instruction = it.next();
-      if (instruction.isArgument()) {
-        argumentRegisters.put(instruction.outValue(), argumentRegister);
-        argumentRegister += instruction.outValue().requiredRegisters();
-        maxRegister = argumentRegister - 1;
-      } else if (instruction.outValue() != null
-          && !(instruction.outValue() instanceof StackValue)) {
-        maxRegister = Math.max(maxRegister, 2 * instruction.outValue().getNumber());
+      Value outValue = instruction.outValue();
+      if (outValue instanceof FixedLocal) {
+        // Phi stores are marked by a "fixed-local" value which share the same local index.
+        FixedLocal fixed = (FixedLocal) outValue;
+        if (!registers.containsKey(fixed.phi)) {
+          registers.put(fixed.phi, nextFreeRegister);
+          nextFreeRegister += fixed.requiredRegisters();
+        }
+      } else if (outValue != null && !(outValue instanceof StackValue)) {
+        registers.put(instruction.outValue(), nextFreeRegister);
+        nextFreeRegister += instruction.outValue().requiredRegisters();
       }
     }
-    maxLocals = maxRegister + 1;
+    maxLocals = nextFreeRegister;
+  }
+
+  private void push(Value value) {
+    assert value instanceof StackValue;
+    currentStack += value.requiredRegisters();
+    maxStack = Math.max(maxStack, currentStack);
+  }
+
+  private void pop(Value value) {
+    assert value instanceof StackValue;
+    currentStack -= value.requiredRegisters();
   }
 
   private CfCode buildCfCode() {
-    int maxStack = 0;
-    int currentStack = 0;
+    List<CfTryCatch> tryCatchRanges = new ArrayList<>();
+    labels = new HashMap<>(code.blocks.size());
     instructions = new ArrayList<>();
     Iterator<BasicBlock> blockIterator = code.listIterator();
-    while (blockIterator.hasNext()) {
-      BasicBlock block = blockIterator.next();
-      InstructionIterator it = block.iterator();
-      while (it.hasNext()) {
-        Instruction instruction = it.next();
-        if (instruction.outValue() != null) {
-          Value outValue = instruction.outValue();
-          if (outValue instanceof StackValue) {
-            currentStack += outValue.requiredRegisters();
-            maxStack = Math.max(maxStack, currentStack);
+    BasicBlock block = blockIterator.next();
+    CfLabel tryCatchStart = null;
+    CatchHandlers<BasicBlock> tryCatchHandlers = CatchHandlers.EMPTY_BASIC_BLOCK;
+    do {
+      CatchHandlers<BasicBlock> handlers = block.getCatchHandlers();
+      if (!tryCatchHandlers.equals(handlers)) {
+        if (!tryCatchHandlers.isEmpty()) {
+          // Close try-catch and save the range.
+          CfLabel tryCatchEnd = getLabel(block);
+          tryCatchRanges.add(new CfTryCatch(tryCatchStart, tryCatchEnd, tryCatchHandlers, this));
+          if (instructions.get(instructions.size() - 1) != tryCatchEnd) {
+            instructions.add(tryCatchEnd);
           }
         }
-        for (Value inValue : instruction.inValues()) {
-          if (inValue instanceof StackValue) {
-            currentStack -= inValue.requiredRegisters();
+        if (!handlers.isEmpty()) {
+          // Open a try-catch.
+          tryCatchStart = getLabel(block);
+          if (instructions.isEmpty()
+              || instructions.get(instructions.size() - 1) != tryCatchStart) {
+            instructions.add(tryCatchStart);
           }
         }
-        instruction.buildCf(this);
+        tryCatchHandlers = handlers;
       }
-    }
+      BasicBlock nextBlock = blockIterator.hasNext() ? blockIterator.next() : null;
+      buildCfInstructions(block, nextBlock);
+      block = nextBlock;
+    } while (block != null);
     assert currentStack == 0;
-    return new CfCode(maxStack, maxLocals, instructions);
+    return new CfCode(maxStack, maxLocals, instructions, tryCatchRanges);
+  }
+
+  private void buildCfInstructions(BasicBlock block, BasicBlock nextBlock) {
+    boolean fallthrough = false;
+    InstructionIterator it = block.iterator();
+    while (it.hasNext()) {
+      Instruction instruction = it.next();
+      if (instruction.isGoto() && instruction.asGoto().getTarget() == nextBlock) {
+        fallthrough = true;
+        continue;
+      }
+      for (Value inValue : instruction.inValues()) {
+        if (inValue instanceof StackValue) {
+          pop(inValue);
+        }
+      }
+      if (instruction.outValue() != null) {
+        Value outValue = instruction.outValue();
+        if (outValue instanceof StackValue) {
+          push(outValue);
+        }
+      }
+      instruction.buildCf(this);
+    }
+    if (nextBlock == null || (fallthrough && nextBlock.getPredecessors().size() == 1)) {
+      return;
+    }
+    instructions.add(getLabel(nextBlock));
   }
 
   // Callbacks
 
+  public CfLabel getLabel(BasicBlock target){
+    return labels.computeIfAbsent(target, (block) -> new CfLabel());
+  }
+
   public int getLocalRegister(Value value) {
-    if (value.isArgument()) {
-      return argumentRegisters.getInt(value);
+    if (value instanceof FixedLocal) {
+      // Phi stores are marked by a "fixed-local" value which share the same local index.
+      FixedLocal fixed = (FixedLocal) value;
+      return registers.getInt(fixed.phi);
     }
-    return 2 * value.getNumber();
+    return registers.getInt(value);
   }
 
   public void add(CfInstruction instruction) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index a1c529a..8b83a8e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -710,6 +710,9 @@
 
   // Replace result uses for methods where something is known about what is returned.
   public void rewriteMoveResult(IRCode code) {
+    if (options.outputClassFiles) {
+      return;
+    }
     AppInfoWithSubtyping appInfoWithSubtyping = appInfo.withSubtyping();
     InstructionIterator iterator = code.instructionIterator();
     while (iterator.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 5783ad4..55ecb45 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.ir.code.And;
 import com.android.tools.r8.ir.code.ArithmeticBinop;
 import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.DebugLocalsChange;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -751,6 +752,7 @@
     while (!unhandled.isEmpty()) {
       LiveIntervals unhandledInterval = unhandled.poll();
 
+      setHintForDestRegOfCheckCast(unhandledInterval);
       setHintToPromote2AddrInstruction(unhandledInterval);
 
       // If this interval value is the src of an argument move. Fix the registers for the
@@ -804,6 +806,20 @@
     return true;
   }
 
+  private void setHintForDestRegOfCheckCast(LiveIntervals unhandledInterval) {
+    if (unhandledInterval.getHint() == null &&
+        unhandledInterval.getValue().definition instanceof CheckCast) {
+      CheckCast checkcast = unhandledInterval.getValue().definition.asCheckCast();
+      Value checkcastInput = checkcast.inValues().get(0);
+      assert checkcastInput != null;
+      if (checkcastInput.getLiveIntervals() != null &&
+          !checkcastInput.getLiveIntervals().overlaps(unhandledInterval) &&
+          checkcastInput.getLocalInfo() == unhandledInterval.getValue().definition.getLocalInfo()) {
+        unhandledInterval.setHint(checkcastInput.getLiveIntervals());
+      }
+    }
+  }
+
   /*
    * This method tries to promote arithmetic binary instruction to use the 2Addr form.
    * To achieve this goal the output interval of the binary instruction is set with an hint
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 3509357..b2760e4 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -57,27 +57,15 @@
     }
   }
 
+  // TODO(zerny): Implement stack-map computation to support Java 1.6 and above.
+  private int downgrade(int version) {
+    return version > 49 ? 49 : version;
+  }
+
   private void writeClass(DexProgramClass clazz, OutputSink outputSink) throws IOException {
-    // If there are CF representations for all methods manually compute stack height and frames.
-    // TODO(zerny): This is a temporary hack since we will need to manually compute stack maps for
-    // any methods that are IR processed.
-    int flags = 0;
-    for (DexEncodedMethod method : clazz.directMethods()) {
-      if (!method.getCode().isCfCode()) {
-        flags = ClassWriter.COMPUTE_FRAMES;
-        break;
-      }
-    }
-    if (flags == 0) {
-      for (DexEncodedMethod method : clazz.virtualMethods()) {
-        if (!method.getCode().isCfCode()) {
-          flags = ClassWriter.COMPUTE_FRAMES;
-        }
-      }
-    }
-    ClassWriter writer = new ClassWriter(flags);
+    ClassWriter writer = new ClassWriter(0);
     writer.visitSource(clazz.sourceFile.toString(), null);
-    int version = clazz.getClassFileVersion();
+    int version = downgrade(clazz.getClassFileVersion());
     int access = clazz.accessFlags.getAsCfAccessFlags();
     String desc = clazz.type.toDescriptorString();
     String name = internalName(clazz.type);
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 0fc2489..c79719a 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -39,6 +39,8 @@
 @RunWith(Parameterized.class)
 public class R8RunExamplesTest {
 
+  private static final boolean ONLY_RUN_CF_TESTS = false;
+
   enum Input {
     DX, JAVAC, JAVAC_ALL, JAVAC_NONE
   }
@@ -125,22 +127,25 @@
         "constants.Constants",
         "hello.Hello",
         "arithmetic.Arithmetic",
+        "barray.BArray",
     };
 
     List<String[]> fullTestList = new ArrayList<>(tests.length * 2);
-    for (String test : tests) {
-      fullTestList.add(makeTest(Input.JAVAC, CompilerUnderTest.D8, CompilationMode.DEBUG, test));
-      fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.D8, CompilationMode.DEBUG,
-          test));
-      fullTestList.add(makeTest(Input.JAVAC_NONE, CompilerUnderTest.D8, CompilationMode.DEBUG,
-          test));
-      fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.D8, CompilationMode.RELEASE,
-          test));
-      fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.R8, CompilationMode.RELEASE,
-          test));
-      fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.R8, CompilationMode.DEBUG,
-          test));
-      fullTestList.add(makeTest(Input.DX, CompilerUnderTest.R8, CompilationMode.RELEASE, test));
+    if (!ONLY_RUN_CF_TESTS) {
+      for (String test : tests) {
+        fullTestList.add(makeTest(Input.JAVAC, CompilerUnderTest.D8, CompilationMode.DEBUG, test));
+        fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.D8, CompilationMode.DEBUG,
+            test));
+        fullTestList.add(makeTest(Input.JAVAC_NONE, CompilerUnderTest.D8, CompilationMode.DEBUG,
+            test));
+        fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.D8, CompilationMode.RELEASE,
+            test));
+        fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.R8, CompilationMode.RELEASE,
+            test));
+        fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.R8, CompilationMode.DEBUG,
+            test));
+        fullTestList.add(makeTest(Input.DX, CompilerUnderTest.R8, CompilationMode.RELEASE, test));
+      }
     }
     // TODO(zerny): Once all tests pass create the java tests in the main test loop.
     for (String test : javaBytecodeTests) {
diff --git a/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java b/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java
index 5dc1e75..162db06 100644
--- a/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java
+++ b/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java
@@ -444,6 +444,6 @@
     // TODO(sgjesse): Maybe this test is too fragile, as it leaves quite a lot of code, so the
     // expectation might need changing with other optimizations.
     // TODO(zerny): Consider optimizing the fallthrough branch of conditionals to not be return.
-    assertEquals(27, code.instructions.length);
+    assertEquals(26, code.instructions.length);
   }
 }