[CF] Add printer for Java bytecode.

Change-Id: Ia989bdf08fc1ee95eb5e3c363793894e8edd7372
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
new file mode 100644
index 0000000..af51fdc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -0,0 +1,306 @@
+// 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;
+
+import com.android.tools.r8.cf.code.CfArrayLoad;
+import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfGoto;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfPop;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfStaticGet;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ValueType;
+import java.util.HashMap;
+import java.util.Map;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Utility to print CF code and instructions.
+ *
+ * <p>This implementation prints the code formatted according to the Jasmin syntax.
+ */
+public class CfPrinter {
+
+  private final String indent;
+  private final Map<CfLabel, String> labels;
+
+  private final StringBuilder builder = new StringBuilder();
+
+  /** Entry for printing single instructions without global knowledge (eg, label numbers). */
+  public CfPrinter() {
+    indent = "";
+    labels = null;
+  }
+
+  /** Entry for printing a complete code object. */
+  public CfPrinter(CfCode code) {
+    indent = "  ";
+    labels = new HashMap<>();
+    int nextLabelNumber = 0;
+    for (CfInstruction instruction : code.getInstructions()) {
+      if (instruction instanceof CfLabel) {
+        labels.put((CfLabel) instruction, "L" + nextLabelNumber++);
+      }
+    }
+    builder.append(".method ");
+    appendMethod(code.getMethod());
+    newline();
+    builder.append(".limit stack ").append(code.getMaxStack());
+    newline();
+    builder.append(".limit locals ").append(code.getMaxLocals());
+    for (CfInstruction instruction : code.getInstructions()) {
+      instruction.print(this);
+    }
+    newline();
+    builder.append(".end method");
+    newline();
+  }
+
+  private void print(String name) {
+    indent();
+    builder.append(name);
+  }
+
+  public void print(CfConstNull constNull) {
+    print("aconst_null");
+  }
+
+  public void print(CfConstNumber constNumber) {
+    indent();
+    // TODO(zerny): Determine when the number matches the tconst_X instructions.
+    switch (constNumber.getType()) {
+      case INT:
+        builder.append("ldc ").append(constNumber.getIntValue());
+        break;
+      case FLOAT:
+        builder.append("ldc ").append(constNumber.getFloatValue());
+        break;
+      case LONG:
+        builder.append("ldc_w ").append(constNumber.getLongValue());
+        break;
+      case DOUBLE:
+        builder.append("ldc_w ").append(constNumber.getDoubleValue());
+        break;
+      default:
+        throw new Unreachable("Unexpected const-number type: " + constNumber.getType());
+    }
+  }
+
+  public void print(CfReturn ret) {
+    print("return");
+  }
+
+  public void print(CfPop pop) {
+    print("pop");
+  }
+
+  public void print(CfConstString constString) {
+    indent();
+    builder.append("ldc ").append(constString.getString());
+  }
+
+  public void print(CfArrayLoad arrayLoad) {
+    switch (arrayLoad.getType()) {
+      case OBJECT:
+        print("aaload");
+        break;
+      case BOOLEAN:
+      case BYTE:
+        print("baload");
+        break;
+      case CHAR:
+        print("caload");
+        break;
+      case SHORT:
+        print("saload");
+        break;
+      case INT:
+        print("iaload");
+        break;
+      case FLOAT:
+        print("faload");
+        break;
+      case LONG:
+        print("laload");
+        break;
+      case DOUBLE:
+        print("daload");
+        break;
+      default:
+        throw new Unreachable("Unexpected array-load type: " + arrayLoad.getType());
+    }
+  }
+
+  public void print(CfInvoke invoke) {
+    indent();
+    switch (invoke.getOpcode()) {
+      case Opcodes.INVOKEVIRTUAL:
+        builder.append("invokevirtual");
+        break;
+      case Opcodes.INVOKESPECIAL:
+        builder.append("invokespecial");
+        break;
+      case Opcodes.INVOKESTATIC:
+        builder.append("invokestatic");
+        break;
+      case Opcodes.INVOKEINTERFACE:
+        builder.append("invokeinterface");
+        break;
+      case Opcodes.INVOKEDYNAMIC:
+        builder.append("invokedynamic");
+        break;
+      default:
+        throw new Unreachable("Unexpected invoke opcode: " + invoke.getOpcode());
+    }
+    builder.append(' ');
+    appendMethod(invoke.getMethod());
+  }
+
+  public void print(CfFrame frame) {
+    comment("frame");
+  }
+
+  public void print(CfStaticGet staticGet) {
+    indent();
+    builder.append("getstatic ");
+    appendField(staticGet.getField());
+    appendDescriptor(staticGet.getField().type);
+  }
+
+  public void print(CfNew newInstance) {
+    indent();
+    builder.append("new ");
+    appendClass(newInstance.getType());
+  }
+
+  public void print(CfLabel label) {
+    newline();
+    builder.append(getLabel(label)).append(':');
+  }
+
+  public void print(CfGoto jump) {
+    indent();
+    builder.append("goto ").append(getLabel(jump.getTarget()));
+  }
+
+  public void print(CfIf conditional) {
+    indent();
+    switch (conditional.getOpcode()) {
+      case Opcodes.IFNULL:
+        builder.append("ifnull");
+        break;
+      case Opcodes.IFNONNULL:
+        builder.append("ifnonnull");
+        break;
+      case Opcodes.IFEQ:
+        builder.append("ifeq");
+        break;
+      case Opcodes.IFNE:
+        builder.append("ifne");
+        break;
+      case Opcodes.IFLT:
+        builder.append("iflt");
+        break;
+      case Opcodes.IFGE:
+        builder.append("ifge");
+        break;
+      case Opcodes.IFGT:
+        builder.append("ifgt");
+        break;
+      case Opcodes.IFLE:
+        builder.append("ifle");
+        break;
+      default:
+        throw new Unreachable();
+    }
+    builder.append(' ').append(getLabel(conditional.getTarget()));
+  }
+
+  public void print(CfLoad load) {
+    print(load.getType(), "load", load.getLocalIndex());
+  }
+
+  public void print(CfStore store) {
+    print(store.getType(), "store", store.getLocalIndex());
+  }
+
+  private void print(ValueType type, String instruction, int local) {
+    indent();
+    builder.append(typePrefix(type)).append(instruction).append(' ').append(local);
+  }
+
+  private char typePrefix(ValueType type) {
+    switch (type) {
+      case OBJECT:
+        return 'a';
+      case INT:
+        return 'i';
+      case FLOAT:
+        return 'f';
+      case LONG:
+        return 'l';
+      case DOUBLE:
+        return 'd';
+      default:
+        throw new Unreachable("Unexpected type for prefix: " + type);
+    }
+  }
+
+  private String getLabel(CfLabel label) {
+    return labels != null ? labels.get(label) : "L?";
+  }
+
+  private void newline() {
+    if (builder.length() > 0) {
+      builder.append('\n');
+    }
+  }
+
+  private void indent() {
+    newline();
+    builder.append(indent);
+  }
+
+  private void comment(String comment) {
+    indent();
+    builder.append("; ").append(comment);
+  }
+
+  private void appendDescriptor(DexType type) {
+    builder.append(type.toDescriptorString());
+  }
+
+  private void appendClass(DexType type) {
+    builder.append(type.getInternalName());
+  }
+
+  private void appendField(DexField field) {
+    appendClass(field.getHolder());
+    builder.append('/').append(field.name);
+  }
+
+  private void appendMethod(DexMethod method) {
+    builder.append(method.qualifiedName());
+    builder.append(method.proto.toDescriptorString());
+  }
+
+  @Override
+  public String toString() {
+    return builder.toString();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
index 9043ab6..3ff24b9 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.MemberType;
 import org.objectweb.asm.MethodVisitor;
@@ -16,6 +17,10 @@
     this.type = type;
   }
 
+  public MemberType getType() {
+    return type;
+  }
+
   private int getLoadType() {
     switch (type) {
       case OBJECT:
@@ -44,4 +49,9 @@
   public void write(MethodVisitor visitor) {
     visitor.visitInsn(getLoadType());
   }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
index f022f78..a788a3b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -12,4 +13,9 @@
   public void write(MethodVisitor visitor) {
     visitor.visitInsn(Opcodes.ACONST_NULL);
   }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
index 1d51bab..c3b4490 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
@@ -3,9 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.ValueType;
 import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
 
 public class CfConstNumber extends CfInstruction {
 
@@ -17,23 +19,84 @@
     this.type = type;
   }
 
+  public ValueType getType() {
+    return type;
+  }
+
+  public long getRawValue() {
+    return value;
+  }
+
+  public int getIntValue() {
+    assert type == ValueType.INT;
+    return (int) value;
+  }
+
+  public long getLongValue() {
+    assert type == ValueType.LONG;
+    return value;
+  }
+
+  public float getFloatValue() {
+    assert type == ValueType.FLOAT;
+    return Float.intBitsToFloat((int) value);
+  }
+
+  public double getDoubleValue() {
+    assert type == ValueType.DOUBLE;
+    return Double.longBitsToDouble(value);
+  }
+
   @Override
   public void write(MethodVisitor visitor) {
     switch (type) {
       case INT:
-        visitor.visitLdcInsn((int)value);
-        break;
+        {
+          int value = getIntValue();
+          if (-1 <= value && value <= 5) {
+            visitor.visitInsn(Opcodes.ICONST_0 + value);
+          } else {
+            visitor.visitLdcInsn(value);
+          }
+          break;
+        }
       case LONG:
-        visitor.visitLdcInsn(value);
-        break;
+        {
+          long value = getLongValue();
+          if (value == 0 || value == 1) {
+            visitor.visitInsn(Opcodes.LCONST_0 + (int) value);
+          } else {
+            visitor.visitLdcInsn(value);
+          }
+          break;
+        }
       case FLOAT:
-        visitor.visitLdcInsn(Float.intBitsToFloat((int)value));
-        break;
+        {
+          float value = getFloatValue();
+          if (value == 0 || value == 1 || value == 2) {
+            visitor.visitInsn(Opcodes.FCONST_0 + (int) value);
+          } else {
+            visitor.visitLdcInsn(value);
+          }
+          break;
+        }
       case DOUBLE:
-        visitor.visitLdcInsn(Double.longBitsToDouble(value));
-        break;
+        {
+          double value = getDoubleValue();
+          if (value == 0 || value == 1) {
+            visitor.visitInsn(Opcodes.DCONST_0 + (int) value);
+          } else {
+            visitor.visitLdcInsn(value);
+          }
+          break;
+        }
       default:
         throw new Unreachable("Non supported type in cf backend: " + type);
     }
   }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
index 62f1537..0f7f9f7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexString;
 import org.objectweb.asm.MethodVisitor;
 
@@ -14,8 +15,17 @@
     this.string = string;
   }
 
+  public DexString getString() {
+    return string;
+  }
+
   @Override
   public void write(MethodVisitor visitor) {
     visitor.visitLdcInsn(string.toString());
   }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
index 78a350d..1d946b2 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -5,6 +5,7 @@
 
 import static org.objectweb.asm.Opcodes.F_NEW;
 
+import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
@@ -12,7 +13,6 @@
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
 
 public class CfFrame extends CfInstruction {
 
@@ -45,7 +45,7 @@
     }
     switch (type.toShorty()) {
       case 'L':
-        return Type.getType(type.toDescriptorString()).getInternalName();
+        return type.getInternalName();
       case 'I':
         return Opcodes.INTEGER;
       case 'F':
@@ -63,4 +63,9 @@
   public String toString() {
     return getClass().getSimpleName();
   }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
 }
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
index 2207e6f..c881e7b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -14,8 +15,17 @@
     this.target = target;
   }
 
+  public CfLabel getTarget() {
+    return target;
+  }
+
   @Override
   public void write(MethodVisitor visitor) {
     visitor.visitJumpInsn(Opcodes.GOTO, target.getLabel());
   }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
 }
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
index b8397e5..5c7a2c7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.ValueType;
@@ -21,7 +22,11 @@
     this.target = target;
   }
 
-  private int getOpcode() {
+  public CfLabel getTarget() {
+    return target;
+  }
+
+  public int getOpcode() {
     switch (kind) {
       case EQ:
         return type.isObject() ? Opcodes.IFNULL : Opcodes.IFEQ;
@@ -41,6 +46,11 @@
   }
 
   @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
+
+  @Override
   public void write(MethodVisitor visitor) {
     visitor.visitJumpInsn(getOpcode(), target.getLabel());
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 915a491..76806f6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -3,14 +3,20 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import org.objectweb.asm.MethodVisitor;
 
 public abstract class CfInstruction {
 
   public abstract void write(MethodVisitor visitor);
 
+  public abstract void print(CfPrinter printer);
+
   @Override
   public String toString() {
-    return getClass().getSimpleName();
+    CfPrinter printer = new CfPrinter();
+    print(printer);
+    return printer.toString();
   }
+
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 0e927ac..d90fb92 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -3,10 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexMethod;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
 
 public class CfInvoke extends CfInstruction {
 
@@ -19,12 +19,25 @@
     this.method = method;
   }
 
+  public DexMethod getMethod() {
+    return method;
+  }
+
+  public int getOpcode() {
+    return opcode;
+  }
+
   @Override
   public void write(MethodVisitor visitor) {
-    String owner = Type.getType(method.getHolder().toDescriptorString()).getInternalName();
+    String owner = method.getHolder().getInternalName();
     String name = method.name.toString();
     String desc = method.proto.toDescriptorString();
     boolean iface = method.holder.isInterface();
     visitor.visitMethodInsn(opcode, owner, name, desc, iface);
   }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
 }
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
index 9c6560d..13d9078 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
 
@@ -18,6 +19,11 @@
   }
 
   @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
+
+  @Override
   public void write(MethodVisitor visitor) {
     visitor.visitLabel(getLabel());
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
index af053a8..7d6be37 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.ValueType;
 import org.objectweb.asm.MethodVisitor;
@@ -39,4 +40,17 @@
   public void write(MethodVisitor visitor) {
     visitor.visitVarInsn(getLoadType(), var);
   }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
+
+  public ValueType getType() {
+    return type;
+  }
+
+  public int getLocalIndex() {
+    return var;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNew.java b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
index b528ea5..48b2ca3 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNew.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
@@ -3,10 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
 
 public class CfNew extends CfInstruction {
 
@@ -16,8 +16,17 @@
     this.type = type;
   }
 
+  public DexType getType() {
+    return type;
+  }
+
   @Override
   public void write(MethodVisitor visitor) {
-    visitor.visitTypeInsn(Opcodes.NEW, Type.getType(type.toDescriptorString()).getInternalName());
+    visitor.visitTypeInsn(Opcodes.NEW, type.getInternalName());
+  }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPop.java b/src/main/java/com/android/tools/r8/cf/code/CfPop.java
index e058df6..68cedb9 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfPop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfPop.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.ir.code.ValueType;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -19,4 +20,9 @@
   public void write(MethodVisitor visitor) {
     visitor.visitInsn(type.isWide() ? Opcodes.POP2 : Opcodes.POP);
   }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
index 4cf2908..1a20d29 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -12,4 +13,9 @@
   public void write(MethodVisitor visitor) {
     visitor.visitInsn(Opcodes.RETURN);
   }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStaticGet.java b/src/main/java/com/android/tools/r8/cf/code/CfStaticGet.java
index 6e71cb5..c5a68b6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStaticGet.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStaticGet.java
@@ -3,10 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexField;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
 
 public class CfStaticGet extends CfInstruction {
 
@@ -16,11 +16,20 @@
     this.field = field;
   }
 
+  public DexField getField() {
+    return field;
+  }
+
   @Override
   public void write(MethodVisitor visitor) {
-    String owner = Type.getType(field.getHolder().toDescriptorString()).getInternalName();
+    String owner = field.getHolder().getInternalName();
     String name = field.name.toString();
     String desc = field.type.toDescriptorString();
     visitor.visitFieldInsn(Opcodes.GETSTATIC, owner, name, desc);
   }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStore.java b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
index 0074abc..2526a58 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.ValueType;
 import org.objectweb.asm.MethodVisitor;
@@ -39,4 +40,17 @@
   public void write(MethodVisitor visitor) {
     visitor.visitVarInsn(getStoreType(), var);
   }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
+
+  public ValueType getType() {
+    return type;
+  }
+
+  public int getLocalIndex() {
+    return var;
+  }
 }
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 7c76969..b0d0827 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -4,35 +4,55 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.cf.CfPrinter;
 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.Collections;
 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 DexMethod method;
   private final int maxStack;
   private final int maxLocals;
   private final List<CfInstruction> instructions;
   private final List<CfTryCatch> tryCatchRanges;
 
   public CfCode(
+      DexMethod method,
       int maxStack,
       int maxLocals,
       List<CfInstruction> instructions,
       List<CfTryCatch> tryCatchRanges) {
+    this.method = method;
     this.maxStack = maxStack;
     this.maxLocals = maxLocals;
     this.instructions = instructions;
     this.tryCatchRanges = tryCatchRanges;
   }
 
+  public DexMethod getMethod() {
+    return method;
+  }
+
+  public int getMaxStack() {
+    return maxStack;
+  }
+
+  public int getMaxLocals() {
+    return maxLocals;
+  }
+
+  public List<CfInstruction> getInstructions() {
+    return Collections.unmodifiableList(instructions);
+  }
+
   @Override
   public boolean isCfCode() {
     return true;
@@ -59,9 +79,7 @@
             start,
             end,
             target,
-            guard == DexItemFactory.catchAllType
-                ? null
-                : Type.getType(guard.toDescriptorString()).getInternalName());
+            guard == DexItemFactory.catchAllType ? null : guard.getInternalName());
       }
     }
   }
@@ -89,11 +107,7 @@
 
   @Override
   public String toString() {
-    StringBuilder builder = new StringBuilder();
-    for (CfInstruction instruction : instructions) {
-      builder.append(instruction.toString()).append('\n');
-    }
-    return builder.toString();
+    return new CfPrinter(this).toString();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 69a71f9..bae6fba 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -457,6 +457,13 @@
     return getPackageOrName(false);
   }
 
+  /** Get the fully qualified name using '/' in place of '.', ala the "internal type name" in ASM */
+  public String getInternalName() {
+    assert isClassType();
+    String descriptor = toDescriptorString();
+    return descriptor.substring(1, descriptor.length() - 1);
+  }
+
   public boolean isImmediateSubtypeOf(DexType type) {
     assert hierarchyLevel != UNKNOWN_LEVEL;
     return type.directSubtypes.contains(this);
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 0ad6c6c..f1c3e32 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
@@ -37,10 +37,12 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
+import java.util.Set;
 
 public class CfBuilder {
 
@@ -50,6 +52,7 @@
 
   private Map<Value, DexType> types;
   private Map<BasicBlock, CfLabel> labels;
+  private Set<CfLabel> emittedLabels;
   private List<CfInstruction> instructions;
   private CfRegisterAllocator registerAllocator;
 
@@ -170,6 +173,7 @@
     Stack stack = new Stack();
     List<CfTryCatch> tryCatchRanges = new ArrayList<>();
     labels = new HashMap<>(code.blocks.size());
+    emittedLabels = new HashSet<>(code.blocks.size());
     instructions = new ArrayList<>();
     Iterator<BasicBlock> blockIterator = code.listIterator();
     BasicBlock block = blockIterator.next();
@@ -182,17 +186,12 @@
           // 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);
-          }
+          emitLabel(tryCatchEnd);
         }
         if (!handlers.isEmpty()) {
           // Open a try-catch.
           tryCatchStart = getLabel(block);
-          if (instructions.isEmpty()
-              || instructions.get(instructions.size() - 1) != tryCatchStart) {
-            instructions.add(tryCatchStart);
-          }
+          emitLabel(tryCatchStart);
         }
         tryCatchHandlers = handlers;
       }
@@ -201,7 +200,7 @@
       buildCfInstructions(block, fallthrough, stack);
       if (nextBlock != null && (!fallthrough || nextBlock.getPredecessors().size() > 1)) {
         assert stack.isEmpty();
-        instructions.add(getLabel(nextBlock));
+        emitLabel(getLabel(nextBlock));
         if (nextBlock.getPredecessors().size() > 1) {
           addFrame(nextBlock, Collections.emptyList());
         }
@@ -210,7 +209,11 @@
     } while (block != null);
     assert stack.isEmpty();
     return new CfCode(
-        stack.maxHeight, registerAllocator.registersUsed(), instructions, tryCatchRanges);
+        method.method,
+        stack.maxHeight,
+        registerAllocator.registersUsed(),
+        instructions,
+        tryCatchRanges);
   }
 
   private void buildCfInstructions(BasicBlock block, boolean fallthrough, Stack stack) {
@@ -268,9 +271,16 @@
     instructions.add(new CfFrame(mapping));
   }
 
+  private void emitLabel(CfLabel label) {
+    if (!emittedLabels.contains(label)) {
+      emittedLabels.add(label);
+      instructions.add(label);
+    }
+  }
+
   // Callbacks
 
-  public CfLabel getLabel(BasicBlock target){
+  public CfLabel getLabel(BasicBlock target) {
     return labels.computeIfAbsent(target, (block) -> new CfLabel());
   }
 
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 b2760e4..d2b2f5e 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.InternalOptions;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -21,7 +20,6 @@
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassWriter;
 import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Type;
 import org.objectweb.asm.tree.ClassNode;
 import org.objectweb.asm.tree.MethodNode;
 import org.objectweb.asm.util.CheckClassAdapter;
@@ -68,13 +66,13 @@
     int version = downgrade(clazz.getClassFileVersion());
     int access = clazz.accessFlags.getAsCfAccessFlags();
     String desc = clazz.type.toDescriptorString();
-    String name = internalName(clazz.type);
+    String name = clazz.type.getInternalName();
     String signature = null; // TODO(zerny): Support generic signatures.
     String superName =
-        clazz.type == options.itemFactory.objectType ? null : internalName(clazz.superType);
+        clazz.type == options.itemFactory.objectType ? null : clazz.superType.getInternalName();
     String[] interfaces = new String[clazz.interfaces.values.length];
     for (int i = 0; i < clazz.interfaces.values.length; i++) {
-      interfaces[i] = internalName(clazz.interfaces.values[i]);
+      interfaces[i] = clazz.interfaces.values[i].getInternalName();
     }
     writer.visit(version, access, name, signature, superName, interfaces);
     // TODO(zerny): Methods and fields.
@@ -107,10 +105,6 @@
     }
   }
 
-  private static String internalName(DexType type) {
-    return Type.getType(type.toDescriptorString()).getInternalName();
-  }
-
   private String printCf(byte[] result) {
     ClassReader reader = new ClassReader(result);
     ClassNode node = new ClassNode(ASM6);