Merge "Make CF frontend for testing lazy"
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 52217a3..08d65f0 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -60,6 +60,10 @@
     throw new Unreachable(getClass().getCanonicalName() + ".asCfCode()");
   }
 
+  public LazyCfCode asLazyCfCode() {
+    throw new Unreachable(getClass().getCanonicalName() + ".asLazyCfCode()");
+  }
+
   public DexCode asDexCode() {
     throw new Unreachable(getClass().getCanonicalName() + ".asDexCode()");
   }
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index a1125af..2f1d3fd 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -3,57 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import static org.objectweb.asm.ClassReader.EXPAND_FRAMES;
 import static org.objectweb.asm.ClassReader.SKIP_FRAMES;
 import static org.objectweb.asm.Opcodes.ACC_DEPRECATED;
 import static org.objectweb.asm.Opcodes.ASM6;
 
 import com.android.tools.r8.ProgramResource.Kind;
-import com.android.tools.r8.cf.code.CfArithmeticBinop;
-import com.android.tools.r8.cf.code.CfArrayLength;
-import com.android.tools.r8.cf.code.CfArrayLoad;
-import com.android.tools.r8.cf.code.CfArrayStore;
-import com.android.tools.r8.cf.code.CfCheckCast;
-import com.android.tools.r8.cf.code.CfCmp;
-import com.android.tools.r8.cf.code.CfConstClass;
-import com.android.tools.r8.cf.code.CfConstMethodHandle;
-import com.android.tools.r8.cf.code.CfConstMethodType;
-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.CfFieldInstruction;
-import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
-import com.android.tools.r8.cf.code.CfGoto;
-import com.android.tools.r8.cf.code.CfIf;
-import com.android.tools.r8.cf.code.CfIfCmp;
-import com.android.tools.r8.cf.code.CfIinc;
-import com.android.tools.r8.cf.code.CfInstanceOf;
-import com.android.tools.r8.cf.code.CfInstruction;
-import com.android.tools.r8.cf.code.CfInvoke;
-import com.android.tools.r8.cf.code.CfInvokeDynamic;
-import com.android.tools.r8.cf.code.CfLabel;
-import com.android.tools.r8.cf.code.CfLoad;
-import com.android.tools.r8.cf.code.CfLogicalBinop;
-import com.android.tools.r8.cf.code.CfMonitor;
-import com.android.tools.r8.cf.code.CfMultiANewArray;
-import com.android.tools.r8.cf.code.CfNeg;
-import com.android.tools.r8.cf.code.CfNew;
-import com.android.tools.r8.cf.code.CfNewArray;
-import com.android.tools.r8.cf.code.CfNop;
-import com.android.tools.r8.cf.code.CfNumberConversion;
-import com.android.tools.r8.cf.code.CfPosition;
-import com.android.tools.r8.cf.code.CfReturn;
-import com.android.tools.r8.cf.code.CfReturnVoid;
-import com.android.tools.r8.cf.code.CfStackInstruction;
-import com.android.tools.r8.cf.code.CfStore;
-import com.android.tools.r8.cf.code.CfSwitch;
-import com.android.tools.r8.cf.code.CfThrow;
-import com.android.tools.r8.cf.code.CfTryCatch;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
 import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
 import com.android.tools.r8.graph.DexValue.DexValueBoolean;
@@ -68,26 +25,15 @@
 import com.android.tools.r8.graph.DexValue.DexValueShort;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.DexValue.DexValueType;
-import com.android.tools.r8.graph.JarCode.ReparseContext;
-import com.android.tools.r8.ir.code.If;
-import com.android.tools.r8.ir.code.MemberType;
-import com.android.tools.r8.ir.code.Monitor;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
-import java.util.IdentityHashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
@@ -96,10 +42,8 @@
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.FieldVisitor;
-import org.objectweb.asm.Handle;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
 import org.objectweb.asm.TypePath;
 
@@ -144,12 +88,9 @@
     input.reset();
 
     ClassReader reader = new ClassReader(input);
-    int flags = SKIP_FRAMES;
-    if (application.options.enableCfFrontend) {
-      flags = EXPAND_FRAMES;
-    }
     reader.accept(
-        new CreateDexClassVisitor(origin, classKind, reader.b, application, classConsumer), flags);
+        new CreateDexClassVisitor(origin, classKind, reader.b, application, classConsumer),
+        SKIP_FRAMES);
   }
 
   private static int cleanAccessFlags(int access) {
@@ -316,9 +257,6 @@
     @Override
     public MethodVisitor visitMethod(
         int access, String name, String desc, String signature, String[] exceptions) {
-      if (application.options.enableCfFrontend) {
-        return new CfCreateMethodVisitor(access, name, desc, signature, exceptions, this);
-      }
       return new CreateMethodVisitor(access, name, desc, signature, exceptions, this);
     }
 
@@ -628,7 +566,11 @@
     public void visitCode() {
       assert !flags.isAbstract() && !flags.isNative();
       if (parent.classKind == ClassKind.PROGRAM) {
-        code = new JarCode(method, parent.origin, parent.context, parent.application);
+        if (parent.application.options.enableCfFrontend) {
+          code = new LazyCfCode(method, parent.origin, parent.context, parent.application);
+        } else {
+          code = new JarCode(method, parent.origin, parent.context, parent.application);
+        }
       }
     }
 
@@ -682,646 +624,6 @@
     }
   }
 
-  private static class CfCreateMethodVisitor extends CreateMethodVisitor {
-
-    private final JarApplicationReader application;
-    private final DexItemFactory factory;
-    private int maxStack;
-    private int maxLocals;
-    private List<CfInstruction> instructions;
-    private List<CfTryCatch> tryCatchRanges;
-    private List<LocalVariableInfo> localVariables;
-    private Map<Label, CfLabel> labelMap;
-
-    public CfCreateMethodVisitor(
-        int access,
-        String name,
-        String desc,
-        String signature,
-        String[] exceptions,
-        CreateDexClassVisitor parent) {
-      super(access, name, desc, signature, exceptions, parent);
-      this.application = parent.application;
-      this.factory = application.getFactory();
-    }
-
-    @Override
-    public void visitCode() {
-      assert !flags.isAbstract() && !flags.isNative();
-      maxStack = 0;
-      maxLocals = 0;
-      instructions = new ArrayList<>();
-      tryCatchRanges = new ArrayList<>();
-      localVariables = new ArrayList<>();
-      labelMap = new IdentityHashMap<>();
-    }
-
-    @Override
-    public void visitEnd() {
-      if (!flags.isAbstract() && !flags.isNative() && parent.classKind == ClassKind.PROGRAM) {
-        code =
-            new CfCode(method, maxStack, maxLocals, instructions, tryCatchRanges, localVariables);
-      }
-      super.visitEnd();
-    }
-
-    @Override
-    public void visitFrame(
-        int frameType, int nLocals, Object[] localTypes, int nStack, Object[] stackTypes) {
-      assert frameType == Opcodes.F_NEW;
-      Int2ReferenceSortedMap<FrameType> parsedLocals = parseLocals(nLocals, localTypes);
-      List<FrameType> parsedStack = parseStack(nStack, stackTypes);
-      instructions.add(new CfFrame(parsedLocals, parsedStack));
-    }
-
-    private Int2ReferenceSortedMap<FrameType> parseLocals(int typeCount, Object[] asmTypes) {
-      Int2ReferenceSortedMap<FrameType> types = new Int2ReferenceAVLTreeMap<>();
-      int i = 0;
-      for (int j = 0; j < typeCount; j++) {
-        Object localType = asmTypes[j];
-        FrameType value = getFrameType(localType);
-        types.put(i++, value);
-        if (value.isWide()) {
-          i++;
-        }
-      }
-      return types;
-    }
-
-    private List<FrameType> parseStack(int nStack, Object[] stackTypes) {
-      List<FrameType> dexStack = new ArrayList<>(nStack);
-      for (int i = 0; i < nStack; i++) {
-        dexStack.add(getFrameType(stackTypes[i]));
-      }
-      return dexStack;
-    }
-
-    private FrameType getFrameType(Object localType) {
-      if (localType instanceof Label) {
-        return FrameType.uninitializedNew(getLabel((Label) localType));
-      } else if (localType == Opcodes.UNINITIALIZED_THIS) {
-        return FrameType.uninitializedThis();
-      } else if (localType == null || localType == Opcodes.TOP) {
-        return FrameType.top();
-      } else {
-        return FrameType.initialized(parseAsmType(localType));
-      }
-    }
-
-    private CfLabel getLabel(Label label) {
-      return labelMap.computeIfAbsent(label, l -> new CfLabel());
-    }
-
-    private DexType parseAsmType(Object local) {
-      assert local != null && local != Opcodes.TOP;
-      if (local == Opcodes.INTEGER) {
-        return factory.intType;
-      } else if (local == Opcodes.FLOAT) {
-        return factory.floatType;
-      } else if (local == Opcodes.LONG) {
-        return factory.longType;
-      } else if (local == Opcodes.DOUBLE) {
-        return factory.doubleType;
-      } else if (local == Opcodes.NULL) {
-        return DexItemFactory.nullValueType;
-      } else if (local instanceof String) {
-        return createTypeFromInternalType((String) local);
-      } else {
-        throw new Unreachable("Unexpected ASM type: " + local);
-      }
-    }
-
-    private DexType createTypeFromInternalType(String local) {
-      assert local.indexOf('.') == -1;
-      return factory.createType("L" + local + ";");
-    }
-
-    @Override
-    public void visitInsn(int opcode) {
-      switch (opcode) {
-        case Opcodes.NOP:
-          instructions.add(new CfNop());
-          break;
-        case Opcodes.ACONST_NULL:
-          instructions.add(new CfConstNull());
-          break;
-        case Opcodes.ICONST_M1:
-        case Opcodes.ICONST_0:
-        case Opcodes.ICONST_1:
-        case Opcodes.ICONST_2:
-        case Opcodes.ICONST_3:
-        case Opcodes.ICONST_4:
-        case Opcodes.ICONST_5:
-          instructions.add(new CfConstNumber(opcode - Opcodes.ICONST_0, ValueType.INT));
-          break;
-        case Opcodes.LCONST_0:
-        case Opcodes.LCONST_1:
-          instructions.add(new CfConstNumber(opcode - Opcodes.LCONST_0, ValueType.LONG));
-          break;
-        case Opcodes.FCONST_0:
-        case Opcodes.FCONST_1:
-        case Opcodes.FCONST_2:
-          instructions.add(
-              new CfConstNumber(
-                  Float.floatToRawIntBits(opcode - Opcodes.FCONST_0), ValueType.FLOAT));
-          break;
-        case Opcodes.DCONST_0:
-        case Opcodes.DCONST_1:
-          instructions.add(
-              new CfConstNumber(
-                  Double.doubleToRawLongBits(opcode - Opcodes.DCONST_0), ValueType.DOUBLE));
-          break;
-        case Opcodes.IALOAD:
-        case Opcodes.LALOAD:
-        case Opcodes.FALOAD:
-        case Opcodes.DALOAD:
-        case Opcodes.AALOAD:
-        case Opcodes.BALOAD:
-        case Opcodes.CALOAD:
-        case Opcodes.SALOAD:
-          instructions.add(new CfArrayLoad(getMemberTypeForOpcode(opcode)));
-          break;
-        case Opcodes.IASTORE:
-        case Opcodes.LASTORE:
-        case Opcodes.FASTORE:
-        case Opcodes.DASTORE:
-        case Opcodes.AASTORE:
-        case Opcodes.BASTORE:
-        case Opcodes.CASTORE:
-        case Opcodes.SASTORE:
-          instructions.add(new CfArrayStore(getMemberTypeForOpcode(opcode)));
-          break;
-        case Opcodes.POP:
-        case Opcodes.POP2:
-        case Opcodes.DUP:
-        case Opcodes.DUP_X1:
-        case Opcodes.DUP_X2:
-        case Opcodes.DUP2:
-        case Opcodes.DUP2_X1:
-        case Opcodes.DUP2_X2:
-        case Opcodes.SWAP:
-          instructions.add(CfStackInstruction.fromAsm(opcode));
-          break;
-        case Opcodes.IADD:
-        case Opcodes.LADD:
-        case Opcodes.FADD:
-        case Opcodes.DADD:
-        case Opcodes.ISUB:
-        case Opcodes.LSUB:
-        case Opcodes.FSUB:
-        case Opcodes.DSUB:
-        case Opcodes.IMUL:
-        case Opcodes.LMUL:
-        case Opcodes.FMUL:
-        case Opcodes.DMUL:
-        case Opcodes.IDIV:
-        case Opcodes.LDIV:
-        case Opcodes.FDIV:
-        case Opcodes.DDIV:
-        case Opcodes.IREM:
-        case Opcodes.LREM:
-        case Opcodes.FREM:
-        case Opcodes.DREM:
-          instructions.add(CfArithmeticBinop.fromAsm(opcode));
-          break;
-        case Opcodes.INEG:
-        case Opcodes.LNEG:
-        case Opcodes.FNEG:
-        case Opcodes.DNEG:
-          instructions.add(CfNeg.fromAsm(opcode));
-          break;
-        case Opcodes.ISHL:
-        case Opcodes.LSHL:
-        case Opcodes.ISHR:
-        case Opcodes.LSHR:
-        case Opcodes.IUSHR:
-        case Opcodes.LUSHR:
-        case Opcodes.IAND:
-        case Opcodes.LAND:
-        case Opcodes.IOR:
-        case Opcodes.LOR:
-        case Opcodes.IXOR:
-        case Opcodes.LXOR:
-          instructions.add(CfLogicalBinop.fromAsm(opcode));
-          break;
-        case Opcodes.I2L:
-        case Opcodes.I2F:
-        case Opcodes.I2D:
-        case Opcodes.L2I:
-        case Opcodes.L2F:
-        case Opcodes.L2D:
-        case Opcodes.F2I:
-        case Opcodes.F2L:
-        case Opcodes.F2D:
-        case Opcodes.D2I:
-        case Opcodes.D2L:
-        case Opcodes.D2F:
-        case Opcodes.I2B:
-        case Opcodes.I2C:
-        case Opcodes.I2S:
-          instructions.add(CfNumberConversion.fromAsm(opcode));
-          break;
-        case Opcodes.LCMP:
-        case Opcodes.FCMPL:
-        case Opcodes.FCMPG:
-        case Opcodes.DCMPL:
-        case Opcodes.DCMPG:
-          instructions.add(CfCmp.fromAsm(opcode));
-          break;
-        case Opcodes.IRETURN:
-          instructions.add(new CfReturn(ValueType.INT));
-          break;
-        case Opcodes.LRETURN:
-          instructions.add(new CfReturn(ValueType.LONG));
-          break;
-        case Opcodes.FRETURN:
-          instructions.add(new CfReturn(ValueType.FLOAT));
-          break;
-        case Opcodes.DRETURN:
-          instructions.add(new CfReturn(ValueType.DOUBLE));
-          break;
-        case Opcodes.ARETURN:
-          instructions.add(new CfReturn(ValueType.OBJECT));
-          break;
-        case Opcodes.RETURN:
-          instructions.add(new CfReturnVoid());
-          break;
-        case Opcodes.ARRAYLENGTH:
-          instructions.add(new CfArrayLength());
-          break;
-        case Opcodes.ATHROW:
-          instructions.add(new CfThrow());
-          break;
-        case Opcodes.MONITORENTER:
-          instructions.add(new CfMonitor(Monitor.Type.ENTER));
-          break;
-        case Opcodes.MONITOREXIT:
-          instructions.add(new CfMonitor(Monitor.Type.EXIT));
-          break;
-        default:
-          throw new Unreachable("Unknown instruction");
-      }
-    }
-
-    private DexType opType(int opcode, DexItemFactory factory) {
-      switch (opcode) {
-        case Opcodes.IADD:
-        case Opcodes.ISUB:
-        case Opcodes.IMUL:
-        case Opcodes.IDIV:
-        case Opcodes.IREM:
-        case Opcodes.INEG:
-        case Opcodes.ISHL:
-        case Opcodes.ISHR:
-        case Opcodes.IUSHR:
-          return factory.intType;
-        case Opcodes.LADD:
-        case Opcodes.LSUB:
-        case Opcodes.LMUL:
-        case Opcodes.LDIV:
-        case Opcodes.LREM:
-        case Opcodes.LNEG:
-        case Opcodes.LSHL:
-        case Opcodes.LSHR:
-        case Opcodes.LUSHR:
-          return factory.longType;
-        case Opcodes.FADD:
-        case Opcodes.FSUB:
-        case Opcodes.FMUL:
-        case Opcodes.FDIV:
-        case Opcodes.FREM:
-        case Opcodes.FNEG:
-          return factory.floatType;
-        case Opcodes.DADD:
-        case Opcodes.DSUB:
-        case Opcodes.DMUL:
-        case Opcodes.DDIV:
-        case Opcodes.DREM:
-        case Opcodes.DNEG:
-          return factory.doubleType;
-        default:
-          throw new Unreachable("Unexpected opcode " + opcode);
-      }
-    }
-
-    private static MemberType getMemberTypeForOpcode(int opcode) {
-      switch (opcode) {
-        case Opcodes.IALOAD:
-        case Opcodes.IASTORE:
-          return MemberType.INT;
-        case Opcodes.FALOAD:
-        case Opcodes.FASTORE:
-          return MemberType.FLOAT;
-        case Opcodes.LALOAD:
-        case Opcodes.LASTORE:
-          return MemberType.LONG;
-        case Opcodes.DALOAD:
-        case Opcodes.DASTORE:
-          return MemberType.DOUBLE;
-        case Opcodes.AALOAD:
-        case Opcodes.AASTORE:
-          return MemberType.OBJECT;
-        case Opcodes.BALOAD:
-        case Opcodes.BASTORE:
-          return MemberType.BOOLEAN; // TODO: Distinguish byte and boolean.
-        case Opcodes.CALOAD:
-        case Opcodes.CASTORE:
-          return MemberType.CHAR;
-        case Opcodes.SALOAD:
-        case Opcodes.SASTORE:
-          return MemberType.SHORT;
-        default:
-          throw new Unreachable("Unexpected array opcode " + opcode);
-      }
-    }
-
-    @Override
-    public void visitIntInsn(int opcode, int operand) {
-      switch (opcode) {
-        case Opcodes.SIPUSH:
-        case Opcodes.BIPUSH:
-          instructions.add(new CfConstNumber(operand, ValueType.INT));
-          break;
-        case Opcodes.NEWARRAY:
-          instructions.add(
-              new CfNewArray(factory.createArrayType(1, arrayTypeDesc(operand, factory))));
-          break;
-        default:
-          throw new Unreachable("Unexpected int opcode " + opcode);
-      }
-    }
-
-    private static DexType arrayTypeDesc(int arrayTypeCode, DexItemFactory factory) {
-      switch (arrayTypeCode) {
-        case Opcodes.T_BOOLEAN:
-          return factory.booleanType;
-        case Opcodes.T_CHAR:
-          return factory.charType;
-        case Opcodes.T_FLOAT:
-          return factory.floatType;
-        case Opcodes.T_DOUBLE:
-          return factory.doubleType;
-        case Opcodes.T_BYTE:
-          return factory.byteType;
-        case Opcodes.T_SHORT:
-          return factory.shortType;
-        case Opcodes.T_INT:
-          return factory.intType;
-        case Opcodes.T_LONG:
-          return factory.longType;
-        default:
-          throw new Unreachable("Unexpected array-type code " + arrayTypeCode);
-      }
-    }
-
-    @Override
-    public void visitVarInsn(int opcode, int var) {
-      ValueType type;
-      switch (opcode) {
-        case Opcodes.ILOAD:
-        case Opcodes.ISTORE:
-          type = ValueType.INT;
-          break;
-        case Opcodes.FLOAD:
-        case Opcodes.FSTORE:
-          type = ValueType.FLOAT;
-          break;
-        case Opcodes.LLOAD:
-        case Opcodes.LSTORE:
-          type = ValueType.LONG;
-          break;
-        case Opcodes.DLOAD:
-        case Opcodes.DSTORE:
-          type = ValueType.DOUBLE;
-          break;
-        case Opcodes.ALOAD:
-        case Opcodes.ASTORE:
-          type = ValueType.OBJECT;
-          break;
-        case Opcodes.RET:
-          throw new Unreachable("RET should be handled by the ASM jsr inliner");
-        default:
-          throw new Unreachable("Unexpected VarInsn opcode: " + opcode);
-      }
-      if (Opcodes.ILOAD <= opcode && opcode <= Opcodes.ALOAD) {
-        instructions.add(new CfLoad(type, var));
-      } else {
-        instructions.add(new CfStore(type, var));
-      }
-    }
-
-    @Override
-    public void visitTypeInsn(int opcode, String typeName) {
-      DexType type = createTypeFromInternalType(typeName);
-      switch (opcode) {
-        case Opcodes.NEW:
-          instructions.add(new CfNew(type));
-          break;
-        case Opcodes.ANEWARRAY:
-          instructions.add(new CfNewArray(factory.createArrayType(1, type)));
-          break;
-        case Opcodes.CHECKCAST:
-          instructions.add(new CfCheckCast(type));
-          break;
-        case Opcodes.INSTANCEOF:
-          instructions.add(new CfInstanceOf(type));
-          break;
-        default:
-          throw new Unreachable("Unexpected TypeInsn opcode: " + opcode);
-      }
-    }
-
-    @Override
-    public void visitFieldInsn(int opcode, String owner, String name, String desc) {
-      DexField field =
-          factory.createField(createTypeFromInternalType(owner), factory.createType(desc), name);
-      // TODO(mathiasr): Don't require CfFieldInstruction::declaringField. It is needed for proper
-      // renaming in the backend, but it is not available here in the frontend.
-      instructions.add(new CfFieldInstruction(opcode, field, field));
-    }
-
-    @Override
-    public void visitMethodInsn(int opcode, String owner, String name, String desc) {
-      visitMethodInsn(opcode, owner, name, desc, false);
-    }
-
-    @Override
-    public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
-      DexMethod method = application.getMethod(owner, name, desc);
-      instructions.add(new CfInvoke(opcode, method, itf));
-    }
-
-    @Override
-    public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
-      DexCallSite callSite =
-          DexCallSite.fromAsmInvokeDynamic(application, parent.type, name, desc, bsm, bsmArgs);
-      instructions.add(new CfInvokeDynamic(callSite));
-    }
-
-    @Override
-    public void visitJumpInsn(int opcode, Label label) {
-      CfLabel target = getLabel(label);
-      if (Opcodes.IFEQ <= opcode && opcode <= Opcodes.IF_ACMPNE) {
-        if (opcode <= Opcodes.IFLE) {
-          // IFEQ, IFNE, IFLT, IFGE, IFGT, or IFLE.
-          instructions.add(new CfIf(ifType(opcode), ValueType.INT, target));
-        } else {
-          // IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, or
-          // IF_ACMPNE.
-          ValueType valueType;
-          if (opcode <= Opcodes.IF_ICMPLE) {
-            valueType = ValueType.INT;
-          } else {
-            valueType = ValueType.OBJECT;
-          }
-          instructions.add(new CfIfCmp(ifType(opcode), valueType, target));
-        }
-      } else {
-        // GOTO, JSR, IFNULL or IFNONNULL.
-        switch (opcode) {
-          case Opcodes.GOTO:
-            instructions.add(new CfGoto(target));
-            break;
-          case Opcodes.IFNULL:
-          case Opcodes.IFNONNULL:
-            If.Type type = opcode == Opcodes.IFNULL ? If.Type.EQ : If.Type.NE;
-            instructions.add(new CfIf(type, ValueType.OBJECT, target));
-            break;
-          case Opcodes.JSR:
-            throw new Unreachable("JSR should be handled by the ASM jsr inliner");
-          default:
-            throw new Unreachable("Unexpected JumpInsn opcode: " + opcode);
-        }
-      }
-    }
-
-    private static If.Type ifType(int opcode) {
-      switch (opcode) {
-        case Opcodes.IFEQ:
-        case Opcodes.IF_ICMPEQ:
-        case Opcodes.IF_ACMPEQ:
-          return If.Type.EQ;
-        case Opcodes.IFNE:
-        case Opcodes.IF_ICMPNE:
-        case Opcodes.IF_ACMPNE:
-          return If.Type.NE;
-        case Opcodes.IFLT:
-        case Opcodes.IF_ICMPLT:
-          return If.Type.LT;
-        case Opcodes.IFGE:
-        case Opcodes.IF_ICMPGE:
-          return If.Type.GE;
-        case Opcodes.IFGT:
-        case Opcodes.IF_ICMPGT:
-          return If.Type.GT;
-        case Opcodes.IFLE:
-        case Opcodes.IF_ICMPLE:
-          return If.Type.LE;
-        default:
-          throw new Unreachable("Unexpected If instruction opcode: " + opcode);
-      }
-    }
-
-    @Override
-    public void visitLabel(Label label) {
-      instructions.add(getLabel(label));
-    }
-
-    @Override
-    public void visitLdcInsn(Object cst) {
-      if (cst instanceof Type) {
-        Type type = (Type) cst;
-        if (type.getSort() == Type.METHOD) {
-          DexProto proto = application.getProto(type.getDescriptor());
-          instructions.add(new CfConstMethodType(proto));
-        } else {
-          instructions.add(new CfConstClass(factory.createType(type.getDescriptor())));
-        }
-      } else if (cst instanceof String) {
-        instructions.add(new CfConstString(factory.createString((String) cst)));
-      } else if (cst instanceof Long) {
-        instructions.add(new CfConstNumber((Long) cst, ValueType.LONG));
-      } else if (cst instanceof Double) {
-        long l = Double.doubleToRawLongBits((Double) cst);
-        instructions.add(new CfConstNumber(l, ValueType.DOUBLE));
-      } else if (cst instanceof Integer) {
-        instructions.add(new CfConstNumber((Integer) cst, ValueType.INT));
-      } else if (cst instanceof Float) {
-        long i = Float.floatToRawIntBits((Float) cst);
-        instructions.add(new CfConstNumber(i, ValueType.FLOAT));
-      } else if (cst instanceof Handle) {
-        instructions.add(
-            new CfConstMethodHandle(
-                DexMethodHandle.fromAsmHandle((Handle) cst, application, parent.type)));
-      } else {
-        throw new CompilationError("Unsupported constant: " + cst.toString());
-      }
-    }
-
-    @Override
-    public void visitIincInsn(int var, int increment) {
-      instructions.add(new CfIinc(var, increment));
-    }
-
-    @Override
-    public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
-      assert max == min + labels.length - 1;
-      ArrayList<CfLabel> targets = new ArrayList<>(labels.length);
-      for (Label label : labels) {
-        targets.add(getLabel(label));
-      }
-      instructions.add(new CfSwitch(CfSwitch.Kind.TABLE, getLabel(dflt), new int[] {min}, targets));
-    }
-
-    @Override
-    public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
-      ArrayList<CfLabel> targets = new ArrayList<>(labels.length);
-      for (Label label : labels) {
-        targets.add(getLabel(label));
-      }
-      instructions.add(new CfSwitch(CfSwitch.Kind.LOOKUP, getLabel(dflt), keys, targets));
-    }
-
-    @Override
-    public void visitMultiANewArrayInsn(String desc, int dims) {
-      instructions.add(new CfMultiANewArray(factory.createType(desc), dims));
-    }
-
-    @Override
-    public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
-      List<DexType> guards =
-          Collections.singletonList(
-              type == null ? DexItemFactory.catchAllType : createTypeFromInternalType(type));
-      List<CfLabel> targets = Collections.singletonList(getLabel(handler));
-      tryCatchRanges.add(new CfTryCatch(getLabel(start), getLabel(end), guards, targets));
-    }
-
-    @Override
-    public void visitLocalVariable(
-        String name, String desc, String signature, Label start, Label end, int index) {
-      DebugLocalInfo debugLocalInfo =
-          new DebugLocalInfo(
-              factory.createString(name),
-              factory.createType(desc),
-              signature == null ? null : factory.createString(signature));
-      localVariables.add(
-          new LocalVariableInfo(index, debugLocalInfo, getLabel(start), getLabel(end)));
-    }
-
-    @Override
-    public void visitLineNumber(int line, Label start) {
-      instructions.add(new CfPosition(getLabel(start), new Position(line, null, method, null)));
-    }
-
-    @Override
-    public void visitMaxs(int maxStack, int maxLocals) {
-      assert maxStack >= 0;
-      assert maxLocals >= 0;
-      this.maxStack = maxStack;
-      this.maxLocals = maxLocals;
-    }
-  }
-
   private static class CreateAnnotationVisitor extends AnnotationVisitor {
 
     private final JarApplicationReader application;
@@ -1466,4 +768,14 @@
       return getDexValueArray(value);
     }
   }
+
+  public static class ReparseContext {
+
+    // This will hold the content of the whole class. Once all the methods of the class are swapped
+    // from this to the actual JarCode, no other references would be left and the content can be
+    // GC'd.
+    public byte[] classCache;
+    public DexProgramClass owner;
+    public final List<Code> codeList = new ArrayList<>();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 55ae757..dc75307 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
+import com.android.tools.r8.graph.JarClassFileReader.ReparseContext;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
@@ -17,9 +18,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import java.io.PrintWriter;
 import java.io.StringWriter;
-import java.util.ArrayList;
 import java.util.Iterator;
-import java.util.List;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.MethodVisitor;
@@ -39,16 +38,6 @@
     node.accept(visitor);
   }
 
-  public static class ReparseContext {
-
-    // This will hold the content of the whole class. Once all the methods of the class are swapped
-    // from this to the actual JarCode, no other references would be left and the content can be
-    // GC'd.
-    public byte[] classCache;
-    public DexProgramClass owner;
-    private final List<JarCode> codeList = new ArrayList<>();
-  }
-
   private final DexMethod method;
   private final Origin origin;
   private MethodNode node;
@@ -227,7 +216,7 @@
       JarCode code = null;
       MethodAccessFlags flags = JarClassFileReader.createMethodAccessFlags(name, access);
       if (!flags.isAbstract() && !flags.isNative()) {
-        code = context.codeList.get(methodIndex++);
+        code = context.codeList.get(methodIndex++).asJarCode();
         assert code.method == application.getMethod(context.owner.type, name, desc);
       }
       if (code != null) {
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
new file mode 100644
index 0000000..a864aba
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -0,0 +1,804 @@
+// Copyright (c) 2018, 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.CfArithmeticBinop;
+import com.android.tools.r8.cf.code.CfArrayLength;
+import com.android.tools.r8.cf.code.CfArrayLoad;
+import com.android.tools.r8.cf.code.CfArrayStore;
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfCmp;
+import com.android.tools.r8.cf.code.CfConstClass;
+import com.android.tools.r8.cf.code.CfConstMethodHandle;
+import com.android.tools.r8.cf.code.CfConstMethodType;
+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.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.cf.code.CfGoto;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfIfCmp;
+import com.android.tools.r8.cf.code.CfIinc;
+import com.android.tools.r8.cf.code.CfInstanceOf;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfLogicalBinop;
+import com.android.tools.r8.cf.code.CfMonitor;
+import com.android.tools.r8.cf.code.CfMultiANewArray;
+import com.android.tools.r8.cf.code.CfNeg;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfNewArray;
+import com.android.tools.r8.cf.code.CfNop;
+import com.android.tools.r8.cf.code.CfNumberConversion;
+import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.cf.code.CfSwitch;
+import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
+import com.android.tools.r8.graph.JarClassFileReader.ReparseContext;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.Monitor;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueNumberGenerator;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.InternalOptions;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.JSRInlinerAdapter;
+
+public class LazyCfCode extends Code {
+
+  public LazyCfCode(
+      DexMethod method, Origin origin, ReparseContext context, JarApplicationReader application) {
+
+    this.method = method;
+    this.origin = origin;
+    this.context = context;
+    this.application = application;
+    context.codeList.add(this);
+  }
+
+  private final DexMethod method;
+  private final Origin origin;
+  private final JarApplicationReader application;
+  private CfCode code;
+  private ReparseContext context;
+
+  @Override
+  public boolean isCfCode() {
+    return true;
+  }
+
+  @Override
+  public LazyCfCode asLazyCfCode() {
+    return this;
+  }
+
+  @Override
+  public CfCode asCfCode() {
+    if (code == null) {
+      assert context != null;
+      // The SecondVistor is in charge of setting the context to null.
+      DexProgramClass owner = context.owner;
+      ClassReader classReader = new ClassReader(context.classCache);
+      classReader.accept(new ClassCodeVisitor(context, application), ClassReader.EXPAND_FRAMES);
+      assert verifyNoReparseContext(owner);
+    }
+    assert code != null;
+    return code;
+  }
+
+  private void setCode(CfCode code) {
+    assert this.code == null;
+    assert this.context != null;
+    this.code = code;
+    this.context = null;
+  }
+
+  @Override
+  protected int computeHashCode() {
+    throw new Unimplemented();
+  }
+
+  @Override
+  protected boolean computeEquals(Object other) {
+    throw new Unimplemented();
+  }
+
+  @Override
+  public boolean isEmptyVoidMethod() {
+    return asCfCode().isEmptyVoidMethod();
+  }
+
+  @Override
+  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+      throws ApiLevelException {
+    return asCfCode().buildIR(encodedMethod, options);
+  }
+
+  @Override
+  public IRCode buildInliningIR(
+      DexEncodedMethod encodedMethod,
+      InternalOptions options,
+      ValueNumberGenerator valueNumberGenerator,
+      Position callerPosition)
+      throws ApiLevelException {
+    return asCfCode().buildInliningIR(encodedMethod, options, valueNumberGenerator, callerPosition);
+  }
+
+  @Override
+  public void registerCodeReferences(UseRegistry registry) {
+    asCfCode().registerCodeReferences(registry);
+  }
+
+  @Override
+  public String toString() {
+    return asCfCode().toString();
+  }
+
+  @Override
+  public String toString(DexEncodedMethod method, ClassNameMapper naming) {
+    return null;
+  }
+
+  private static class ClassCodeVisitor extends ClassVisitor {
+
+    private final ReparseContext context;
+    private final JarApplicationReader application;
+    private int methodIndex = 0;
+
+    ClassCodeVisitor(ReparseContext context, JarApplicationReader application) {
+      super(Opcodes.ASM6);
+      this.context = context;
+      this.application = application;
+    }
+
+    @Override
+    public MethodVisitor visitMethod(
+        int access, String name, String desc, String signature, String[] exceptions) {
+      MethodAccessFlags flags = JarClassFileReader.createMethodAccessFlags(name, access);
+      if (!flags.isAbstract() && !flags.isNative()) {
+        LazyCfCode code = context.codeList.get(methodIndex++).asLazyCfCode();
+        DexMethod method = application.getMethod(context.owner.type, name, desc);
+        assert code.method == method;
+        MethodCodeVisitor methodVisitor = new MethodCodeVisitor(application, code);
+        return new JSRInlinerAdapter(methodVisitor, access, name, desc, signature, exceptions);
+      }
+      return null;
+    }
+  }
+
+  private static class MethodCodeVisitor extends MethodVisitor {
+    private final JarApplicationReader application;
+    private final DexItemFactory factory;
+    private int maxStack;
+    private int maxLocals;
+    private List<CfInstruction> instructions;
+    private List<CfTryCatch> tryCatchRanges;
+    private List<LocalVariableInfo> localVariables;
+    private Map<Label, CfLabel> labelMap;
+    private final LazyCfCode code;
+    private DexMethod method;
+
+    MethodCodeVisitor(JarApplicationReader application, LazyCfCode code) {
+      super(Opcodes.ASM6);
+      this.application = application;
+      this.factory = application.getFactory();
+      this.method = code.method;
+      this.code = code;
+    }
+
+    @Override
+    public void visitCode() {
+      maxStack = 0;
+      maxLocals = 0;
+      instructions = new ArrayList<>();
+      tryCatchRanges = new ArrayList<>();
+      localVariables = new ArrayList<>();
+      labelMap = new IdentityHashMap<>();
+    }
+
+    @Override
+    public void visitEnd() {
+      code.setCode(
+          new CfCode(method, maxStack, maxLocals, instructions, tryCatchRanges, localVariables));
+    }
+
+    @Override
+    public void visitFrame(
+        int frameType, int nLocals, Object[] localTypes, int nStack, Object[] stackTypes) {
+      assert frameType == Opcodes.F_NEW;
+      Int2ReferenceSortedMap<FrameType> parsedLocals = parseLocals(nLocals, localTypes);
+      List<FrameType> parsedStack = parseStack(nStack, stackTypes);
+      instructions.add(new CfFrame(parsedLocals, parsedStack));
+    }
+
+    private Int2ReferenceSortedMap<FrameType> parseLocals(int typeCount, Object[] asmTypes) {
+      Int2ReferenceSortedMap<FrameType> types = new Int2ReferenceAVLTreeMap<>();
+      int i = 0;
+      for (int j = 0; j < typeCount; j++) {
+        Object localType = asmTypes[j];
+        FrameType value = getFrameType(localType);
+        types.put(i++, value);
+        if (value.isWide()) {
+          i++;
+        }
+      }
+      return types;
+    }
+
+    private List<FrameType> parseStack(int nStack, Object[] stackTypes) {
+      List<FrameType> dexStack = new ArrayList<>(nStack);
+      for (int i = 0; i < nStack; i++) {
+        dexStack.add(getFrameType(stackTypes[i]));
+      }
+      return dexStack;
+    }
+
+    private FrameType getFrameType(Object localType) {
+      if (localType instanceof Label) {
+        return FrameType.uninitializedNew(getLabel((Label) localType));
+      } else if (localType == Opcodes.UNINITIALIZED_THIS) {
+        return FrameType.uninitializedThis();
+      } else if (localType == null || localType == Opcodes.TOP) {
+        return FrameType.top();
+      } else {
+        return FrameType.initialized(parseAsmType(localType));
+      }
+    }
+
+    private CfLabel getLabel(Label label) {
+      return labelMap.computeIfAbsent(label, l -> new CfLabel());
+    }
+
+    private DexType parseAsmType(Object local) {
+      assert local != null && local != Opcodes.TOP;
+      if (local == Opcodes.INTEGER) {
+        return factory.intType;
+      } else if (local == Opcodes.FLOAT) {
+        return factory.floatType;
+      } else if (local == Opcodes.LONG) {
+        return factory.longType;
+      } else if (local == Opcodes.DOUBLE) {
+        return factory.doubleType;
+      } else if (local == Opcodes.NULL) {
+        return DexItemFactory.nullValueType;
+      } else if (local instanceof String) {
+        return createTypeFromInternalType((String) local);
+      } else {
+        throw new Unreachable("Unexpected ASM type: " + local);
+      }
+    }
+
+    private DexType createTypeFromInternalType(String local) {
+      assert local.indexOf('.') == -1;
+      return factory.createType("L" + local + ";");
+    }
+
+    @Override
+    public void visitInsn(int opcode) {
+      switch (opcode) {
+        case Opcodes.NOP:
+          instructions.add(new CfNop());
+          break;
+        case Opcodes.ACONST_NULL:
+          instructions.add(new CfConstNull());
+          break;
+        case Opcodes.ICONST_M1:
+        case Opcodes.ICONST_0:
+        case Opcodes.ICONST_1:
+        case Opcodes.ICONST_2:
+        case Opcodes.ICONST_3:
+        case Opcodes.ICONST_4:
+        case Opcodes.ICONST_5:
+          instructions.add(new CfConstNumber(opcode - Opcodes.ICONST_0, ValueType.INT));
+          break;
+        case Opcodes.LCONST_0:
+        case Opcodes.LCONST_1:
+          instructions.add(new CfConstNumber(opcode - Opcodes.LCONST_0, ValueType.LONG));
+          break;
+        case Opcodes.FCONST_0:
+        case Opcodes.FCONST_1:
+        case Opcodes.FCONST_2:
+          instructions.add(
+              new CfConstNumber(
+                  Float.floatToRawIntBits(opcode - Opcodes.FCONST_0), ValueType.FLOAT));
+          break;
+        case Opcodes.DCONST_0:
+        case Opcodes.DCONST_1:
+          instructions.add(
+              new CfConstNumber(
+                  Double.doubleToRawLongBits(opcode - Opcodes.DCONST_0), ValueType.DOUBLE));
+          break;
+        case Opcodes.IALOAD:
+        case Opcodes.LALOAD:
+        case Opcodes.FALOAD:
+        case Opcodes.DALOAD:
+        case Opcodes.AALOAD:
+        case Opcodes.BALOAD:
+        case Opcodes.CALOAD:
+        case Opcodes.SALOAD:
+          instructions.add(new CfArrayLoad(getMemberTypeForOpcode(opcode)));
+          break;
+        case Opcodes.IASTORE:
+        case Opcodes.LASTORE:
+        case Opcodes.FASTORE:
+        case Opcodes.DASTORE:
+        case Opcodes.AASTORE:
+        case Opcodes.BASTORE:
+        case Opcodes.CASTORE:
+        case Opcodes.SASTORE:
+          instructions.add(new CfArrayStore(getMemberTypeForOpcode(opcode)));
+          break;
+        case Opcodes.POP:
+        case Opcodes.POP2:
+        case Opcodes.DUP:
+        case Opcodes.DUP_X1:
+        case Opcodes.DUP_X2:
+        case Opcodes.DUP2:
+        case Opcodes.DUP2_X1:
+        case Opcodes.DUP2_X2:
+        case Opcodes.SWAP:
+          instructions.add(CfStackInstruction.fromAsm(opcode));
+          break;
+        case Opcodes.IADD:
+        case Opcodes.LADD:
+        case Opcodes.FADD:
+        case Opcodes.DADD:
+        case Opcodes.ISUB:
+        case Opcodes.LSUB:
+        case Opcodes.FSUB:
+        case Opcodes.DSUB:
+        case Opcodes.IMUL:
+        case Opcodes.LMUL:
+        case Opcodes.FMUL:
+        case Opcodes.DMUL:
+        case Opcodes.IDIV:
+        case Opcodes.LDIV:
+        case Opcodes.FDIV:
+        case Opcodes.DDIV:
+        case Opcodes.IREM:
+        case Opcodes.LREM:
+        case Opcodes.FREM:
+        case Opcodes.DREM:
+          instructions.add(CfArithmeticBinop.fromAsm(opcode));
+          break;
+        case Opcodes.INEG:
+        case Opcodes.LNEG:
+        case Opcodes.FNEG:
+        case Opcodes.DNEG:
+          instructions.add(CfNeg.fromAsm(opcode));
+          break;
+        case Opcodes.ISHL:
+        case Opcodes.LSHL:
+        case Opcodes.ISHR:
+        case Opcodes.LSHR:
+        case Opcodes.IUSHR:
+        case Opcodes.LUSHR:
+        case Opcodes.IAND:
+        case Opcodes.LAND:
+        case Opcodes.IOR:
+        case Opcodes.LOR:
+        case Opcodes.IXOR:
+        case Opcodes.LXOR:
+          instructions.add(CfLogicalBinop.fromAsm(opcode));
+          break;
+        case Opcodes.I2L:
+        case Opcodes.I2F:
+        case Opcodes.I2D:
+        case Opcodes.L2I:
+        case Opcodes.L2F:
+        case Opcodes.L2D:
+        case Opcodes.F2I:
+        case Opcodes.F2L:
+        case Opcodes.F2D:
+        case Opcodes.D2I:
+        case Opcodes.D2L:
+        case Opcodes.D2F:
+        case Opcodes.I2B:
+        case Opcodes.I2C:
+        case Opcodes.I2S:
+          instructions.add(CfNumberConversion.fromAsm(opcode));
+          break;
+        case Opcodes.LCMP:
+        case Opcodes.FCMPL:
+        case Opcodes.FCMPG:
+        case Opcodes.DCMPL:
+        case Opcodes.DCMPG:
+          instructions.add(CfCmp.fromAsm(opcode));
+          break;
+        case Opcodes.IRETURN:
+          instructions.add(new CfReturn(ValueType.INT));
+          break;
+        case Opcodes.LRETURN:
+          instructions.add(new CfReturn(ValueType.LONG));
+          break;
+        case Opcodes.FRETURN:
+          instructions.add(new CfReturn(ValueType.FLOAT));
+          break;
+        case Opcodes.DRETURN:
+          instructions.add(new CfReturn(ValueType.DOUBLE));
+          break;
+        case Opcodes.ARETURN:
+          instructions.add(new CfReturn(ValueType.OBJECT));
+          break;
+        case Opcodes.RETURN:
+          instructions.add(new CfReturnVoid());
+          break;
+        case Opcodes.ARRAYLENGTH:
+          instructions.add(new CfArrayLength());
+          break;
+        case Opcodes.ATHROW:
+          instructions.add(new CfThrow());
+          break;
+        case Opcodes.MONITORENTER:
+          instructions.add(new CfMonitor(Monitor.Type.ENTER));
+          break;
+        case Opcodes.MONITOREXIT:
+          instructions.add(new CfMonitor(Monitor.Type.EXIT));
+          break;
+        default:
+          throw new Unreachable("Unknown instruction");
+      }
+    }
+
+    private static MemberType getMemberTypeForOpcode(int opcode) {
+      switch (opcode) {
+        case Opcodes.IALOAD:
+        case Opcodes.IASTORE:
+          return MemberType.INT;
+        case Opcodes.FALOAD:
+        case Opcodes.FASTORE:
+          return MemberType.FLOAT;
+        case Opcodes.LALOAD:
+        case Opcodes.LASTORE:
+          return MemberType.LONG;
+        case Opcodes.DALOAD:
+        case Opcodes.DASTORE:
+          return MemberType.DOUBLE;
+        case Opcodes.AALOAD:
+        case Opcodes.AASTORE:
+          return MemberType.OBJECT;
+        case Opcodes.BALOAD:
+        case Opcodes.BASTORE:
+          return MemberType.BOOLEAN; // TODO: Distinguish byte and boolean.
+        case Opcodes.CALOAD:
+        case Opcodes.CASTORE:
+          return MemberType.CHAR;
+        case Opcodes.SALOAD:
+        case Opcodes.SASTORE:
+          return MemberType.SHORT;
+        default:
+          throw new Unreachable("Unexpected array opcode " + opcode);
+      }
+    }
+
+    @Override
+    public void visitIntInsn(int opcode, int operand) {
+      switch (opcode) {
+        case Opcodes.SIPUSH:
+        case Opcodes.BIPUSH:
+          instructions.add(new CfConstNumber(operand, ValueType.INT));
+          break;
+        case Opcodes.NEWARRAY:
+          instructions.add(
+              new CfNewArray(factory.createArrayType(1, arrayTypeDesc(operand, factory))));
+          break;
+        default:
+          throw new Unreachable("Unexpected int opcode " + opcode);
+      }
+    }
+
+    private static DexType arrayTypeDesc(int arrayTypeCode, DexItemFactory factory) {
+      switch (arrayTypeCode) {
+        case Opcodes.T_BOOLEAN:
+          return factory.booleanType;
+        case Opcodes.T_CHAR:
+          return factory.charType;
+        case Opcodes.T_FLOAT:
+          return factory.floatType;
+        case Opcodes.T_DOUBLE:
+          return factory.doubleType;
+        case Opcodes.T_BYTE:
+          return factory.byteType;
+        case Opcodes.T_SHORT:
+          return factory.shortType;
+        case Opcodes.T_INT:
+          return factory.intType;
+        case Opcodes.T_LONG:
+          return factory.longType;
+        default:
+          throw new Unreachable("Unexpected array-type code " + arrayTypeCode);
+      }
+    }
+
+    @Override
+    public void visitVarInsn(int opcode, int var) {
+      ValueType type;
+      switch (opcode) {
+        case Opcodes.ILOAD:
+        case Opcodes.ISTORE:
+          type = ValueType.INT;
+          break;
+        case Opcodes.FLOAD:
+        case Opcodes.FSTORE:
+          type = ValueType.FLOAT;
+          break;
+        case Opcodes.LLOAD:
+        case Opcodes.LSTORE:
+          type = ValueType.LONG;
+          break;
+        case Opcodes.DLOAD:
+        case Opcodes.DSTORE:
+          type = ValueType.DOUBLE;
+          break;
+        case Opcodes.ALOAD:
+        case Opcodes.ASTORE:
+          type = ValueType.OBJECT;
+          break;
+        case Opcodes.RET:
+          throw new Unreachable("RET should be handled by the ASM jsr inliner");
+        default:
+          throw new Unreachable("Unexpected VarInsn opcode: " + opcode);
+      }
+      if (Opcodes.ILOAD <= opcode && opcode <= Opcodes.ALOAD) {
+        instructions.add(new CfLoad(type, var));
+      } else {
+        instructions.add(new CfStore(type, var));
+      }
+    }
+
+    @Override
+    public void visitTypeInsn(int opcode, String typeName) {
+      DexType type = factory.createType(Type.getObjectType(typeName).getDescriptor());
+      switch (opcode) {
+        case Opcodes.NEW:
+          instructions.add(new CfNew(type));
+          break;
+        case Opcodes.ANEWARRAY:
+          instructions.add(new CfNewArray(factory.createArrayType(1, type)));
+          break;
+        case Opcodes.CHECKCAST:
+          instructions.add(new CfCheckCast(type));
+          break;
+        case Opcodes.INSTANCEOF:
+          instructions.add(new CfInstanceOf(type));
+          break;
+        default:
+          throw new Unreachable("Unexpected TypeInsn opcode: " + opcode);
+      }
+    }
+
+    @Override
+    public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+      DexField field =
+          factory.createField(createTypeFromInternalType(owner), factory.createType(desc), name);
+      // TODO(mathiasr): Don't require CfFieldInstruction::declaringField. It is needed for proper
+      // renaming in the backend, but it is not available here in the frontend.
+      instructions.add(new CfFieldInstruction(opcode, field, field));
+    }
+
+    @Override
+    public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+      visitMethodInsn(opcode, owner, name, desc, false);
+    }
+
+    @Override
+    public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
+      DexMethod method = application.getMethod(owner, name, desc);
+      instructions.add(new CfInvoke(opcode, method, itf));
+    }
+
+    @Override
+    public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
+      DexCallSite callSite =
+          DexCallSite.fromAsmInvokeDynamic(application, method.holder, name, desc, bsm, bsmArgs);
+      instructions.add(new CfInvokeDynamic(callSite));
+    }
+
+    @Override
+    public void visitJumpInsn(int opcode, Label label) {
+      CfLabel target = getLabel(label);
+      if (Opcodes.IFEQ <= opcode && opcode <= Opcodes.IF_ACMPNE) {
+        if (opcode <= Opcodes.IFLE) {
+          // IFEQ, IFNE, IFLT, IFGE, IFGT, or IFLE.
+          instructions.add(new CfIf(ifType(opcode), ValueType.INT, target));
+        } else {
+          // IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, or
+          // IF_ACMPNE.
+          ValueType valueType;
+          if (opcode <= Opcodes.IF_ICMPLE) {
+            valueType = ValueType.INT;
+          } else {
+            valueType = ValueType.OBJECT;
+          }
+          instructions.add(new CfIfCmp(ifType(opcode), valueType, target));
+        }
+      } else {
+        // GOTO, JSR, IFNULL or IFNONNULL.
+        switch (opcode) {
+          case Opcodes.GOTO:
+            instructions.add(new CfGoto(target));
+            break;
+          case Opcodes.IFNULL:
+          case Opcodes.IFNONNULL:
+            If.Type type = opcode == Opcodes.IFNULL ? If.Type.EQ : If.Type.NE;
+            instructions.add(new CfIf(type, ValueType.OBJECT, target));
+            break;
+          case Opcodes.JSR:
+            throw new Unreachable("JSR should be handled by the ASM jsr inliner");
+          default:
+            throw new Unreachable("Unexpected JumpInsn opcode: " + opcode);
+        }
+      }
+    }
+
+    private static If.Type ifType(int opcode) {
+      switch (opcode) {
+        case Opcodes.IFEQ:
+        case Opcodes.IF_ICMPEQ:
+        case Opcodes.IF_ACMPEQ:
+          return If.Type.EQ;
+        case Opcodes.IFNE:
+        case Opcodes.IF_ICMPNE:
+        case Opcodes.IF_ACMPNE:
+          return If.Type.NE;
+        case Opcodes.IFLT:
+        case Opcodes.IF_ICMPLT:
+          return If.Type.LT;
+        case Opcodes.IFGE:
+        case Opcodes.IF_ICMPGE:
+          return If.Type.GE;
+        case Opcodes.IFGT:
+        case Opcodes.IF_ICMPGT:
+          return If.Type.GT;
+        case Opcodes.IFLE:
+        case Opcodes.IF_ICMPLE:
+          return If.Type.LE;
+        default:
+          throw new Unreachable("Unexpected If instruction opcode: " + opcode);
+      }
+    }
+
+    @Override
+    public void visitLabel(Label label) {
+      instructions.add(getLabel(label));
+    }
+
+    @Override
+    public void visitLdcInsn(Object cst) {
+      if (cst instanceof Type) {
+        Type type = (Type) cst;
+        if (type.getSort() == Type.METHOD) {
+          DexProto proto = application.getProto(type.getDescriptor());
+          instructions.add(new CfConstMethodType(proto));
+        } else {
+          instructions.add(new CfConstClass(factory.createType(type.getDescriptor())));
+        }
+      } else if (cst instanceof String) {
+        instructions.add(new CfConstString(factory.createString((String) cst)));
+      } else if (cst instanceof Long) {
+        instructions.add(new CfConstNumber((Long) cst, ValueType.LONG));
+      } else if (cst instanceof Double) {
+        long l = Double.doubleToRawLongBits((Double) cst);
+        instructions.add(new CfConstNumber(l, ValueType.DOUBLE));
+      } else if (cst instanceof Integer) {
+        instructions.add(new CfConstNumber((Integer) cst, ValueType.INT));
+      } else if (cst instanceof Float) {
+        long i = Float.floatToRawIntBits((Float) cst);
+        instructions.add(new CfConstNumber(i, ValueType.FLOAT));
+      } else if (cst instanceof Handle) {
+        instructions.add(
+            new CfConstMethodHandle(
+                DexMethodHandle.fromAsmHandle((Handle) cst, application, method.holder)));
+      } else {
+        throw new CompilationError("Unsupported constant: " + cst.toString());
+      }
+    }
+
+    @Override
+    public void visitIincInsn(int var, int increment) {
+      instructions.add(new CfIinc(var, increment));
+    }
+
+    @Override
+    public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
+      assert max == min + labels.length - 1;
+      ArrayList<CfLabel> targets = new ArrayList<>(labels.length);
+      for (Label label : labels) {
+        targets.add(getLabel(label));
+      }
+      instructions.add(new CfSwitch(CfSwitch.Kind.TABLE, getLabel(dflt), new int[] {min}, targets));
+    }
+
+    @Override
+    public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+      ArrayList<CfLabel> targets = new ArrayList<>(labels.length);
+      for (Label label : labels) {
+        targets.add(getLabel(label));
+      }
+      instructions.add(new CfSwitch(CfSwitch.Kind.LOOKUP, getLabel(dflt), keys, targets));
+    }
+
+    @Override
+    public void visitMultiANewArrayInsn(String desc, int dims) {
+      instructions.add(new CfMultiANewArray(factory.createType(desc), dims));
+    }
+
+    @Override
+    public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+      List<DexType> guards =
+          Collections.singletonList(
+              type == null ? DexItemFactory.catchAllType : createTypeFromInternalType(type));
+      List<CfLabel> targets = Collections.singletonList(getLabel(handler));
+      tryCatchRanges.add(new CfTryCatch(getLabel(start), getLabel(end), guards, targets));
+    }
+
+    @Override
+    public void visitLocalVariable(
+        String name, String desc, String signature, Label start, Label end, int index) {
+      DebugLocalInfo debugLocalInfo =
+          new DebugLocalInfo(
+              factory.createString(name),
+              factory.createType(desc),
+              signature == null ? null : factory.createString(signature));
+      localVariables.add(
+          new LocalVariableInfo(index, debugLocalInfo, getLabel(start), getLabel(end)));
+    }
+
+    @Override
+    public void visitLineNumber(int line, Label start) {
+      instructions.add(new CfPosition(getLabel(start), new Position(line, null, method, null)));
+    }
+
+    @Override
+    public void visitMaxs(int maxStack, int maxLocals) {
+      assert maxStack >= 0;
+      assert maxLocals >= 0;
+      this.maxStack = maxStack;
+      this.maxLocals = maxLocals;
+    }
+  }
+
+  private static boolean verifyNoReparseContext(DexProgramClass owner) {
+    for (DexEncodedMethod method : owner.virtualMethods()) {
+      Code code = method.getCode();
+      assert code == null || !(code instanceof LazyCfCode) || ((LazyCfCode) code).context == null;
+    }
+    for (DexEncodedMethod method : owner.directMethods()) {
+      Code code = method.getCode();
+      assert code == null || !(code instanceof LazyCfCode) || ((LazyCfCode) code).context == null;
+    }
+    return true;
+  }
+}