Implement IR to CF conversion for the hello-world example program.

Change-Id: If83e89b992bd56559e92673bd0616e4de1493f5a
diff --git a/build.gradle b/build.gradle
index c97aa9d..61360dc 100644
--- a/build.gradle
+++ b/build.gradle
@@ -170,6 +170,7 @@
     compile group: 'org.ow2.asm', name: 'asm', version: '6.0'
     compile group: 'org.ow2.asm', name: 'asm-commons', version: '6.0'
     compile group: 'org.ow2.asm', name: 'asm-tree', version: '6.0'
+    compile group: 'org.ow2.asm', name: 'asm-analysis', version: '6.0'
     compile group: 'org.ow2.asm', name: 'asm-util', version: '6.0'
     testCompile sourceSets.examples.output
     testCompile 'junit:junit:4.12'
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
new file mode 100644
index 0000000..62f1537
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstString.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 com.android.tools.r8.graph.DexString;
+import org.objectweb.asm.MethodVisitor;
+
+public class CfConstString extends CfInstruction {
+
+  private final DexString string;
+
+  public CfConstString(DexString string) {
+    this.string = string;
+  }
+
+  @Override
+  public void write(MethodVisitor visitor) {
+    visitor.visitLdcInsn(string.toString());
+  }
+}
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
new file mode 100644
index 0000000..915a491
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -0,0 +1,16 @@
+// 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;
+
+public abstract class CfInstruction {
+
+  public abstract void write(MethodVisitor visitor);
+
+  @Override
+  public String toString() {
+    return getClass().getSimpleName();
+  }
+}
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
new file mode 100644
index 0000000..0e927ac
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -0,0 +1,30 @@
+// 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.DexMethod;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+public class CfInvoke extends CfInstruction {
+
+  private final DexMethod method;
+  private final int opcode;
+
+  public CfInvoke(int opcode, DexMethod method) {
+    assert Opcodes.INVOKEVIRTUAL <= opcode && opcode <= Opcodes.INVOKEDYNAMIC;
+    this.opcode = opcode;
+    this.method = method;
+  }
+
+  @Override
+  public void write(MethodVisitor visitor) {
+    String owner = Type.getType(method.getHolder().toDescriptorString()).getInternalName();
+    String name = method.name.toString();
+    String desc = method.proto.toDescriptorString();
+    boolean iface = method.holder.isInterface();
+    visitor.visitMethodInsn(opcode, owner, name, desc, iface);
+  }
+}
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
new file mode 100644
index 0000000..0d63eaf
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
@@ -0,0 +1,42 @@
+// 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.conversion.CfBuilder.LocalType;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class CfLoad extends CfInstruction {
+
+  private final int var;
+  private final LocalType type;
+
+  public CfLoad(LocalType type, int var) {
+    this.var = var;
+    this.type = type;
+  }
+
+  private int getLoadType() {
+    switch (type) {
+      case REFERENCE:
+        return Opcodes.ALOAD;
+      case INTEGER:
+        return Opcodes.ILOAD;
+      case FLOAT:
+        return Opcodes.FLOAD;
+      case LONG:
+        return Opcodes.LLOAD;
+      case DOUBLE:
+        return Opcodes.DLOAD;
+      default:
+        throw new Unreachable("Unexpected type " + type);
+    }
+  }
+
+  @Override
+  public void write(MethodVisitor visitor) {
+    visitor.visitVarInsn(getLoadType(), var);
+  }
+}
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
new file mode 100644
index 0000000..4cf2908
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
@@ -0,0 +1,15 @@
+// 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 CfReturn extends CfInstruction {
+
+  @Override
+  public void write(MethodVisitor visitor) {
+    visitor.visitInsn(Opcodes.RETURN);
+  }
+}
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
new file mode 100644
index 0000000..6e71cb5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStaticGet.java
@@ -0,0 +1,26 @@
+// 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.DexField;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+public class CfStaticGet extends CfInstruction {
+
+  private final DexField field;
+
+  public CfStaticGet(DexField field) {
+    this.field = field;
+  }
+
+  @Override
+  public void write(MethodVisitor visitor) {
+    String owner = Type.getType(field.getHolder().toDescriptorString()).getInternalName();
+    String name = field.name.toString();
+    String desc = field.type.toDescriptorString();
+    visitor.visitFieldInsn(Opcodes.GETSTATIC, owner, name, desc);
+  }
+}
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
new file mode 100644
index 0000000..c18ae55
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
@@ -0,0 +1,42 @@
+// 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.conversion.CfBuilder.LocalType;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class CfStore extends CfInstruction {
+
+  private final int var;
+  private final LocalType type;
+
+  public CfStore(LocalType type, int var) {
+    this.var = var;
+    this.type = type;
+  }
+
+  private int getStoreType() {
+    switch (type) {
+      case REFERENCE:
+        return Opcodes.ASTORE;
+      case INTEGER:
+        return Opcodes.ISTORE;
+      case FLOAT:
+        return Opcodes.FSTORE;
+      case LONG:
+        return Opcodes.LSTORE;
+      case DOUBLE:
+        return Opcodes.DSTORE;
+      default:
+        throw new Unreachable("Unexpected type " + type);
+    }
+  }
+
+  @Override
+  public void write(MethodVisitor visitor) {
+    visitor.visitVarInsn(getStoreType(), 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
new file mode 100644
index 0000000..fa19a7c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -0,0 +1,76 @@
+// 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.graph;
+
+import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.cf.code.CfInstruction;
+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.MethodVisitor;
+
+public class CfCode extends Code {
+
+  private final List<CfInstruction> instructions;
+
+  public CfCode(List<CfInstruction> instructions) {
+    this.instructions = instructions;
+  }
+
+  @Override
+  public boolean isCfCode() {
+    return true;
+  }
+
+  @Override
+  public CfCode asCfCode() {
+    return this;
+  }
+
+  public void write(MethodVisitor visitor) {
+    for (CfInstruction instruction : instructions) {
+      instruction.write(visitor);
+    }
+    visitor.visitEnd();
+    // TODO(zerny): Consider computing max-stack (and frames?) height as part of building Cf.
+    visitor.visitMaxs(0, 0); // Trigger computation of max stack (and frames).
+  }
+
+  @Override
+  protected int computeHashCode() {
+    throw new Unimplemented();
+  }
+
+  @Override
+  protected boolean computeEquals(Object other) {
+    throw new Unimplemented();
+  }
+
+  @Override
+  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+      throws ApiLevelException {
+    throw new Unimplemented("Converting Java class- file bytecode to IR not yet supported");
+  }
+
+  @Override
+  public void registerReachableDefinitions(UseRegistry registry) {
+    throw new Unimplemented("Inspecting Java class-file bytecode not yet supported");
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder builder = new StringBuilder();
+    for (CfInstruction instruction : instructions) {
+      builder.append(instruction.toString()).append('\n');
+    }
+    return builder.toString();
+  }
+
+  @Override
+  public String toString(DexEncodedMethod method, ClassNameMapper naming) {
+    return null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 47bf267..b197e93 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -24,6 +24,10 @@
 
   public abstract String toString(DexEncodedMethod method, ClassNameMapper naming);
 
+  public boolean isCfCode() {
+    return false;
+  }
+
   public boolean isDexCode() {
     return false;
   }
@@ -40,6 +44,10 @@
     return Integer.MAX_VALUE;
   }
 
+  public CfCode asCfCode() {
+    throw new Unreachable(getClass().getCanonicalName() + ".asCfCode()");
+  }
+
   public DexCode asDexCode() {
     throw new Unreachable(getClass().getCanonicalName() + ".asDexCode()");
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 9c0c39c..8b5b0a6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -276,6 +276,10 @@
         : code.asDexCode().buildIR(this, options, valueNumberGenerator);
   }
 
+  public void setCode(Code code) {
+    this.code = code;
+  }
+
   public void setCode(
       IRCode ir,
       RegisterAllocator registerAllocator,
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index 0cc3962..4da6eaf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.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;
@@ -76,4 +78,14 @@
   public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
     return Constraint.ALWAYS;
   }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+    // Arguments are defined by locals so nothing to load or store.
+  }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    builder.addArgument(this);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index 9d869aa..fe90f84 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -3,8 +3,12 @@
 // 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.CfConstString;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.CfBuilder.LocalType;
+import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 
 public class ConstString extends ConstInstruction {
@@ -77,4 +81,14 @@
   public ConstString asConstString() {
     return this;
   }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+    stack.storeOutValue(this, LocalType.REFERENCE, it);
+  }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    builder.add(new CfConstString(value));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index 48cc8e6..dbcf734 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -6,6 +6,8 @@
 import com.android.tools.r8.errors.Unreachable;
 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;
@@ -62,4 +64,14 @@
   public boolean canBeDeadCode(IRCode code, InternalOptions options) {
     return false;
   }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+    // Nothing to do for positions which are not actual instructions.
+  }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    // Nothing so far...
+  }
 }
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 54ff97f..6e5a893 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
@@ -3,10 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DebugLocalInfo;
 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.ir.regalloc.RegisterAllocator;
@@ -93,6 +96,16 @@
     }
   }
 
+  public Value swapOutValue(Value newOutValue) {
+    Value oldOutValue = outValue;
+    outValue = null;
+    setOutValue(newOutValue);
+    if (oldOutValue != null) {
+      oldOutValue.definition = null;
+    }
+    return oldOutValue;
+  }
+
   public void addDebugValue(Value value) {
     assert value.hasLocalInfo();
     if (debugValues == null) {
@@ -118,7 +131,13 @@
     return outValue.outType();
   }
 
-  public abstract void buildDex(DexBuilder builder);
+  public void buildDex(DexBuilder builder) {
+    throw new Unreachable("Unexpected instruction when converting to DEX: " + getInstructionName());
+  }
+
+  public void buildCf(CfBuilder builder) {
+    throw new Unimplemented("No support for building CF instructions for: " + getInstructionName());
+  }
 
   public void replaceValue(Value oldValue, Value newValue) {
     for (int i = 0; i < inValues.size(); i++) {
@@ -903,4 +922,8 @@
 
   // Returns the inlining constraint for this instruction.
   public abstract Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder);
+
+  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+    throw new Unimplemented("Implment load/store insertion for: " + getInstructionName());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index a56f840..0b6494d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -3,14 +3,17 @@
 // 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.CfInvoke;
 import com.android.tools.r8.code.InvokeDirectRange;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.List;
+import org.objectweb.asm.Opcodes;
 
 public class InvokeDirect extends InvokeMethodWithReceiver {
 
@@ -91,4 +94,9 @@
   DexEncodedMethod lookupTarget(AppInfo appInfo) {
     return appInfo.lookupDirectTarget(getInvokedMethod());
   }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    builder.add(new CfInvoke(Opcodes.INVOKESPECIAL, getInvokedMethod()));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index cd6497d..75a449b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -9,9 +9,12 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.conversion.CfBuilder.LocalType;
+import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.InliningOracle;
+import java.util.ArrayList;
 import java.util.List;
 
 public abstract class InvokeMethod extends Invoke {
@@ -76,4 +79,28 @@
   }
 
   public abstract InlineAction computeInlining(InliningOracle decider);
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+    List<LocalType> types;
+    if (isInvokeMethodWithReceiver()) {
+      types = new ArrayList<>(method.getArity() + 1);
+      types.add(LocalType.REFERENCE);
+    } else {
+      types = new ArrayList<>(method.getArity());
+    }
+    for (DexType type : method.proto.parameters.values) {
+      types.add(LocalType.fromDexType(type));
+    }
+    stack.loadInValues(this, inValues, types, it);
+    if (method.proto.returnType.isVoidType()) {
+      return;
+    }
+    if (outValue == null) {
+      stack.popOutValue(this, MoveType.fromDexType(method.proto.returnType), it);
+    } else {
+      LocalType returnType = LocalType.fromDexType(getInvokedMethod().proto.returnType);
+      stack.storeOutValue(this, returnType, it);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index c5cfd50..41418b3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -3,13 +3,16 @@
 // 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.CfInvoke;
 import com.android.tools.r8.code.InvokeVirtualRange;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.List;
+import org.objectweb.asm.Opcodes;
 
 public class InvokeVirtual extends InvokeMethodWithReceiver {
 
@@ -79,4 +82,9 @@
     DexMethod method = getInvokedMethod();
     return appInfo.lookupVirtualTarget(method.holder, method);
   }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    builder.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, getInvokedMethod()));
+  }
 }
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
new file mode 100644
index 0000000..e82149a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/Load.java
@@ -0,0 +1,53 @@
+// 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.ir.code;
+
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.errors.Unreachable;
+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.LocalType;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+
+public class Load extends Instruction {
+
+  private final LocalType type;
+
+  public Load(LocalType type, StackValue dest, Value src) {
+    super(dest, src);
+    this.type = type;
+  }
+
+  @Override
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
+    return true;
+  }
+
+  @Override
+  public int compareNonValueParts(Instruction other) {
+    return 0;
+  }
+
+  @Override
+  public int maxInValueRegister() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public int maxOutValueRegister() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    Value value = inValues.get(0);
+    builder.add(new CfLoad(type, value.getNumber()));
+  }
+}
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
new file mode 100644
index 0000000..61ab130
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/Pop.java
@@ -0,0 +1,41 @@
+// 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.ir.code;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+
+public class Pop extends Instruction {
+
+  public Pop(StackValue src) {
+    super(null, src);
+  }
+
+  @Override
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
+    return true;
+  }
+
+  @Override
+  public int compareNonValueParts(Instruction other) {
+    return 0;
+  }
+
+  @Override
+  public int maxInValueRegister() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public int maxOutValueRegister() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+    throw new Unreachable();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Return.java b/src/main/java/com/android/tools/r8/ir/code/Return.java
index e9311e3..e16cf95 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Return.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Return.java
@@ -3,6 +3,7 @@
 // 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.CfReturn;
 import com.android.tools.r8.code.ReturnObject;
 import com.android.tools.r8.code.ReturnVoid;
 import com.android.tools.r8.code.ReturnWide;
@@ -10,6 +11,9 @@
 import com.android.tools.r8.errors.Unreachable;
 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.LocalType;
+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;
 
@@ -113,4 +117,25 @@
   public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
     return Constraint.ALWAYS;
   }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+    if (isReturnVoid()) {
+      return;
+    }
+    Value oldValue = returnValue();
+    StackValue stackValue = new StackValue(returnType);
+    replaceValue(oldValue, stackValue);
+    Load load =
+        new Load(LocalType.fromDexType(stack.method.proto.returnType), stackValue, oldValue);
+    load.setBlock(getBlock());
+    it.previous();
+    it.add(load);
+    it.next();
+  }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    builder.add(new CfReturn());
+  }
 }
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
new file mode 100644
index 0000000..f743e9e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/StackValue.java
@@ -0,0 +1,16 @@
+// 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.ir.code;
+
+public class StackValue extends Value {
+
+  public StackValue(MoveType type) {
+    super(Value.UNDEFINED_NUMBER, type, null);
+  }
+
+  @Override
+  public String toString() {
+    return "s";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index 5075e7a..8338db5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -3,6 +3,7 @@
 // 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.CfStaticGet;
 import com.android.tools.r8.code.Sget;
 import com.android.tools.r8.code.SgetBoolean;
 import com.android.tools.r8.code.SgetByte;
@@ -16,6 +17,9 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.CfBuilder.LocalType;
+import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 
 public class StaticGet extends FieldInstruction {
@@ -112,4 +116,14 @@
   public StaticGet asStaticGet() {
     return this;
   }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+    stack.storeOutValue(this, LocalType.fromDexType(field.type), it);
+  }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    builder.add(new CfStaticGet(field));
+  }
 }
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
new file mode 100644
index 0000000..2124a40
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/Store.java
@@ -0,0 +1,52 @@
+// 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.ir.code;
+
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.errors.Unreachable;
+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.LocalType;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+
+public class Store extends Instruction {
+
+  private final LocalType type;
+
+  public Store(LocalType type, Value dest, StackValue src) {
+    super(dest, src);
+    this.type = type;
+  }
+
+  @Override
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
+    return true;
+  }
+
+  @Override
+  public int compareNonValueParts(Instruction other) {
+    return 0;
+  }
+
+  @Override
+  public int maxInValueRegister() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public int maxOutValueRegister() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    builder.add(new CfStore(type, outValue.getNumber()));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index ea43cf8..4baf23f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -68,7 +68,9 @@
     }
   }
 
-  public static final Value UNDEFINED = new Value(-1, MoveType.OBJECT, null);
+  public static final int UNDEFINED_NUMBER = -1;
+
+  public static final Value UNDEFINED = new Value(UNDEFINED_NUMBER, MoveType.OBJECT, null);
 
   protected final int number;
   protected final MoveType type;
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
new file mode 100644
index 0000000..750003f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -0,0 +1,156 @@
+// 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.ir.conversion;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+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.MoveType;
+import com.android.tools.r8.ir.code.Pop;
+import com.android.tools.r8.ir.code.StackValue;
+import com.android.tools.r8.ir.code.Store;
+import com.android.tools.r8.ir.code.Value;
+import java.util.ArrayList;
+import java.util.List;
+
+public class CfBuilder {
+
+  public enum LocalType {
+    REFERENCE,
+    INTEGER,
+    FLOAT,
+    LONG,
+    DOUBLE;
+
+    public static LocalType fromDexType(DexType type) {
+      switch (type.toShorty()) {
+        case 'Z':
+        case 'B':
+        case 'C':
+        case 'S':
+        case 'I':
+          return INTEGER;
+        case 'F':
+          return FLOAT;
+        case 'J':
+          return LONG;
+        case 'D':
+          return DOUBLE;
+        case 'L':
+          return REFERENCE;
+        default:
+          throw new Unreachable("Unexpected type " + type);
+      }
+    }
+  }
+
+  private final DexEncodedMethod method;
+  private final IRCode code;
+  private List<CfInstruction> instructions;
+
+  public static class StackHelper {
+
+    public final DexMethod method;
+
+    public StackHelper(DexMethod method) {
+      this.method = method;
+    }
+
+    public void loadInValues(
+        Instruction instruction,
+        List<Value> values,
+        List<LocalType> types,
+        InstructionListIterator it) {
+      assert values.size() == types.size();
+      it.previous();
+      for (int i = 0; i < values.size(); i++) {
+        Value value = values.get(i);
+        LocalType type = types.get(i);
+        StackValue stackValue = new StackValue(value.outType());
+        instruction.replaceValue(value, stackValue);
+        add(new Load(type, stackValue, value), instruction, it);
+      }
+      it.next();
+    }
+
+    public void storeOutValue(Instruction instruction, LocalType type, InstructionListIterator it) {
+      StackValue newOutValue = new StackValue(instruction.outType());
+      Value oldOutValue = instruction.swapOutValue(newOutValue);
+      add(new Store(type, oldOutValue, newOutValue), instruction, it);
+    }
+
+    public void popOutValue(Instruction instruction, MoveType type, InstructionListIterator it) {
+      StackValue newOutValue = new StackValue(type);
+      instruction.swapOutValue(newOutValue);
+      add(new Pop(newOutValue), instruction, it);
+    }
+
+    private static void add(
+        Instruction newInstruction, Instruction existingInstruction, InstructionListIterator it) {
+      newInstruction.setBlock(existingInstruction.getBlock());
+      newInstruction.setPosition(existingInstruction.getPosition());
+      it.add(newInstruction);
+    }
+  }
+
+  public CfBuilder(DexEncodedMethod method, IRCode code) {
+    this.method = method;
+    this.code = code;
+  }
+
+  public Code build() {
+    try {
+      loadStoreInsertion();
+      // TODO(zerny): Optimize load/store patterns.
+      // TODO(zerny): Compute locals/register allocation.
+      // TODO(zerny): Compute debug info.
+      return buildCfCode();
+    } catch (Unimplemented e) {
+      System.out.println("Incomplete CF construction: " + e.getMessage());
+      return method.getCode().asJarCode();
+    }
+  }
+
+  private void loadStoreInsertion() {
+    StackHelper stack = new StackHelper(method.method);
+    for (BasicBlock block : code.blocks) {
+      InstructionListIterator it = block.listIterator();
+      while (it.hasNext()) {
+        Instruction current = it.next();
+        current.insertLoadAndStores(it, stack);
+      }
+    }
+  }
+
+  private CfCode buildCfCode() {
+    instructions = new ArrayList<>();
+    InstructionIterator it = code.instructionIterator();
+    while (it.hasNext()) {
+      it.next().buildCf(this);
+    }
+    return new CfCode(instructions);
+  }
+
+  // Callbacks
+
+  public void add(CfInstruction instruction) {
+    instructions.add(instruction);
+  }
+
+  public void addArgument(Argument argument) {
+    // Nothing so far.
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index da022fe..de6d86b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -587,7 +587,11 @@
 
   private void finalizeToCf(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     assert method.getCode().isJarCode();
-    // TODO(zerny): Actually convert IR back to Java bytecode.
+    CfBuilder builder = new CfBuilder(method, code);
+    // TODO(zerny): Change the return type of CfBuilder::build CfCode once complete.
+    Code result = builder.build();
+    assert result.isCfCode() || result.isJarCode();
+    method.setCode(result);
   }
 
   private void finalizeToDex(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
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 483327b..bd5ae63 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -3,19 +3,30 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jar;
 
+import static org.objectweb.asm.Opcodes.ASM6;
+
 import com.android.tools.r8.OutputSink;
 import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.Code;
 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;
+import java.io.StringWriter;
 import java.util.Collections;
 import java.util.concurrent.ExecutorService;
+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;
+import org.objectweb.asm.util.Textifier;
+import org.objectweb.asm.util.TraceMethodVisitor;
 
 public class CfApplicationWriter {
   private final DexApplication application;
@@ -65,7 +76,11 @@
     for (DexEncodedMethod method : clazz.directMethods()) {
       writeMethod(method, writer);
     }
-    outputSink.writeClassFile(writer.toByteArray(), Collections.singleton(desc), desc);
+    writer.visitEnd();
+
+    byte[] result = writer.toByteArray();
+    assert verifyCf(result);
+    outputSink.writeClassFile(result, Collections.singleton(desc), desc);
   }
 
   private void writeMethod(DexEncodedMethod method, ClassWriter writer) {
@@ -75,10 +90,40 @@
     String signature = null; // TODO(zerny): Support generic signatures.
     String[] exceptions = null;
     MethodVisitor visitor = writer.visitMethod(access, name, desc, signature, exceptions);
-    method.getCode().asJarCode().writeTo(visitor);
+    writeCode(method.getCode(), visitor);
+  }
+
+  private void writeCode(Code code, MethodVisitor visitor) {
+    if (code.isJarCode()) {
+      code.asJarCode().writeTo(visitor);
+    } else {
+      assert code.isCfCode();
+      code.asCfCode().write(visitor);
+    }
   }
 
   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);
+    reader.accept(node, ASM6);
+    StringWriter writer = new StringWriter();
+    for (MethodNode method : node.methods) {
+      TraceMethodVisitor visitor = new TraceMethodVisitor(new Textifier());
+      method.accept(visitor);
+      visitor.p.print(new PrintWriter(writer));
+      writer.append('\n');
+    }
+    return writer.toString();
+  }
+
+  private static boolean verifyCf(byte[] result) {
+    ClassReader reader = new ClassReader(result);
+    PrintWriter pw = new PrintWriter(System.out);
+    CheckClassAdapter.verify(reader, false, pw);
+    return true;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 96d9e8b..d353d88 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -122,7 +122,8 @@
     };
 
     String[] javaBytecodeTests = {
-      "hello.Hello",
+        "hello.Hello",
+        "arithmetic.Arithmetic",
     };
 
     List<String[]> fullTestList = new ArrayList<>(tests.length * 2);
@@ -148,6 +149,7 @@
           makeTest(
               Input.JAVAC_ALL, CompilerUnderTest.R8, CompilationMode.RELEASE, test, Output.CF));
     }
+
     return fullTestList;
   }
 
@@ -264,6 +266,8 @@
     ToolHelper.ProcessResult javaResult =
         ToolHelper.runJava(ImmutableList.of(getOriginalJarFile("").toString()), mainClass);
     if (javaResult.exitCode != 0) {
+      System.out.println(javaResult.stdout);
+      System.err.println(javaResult.stderr);
       fail("JVM failed for: " + mainClass);
     }