Merge "Archive repackaged r8 for maven compatibility"
diff --git a/build.gradle b/build.gradle
index 94c002f..86ba02f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -703,6 +703,20 @@
     }
 }
 
+task jardiff(type: Jar) {
+    from sourceSets.main.output
+    baseName 'jardiff'
+    manifest {
+      attributes 'Main-Class': 'com.android.tools.r8.JarDiff'
+    }
+    // In order to build without dependencies, pass the exclude_deps property using:
+    // gradle -Pexclude_deps jardiff
+    if (!project.hasProperty('exclude_deps')) {
+        // Also include dependencies
+        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
+    }
+}
+
 task sourceJar(type: Jar, dependsOn: classes) {
     classifier = 'src'
     from sourceSets.main.allSource
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 3db9699..26097c1 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -59,7 +59,7 @@
     // Print -whyareyoukeeping results if any.
     if (mainDexRootSet.reasonAsked.size() > 0) {
       // Print reasons on the application after pruning, so that we reflect the actual result.
-      TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
+      TreePruner pruner = new TreePruner(application, mainDexAppInfo.withLiveness(), options);
       application = pruner.run();
       ReasonPrinter reasonPrinter = enqueuer.getReasonPrinter(mainDexRootSet.reasonAsked);
       reasonPrinter.run(application);
diff --git a/src/main/java/com/android/tools/r8/JarDiff.java b/src/main/java/com/android/tools/r8/JarDiff.java
new file mode 100644
index 0000000..dad57d4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/JarDiff.java
@@ -0,0 +1,260 @@
+// 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;
+
+import static com.google.common.io.ByteStreams.toByteArray;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.graph.ClassKind;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.JarApplicationReader;
+import com.android.tools.r8.graph.JarClassFileReader;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.InternalOptions;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+/**
+ * Command-line program to compare two JARs. Given two JARs as input, the program first outputs a
+ * list of classes only in one of the input JARs. Then, for each common class, the program outputs a
+ * list of methods only in one of the input JARs. For each common method, the output of
+ * CfInstruction.toString() on each instruction is compared to find instruction-level differences. A
+ * simple diffing algorithm is used that simply removes the common prefix and common suffix and
+ * prints everything from the first difference to the last difference in the method code.
+ */
+public class JarDiff {
+
+  private static final String USAGE =
+      "Arguments: <input1.jar> <input2.jar>\n"
+          + "\n"
+          + "JarDiff computes the difference between two JAR files that contain Java classes.\n"
+          + "\n"
+          + "Only method codes are compared. Fields, parameters, annotations, generic\n"
+          + "signatures etc. are ignored.\n"
+          + "\n"
+          + "Note: Jump targets are ignored, so if two methods differ only in what label an\n"
+          + "IF or GOTO instruction jumps to, no difference is output.";
+
+  public static void main(String[] args) throws Exception {
+    JarDiff jarDiff = JarDiff.parse(args);
+    if (jarDiff == null) {
+      System.out.println(USAGE);
+    } else {
+      jarDiff.run();
+    }
+  }
+
+  public static JarDiff parse(String[] args) {
+    int arg = 0;
+    int before = 3;
+    int after = 3;
+    while (arg + 1 < args.length) {
+      if (args[arg].equals("-B")) {
+        before = Integer.parseInt(args[arg + 1]);
+        arg += 2;
+      } else if (args[arg].equals("-A")) {
+        after = Integer.parseInt(args[arg + 1]);
+        arg += 2;
+      } else {
+        break;
+      }
+    }
+    if (args.length != arg + 2) {
+      return null;
+    }
+    return new JarDiff(Paths.get(args[arg]), Paths.get(args[arg + 1]), before, after);
+  }
+
+  private final Path path1;
+  private final Path path2;
+  private final int before;
+  private final int after;
+  private final JarApplicationReader applicationReader;
+  private ArchiveClassFileProvider archive1;
+  private ArchiveClassFileProvider archive2;
+
+  public JarDiff(Path path1, Path path2, int before, int after) {
+    this.path1 = path1;
+    this.path2 = path2;
+    this.before = before;
+    this.after = after;
+    InternalOptions options = new InternalOptions();
+    options.enableCfFrontend = true;
+    applicationReader = new JarApplicationReader(options);
+  }
+
+  public void run() throws Exception {
+    archive1 = new ArchiveClassFileProvider(path1);
+    archive2 = new ArchiveClassFileProvider(path2);
+    for (String descriptor : getCommonDescriptors()) {
+      byte[] bytes1 = getClassAsBytes(archive1, descriptor);
+      byte[] bytes2 = getClassAsBytes(archive2, descriptor);
+      if (Arrays.equals(bytes1, bytes2)) {
+        continue;
+      }
+      DexProgramClass class1 = getDexProgramClass(path1, bytes1);
+      DexProgramClass class2 = getDexProgramClass(path2, bytes2);
+      compareMethods(class1, class2);
+    }
+  }
+
+  private List<String> getCommonDescriptors() {
+    List<String> descriptors1 = getSortedDescriptorList(archive1);
+    List<String> descriptors2 = getSortedDescriptorList(archive2);
+    if (descriptors1.equals(descriptors2)) {
+      return descriptors1;
+    }
+    List<String> only1 = setMinus(descriptors1, descriptors2);
+    List<String> only2 = setMinus(descriptors2, descriptors1);
+    if (!only1.isEmpty()) {
+      System.out.println("Only in " + path1 + ": " + only1);
+    }
+    if (!only2.isEmpty()) {
+      System.out.println("Only in " + path2 + ": " + only2);
+    }
+    return setIntersection(descriptors1, descriptors2);
+  }
+
+  private List<String> getSortedDescriptorList(ArchiveClassFileProvider inputJar) {
+    ArrayList<String> descriptorList = new ArrayList<>(inputJar.getClassDescriptors());
+    Collections.sort(descriptorList);
+    return descriptorList;
+  }
+
+  private byte[] getClassAsBytes(ArchiveClassFileProvider inputJar, String descriptor)
+      throws Exception {
+    return toByteArray(inputJar.getProgramResource(descriptor).getByteStream());
+  }
+
+  private DexProgramClass getDexProgramClass(Path path, byte[] bytes) throws IOException {
+
+    class Collector implements Consumer<DexClass> {
+
+      private DexClass dexClass;
+
+      @Override
+      public void accept(DexClass dexClass) {
+        this.dexClass = dexClass;
+      }
+
+      public DexClass get() {
+        assert dexClass != null;
+        return dexClass;
+      }
+    }
+
+    Collector collector = new Collector();
+    JarClassFileReader reader = new JarClassFileReader(applicationReader, collector);
+    reader.read(new PathOrigin(path), ClassKind.PROGRAM, new ByteArrayInputStream(bytes));
+    return collector.get().asProgramClass();
+  }
+
+  private void compareMethods(DexProgramClass class1, DexProgramClass class2) {
+    class1.forEachMethod(
+        method1 -> {
+          DexEncodedMethod method2 = class2.lookupMethod(method1.method);
+          if (method2 == null) {
+            compareMethods(method1, method2);
+          }
+        });
+    class2.forEachMethod(
+        method2 -> {
+          DexEncodedMethod method1 = class1.lookupMethod(method2.method);
+          compareMethods(method1, method2);
+        });
+  }
+
+  private void compareMethods(DexEncodedMethod m1, DexEncodedMethod m2) {
+    if (m1 == null) {
+      System.out.println("Only in " + path2 + ": " + m2.method.toSourceString());
+      return;
+    }
+    if (m2 == null) {
+      System.out.println("Only in " + path1 + ": " + m1.method.toSourceString());
+      return;
+    }
+    List<String> code1 = getInstructionStrings(m1);
+    List<String> code2 = getInstructionStrings(m2);
+    if (code1.equals(code2)) {
+      return;
+    }
+    int i = getCommonPrefix(code1, code2);
+    int j = getCommonSuffix(code1, code2);
+    int length1 = code1.size() - i - j;
+    int length2 = code2.size() - i - j;
+    int before = Math.min(i, this.before);
+    int after = Math.min(j, this.after);
+    int context = before + after;
+    System.out.println("--- " + path1 + "/" + m1.method.toSmaliString());
+    System.out.println("+++ " + path2 + "/" + m2.method.toSmaliString());
+    System.out.println(
+        "@@ -" + (i - before) + "," + (length1 + context)
+            + " +" + (i - before) + "," + (length2 + context) + " @@ "
+            + m1.method.toSourceString());
+    for (int k = 0; k < before; k++) {
+      System.out.println(" " + code1.get(i - before + k));
+    }
+    for (int k = 0; k < length1; k++) {
+      System.out.println("-" + code1.get(i + k));
+    }
+    for (int k = 0; k < length2; k++) {
+      System.out.println("+" + code2.get(i + k));
+    }
+    for (int k = 0; k < after; k++) {
+      System.out.println(" " + code1.get(i + length1 + k));
+    }
+  }
+
+  private static List<String> getInstructionStrings(DexEncodedMethod method) {
+    List<CfInstruction> instructions = method.getCode().asCfCode().getInstructions();
+    return instructions.stream().map(CfInstruction::toString).collect(Collectors.toList());
+  }
+
+  private static List<String> setIntersection(List<String> set1, List<String> set2) {
+    ArrayList<String> result = new ArrayList<>(set1);
+    result.retainAll(new HashSet<>(set2));
+    return result;
+  }
+
+  private static List<String> setMinus(List<String> set, List<String> toRemove) {
+    ArrayList<String> result = new ArrayList<>(set);
+    result.removeAll(new HashSet<>(toRemove));
+    return result;
+  }
+
+  private static int getCommonPrefix(List<String> code1, List<String> code2) {
+    int i = 0;
+    while (i < code1.size() && i < code2.size()) {
+      if (code1.get(i).equals(code2.get(i))) {
+        i++;
+      } else {
+        break;
+      }
+    }
+    return i;
+  }
+
+  private static int getCommonSuffix(List<String> code1, List<String> code2) {
+    int j = 0;
+    while (j < code1.size() && j < code2.size()) {
+      if (code1.get(code1.size() - j - 1).equals(code2.get(code2.size() - j - 1))) {
+        j++;
+      } else {
+        break;
+      }
+    }
+    return j;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 6a1d7c8..53e2f87 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -358,12 +358,9 @@
         }
         application = application.asDirect().rewrittenWithLense(graphLense);
         appInfo = appInfo.withLiveness().rewrittenWithLense(application.asDirect(), graphLense);
-        // TODO(mathiasr): Remove this check when CF->IR construction is complete.
-        if (!options.skipIR) {
-          // Collect switch maps and ordinals maps.
-          appInfo = new SwitchMapCollector(appInfo.withLiveness(), options).run();
-          appInfo = new EnumOrdinalMapCollector(appInfo.withLiveness(), options).run();
-        }
+        // Collect switch maps and ordinals maps.
+        appInfo = new SwitchMapCollector(appInfo.withLiveness(), options).run();
+        appInfo = new EnumOrdinalMapCollector(appInfo.withLiveness(), options).run();
 
         // TODO(b/79143143): re-enable once fixed.
         // graphLense = new BridgeMethodAnalysis(graphLense, appInfo.withLiveness()).run();
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index a52d694..8a46d57 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -12,9 +12,11 @@
 import com.android.tools.r8.shaking.ProguardConfigurationParser;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.shaking.ProguardConfigurationSource;
+import com.android.tools.r8.shaking.ProguardConfigurationSourceBytes;
 import com.android.tools.r8.shaking.ProguardConfigurationSourceFile;
 import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
@@ -22,15 +24,17 @@
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
+import java.io.InputStream;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Consumer;
 
 /**
- * Immutable command structure for an invocation of the {@link D8} compiler.
+ * Immutable command structure for an invocation of the {@link R8} compiler.
  *
  * <p>To build a R8 command use the {@link R8Command.Builder} class. For example:
  *
@@ -282,21 +286,54 @@
         mainDexKeepRules = parser.getConfig().getRules();
       }
 
-      ProguardConfiguration.Builder configurationBuilder;
-      if (proguardConfigs.isEmpty()) {
-        configurationBuilder = ProguardConfiguration.builder(factory, reporter);
-      } else {
-        ProguardConfigurationParser parser =
-            new ProguardConfigurationParser(factory, reporter);
+      ProguardConfigurationParser parser = new ProguardConfigurationParser(factory, reporter);
+      if (!proguardConfigs.isEmpty()) {
         parser.parse(proguardConfigs);
-        configurationBuilder = parser.getConfigurationBuilder();
-        configurationBuilder.setForceProguardCompatibility(forceProguardCompatibility);
       }
+      ProguardConfiguration.Builder configurationBuilder = parser.getConfigurationBuilder();
+      configurationBuilder.setForceProguardCompatibility(forceProguardCompatibility);
 
       if (proguardConfigurationConsumer != null) {
         proguardConfigurationConsumer.accept(configurationBuilder);
       }
 
+      // Process Proguard configurations supplied through data resources in the input.
+      DataResourceProvider.Visitor embeddedProguardConfigurationVisitor =
+          new DataResourceProvider.Visitor() {
+            @Override
+            public void visit(DataDirectoryResource directory) {
+              // Don't do anything.
+            }
+
+            @Override
+            public void visit(DataEntryResource resource) {
+              if (resource.getName().startsWith("META-INF/proguard/")) {
+                try (InputStream in = resource.getByteStream()) {
+                  ProguardConfigurationSource source =
+                      new ProguardConfigurationSourceBytes(in, resource.getOrigin());
+                  parser.parse(source);
+                } catch (ResourceException e) {
+                  reporter.error(new StringDiagnostic("Failed to open input: " + e.getMessage(),
+                      resource.getOrigin()));
+                } catch (Exception e) {
+                  reporter.error(new ExceptionDiagnostic(e, resource.getOrigin()));
+                }
+              }
+            }
+          };
+
+      getAppBuilder().getProgramResourceProviders()
+          .stream()
+          .map(ProgramResourceProvider::getDataResourceProvider)
+          .filter(Objects::nonNull)
+          .forEach(dataResourceProvider -> {
+              try {
+                dataResourceProvider.accept(embeddedProguardConfigurationVisitor);
+              } catch (ResourceException e) {
+                reporter.error(new ExceptionDiagnostic(e));
+              }
+          });
+
       if (disableTreeShaking) {
         configurationBuilder.disableShrinking();
       }
@@ -369,6 +406,11 @@
       }
       return resources;
     }
+
+    @Override
+    public DataResourceProvider getDataResourceProvider() {
+      return provider.getDataResourceProvider();
+    }
   }
 
   // Internal state to verify parsing properties not enforced by the builder.
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 6d7ae19..f907ffb 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -50,6 +50,7 @@
 import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.If;
@@ -124,9 +125,10 @@
       for (int i = 0; i < tryCatch.guards.size(); i++) {
         newline();
         DexType guard = tryCatch.guards.get(i);
+        assert guard != null;
         builder
             .append(".catch ")
-            .append(guard == null ? "all" : guard.getInternalName())
+            .append(guard == DexItemFactory.catchAllType ? "all" : guard.getInternalName())
             .append(" from ")
             .append(getLabel(tryCatch.start))
             .append(" to ")
@@ -430,7 +432,7 @@
     Kind kind = cfSwitch.getKind();
     builder.append(kind == Kind.LOOKUP ? "lookup" : "table").append("switch");
     IntList keys = cfSwitch.getKeys();
-    List<CfLabel> targets = cfSwitch.getTargets();
+    List<CfLabel> targets = cfSwitch.getSwitchTargets();
     for (int i = 0; i < targets.size(); i++) {
       indent();
       int key = kind == Kind.LOOKUP ? keys.getInt(i) : (keys.getInt(0) + i);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
index 423bba2..842cbe9 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
@@ -6,6 +6,10 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -116,4 +120,36 @@
   public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitInsn(getAsmOpcode());
   }
+
+  @Override
+  public boolean canThrow() {
+    return (type != NumericType.FLOAT && type != NumericType.DOUBLE)
+        && (opcode == Opcode.Div || opcode == Opcode.Rem);
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    int right = state.pop().register;
+    int left = state.pop().register;
+    int dest = state.push(ValueType.fromNumericType(type)).register;
+    switch (opcode) {
+      case Add:
+        builder.addAdd(type, dest, left, right);
+        break;
+      case Sub:
+        builder.addSub(type, dest, left, right);
+        break;
+      case Mul:
+        builder.addMul(type, dest, left, right);
+        break;
+      case Div:
+        builder.addDiv(type, dest, left, right);
+        break;
+      case Rem:
+        builder.addRem(type, dest, left, right);
+        break;
+      default:
+        throw new Unreachable("CfArithmeticBinop has unknown opcode " + opcode);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
index dfe7c1b..8fb77c1 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
@@ -4,6 +4,10 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -19,4 +23,16 @@
   public void print(CfPrinter printer) {
     printer.print(this);
   }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    Slot array = state.pop();
+    assert array.type.isObjectOrNull();
+    builder.addArrayLength(state.push(builder.getFactory().intType).register, array.register);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
index 85405e4..c0b1a07 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -6,6 +6,11 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -55,4 +60,25 @@
   public void print(CfPrinter printer) {
     printer.print(this);
   }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    Slot index = state.pop();
+    Slot array = state.pop();
+    Slot value;
+    assert array.type.isObjectOrNull();
+    ValueType memberType = ValueType.fromMemberType(type);
+    if (array.preciseType != null) {
+      value = state.push(array.preciseType.toArrayElementType(builder.getFactory()));
+      assert state.peek().type == memberType;
+    } else {
+      value = state.push(memberType);
+    }
+    builder.addArrayGet(type, value.register, array.register, index.register);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
index 4b13af4..ff76777 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
@@ -6,6 +6,10 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -55,4 +59,17 @@
   public void print(CfPrinter printer) {
     printer.print(this);
   }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    Slot value = state.pop();
+    Slot index = state.pop();
+    Slot array = state.pop();
+    builder.addArrayPut(type, value.register, array.register, index.register);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
index e46d475..c01d6d6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -6,6 +6,10 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -36,4 +40,17 @@
   public void registerUse(UseRegistry registry, DexType clazz) {
     registry.registerCheckCast(type);
   }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    // Pop the top value and push it back on with the checked type.
+    state.pop();
+    Slot object = state.push(type);
+    builder.addCheckCast(object.register, type);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
index 9c6d3d7..b96ca89 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
@@ -8,6 +8,10 @@
 import com.android.tools.r8.ir.code.Cmp;
 import com.android.tools.r8.ir.code.Cmp.Bias;
 import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -66,4 +70,11 @@
   public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitInsn(getAsmOpcode());
   }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    int right = state.pop().register;
+    int left = state.pop().register;
+    builder.addCmp(type, bias, state.push(ValueType.fromNumericType(type)).register, left, right);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
index e4037e4..4da0b5c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
@@ -7,6 +7,9 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Type;
@@ -33,6 +36,11 @@
     printer.print(this);
   }
 
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+
   private String getInternalName(NamingLens lens) {
     switch (type.toShorty()) {
       case '[':
@@ -63,4 +71,9 @@
   public void registerUse(UseRegistry registry, DexType clazz) {
     registry.registerConstClass(type);
   }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    builder.addConstClass(state.push(builder.getFactory().classType).register, type);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
index ca34be7..0a98cb3 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
@@ -3,10 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 
@@ -36,4 +40,17 @@
   public void registerUse(UseRegistry registry, DexType clazz) {
     registry.registerMethodHandle(handle);
   }
+
+  @Override
+  public boolean canThrow() {
+    // const-class and const-string* may throw in dex.
+    return true;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code)
+      throws ApiLevelException {
+    builder.addConstMethodHandle(
+        state.push(builder.getFactory().methodHandleType).register, handle);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
index 7e06a88..e933c11 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
@@ -3,10 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Type;
@@ -37,4 +41,16 @@
   public void registerUse(UseRegistry registry, DexType clazz) {
     registry.registerProto(type);
   }
+
+  @Override
+  public boolean canThrow() {
+    // const-class and const-string* may throw in dex.
+    return true;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code)
+      throws ApiLevelException {
+    builder.addConstMethodType(state.push(builder.getFactory().methodTypeType).register, type);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
index 385669d..e72c9e1 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
@@ -4,6 +4,10 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -19,4 +23,9 @@
   public void print(CfPrinter printer) {
     printer.print(this);
   }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    builder.addNullConst(state.push(ValueType.OBJECT).register);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
index f3aa7f3..c24490c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
@@ -6,6 +6,9 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -118,4 +121,9 @@
   public void print(CfPrinter printer) {
     printer.print(this);
   }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    builder.addConst(type, state.push(type).register, value);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
index a22f279..54ae1c6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
@@ -5,6 +5,9 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 
@@ -29,4 +32,15 @@
   public void print(CfPrinter printer) {
     printer.print(this);
   }
+
+  @Override
+  public boolean canThrow() {
+    // const-class and const-string* may throw in dex.
+    return true;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    builder.addConstString(state.push(builder.getFactory().stringType).register, string);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index 130362f..350c1c9 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -8,6 +8,10 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -65,4 +69,42 @@
         throw new Unreachable("Unexpected opcode " + opcode);
     }
   }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    DexType type = field.type;
+    switch (opcode) {
+      case Opcodes.GETSTATIC:
+        {
+          builder.addStaticGet(state.push(type).register, field);
+          break;
+        }
+      case Opcodes.PUTSTATIC:
+        {
+          Slot value = state.pop();
+          builder.addStaticPut(value.register, field);
+          break;
+        }
+      case Opcodes.GETFIELD:
+        {
+          Slot object = state.pop();
+          builder.addInstanceGet(state.push(type).register, object.register, field);
+          break;
+        }
+      case Opcodes.PUTFIELD:
+        {
+          Slot value = state.pop();
+          Slot object = state.pop();
+          builder.addInstancePut(value.register, object.register, field);
+          break;
+        }
+      default:
+        throw new Unreachable("Unexpected opcode " + opcode);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
index d015de1..6e46028 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -9,6 +9,9 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import java.util.List;
@@ -50,6 +53,22 @@
       return null;
     }
 
+    public boolean isUninitializedThis() {
+      return false;
+    }
+
+    public boolean isInitialized() {
+      return false;
+    }
+
+    public DexType getInitializedType() {
+      return null;
+    }
+
+    public boolean isTop() {
+      return false;
+    }
+
     private FrameType() {}
   }
 
@@ -92,6 +111,16 @@
     public boolean isWide() {
       return type.isPrimitiveType() && (type.toShorty() == 'J' || type.toShorty() == 'D');
     }
+
+    @Override
+    public boolean isInitialized() {
+      return true;
+    }
+
+    @Override
+    public DexType getInitializedType() {
+      return type;
+    }
   }
 
   private static class Top extends FrameType {
@@ -107,6 +136,11 @@
     Object getTypeOpcode(NamingLens lens) {
       return Opcodes.TOP;
     }
+
+    @Override
+    public boolean isTop() {
+      return true;
+    }
   }
 
   private static class UninitializedNew extends FrameType {
@@ -127,6 +161,11 @@
     }
 
     @Override
+    public boolean isUninitializedNew() {
+      return true;
+    }
+
+    @Override
     public CfLabel getUninitializedLabel() {
       return label;
     }
@@ -144,6 +183,11 @@
     public String toString() {
       return "uninitialized this";
     }
+
+    @Override
+    public boolean isUninitializedThis() {
+      return true;
+    }
   }
 
   private final Int2ReferenceSortedMap<FrameType> locals;
@@ -232,4 +276,15 @@
   public void print(CfPrinter printer) {
     printer.print(this);
   }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    // TODO(mathiasr): Verify stack map frames before building IR.
+    code.setStateFromFrame(this);
+  }
+
+  @Override
+  public boolean emitsIR() {
+    return false;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
index 47cfed6..c4975d8 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
@@ -4,6 +4,9 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -16,6 +19,7 @@
     this.target = target;
   }
 
+  @Override
   public CfLabel getTarget() {
     return target;
   }
@@ -29,4 +33,9 @@
   public void print(CfPrinter printer) {
     printer.print(this);
   }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    builder.addGoto(code.getLabelOffset(target));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIf.java b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
index d39c358..c97f3b6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
@@ -8,6 +8,9 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.If.Type;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -32,6 +35,7 @@
     return kind;
   }
 
+  @Override
   public CfLabel getTarget() {
     return target;
   }
@@ -64,4 +68,17 @@
   public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitJumpInsn(getOpcode(), target.getLabel());
   }
+
+  @Override
+  public boolean isConditionalJump() {
+    return true;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    int value = state.pop().register;
+    int trueTargetOffset = code.getLabelOffset(target);
+    int falseTargetOffset = code.getCurrentInstructionIndex() + 1;
+    builder.addIfZero(kind, type, value, trueTargetOffset, falseTargetOffset);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
index 2a076e4..26410c8 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
@@ -8,6 +8,9 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.If.Type;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -32,6 +35,7 @@
     return type;
   }
 
+  @Override
   public CfLabel getTarget() {
     return target;
   }
@@ -64,4 +68,18 @@
   public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitJumpInsn(getOpcode(), target.getLabel());
   }
+
+  @Override
+  public boolean isConditionalJump() {
+    return true;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    int right = state.pop().register;
+    int left = state.pop().register;
+    int trueTargetOffset = code.getLabelOffset(target);
+    int falseTargetOffset = code.getCurrentInstructionIndex() + 1;
+    builder.addIf(kind, type, left, right, trueTargetOffset, falseTargetOffset);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
index 6db7250..20b3928 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
@@ -4,6 +4,10 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 
@@ -34,4 +38,10 @@
   public int getIncrement() {
     return increment;
   }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    int local = state.read(var).register;
+    builder.addAddLiteral(NumericType.INT, local, local, increment);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
index deba4ad..6a093df 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
@@ -6,6 +6,9 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -36,4 +39,15 @@
   public void registerUse(UseRegistry registry, DexType clazz) {
     registry.registerTypeReference(type);
   }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    int value = state.pop().register;
+    builder.addInstanceOf(state.push(builder.getFactory().booleanType).register, value, type);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 04ab380..eaa7b2a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -3,9 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 
@@ -25,4 +29,31 @@
   public void registerUse(UseRegistry registry, DexType clazz) {
     // Intentionally empty.
   }
+
+  public CfLabel getTarget() {
+    return null;
+  }
+
+  /** Return true if this instruction is CfReturn or CfReturnVoid. */
+  public boolean isReturn() {
+    return false;
+  }
+
+  /** Return true if this instruction is CfIf or CfIfCmp. */
+  public boolean isConditionalJump() {
+    return false;
+  }
+
+  /** Return true if this instruction or its DEX equivalent can throw. */
+  public boolean canThrow() {
+    return false;
+  }
+
+  public abstract void buildIR(IRBuilder builder, CfState state, CfSourceCode code)
+      throws ApiLevelException;
+
+  /** Return true if this instruction directly emits IR instructions. */
+  public boolean emitsIR() {
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 82f2438..32c7022 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -3,13 +3,23 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
+import java.util.Arrays;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -74,4 +84,73 @@
         throw new Unreachable("unknown CfInvoke opcode " + opcode);
     }
   }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code)
+      throws ApiLevelException {
+    Invoke.Type type;
+    DexMethod canonicalMethod;
+    DexProto callSiteProto = null;
+    switch (opcode) {
+      case Opcodes.INVOKEINTERFACE:
+        {
+          canonicalMethod = method;
+          type = Type.INTERFACE;
+          break;
+        }
+      case Opcodes.INVOKEVIRTUAL:
+        {
+          canonicalMethod = builder.getFactory().polymorphicMethods.canonicalize(method);
+          if (canonicalMethod == null) {
+            type = Type.VIRTUAL;
+            canonicalMethod = method;
+          } else {
+            type = Type.POLYMORPHIC;
+            callSiteProto = method.proto;
+          }
+          break;
+        }
+      case Opcodes.INVOKESPECIAL:
+        {
+          canonicalMethod = method;
+          if (method.name.toString().equals(Constants.INSTANCE_INITIALIZER_NAME)) {
+            type = Type.DIRECT;
+          } else if (builder.getMethod().holder == method.holder) {
+            type = Type.DIRECT;
+          } else {
+            type = Type.SUPER;
+          }
+          break;
+        }
+      case Opcodes.INVOKESTATIC:
+        {
+          canonicalMethod = method;
+          type = Type.STATIC;
+          break;
+        }
+      default:
+        throw new Unreachable("unknown CfInvoke opcode " + opcode);
+    }
+    int parameterCount = method.proto.parameters.size();
+    if (type != Type.STATIC) {
+      parameterCount += 1;
+    }
+    ValueType[] types = new ValueType[parameterCount];
+    Integer[] registers = new Integer[parameterCount];
+    for (int i = parameterCount - 1; i >= 0; i--) {
+      Slot slot = state.pop();
+      types[i] = slot.type;
+      registers[i] = slot.register;
+    }
+    builder.addInvoke(
+        type, canonicalMethod, callSiteProto, Arrays.asList(types), Arrays.asList(registers), itf);
+    if (!method.proto.returnType.isVoidType()) {
+      builder.addMoveResult(state.push(method.proto.returnType).register);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index ca6c1de..f93709e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -19,7 +19,12 @@
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
+import java.util.ArrayList;
 import java.util.List;
 import org.objectweb.asm.Handle;
 import org.objectweb.asm.MethodVisitor;
@@ -83,4 +88,26 @@
   public void registerUse(UseRegistry registry, DexType clazz) {
     registry.registerCallSite(callSite);
   }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    DexType[] parameterTypes = callSite.methodProto.parameters.values;
+    List<Integer> registers = new ArrayList<>(parameterTypes.length);
+    for (int register : state.popReverse(parameterTypes.length)) {
+      registers.add(register);
+    }
+    List<ValueType> types = new ArrayList<>(parameterTypes.length);
+    for (DexType value : parameterTypes) {
+      types.add(ValueType.fromDexType(value));
+    }
+    builder.addInvokeCustom(callSite, types, registers);
+    if (!callSite.methodProto.returnType.isVoidType()) {
+      builder.addMoveResult(state.push(callSite.methodProto.returnType).register);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
index 401e4e8..da24ade 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
@@ -4,6 +4,9 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
@@ -28,4 +31,14 @@
   public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitLabel(getLabel());
   }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    // Intentionally left empty.
+  }
+
+  @Override
+  public boolean emitsIR() {
+    return false;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
index 58c59a5..1f4c3e0 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
@@ -6,6 +6,10 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -54,4 +58,16 @@
   public int getLocalIndex() {
     return var;
   }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    Slot local = state.read(var);
+    Slot stack = state.push(local);
+    builder.addMove(local.type, stack.register, local.register);
+  }
+
+  @Override
+  public boolean emitsIR() {
+    return false;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
index 2aab288..a8527f1 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
@@ -6,6 +6,10 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -91,4 +95,33 @@
   public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitInsn(getAsmOpcode());
   }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    int right = state.pop().register;
+    int left = state.pop().register;
+    int dest = state.push(ValueType.fromNumericType(type)).register;
+    switch (opcode) {
+      case Shl:
+        builder.addShl(type, dest, left, right);
+        break;
+      case Shr:
+        builder.addShr(type, dest, left, right);
+        break;
+      case Ushr:
+        builder.addUshr(type, dest, left, right);
+        break;
+      case And:
+        builder.addAnd(type, dest, left, right);
+        break;
+      case Or:
+        builder.addOr(type, dest, left, right);
+        break;
+      case Xor:
+        builder.addXor(type, dest, left, right);
+        break;
+      default:
+        throw new Unreachable("CfLogicalBinop has unknown opcode " + opcode);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
index b38ef12..3b19b03 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
@@ -5,6 +5,10 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.ir.code.Monitor.Type;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -30,4 +34,15 @@
   public void print(CfPrinter printer) {
     printer.print(this);
   }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    Slot object = state.pop();
+    builder.addMonitor(type, object.register);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
index a2f3da2..2dfc8fa 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
@@ -3,9 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 
@@ -41,4 +45,16 @@
   public void registerUse(UseRegistry registry, DexType clazz) {
     registry.registerTypeReference(type);
   }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code)
+      throws ApiLevelException {
+    int[] dimensions = state.popReverse(this.dimensions);
+    builder.addMultiNewArray(type, state.push(type).register, dimensions);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
index db0019a..daac41d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
@@ -6,6 +6,10 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -60,4 +64,10 @@
         throw new Unreachable("Invalid opcode for CfNeg " + opcode);
     }
   }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    int value = state.pop().register;
+    builder.addNeg(type, state.push(ValueType.fromNumericType(type)).register, value);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNew.java b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
index b7261b6..70e5add 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNew.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
@@ -6,6 +6,9 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -36,4 +39,14 @@
   public void registerUse(UseRegistry registry, DexType clazz) {
     registry.registerNewInstance(type);
   }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    builder.addNewInstance(state.push(type).register, type);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
index 708a414..92feb63 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
@@ -7,6 +7,10 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.DescriptorUtils;
 import org.objectweb.asm.MethodVisitor;
@@ -76,4 +80,16 @@
       registry.registerTypeReference(type);
     }
   }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    Slot size = state.pop();
+    Slot push = state.push(type);
+    builder.addNewArrayEmpty(push.register, size.register, type);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNop.java b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
index 2cdd37c..b6308f4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
@@ -4,6 +4,9 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -19,4 +22,14 @@
   public void print(CfPrinter printer) {
     printer.print(this);
   }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    // Intentionally left empty.
+  }
+
+  @Override
+  public boolean emitsIR() {
+    return false;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
index b585cb6..e9cefb7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
@@ -6,6 +6,10 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -127,4 +131,10 @@
         throw new Unreachable("Unexpected CfNumberConversion opcode " + opcode);
     }
   }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    int source = state.pop().register;
+    builder.addConversion(to, from, state.push(ValueType.fromNumericType(to)).register, source);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
index 169d252..7508ea0 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
@@ -5,6 +5,9 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 
@@ -31,4 +34,10 @@
   public Position getPosition() {
     return position;
   }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    state.setPosition(position);
+    builder.addDebugPosition(position);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
index 7993919..532800d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
@@ -6,6 +6,10 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -48,4 +52,15 @@
   public void print(CfPrinter printer) {
     printer.print(this);
   }
+
+  @Override
+  public boolean isReturn() {
+    return true;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    Slot pop = state.pop();
+    builder.addReturn(pop.type, pop.register);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
index 7d98073..cac89e0 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
@@ -4,6 +4,9 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -19,4 +22,14 @@
   public void print(CfPrinter printer) {
     printer.print(this);
   }
+
+  @Override
+  public boolean isReturn() {
+    return true;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    builder.addReturn();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
index efda3f6..733f2d9 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
@@ -4,8 +4,13 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -78,4 +83,162 @@
   public Opcode getOpcode() {
     return opcode;
   }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    switch (opcode) {
+      case Pop:
+        {
+          Slot pop = state.pop();
+          assert !pop.type.isWide();
+          break;
+        }
+      case Pop2:
+        {
+          Slot value = state.pop();
+          if (!value.type.isWide()) {
+            state.pop();
+            throw new Unimplemented("Building IR for Pop2 of narrow value not supported");
+          }
+          break;
+        }
+      case Dup:
+        {
+          Slot dupValue = state.peek();
+          assert !dupValue.type.isWide();
+          builder.addMove(dupValue.type, state.push(dupValue).register, dupValue.register);
+          break;
+        }
+      case DupX1:
+        {
+          Slot value1 = state.pop();
+          Slot value2 = state.pop();
+          assert !value1.type.isWide();
+          assert !value2.type.isWide();
+          dupX1(builder, state, value1, value2);
+          break;
+        }
+      case DupX2:
+        {
+          Slot value1 = state.pop();
+          Slot value2 = state.pop();
+          assert !value1.type.isWide();
+          if (value2.type.isWide()) {
+            dupX1(builder, state, value1, value2);
+            throw new Unimplemented("Building IR for DupX2 of wide value not supported");
+          } else {
+            Slot value3 = state.pop();
+            assert !value3.type.isWide();
+            // Input stack: ..., A:value3, B:value2, C:value1
+            Slot outValue1 = state.push(value1);
+            Slot outValue3 = state.push(value3);
+            Slot outValue2 = state.push(value2);
+            Slot outValue1Copy = state.push(value1);
+            // Output stack: ..., A:outValue1, B:outValue3, C:outValue2, D:outValue1Copy
+            // Move D(outValue1Copy) <- C(value1)
+            builder.addMove(value1.type, outValue1Copy.register, value1.register);
+            // Move C(outValue2) <- B(value2)
+            builder.addMove(value2.type, outValue2.register, value2.register);
+            // Move B(outValue3) <- A(value3)
+            builder.addMove(value3.type, outValue3.register, value3.register);
+            // Move A(outValue1) <- D(outValue1Copy)
+            builder.addMove(value1.type, outValue1.register, outValue1Copy.register);
+          }
+          break;
+        }
+      case Dup2:
+        {
+          Slot value1 = state.peek();
+          if (value1.type.isWide()) {
+            builder.addMove(value1.type, state.push(value1).register, value1.register);
+          } else {
+            Slot value2 = state.peek(1);
+            builder.addMove(value2.type, state.push(value2).register, value2.register);
+            builder.addMove(value1.type, state.push(value1).register, value1.register);
+          }
+          break;
+        }
+      case Dup2X1:
+        {
+          Slot value1 = state.pop();
+          Slot value2 = state.pop();
+          assert !value2.type.isWide();
+          if (value1.type.isWide()) {
+            dupX1(builder, state, value1, value2);
+          } else {
+            // Input stack: ..., A:value3, B:value2, C:value1
+            Slot value3 = state.pop();
+            assert !value3.type.isWide();
+            Slot outValue2 = state.push(value2);
+            Slot outValue1 = state.push(value1);
+            Slot outValue3 = state.push(value3);
+            Slot outValue2Copy = state.push(value2);
+            Slot outValue1Copy = state.push(value1);
+            // Output: ..., A:outValue2, B:outValue1, C:outValue3, D:outValue2Copy, E:outValue1Copy
+            // Move E(outValue1Copy) <- C(value1)
+            builder.addMove(value1.type, outValue1Copy.register, value1.register);
+            // Move D(outValue2Copy) <- B(value2)
+            builder.addMove(value2.type, outValue2Copy.register, value2.register);
+            // Move C(outValue3) <- A(value3)
+            builder.addMove(value3.type, outValue3.register, value3.register);
+            // Move B(outValue1) <- E(outValue1Copy)
+            builder.addMove(value1.type, outValue1.register, outValue1Copy.register);
+            // Move A(outValue2) <- D(outValue2Copy)
+            builder.addMove(value2.type, outValue2.register, outValue2Copy.register);
+            throw new Unimplemented("Building IR for Dup2X1 narrow not supported");
+          }
+          break;
+        }
+      case Dup2X2:
+        {
+          // Input stack:
+          Slot value1 = state.pop();
+          Slot value2 = state.pop();
+          assert !value2.type.isWide();
+          if (value1.type.isWide()) {
+            // Input stack: ..., value2, value1
+            dupX1(builder, state, value1, value2);
+            // Output stack: ..., value1, value2, value1
+            throw new Unimplemented("Building IR for Dup2X2 wide not supported");
+          } else {
+            throw new Unimplemented("Building IR for Dup2X2 narrow not supported");
+          }
+          // break;
+        }
+      case Swap:
+        {
+          Slot value1 = state.pop();
+          Slot value2 = state.pop();
+          assert !value1.type.isWide();
+          assert !value2.type.isWide();
+          // Input stack: ..., value2, value1
+          dupX1(builder, state, value1, value2);
+          // Current stack: ..., value1, value2, value1copy
+          state.pop();
+          // Output stack: ..., value1, value2
+          throw new Unimplemented(
+              "Building IR for CfStackInstruction " + opcode + " not supported");
+          // break;
+        }
+    }
+  }
+
+  private void dupX1(IRBuilder builder, CfState state, Slot inValue1, Slot inValue2) {
+    // Input stack: ..., A:inValue2, B:inValue1 (values already popped)
+    Slot outValue1 = state.push(inValue1);
+    Slot outValue2 = state.push(inValue2);
+    Slot outValue1Copy = state.push(inValue1);
+    // Output stack: ..., A:outValue1, B:outValue2, C:outValue1Copy
+    // Move C(outValue1Copy) <- B(inValue1)
+    builder.addMove(inValue1.type, outValue1Copy.register, inValue1.register);
+    // Move B(outValue2) <- A(inValue2)
+    builder.addMove(inValue2.type, outValue2.register, inValue2.register);
+    // Move A(outValue1) <- C(outValue1Copy)
+    builder.addMove(outValue1Copy.type, outValue1.register, outValue1Copy.register);
+  }
+
+  @Override
+  public boolean emitsIR() {
+    return false;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStore.java b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
index 817045c..f364eaa 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
@@ -6,6 +6,10 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -54,4 +58,15 @@
   public int getLocalIndex() {
     return var;
   }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    Slot pop = state.pop();
+    builder.addMove(type, state.write(var, pop).register, pop.register);
+  }
+
+  @Override
+  public boolean emitsIR() {
+    return false;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
index 1dc72d4..e0ed773 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
@@ -4,6 +4,10 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntList;
@@ -41,7 +45,7 @@
     return new IntArrayList(keys);
   }
 
-  public List<CfLabel> getTargets() {
+  public List<CfLabel> getSwitchTargets() {
     return targets;
   }
 
@@ -67,4 +71,14 @@
   public void print(CfPrinter printer) {
     printer.print(this);
   }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    int[] labelOffsets = new int[targets.size()];
+    for (int i = 0; i < targets.size(); i++) {
+      labelOffsets[i] = code.getLabelOffset(targets.get(i));
+    }
+    Slot value = state.pop();
+    builder.addSwitch(value.register, keys, code.getLabelOffset(defaultTarget), labelOffsets);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
index ef73d10..483d3a8 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
@@ -4,6 +4,10 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -19,4 +23,15 @@
   public void print(CfPrinter printer) {
     printer.print(this);
   }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    Slot exception = state.pop();
+    builder.addThrow(exception.register);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java b/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
index 6dceda0..5c1aa30 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
@@ -21,6 +21,14 @@
     this.end = end;
     this.guards = guards;
     this.targets = targets;
+    assert verifyAllNonNull(guards);
+  }
+
+  private static boolean verifyAllNonNull(List<DexType> types) {
+    for (DexType type : types) {
+      assert type != null;
+    }
+    return true;
   }
 
   public static CfTryCatch fromBuilder(
diff --git a/src/main/java/com/android/tools/r8/code/ConstMethodType.java b/src/main/java/com/android/tools/r8/code/ConstMethodType.java
index aba9e08..6a1bef5 100644
--- a/src/main/java/com/android/tools/r8/code/ConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/code/ConstMethodType.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
@@ -56,6 +57,11 @@
   }
 
   @Override
+  public void registerUse(UseRegistry registry) {
+    registry.registerProto(getMethodType());
+  }
+
+  @Override
   public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
     int index = BBBB.getOffset(mapping);
     if (index != (index & 0xffff)) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 2ba0890..55bc2db 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -62,6 +63,11 @@
     return app.definitionFor(type);
   }
 
+  public Origin originFor(DexType type) {
+    DexClass definition = app.definitionFor(type);
+    return definition == null ? Origin.unknown() : definition.origin;
+  }
+
   public DexEncodedMethod definitionFor(DexMethod method) {
     return (DexEncodedMethod) getDefinitions(method.getHolder()).get(method);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index ab1eca5..1ac2246 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -22,8 +22,11 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.Collections;
 import java.util.LinkedList;
@@ -120,6 +123,29 @@
   }
 
   @Override
+  public int estimatedSizeForInlining() {
+    return countNonStackOperations(Integer.MAX_VALUE);
+  }
+
+  @Override
+  public boolean estimatedSizeForInliningAtMost(int threshold) {
+    return countNonStackOperations(threshold) <= threshold;
+  }
+
+  private int countNonStackOperations(int threshold) {
+    int result = 0;
+    for (CfInstruction instruction : instructions) {
+      if (instruction.emitsIR()) {
+        result++;
+        if (result > threshold) {
+          break;
+        }
+      }
+    }
+    return result;
+  }
+
+  @Override
   public boolean isCfCode() {
     return true;
   }
@@ -183,7 +209,7 @@
   }
 
   @Override
-  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
       throws ApiLevelException {
     if (instructions.size() == 2
         && instructions.get(0) instanceof CfConstNull
@@ -200,7 +226,7 @@
       LinkedList<BasicBlock> blocks = new LinkedList<>(Collections.singleton(block));
       return new IRCode(options, encodedMethod, blocks, null, false);
     }
-    throw new Unimplemented("Converting Java class-file bytecode to IR not yet supported");
+    return internalBuild(encodedMethod, options, null, null, origin);
   }
 
   @Override
@@ -208,9 +234,29 @@
       DexEncodedMethod encodedMethod,
       InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
-      Position callerPosition)
+      Position callerPosition,
+      Origin origin)
       throws ApiLevelException {
-    throw new Unimplemented("Converting Java class-file bytecode to IR not yet supported");
+    assert valueNumberGenerator != null;
+    assert callerPosition != null;
+    return internalBuild(encodedMethod, options, valueNumberGenerator, callerPosition, origin);
+  }
+
+  private IRCode internalBuild(
+      DexEncodedMethod encodedMethod,
+      InternalOptions options,
+      ValueNumberGenerator generator,
+      Position callerPosition,
+      Origin origin)
+      throws ApiLevelException {
+    assert !options.isGeneratingDex() || !encodedMethod.accessFlags.isSynchronized()
+        : "Converting CfCode to IR not supported for DEX output of synchronized methods.";
+    CfSourceCode source = new CfSourceCode(this, encodedMethod, callerPosition, origin);
+    IRBuilder builder =
+        (generator == null)
+            ? new IRBuilder(encodedMethod, source, options)
+            : new IRBuilder(encodedMethod, source, options, generator);
+    return builder.build();
   }
 
   @Override
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 db9306d..5a4faff 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -12,18 +12,21 @@
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.optimize.Outliner.OutlineCode;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
 
 public abstract class Code extends CachedHashValueDexItem {
 
-  public abstract IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+  public abstract IRCode buildIR(
+      DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
       throws ApiLevelException;
 
   public IRCode buildInliningIR(
       DexEncodedMethod encodedMethod,
       InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
-      Position callerPosition)
+      Position callerPosition,
+      Origin origin)
       throws ApiLevelException {
     throw new Unreachable("Unexpected attempt to build IR graph for inlining from: "
         + getClass().getCanonicalName());
@@ -52,10 +55,16 @@
     return false;
   }
 
+  /** Estimate the number of IR instructions emitted by buildIR(). */
   public int estimatedSizeForInlining() {
     return Integer.MAX_VALUE;
   }
 
+  /** Compute estimatedSizeForInlining() <= threshold. */
+  public boolean estimatedSizeForInliningAtMost(int threshold) {
+    return estimatedSizeForInlining() <= threshold;
+  }
+
   public CfCode asCfCode() {
     throw new Unreachable(getClass().getCanonicalName() + ".asCfCode()");
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index a023357..d9560c8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ir.conversion.DexSourceCode;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.android.tools.r8.utils.StringUtils;
@@ -163,7 +164,7 @@
   }
 
   @Override
-  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
       throws ApiLevelException {
     DexSourceCode source =
         new DexSourceCode(
@@ -177,7 +178,8 @@
       DexEncodedMethod encodedMethod,
       InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
-      Position callerPosition)
+      Position callerPosition,
+      Origin origin)
       throws ApiLevelException {
     DexSourceCode source =
         new DexSourceCode(
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 d6bde0b..d514a9a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -42,6 +42,7 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.Arrays;
 import java.util.Collections;
@@ -227,20 +228,23 @@
     compilationState = CompilationState.NOT_PROCESSED;
   }
 
-  public IRCode buildIR(InternalOptions options) throws ApiLevelException {
-    return code == null ? null : code.buildIR(this, options);
+  public IRCode buildIR(InternalOptions options, Origin origin) throws ApiLevelException {
+    return code == null ? null : code.buildIR(this, options, origin);
   }
 
   public IRCode buildInliningIRForTesting(
       InternalOptions options, ValueNumberGenerator valueNumberGenerator)
       throws ApiLevelException {
-    return buildInliningIR(options, valueNumberGenerator, null);
+    return buildInliningIR(options, valueNumberGenerator, null, Origin.unknown());
   }
 
   public IRCode buildInliningIR(
-      InternalOptions options, ValueNumberGenerator valueNumberGenerator, Position callerPosition)
+      InternalOptions options,
+      ValueNumberGenerator valueNumberGenerator,
+      Position callerPosition,
+      Origin origin)
       throws ApiLevelException {
-    return code.buildInliningIR(this, options, valueNumberGenerator, callerPosition);
+    return code.buildInliningIR(this, options, valueNumberGenerator, callerPosition, origin);
   }
 
   public void setCode(Code code) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 9e77dd5..b971ab4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -125,6 +125,8 @@
   public final DexString getMethodName = createString("getMethod");
   public final DexString getDeclaredMethodName = createString("getDeclaredMethod");
   public final DexString assertionsDisabled = createString("$assertionsDisabled");
+  public final DexString invokeMethodName = createString("invoke");
+  public final DexString invokeExactMethodName = createString("invokeExact");
 
   public final DexString stringDescriptor = createString("Ljava/lang/String;");
   public final DexString stringArrayDescriptor = createString("[Ljava/lang/String;");
@@ -209,6 +211,7 @@
   public final AtomicFieldUpdaterMethods atomicFieldUpdaterMethods =
       new AtomicFieldUpdaterMethods();
   public final Kotlin kotlin;
+  public final PolymorphicMethods polymorphicMethods = new PolymorphicMethods();
 
   // Dex system annotations.
   // See https://source.android.com/devices/tech/dalvik/dex-format.html#system-annotation
@@ -471,6 +474,75 @@
     }
   }
 
+  public class PolymorphicMethods {
+
+    private final DexProto signature = createProto(objectType, objectArrayType);
+    private final DexProto setSignature = createProto(voidType, objectArrayType);
+    private final DexProto compareAndSetSignature = createProto(booleanType, objectArrayType);
+
+    private final Set<DexString> varHandleMethods =
+        createStrings(
+            "compareAndExchange",
+            "compareAndExchangeAcquire",
+            "compareAndExchangeRelease",
+            "get",
+            "getAcquire",
+            "getAndAdd",
+            "getAndAddAcquire",
+            "getAndAddRelease",
+            "getAndBitwiseAnd",
+            "getAndBitwiseAndAcquire",
+            "getAndBitwiseAndRelease",
+            "getAndBitwiseOr",
+            "getAndBitwiseOrAcquire",
+            "getAndBitwiseOrRelease",
+            "getAndBitwiseXor",
+            "getAndBitwiseXorAcquire",
+            "getAndBitwiseXorRelease",
+            "getAndSet",
+            "getAndSetAcquire",
+            "getAndSetRelease",
+            "getOpaque",
+            "getVolatile");
+
+    private final Set<DexString> varHandleSetMethods =
+        createStrings("set", "setOpaque", "setRelease", "setVolatile");
+
+    private final Set<DexString> varHandleCompareAndSetMethods =
+        createStrings(
+            "compareAndSet",
+            "weakCompareAndSet",
+            "weakCompareAndSetAcquire",
+            "weakCompareAndSetPlain",
+            "weakCompareAndSetRelease");
+
+    public DexMethod canonicalize(DexMethod invokeProto) {
+      if (invokeProto.holder == methodHandleType) {
+        if (invokeProto.name == invokeMethodName || invokeProto.name == invokeExactMethodName) {
+          return createMethod(methodHandleType, signature, invokeProto.name);
+        }
+      } else if (invokeProto.holder == varHandleType) {
+        if (varHandleMethods.contains(invokeProto.name)) {
+          return createMethod(varHandleType, signature, invokeProto.name);
+        } else if (varHandleSetMethods.contains(invokeProto.name)) {
+          return createMethod(varHandleType, setSignature, invokeProto.name);
+        } else if (varHandleCompareAndSetMethods.contains(invokeProto.name)) {
+          return createMethod(varHandleType, compareAndSetSignature, invokeProto.name);
+        }
+      }
+      return null;
+    }
+
+    private Set<DexString> createStrings(String... strings) {
+      IdentityHashMap<DexString, DexString> map = new IdentityHashMap<>();
+      for (String string : strings) {
+        DexString dexString = createString(string);
+        map.put(dexString, dexString);
+      }
+      return map.keySet();
+    }
+  }
+
   private static <T extends DexItem> T canonicalize(ConcurrentHashMap<T, T> map, T item) {
     assert item != null;
     assert !DexItemFactory.isInternalSentinel(item);
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 dc75307..6c414ac 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -104,7 +104,7 @@
   }
 
   @Override
-  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
       throws ApiLevelException {
     triggerDelayedParsingIfNeccessary();
     return options.debug
@@ -117,7 +117,8 @@
       DexEncodedMethod encodedMethod,
       InternalOptions options,
       ValueNumberGenerator generator,
-      Position callerPosition)
+      Position callerPosition,
+      Origin origin)
       throws ApiLevelException {
     assert generator != null;
     triggerDelayedParsingIfNeccessary();
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index a864aba..e816bd5 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -141,9 +141,19 @@
   }
 
   @Override
-  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+  public int estimatedSizeForInlining() {
+    return asCfCode().estimatedSizeForInlining();
+  }
+
+  @Override
+  public boolean estimatedSizeForInliningAtMost(int threshold) {
+    return asCfCode().estimatedSizeForInliningAtMost(threshold);
+  }
+
+  @Override
+  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
       throws ApiLevelException {
-    return asCfCode().buildIR(encodedMethod, options);
+    return asCfCode().buildIR(encodedMethod, options, origin);
   }
 
   @Override
@@ -151,9 +161,11 @@
       DexEncodedMethod encodedMethod,
       InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
-      Position callerPosition)
+      Position callerPosition,
+      Origin origin)
       throws ApiLevelException {
-    return asCfCode().buildInliningIR(encodedMethod, options, valueNumberGenerator, callerPosition);
+    return asCfCode().buildInliningIR(
+        encodedMethod, options, valueNumberGenerator, callerPosition, origin);
   }
 
   @Override
@@ -302,7 +314,7 @@
 
     private DexType createTypeFromInternalType(String local) {
       assert local.indexOf('.') == -1;
-      return factory.createType("L" + local + ";");
+      return factory.createType(Type.getObjectType(local).getDescriptor());
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index d59df07..113d522 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -424,6 +424,10 @@
     this.number = number;
   }
 
+  public String getNumberAsString() {
+    return number >= 0 ? "" + number : "<unknown>";
+  }
+
   public int numberInstructions(int nextInstructionNumber) {
     for (Instruction instruction : instructions) {
       instruction.setNumber(nextInstructionNumber);
@@ -826,7 +830,7 @@
       StringBuilder builder, List<BasicBlock> list, Function<BasicBlock, String> postfix) {
     if (list.size() > 0) {
       for (BasicBlock block : list) {
-        builder.append(block.number >= 0 ? block.number : "<unknown>");
+        builder.append(block.getNumberAsString());
         builder.append(postfix.apply(block));
         builder.append(' ');
       }
@@ -901,16 +905,15 @@
     int lineColumn = 0;
     int numberColumn = 0;
     for (Instruction instruction : instructions) {
-      lineColumn = Math.max(lineColumn, instruction.getPosition().toString().length());
+      lineColumn = Math.max(lineColumn, instruction.getPositionAsString().length());
       numberColumn = Math.max(numberColumn, digits(instruction.getNumber()));
     }
-    Position currentPosition = null;
+    String currentPosition = null;
     for (Instruction instruction : instructions) {
       if (lineColumn > 0) {
         String line = "";
-        if (!instruction.getPosition().equals(currentPosition)) {
-          currentPosition = instruction.getPosition();
-          line = currentPosition.toString();
+        if (!instruction.getPositionAsString().equals(currentPosition)) {
+          line = currentPosition = instruction.getPositionAsString();
         }
         StringUtils.appendLeftPadded(builder, line, lineColumn + 1);
         builder.append(": ");
diff --git a/src/main/java/com/android/tools/r8/ir/code/Goto.java b/src/main/java/com/android/tools/r8/ir/code/Goto.java
index b763298..cd5fcc8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Goto.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Goto.java
@@ -65,7 +65,7 @@
   @Override
   public String toString() {
     if (getBlock() != null && !getBlock().getSuccessors().isEmpty()) {
-      return super.toString() + "block " + blockNumberToString(getTarget());
+      return super.toString() + "block " + getTarget().getNumberAsString();
     }
     return super.toString() + "block <unknown>";
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/If.java b/src/main/java/com/android/tools/r8/ir/code/If.java
index 0235a14..2e1609d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/If.java
+++ b/src/main/java/com/android/tools/r8/ir/code/If.java
@@ -130,9 +130,9 @@
         + " "
         + type
         + " block "
-        + blockNumberToString(getTrueTarget())
+        + getTrueTarget().getNumberAsString()
         + " (fallthrough "
-        + blockNumberToString(fallthroughBlock())
+        + fallthroughBlock().getNumberAsString()
         + ")";
   }
 
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 1c3423c..999699c 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
@@ -72,6 +72,10 @@
     this.position = position;
   }
 
+  public String getPositionAsString() {
+    return position == null ? "???" : position.toString();
+  }
+
   public List<Value> inValues() {
     return inValues;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java b/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
index 349d5df..db151c6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
@@ -51,11 +51,4 @@
     return Constraint.ALWAYS;
   }
 
-  static String blockNumberToString(BasicBlock block) {
-    try {
-      return "" + block.getNumber();
-    } catch (AssertionError e) {
-      return "<invalid>";
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Switch.java b/src/main/java/com/android/tools/r8/ir/code/Switch.java
index 50e59f1..871500c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Switch.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Switch.java
@@ -266,7 +266,7 @@
       builder.append("          ");
       builder.append(getKey(i));
       builder.append(" -> ");
-      builder.append(blockNumberToString(targetBlock(i)));
+      builder.append(targetBlock(i).getNumberAsString());
       builder.append("\n");
     }
     builder.append("          F -> ");
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
new file mode 100644
index 0000000..382a0a9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -0,0 +1,627 @@
+// 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.ir.conversion;
+
+import static it.unimi.dsi.fastutil.ints.Int2ObjectSortedMaps.emptyMap;
+
+import com.android.tools.r8.ApiLevelException;
+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.CfInstruction;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfPosition;
+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.graph.CfCode;
+import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
+import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfState.Snapshot;
+import com.android.tools.r8.ir.conversion.IRBuilder.BlockInfo;
+import com.android.tools.r8.origin.Origin;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntSet;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
+import it.unimi.dsi.fastutil.objects.ReferenceSet;
+import java.util.ArrayList;
+import java.util.List;
+
+public class CfSourceCode implements SourceCode {
+
+  private BlockInfo currentBlockInfo;
+
+  private static class TryHandlerList {
+
+    public final int startOffset;
+    public final int endOffset;
+    public final List<DexType> guards;
+    public final IntList offsets;
+
+    TryHandlerList(int startOffset, int endOffset, List<DexType> guards, IntList offsets) {
+      this.startOffset = startOffset;
+      this.endOffset = endOffset;
+      this.guards = guards;
+      this.offsets = offsets;
+    }
+
+    boolean validFor(int instructionOffset) {
+      return startOffset <= instructionOffset && instructionOffset < endOffset;
+    }
+
+    boolean isEmpty() {
+      assert guards.isEmpty() == offsets.isEmpty();
+      return guards.isEmpty();
+    }
+
+    static TryHandlerList computeTryHandlers(
+        int instructionOffset,
+        List<CfTryCatch> tryCatchRanges,
+        Reference2IntMap<CfLabel> labelOffsets) {
+      int startOffset = Integer.MIN_VALUE;
+      int endOffset = Integer.MAX_VALUE;
+      List<DexType> guards = new ArrayList<>();
+      IntList offsets = new IntArrayList();
+      ReferenceSet<DexType> seen = new ReferenceOpenHashSet<>();
+      for (CfTryCatch tryCatch : tryCatchRanges) {
+        int start = labelOffsets.getInt(tryCatch.start);
+        int end = labelOffsets.getInt(tryCatch.end);
+        if (start > instructionOffset) {
+          endOffset = Math.min(endOffset, start);
+          continue;
+        } else if (instructionOffset >= end) {
+          startOffset = Math.max(startOffset, end);
+          continue;
+        }
+        startOffset = Math.max(startOffset, start);
+        endOffset = Math.min(endOffset, end);
+        boolean seenCatchAll = false;
+        for (int i = 0; i < tryCatch.guards.size() && !seenCatchAll; i++) {
+          DexType guard = tryCatch.guards.get(i);
+          if (seen.add(guard)) {
+            guards.add(guard);
+            offsets.add(labelOffsets.getInt(tryCatch.targets.get(i)));
+            seenCatchAll = guard == DexItemFactory.catchAllType;
+          }
+        }
+        if (seenCatchAll) {
+          break;
+        }
+      }
+      return new TryHandlerList(startOffset, endOffset, guards, offsets);
+    }
+  }
+
+  private static class LocalVariableList {
+
+    public static final LocalVariableList EMPTY = new LocalVariableList(0, 0, emptyMap());
+    public final int startOffset;
+    public final int endOffset;
+    public final Int2ObjectMap<DebugLocalInfo> locals;
+
+    private LocalVariableList(
+        int startOffset, int endOffset, Int2ObjectMap<DebugLocalInfo> locals) {
+      this.startOffset = startOffset;
+      this.endOffset = endOffset;
+      this.locals = locals;
+    }
+
+    static LocalVariableList compute(
+        int instructionOffset,
+        List<CfCode.LocalVariableInfo> locals,
+        Reference2IntMap<CfLabel> labelOffsets) {
+      int startOffset = Integer.MIN_VALUE;
+      int endOffset = Integer.MAX_VALUE;
+      Int2ObjectMap<DebugLocalInfo> currentLocals = null;
+      for (LocalVariableInfo local : locals) {
+        int start = labelOffsets.getInt(local.getStart());
+        int end = labelOffsets.getInt(local.getEnd());
+        if (start > instructionOffset) {
+          endOffset = Math.min(endOffset, start);
+          continue;
+        } else if (instructionOffset >= end) {
+          startOffset = Math.max(startOffset, end);
+          continue;
+        }
+        if (currentLocals == null) {
+          currentLocals = new Int2ObjectOpenHashMap<>();
+        }
+        startOffset = Math.max(startOffset, start);
+        endOffset = Math.min(endOffset, end);
+        currentLocals.put(local.getIndex(), local.getLocal());
+      }
+      return new LocalVariableList(
+          startOffset, endOffset, currentLocals == null ? emptyMap() : currentLocals);
+    }
+
+    boolean validFor(int instructionOffset) {
+      return startOffset <= instructionOffset && instructionOffset < endOffset;
+    }
+
+    public DebugLocalInfo getLocal(int register) {
+      return locals.get(register);
+    }
+
+    public Int2ObjectOpenHashMap<DebugLocalInfo> merge(LocalVariableList other) {
+      return merge(this, other);
+    }
+
+    private static Int2ObjectOpenHashMap<DebugLocalInfo> merge(
+        LocalVariableList a, LocalVariableList b) {
+      if (a.locals.size() > b.locals.size()) {
+        return merge(b, a);
+      }
+      Int2ObjectOpenHashMap<DebugLocalInfo> result = new Int2ObjectOpenHashMap<>();
+      for (Entry<DebugLocalInfo> local : a.locals.int2ObjectEntrySet()) {
+        if (local.getValue().equals(b.getLocal(local.getIntKey()))) {
+          result.put(local.getIntKey(), local.getValue());
+        }
+      }
+      return result;
+    }
+  }
+
+  private CfState state;
+  private final CfCode code;
+  private final DexEncodedMethod method;
+  private final Position callerPosition;
+  private final Origin origin;
+
+  // Synthetic position with line = 0.
+  private final Position preamblePosition;
+  private final Reference2IntMap<CfLabel> labelOffsets = new Reference2IntOpenHashMap<>();
+  private TryHandlerList cachedTryHandlerList;
+  private LocalVariableList cachedLocalVariableList;
+  private int currentInstructionIndex;
+  private boolean inPrelude;
+  private Int2ObjectMap<DebugLocalInfo> incomingLocals;
+  private Int2ObjectMap<DebugLocalInfo> outgoingLocals;
+  private Int2ReferenceMap<Int2ObjectMap<DebugLocalInfo>> definitelyLiveIncomingLocals =
+      new Int2ReferenceOpenHashMap<>();
+  private Int2ReferenceMap<CfState.Snapshot> incomingState = new Int2ReferenceOpenHashMap<>();
+
+  public CfSourceCode(
+      CfCode code, DexEncodedMethod method, Position callerPosition, Origin origin) {
+    this.code = code;
+    this.method = method;
+    this.callerPosition = callerPosition;
+    this.origin = origin;
+    preamblePosition = Position.synthetic(0, method.method, null);
+    for (int i = 0; i < code.getInstructions().size(); i++) {
+      CfInstruction instruction = code.getInstructions().get(i);
+      if (instruction instanceof CfLabel) {
+        labelOffsets.put((CfLabel) instruction, instructionOffset(i));
+      }
+    }
+    this.state = new CfState(origin);
+  }
+
+  @Override
+  public int instructionCount() {
+    return code.getInstructions().size();
+  }
+
+  @Override
+  public int instructionIndex(int instructionOffset) {
+    return instructionOffset;
+  }
+
+  @Override
+  public int instructionOffset(int instructionIndex) {
+    return instructionIndex;
+  }
+
+  @Override
+  public boolean verifyRegister(int register) {
+    return true;
+  }
+
+  @Override
+  public void setUp() {}
+
+  @Override
+  public void clear() {}
+
+  @Override
+  public int traceInstruction(int instructionIndex, IRBuilder builder) {
+    CfInstruction instruction = code.getInstructions().get(instructionIndex);
+    if (instruction.canThrow()) {
+      TryHandlerList tryHandlers = getTryHandlers(instructionIndex);
+      if (!tryHandlers.isEmpty()) {
+        // Ensure the block starts at the start of the try-range (don't enqueue, not a target).
+        builder.ensureBlockWithoutEnqueuing(tryHandlers.startOffset);
+        IntSet seen = new IntOpenHashSet();
+        for (int offset : tryHandlers.offsets) {
+          if (seen.add(offset)) {
+            builder.ensureExceptionalSuccessorBlock(instructionIndex, offset);
+          }
+        }
+        if (!(instruction instanceof CfThrow)) {
+          builder.ensureNormalSuccessorBlock(instructionIndex, instructionIndex + 1);
+        }
+        return instructionIndex;
+      }
+      // If the throwable instruction is "throw" it closes the block.
+      return (instruction instanceof CfThrow) ? instructionIndex : -1;
+    }
+    if (isControlFlow(instruction)) {
+      for (int target : getTargets(instructionIndex)) {
+        builder.ensureNormalSuccessorBlock(instructionIndex, target);
+      }
+      return instructionIndex;
+    }
+    return -1;
+  }
+
+  private TryHandlerList getTryHandlers(int instructionOffset) {
+    if (cachedTryHandlerList == null || !cachedTryHandlerList.validFor(instructionOffset)) {
+      cachedTryHandlerList =
+          TryHandlerList.computeTryHandlers(
+              instructionOffset, code.getTryCatchRanges(), labelOffsets);
+    }
+    return cachedTryHandlerList;
+  }
+
+  private LocalVariableList getLocalVariables(int instructionOffset) {
+    if (cachedLocalVariableList == null || !cachedLocalVariableList.validFor(instructionOffset)) {
+      cachedLocalVariableList =
+          LocalVariableList.compute(instructionOffset, code.getLocalVariables(), labelOffsets);
+    }
+    return cachedLocalVariableList;
+  }
+
+  private int[] getTargets(int instructionIndex) {
+    CfInstruction instruction = code.getInstructions().get(instructionIndex);
+    assert isControlFlow(instruction);
+    CfLabel target = instruction.getTarget();
+    if (instruction.isReturn() || instruction instanceof CfThrow) {
+      assert target == null;
+      return new int[] {};
+    }
+    assert instruction instanceof CfSwitch || target != null
+        : "getTargets(): Non-control flow instruction " + instruction.getClass();
+    if (instruction instanceof CfSwitch) {
+      CfSwitch cfSwitch = (CfSwitch) instruction;
+      List<CfLabel> targets = cfSwitch.getSwitchTargets();
+      int[] res = new int[targets.size() + 1];
+      for (int i = 0; i < targets.size(); i++) {
+        res[i] = labelOffsets.getInt(targets.get(i));
+      }
+      res[targets.size()] = labelOffsets.getInt(cfSwitch.getDefaultTarget());
+      return res;
+    }
+    int targetIndex = labelOffsets.getInt(target);
+    if (instruction instanceof CfGoto) {
+      return new int[] {targetIndex};
+    }
+    assert instruction.isConditionalJump();
+    return new int[] {instructionIndex + 1, targetIndex};
+  }
+
+  @Override
+  public void buildPrelude(IRBuilder builder) {
+    assert !inPrelude;
+    inPrelude = true;
+    state.buildPrelude();
+    setLocalVariableLists();
+    buildArgumentInstructions(builder);
+    recordStateForTarget(0, state.getSnapshot());
+    // TODO: addDebugLocalUninitialized + addDebugLocalStart for non-argument locals live at 0
+    // TODO: Generate method synchronization
+    inPrelude = false;
+  }
+
+  private void buildArgumentInstructions(IRBuilder builder) {
+    int argumentRegister = 0;
+    if (!isStatic()) {
+      DexType type = method.method.holder;
+      state.write(argumentRegister, type);
+      builder.addThisArgument(argumentRegister++);
+    }
+    for (DexType type : method.method.proto.parameters.values) {
+      state.write(argumentRegister, type);
+      if (type.isBooleanType()) {
+        builder.addBooleanNonThisArgument(argumentRegister++);
+      } else {
+        ValueType valueType = ValueType.fromDexType(type);
+        builder.addNonThisArgument(argumentRegister, valueType);
+        argumentRegister += valueType.requiredRegisters();
+      }
+    }
+  }
+
+  private boolean isStatic() {
+    return method.accessFlags.isStatic();
+  }
+
+  @Override
+  public void buildPostlude(IRBuilder builder) {
+    // Since we're generating classfiles, we never need to synthesize monitor enter/exit.
+  }
+
+  @Override
+  public void buildInstruction(
+      IRBuilder builder, int instructionIndex, boolean firstBlockInstruction)
+      throws ApiLevelException {
+    CfInstruction instruction = code.getInstructions().get(instructionIndex);
+    currentInstructionIndex = instructionIndex;
+    if (firstBlockInstruction) {
+      currentBlockInfo = builder.getCFG().get(instructionIndex);
+      state.reset(incomingState.get(instructionIndex), instructionIndex == 0);
+    }
+    setLocalVariableLists();
+    readEndingLocals(builder);
+    if (isControlFlow(instruction)) {
+      ensureDebugValueLivenessControl(builder);
+      instruction.buildIR(builder, state, this);
+      Snapshot stateSnapshot = state.getSnapshot();
+      for (int target : getTargets(instructionIndex)) {
+        recordStateForTarget(target, stateSnapshot);
+      }
+      state.clear();
+    } else {
+      if (currentBlockInfo != null && instruction.canThrow()) {
+        Snapshot exceptionTransfer =
+            state.getSnapshot().exceptionTransfer(builder.getFactory().throwableType);
+        for (int target : currentBlockInfo.exceptionalSuccessors) {
+          recordStateForTarget(target, exceptionTransfer);
+        }
+      }
+      instruction.buildIR(builder, state, this);
+      ensureDebugValueLiveness(builder);
+      if (builder.getCFG().containsKey(currentInstructionIndex + 1)) {
+        recordStateForTarget(currentInstructionIndex + 1, state.getSnapshot());
+      }
+    }
+  }
+
+  private void recordStateForTarget(int target, Snapshot snapshot) {
+    Snapshot existing = incomingState.get(target);
+    Snapshot merged = CfState.merge(existing, snapshot, origin);
+    if (merged != existing) {
+      incomingState.put(target, merged);
+    }
+  }
+
+  public int getCurrentInstructionIndex() {
+    return currentInstructionIndex;
+  }
+
+  public int getLabelOffset(CfLabel label) {
+    assert labelOffsets.containsKey(label);
+    return labelOffsets.getInt(label);
+  }
+
+  public void setStateFromFrame(CfFrame frame) {
+    Int2ReferenceSortedMap<FrameType> frameLocals = frame.getLocals();
+    DexType[] locals = new DexType[frameLocals.isEmpty() ? 0 : frameLocals.lastIntKey() + 1];
+    DexType[] stack = new DexType[frame.getStack().size()];
+    for (Int2ReferenceMap.Entry<FrameType> entry : frameLocals.int2ReferenceEntrySet()) {
+      locals[entry.getIntKey()] = convertUninitialized(entry.getValue());
+    }
+    for (int i = 0; i < stack.length; i++) {
+      stack[i] = convertUninitialized(frame.getStack().get(i));
+    }
+    state.setStateFromFrame(locals, stack, getDebugPositionAtOffset(currentInstructionIndex));
+  }
+
+  private DexType convertUninitialized(FrameType type) {
+    if (type.isInitialized()) {
+      return type.getInitializedType();
+    }
+    if (type.isUninitializedNew()) {
+      int labelOffset = getLabelOffset(type.getUninitializedLabel());
+      int insnOffset = labelOffset + 1;
+      while (insnOffset < code.getInstructions().size()) {
+        CfInstruction instruction = code.getInstructions().get(insnOffset);
+        if (!(instruction instanceof CfLabel)
+            && !(instruction instanceof CfFrame)
+            && !(instruction instanceof CfPosition)) {
+          assert instruction instanceof CfNew;
+          break;
+        }
+        insnOffset += 1;
+      }
+      CfInstruction instruction = code.getInstructions().get(insnOffset);
+      assert instruction instanceof CfNew;
+      return ((CfNew) instruction).getType();
+    }
+    if (type.isUninitializedThis()) {
+      return method.method.holder;
+    }
+    assert type.isTop();
+    return null;
+  }
+
+  @Override
+  public void resolveAndBuildSwitch(
+      int value, int fallthroughOffset, int payloadOffset, IRBuilder builder) {}
+
+  @Override
+  public void resolveAndBuildNewArrayFilledData(
+      int arrayRef, int payloadOffset, IRBuilder builder) {}
+
+  @Override
+  public DebugLocalInfo getIncomingLocal(int register) {
+    return incomingLocals.get(register);
+  }
+
+  @Override
+  public DebugLocalInfo getOutgoingLocal(int register) {
+    if (inPrelude) {
+      return getIncomingLocal(register);
+    }
+    assert !isControlFlow(code.getInstructions().get(currentInstructionIndex))
+        : "Outgoing local is undefined for control-flow instructions";
+    return outgoingLocals.get(register);
+  }
+
+  private void setLocalVariableLists() {
+    incomingLocals = getLocalVariables(currentInstructionIndex).locals;
+    CfInstruction currentInstruction = code.getInstructions().get(currentInstructionIndex);
+    if (inPrelude) {
+      outgoingLocals = incomingLocals;
+      return;
+    }
+    if (currentInstruction.isReturn() || currentInstruction instanceof CfThrow) {
+      outgoingLocals = emptyMap();
+      return;
+    }
+    if (isControlFlow(currentInstruction)) {
+      // We need to read all locals that are not live on all successors to ensure liveness.
+      // Determine outgoingLocals as the intersection of all successors' locals.
+      outgoingLocals = null;
+      int[] targets = getTargets(currentInstructionIndex);
+      for (int target : targets) {
+        Int2ObjectMap<DebugLocalInfo> locals = getLocalVariables(target).locals;
+        outgoingLocals = intersectMaps(outgoingLocals, locals);
+      }
+      assert outgoingLocals != null;
+      // Pass outgoingLocals to all successors.
+      for (int target : targets) {
+        Int2ObjectMap<DebugLocalInfo> existing = definitelyLiveIncomingLocals.get(target);
+        definitelyLiveIncomingLocals.put(target, intersectMaps(existing, outgoingLocals));
+      }
+    } else {
+      outgoingLocals = getLocalVariables(currentInstructionIndex + 1).locals;
+    }
+  }
+
+  private void readEndingLocals(IRBuilder builder) {
+    if (!outgoingLocals.equals(incomingLocals)) {
+      // Add reads of locals ending after the current instruction.
+      for (Entry<DebugLocalInfo> entry : incomingLocals.int2ObjectEntrySet()) {
+        if (!entry.getValue().equals(outgoingLocals.get(entry.getIntKey()))) {
+          builder.addDebugLocalRead(entry.getIntKey(), entry.getValue());
+        }
+      }
+    }
+  }
+
+  private Int2ObjectMap<DebugLocalInfo> intersectMaps(
+      Int2ObjectMap<DebugLocalInfo> existing, Int2ObjectMap<DebugLocalInfo> update) {
+    assert update != null;
+    if (existing == null) {
+      return update;
+    }
+    if (existing.size() > update.size()) {
+      return intersectMaps(update, existing);
+    }
+    if (existing.equals(update)) {
+      return existing;
+    }
+    Int2ObjectOpenHashMap<DebugLocalInfo> result = new Int2ObjectOpenHashMap<>();
+    for (Entry<DebugLocalInfo> local : existing.int2ObjectEntrySet()) {
+      if (local.getValue().equals(update.get(local.getIntKey()))) {
+        result.put(local.getIntKey(), local.getValue());
+      }
+    }
+    return result;
+  }
+
+  private void ensureDebugValueLiveness(IRBuilder builder) {
+    if (incomingLocals.equals(outgoingLocals)) {
+      return;
+    }
+    for (Entry<DebugLocalInfo> entry : incomingLocals.int2ObjectEntrySet()) {
+      if (entry.getValue().equals(outgoingLocals.get(entry.getIntKey()))) {
+        continue;
+      }
+      builder.addDebugLocalEnd(entry.getIntKey(), entry.getValue());
+    }
+    for (Entry<DebugLocalInfo> entry : outgoingLocals.int2ObjectEntrySet()) {
+      if (entry.getValue().equals(incomingLocals.get(entry.getIntKey()))) {
+        continue;
+      }
+      builder.addDebugLocalStart(entry.getIntKey(), entry.getValue());
+    }
+  }
+
+  private void ensureDebugValueLivenessControl(IRBuilder builder) {
+    if (incomingLocals.equals(outgoingLocals)) {
+      return;
+    }
+    for (Entry<DebugLocalInfo> entry : incomingLocals.int2ObjectEntrySet()) {
+      if (entry.getValue().equals(outgoingLocals.get(entry.getIntKey()))) {
+        continue;
+      }
+      builder.addDebugLocalRead(entry.getIntKey(), entry.getValue());
+    }
+    assert outgoingLocals
+        .int2ObjectEntrySet()
+        .stream()
+        .allMatch(entry -> entry.getValue().equals(incomingLocals.get(entry.getIntKey())));
+  }
+
+  private boolean isControlFlow(CfInstruction currentInstruction) {
+    return currentInstruction.isReturn()
+        || currentInstruction.getTarget() != null
+        || currentInstruction instanceof CfSwitch
+        || currentInstruction instanceof CfThrow;
+  }
+
+  @Override
+  public CatchHandlers<Integer> getCurrentCatchHandlers() {
+    TryHandlerList tryHandlers = getTryHandlers(instructionOffset(currentInstructionIndex));
+    if (tryHandlers.isEmpty()) {
+      return null;
+    }
+    return new CatchHandlers<>(tryHandlers.guards, tryHandlers.offsets);
+  }
+
+  @Override
+  public int getMoveExceptionRegister() {
+    return CfState.Slot.STACK_OFFSET;
+  }
+
+  @Override
+  public boolean verifyCurrentInstructionCanThrow() {
+    return code.getInstructions().get(currentInstructionIndex).canThrow();
+  }
+
+  @Override
+  public boolean verifyLocalInScope(DebugLocalInfo local) {
+    return false;
+  }
+
+  @Override
+  public Position getDebugPositionAtOffset(int offset) {
+    while (offset + 1 < code.getInstructions().size()) {
+      CfInstruction insn = code.getInstructions().get(offset);
+      if (!(insn instanceof CfLabel) && !(insn instanceof CfFrame)) {
+        break;
+      }
+      offset += 1;
+    }
+    while (offset >= 0 && !(code.getInstructions().get(offset) instanceof CfPosition)) {
+      offset -= 1;
+    }
+    if (offset < 0) {
+      return Position.noneWithMethod(method.method, callerPosition);
+    }
+    return ((CfPosition) code.getInstructions().get(offset)).getPosition();
+  }
+
+  @Override
+  public Position getCurrentPosition() {
+    return state.getPosition();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfState.java b/src/main/java/com/android/tools/r8/ir/conversion/CfState.java
new file mode 100644
index 0000000..2ac8e60
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfState.java
@@ -0,0 +1,542 @@
+// 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.ir.conversion;
+
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.origin.Origin;
+
+public class CfState {
+
+  private abstract static class SlotType {
+
+    public abstract DexType getPrecise();
+
+    public abstract ValueType getImprecise();
+
+    private static class Precise extends SlotType {
+      private final DexType type;
+
+      Precise(DexType type) {
+        this.type = type;
+      }
+
+      @Override
+      public DexType getPrecise() {
+        return type;
+      }
+
+      @Override
+      public ValueType getImprecise() {
+        return ValueType.fromDexType(type);
+      }
+
+      @Override
+      public String toString() {
+        return "Precise(" + type + ")";
+      }
+    }
+
+    private static class Imprecise extends SlotType {
+
+      private final ValueType type;
+
+      Imprecise(ValueType type) {
+        this.type = type;
+      }
+
+      @Override
+      public DexType getPrecise() {
+        return null;
+      }
+
+      @Override
+      public ValueType getImprecise() {
+        return type;
+      }
+
+      @Override
+      public String toString() {
+        return "Imprecise(" + type + ")";
+      }
+    }
+  }
+
+  private final Origin origin;
+  private Snapshot current;
+
+  public CfState(Origin origin) {
+    this.origin = origin;
+  }
+
+  private static final int MAX_UPDATES = 4;
+
+  public void buildPrelude() {
+    current = new BaseSnapshot();
+  }
+
+  public void clear() {
+    current = null;
+  }
+
+  public void reset(Snapshot snapshot, boolean isMethodEntry) {
+    assert !isMethodEntry || snapshot != null : "Must have snapshot for method entry.";
+    current = snapshot;
+  }
+
+  public void setStateFromFrame(DexType[] locals, DexType[] stack, Position position) {
+    assert current == null || stackHeight() == stack.length;
+    current = new BaseSnapshot(locals, stack, position);
+  }
+
+  public void merge(Snapshot snapshot) {
+    if (current == null) {
+      current = snapshot == null ? new BaseSnapshot() : snapshot;
+    } else {
+      current = merge(current, snapshot, origin);
+    }
+  }
+
+  public Snapshot getSnapshot() {
+    return current;
+  }
+
+  public static Snapshot merge(Snapshot current, Snapshot update, Origin origin) {
+    assert update != null;
+    if (current == null) {
+      return update;
+    }
+    return merge(current.asBase(), update.asBase(), origin);
+  }
+
+  private static Snapshot merge(BaseSnapshot current, BaseSnapshot update, Origin origin) {
+    if (current.stack.length != update.stack.length) {
+      throw new CompilationError(
+          "Different stack heights at jump target: "
+              + current.stack.length
+              + " != "
+              + update.stack.length,
+          origin);
+    }
+    // At this point, JarState checks if `current` has special "NULL" or "BYTE/BOOL" types
+    // that `update` does not have, and if so it computes a refinement.
+    // For now, let's just check that we didn't mix single/wide/reference types.
+    for (int i = 0; i < current.stack.length; i++) {
+      ValueType currentType = current.stack[i].getImprecise();
+      ValueType updateType = update.stack[i].getImprecise();
+      if (!currentType.compatible(updateType)) {
+        throw new CompilationError(
+            "Incompatible types in stack position "
+                + i
+                + ": "
+                + current.stack[i]
+                + " and "
+                + update.stack[i],
+            origin);
+      }
+    }
+    // We could check that locals are compatible, but that doesn't make sense since locals can be
+    // dead at this point.
+    return current;
+  }
+
+  public int stackHeight() {
+    return current.stackHeight();
+  }
+
+  public Slot push(Slot fromSlot) {
+    return push(fromSlot.slotType);
+  }
+
+  public Slot push(DexType type) {
+    return push(new SlotType.Precise(type));
+  }
+
+  public Slot push(ValueType type) {
+    return push(new SlotType.Imprecise(type));
+  }
+
+  private Slot push(SlotType slotType) {
+    Push newSnapshot = new Push(this.current, slotType);
+    updateState(newSnapshot);
+    return current.peek();
+  }
+
+  private void updateState(Snapshot newSnapshot) {
+    current = newSnapshot.updates >= MAX_UPDATES ? new BaseSnapshot(newSnapshot) : newSnapshot;
+  }
+
+  public Slot pop() {
+    Slot top = current.peek();
+    updateState(new Pop(current));
+    return top;
+  }
+
+  public int[] popReverse(int count) {
+    int[] registers = new int[count];
+    for (int i = count - 1; i >= 0; i--) {
+      registers[i] = pop().register;
+    }
+    return registers;
+  }
+
+  public Slot peek() {
+    return current.peek();
+  }
+
+  public Slot peek(int skip) {
+    return current.getStack(current.stackHeight() - 1 - skip);
+  }
+
+  public Slot read(int localIndex) {
+    return current.getLocal(localIndex);
+  }
+
+  public Slot write(int localIndex, DexType type) {
+    return write(localIndex, new SlotType.Precise(type));
+  }
+
+  public Slot write(int localIndex, Slot src) {
+    return write(localIndex, src.slotType);
+  }
+
+  private Slot write(int localIndex, SlotType slotType) {
+    updateState(new Write(current, localIndex, slotType));
+    return current.getLocal(localIndex);
+  }
+
+  public Position getPosition() {
+    return current.getPosition();
+  }
+
+  public void setPosition(Position position) {
+    assert position != null;
+    updateState(new SetPosition(current, position));
+  }
+
+  @Override
+  public String toString() {
+    return new BaseSnapshot(current).toString();
+  }
+
+  public static class Slot {
+
+    public static final int STACK_OFFSET = 100000;
+    public final int register;
+    public final ValueType type;
+    public final DexType preciseType;
+    private final SlotType slotType;
+
+    private Slot(int register, DexType preciseType) {
+      this(register, new SlotType.Precise(preciseType));
+    }
+
+    private Slot(int register, SlotType type) {
+      this.register = register;
+      this.slotType = type;
+      this.type = type.getImprecise();
+      this.preciseType = type.getPrecise();
+    }
+
+    private static Slot stackSlot(int stackPosition, SlotType type) {
+      return new Slot(stackPosition + STACK_OFFSET, type);
+    }
+
+    private int stackPosition() {
+      assert register >= STACK_OFFSET;
+      return register - STACK_OFFSET;
+    }
+
+    @Override
+    public String toString() {
+      return register < STACK_OFFSET
+          ? register + "=" + slotType
+          : "s" + (register - STACK_OFFSET) + "=" + slotType;
+    }
+  }
+
+  public abstract static class Snapshot {
+    final Snapshot parent;
+    final int updates;
+
+    private Snapshot(Snapshot parent, int updates) {
+      this.parent = parent;
+      this.updates = updates;
+    }
+
+    public int stackHeight() {
+      return parent.stackHeight();
+    }
+
+    public int maxLocal() {
+      return parent.maxLocal();
+    }
+
+    public Slot getStack(int i) {
+      return parent.getStack(i);
+    }
+
+    public Slot peek() {
+      return parent.peek();
+    }
+
+    public Slot getLocal(int i) {
+      return parent.getLocal(i);
+    }
+
+    public Position getPosition() {
+      return parent.getPosition();
+    }
+
+    void build(BaseSnapshot base) {
+      parent.build(base);
+    }
+
+    BaseSnapshot asBase() {
+      return new BaseSnapshot(this);
+    }
+
+    public Snapshot exceptionTransfer(DexType throwableType) {
+      BaseSnapshot result = new BaseSnapshot(maxLocal() + 1, 1, getPosition());
+      build(result);
+      result.stack[0] = new SlotType.Precise(throwableType);
+      return result;
+    }
+  }
+
+  private static class BaseSnapshot extends Snapshot {
+    final SlotType[] locals;
+    final SlotType[] stack;
+    final Position position;
+
+    BaseSnapshot() {
+      this(0, 0, Position.none());
+    }
+
+    BaseSnapshot(int locals, int stack, Position position) {
+      super(null, 0);
+      assert position != null;
+      this.locals = new SlotType[locals];
+      this.stack = new SlotType[stack];
+      this.position = position;
+    }
+
+    BaseSnapshot(Snapshot newSnapshot) {
+      this(newSnapshot.maxLocal() + 1, newSnapshot.stackHeight(), newSnapshot.getPosition());
+      newSnapshot.build(this);
+    }
+
+    BaseSnapshot(DexType[] locals, DexType[] stack, Position position) {
+      super(null, 0);
+      assert position != null;
+      this.locals = new SlotType[locals.length];
+      this.stack = new SlotType[stack.length];
+      this.position = position;
+      for (int i = 0; i < locals.length; i++) {
+        this.locals[i] = locals[i] == null ? null : getSlotType(locals[i]);
+      }
+      for (int i = 0; i < stack.length; i++) {
+        assert stack[i] != null;
+        this.stack[i] = getSlotType(stack[i]);
+      }
+    }
+
+    private SlotType getSlotType(DexType local) {
+      return local.toDescriptorString().equals("NULL")
+          ? new SlotType.Imprecise(ValueType.OBJECT)
+          : new SlotType.Precise(local);
+    }
+
+    @Override
+    public int stackHeight() {
+      return stack.length;
+    }
+
+    @Override
+    public int maxLocal() {
+      return locals.length - 1;
+    }
+
+    @Override
+    public Slot getStack(int i) {
+      return Slot.stackSlot(i, stack[i]);
+    }
+
+    @Override
+    public Slot peek() {
+      assert stackHeight() > 0;
+      return getStack(stackHeight() - 1);
+    }
+
+    @Override
+    public Slot getLocal(int i) {
+      return new Slot(i, locals[i]);
+    }
+
+    @Override
+    public Position getPosition() {
+      return position;
+    }
+
+    @Override
+    void build(BaseSnapshot dest) {
+      for (int i = 0; i < locals.length && i < dest.locals.length; i++) {
+        dest.locals[i] = locals[i];
+      }
+      for (int i = 0; i < stack.length && i < dest.stack.length; i++) {
+        dest.stack[i] = stack[i];
+      }
+    }
+
+    @Override
+    BaseSnapshot asBase() {
+      return this;
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder stringBuilder =
+          new StringBuilder().append("position: ").append(position).append(" stack: [");
+      String sep = "";
+      for (SlotType type : stack) {
+        stringBuilder.append(sep).append(type);
+        sep = ", ";
+      }
+      stringBuilder.append("] locals: [");
+      sep = "";
+      for (int i = 0; i < locals.length; i++) {
+        if (locals[i] != null) {
+          stringBuilder.append(sep).append(i).append(':').append(locals[i]);
+          sep = ", ";
+        }
+      }
+      return stringBuilder.append(']').toString();
+    }
+  }
+
+  private static class Push extends Snapshot {
+
+    private final Slot slot;
+
+    Push(Snapshot parent, SlotType type) {
+      super(parent, parent.updates + 1);
+      slot = Slot.stackSlot(parent.stackHeight(), type);
+      assert 100000 <= slot.register && slot.register < 200000;
+    }
+
+    @Override
+    public int stackHeight() {
+      return slot.stackPosition() + 1;
+    }
+
+    @Override
+    public Slot getStack(int i) {
+      return i == slot.stackPosition() ? peek() : parent.getStack(i);
+    }
+
+    @Override
+    public Slot peek() {
+      return slot;
+    }
+
+    @Override
+    void build(BaseSnapshot base) {
+      parent.build(base);
+      if (slot.stackPosition() < base.stack.length) {
+        base.stack[slot.stackPosition()] = slot.slotType;
+      }
+    }
+
+    @Override
+    public String toString() {
+      return parent.toString() + "; push(" + slot.slotType + ")";
+    }
+  }
+
+  private static class Pop extends Snapshot {
+
+    private final int stackHeight;
+
+    Pop(Snapshot parent) {
+      super(parent, parent.updates + 1);
+      stackHeight = parent.stackHeight() - 1;
+      assert stackHeight >= 0;
+    }
+
+    @Override
+    public int stackHeight() {
+      return stackHeight;
+    }
+
+    @Override
+    public Slot getStack(int i) {
+      assert i < stackHeight;
+      return parent.getStack(i);
+    }
+
+    @Override
+    public Slot peek() {
+      return parent.getStack(stackHeight - 1);
+    }
+
+    @Override
+    public String toString() {
+      return parent.toString() + "; pop";
+    }
+  }
+
+  private static class Write extends Snapshot {
+
+    private final Slot slot;
+
+    Write(Snapshot parent, int slotIndex, SlotType type) {
+      super(parent, parent.updates + 1);
+      slot = new Slot(slotIndex, type);
+      assert 0 <= slotIndex && slotIndex < 100000;
+    }
+
+    @Override
+    public int maxLocal() {
+      return Math.max(slot.register, parent.maxLocal());
+    }
+
+    @Override
+    public Slot getLocal(int i) {
+      return i == slot.register ? slot : parent.getLocal(i);
+    }
+
+    @Override
+    void build(BaseSnapshot base) {
+      parent.build(base);
+      base.locals[slot.register] = slot.slotType;
+    }
+
+    @Override
+    public String toString() {
+      return parent.toString() + "; write " + slot.register + " := " + slot.slotType;
+    }
+  }
+
+  private static class SetPosition extends Snapshot {
+
+    private final Position position;
+
+    SetPosition(Snapshot parent, Position position) {
+      super(parent, parent.updates + 1);
+      this.position = position;
+    }
+
+    @Override
+    public Position getPosition() {
+      return position;
+    }
+
+    @Override
+    public String toString() {
+      return parent.toString() + "; set pos " + position;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 58ed822..1a210b2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItem;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexProto;
@@ -113,6 +114,10 @@
 
   public static final int INITIAL_BLOCK_OFFSET = -1;
 
+  public DexItemFactory getFactory() {
+    return options.itemFactory;
+  }
+
   // SSA construction uses a worklist of basic blocks reachable from the entry and their
   // instruction offsets.
   private static class WorklistItem {
@@ -240,6 +245,35 @@
       fallthroughInfo.exceptionalSuccessors = new IntArraySet(this.exceptionalSuccessors);
       return fallthroughInfo;
     }
+
+    @Override
+    public String toString() {
+      StringBuilder stringBuilder =
+          new StringBuilder()
+              .append("block ")
+              .append(block.getNumberAsString())
+              .append(" predecessors: ");
+      String sep = "";
+      for (int offset : normalPredecessors) {
+        stringBuilder.append(sep).append(offset);
+        sep = ", ";
+      }
+      for (int offset : exceptionalPredecessors) {
+        stringBuilder.append(sep).append('*').append(offset);
+        sep = ", ";
+      }
+      stringBuilder.append(" successors: ");
+      sep = "";
+      for (int offset : normalSuccessors) {
+        stringBuilder.append(sep).append(offset);
+        sep = ", ";
+      }
+      for (int offset : exceptionalSuccessors) {
+        stringBuilder.append(sep).append('*').append(offset);
+        sep = ", ";
+      }
+      return stringBuilder.toString();
+    }
   }
 
   // Mapping from instruction offsets to basic-block targets.
@@ -304,6 +338,10 @@
     return targets;
   }
 
+  public DexMethod getMethod() {
+    return method.method;
+  }
+
   private void addToWorklist(BasicBlock block, int firstInstructionIndex) {
     // TODO(ager): Filter out the ones that are already in the worklist, mark bit in block?
     if (!block.isFilled()) {
@@ -615,6 +653,7 @@
     assert local != null;
     assert local == getOutgoingLocal(register);
     ValueType valueType = ValueType.fromDexType(local.type);
+    // TODO(mathiasr): Here we create a Phi with type based on debug info. That's just wrong!
     Value incomingValue = readRegisterIgnoreLocal(register, valueType);
 
     // TODO(mathiasr): This can be simplified once trivial phi removal is local-info aware.
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 7e573f5..20cbd10 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
@@ -578,7 +578,7 @@
       feedback.markProcessed(method, Constraint.NEVER);
       return;
     }
-    IRCode code = method.buildIR(options);
+    IRCode code = method.buildIR(options, appInfo.originFor(method.method.holder));
     if (code == null) {
       feedback.markProcessed(method, Constraint.NEVER);
       return;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
index 31fc38f..c2af6ea 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
@@ -56,7 +56,7 @@
       return;
     }
     DexEncodedMethod initializer = clazz.getClassInitializer();
-    IRCode code = initializer.getCode().buildIR(initializer, options);
+    IRCode code = initializer.getCode().buildIR(initializer, options, clazz.origin);
     Reference2IntMap<DexField> ordinalsMap = new Reference2IntArrayMap<>();
     ordinalsMap.defaultReturnValue(-1);
     InstructionIterator it = code.instructionIterator();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 678ddae..d6efdbb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -29,6 +29,7 @@
 import com.android.tools.r8.ir.conversion.LensCodeRewriter;
 import com.android.tools.r8.ir.conversion.OptimizationFeedback;
 import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Sets;
@@ -268,7 +269,8 @@
         Position callerPosition)
         throws ApiLevelException {
       // Build the IR for a yet not processed method, and perform minimal IR processing.
-      IRCode code = target.buildInliningIR(options, generator, callerPosition);
+      Origin origin = appInfo.originFor(target.method.holder);
+      IRCode code = target.buildInliningIR(options, generator, callerPosition, origin);
       if (!target.isProcessed()) {
         new LensCodeRewriter(graphLense, appInfo).rewrite(code, target);
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index 7947c01..beb4caa 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -139,8 +139,7 @@
   private synchronized boolean isDoubleInliningTarget(DexEncodedMethod candidate) {
     // 10 is found from measuring.
     return inliner.isDoubleInliningTarget(callSiteInformation, candidate)
-        && candidate.getCode().isDexCode()
-        && (candidate.getCode().asDexCode().instructions.length <= 10);
+        && candidate.getCode().estimatedSizeForInliningAtMost(10);
   }
 
   private boolean passesInliningConstraints(InvokeMethod invoke, DexEncodedMethod candidate,
@@ -204,7 +203,7 @@
     if (reason == Reason.SIMPLE) {
       // If we are looking for a simple method, only inline if actually simple.
       Code code = candidate.getCode();
-      if (code.estimatedSizeForInlining() > inliningInstructionLimit) {
+      if (!code.estimatedSizeForInliningAtMost(inliningInstructionLimit)) {
         return false;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index e9781b4..5a257df 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -46,6 +46,7 @@
 import com.android.tools.r8.ir.conversion.SourceCode;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
@@ -1002,7 +1003,7 @@
     }
 
     @Override
-    public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+    public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
         throws ApiLevelException {
       OutlineSourceCode source = new OutlineSourceCode(outline);
       IRBuilder builder = new IRBuilder(encodedMethod, source, options);
@@ -1085,7 +1086,30 @@
             direct,
             DexEncodedMethod.EMPTY_ARRAY, // Virtual methods.
             options.itemFactory.getSkipNameValidationForTesting());
+    if (options.isGeneratingClassFiles()) {
+      // Don't set class file version below 50.0 (JDK release 1.6).
+      clazz.setClassFileVersion(Math.max(50, getClassFileVersion()));
+    }
 
     return clazz;
   }
+
+  private int getClassFileVersion() {
+    assert options.isGeneratingClassFiles();
+    int classFileVersion = -1;
+    Set<DexType> seen = Sets.newIdentityHashSet();
+    for (List<DexEncodedMethod> methods : candidates.values()) {
+      for (DexEncodedMethod method : methods) {
+        DexType holder = method.method.holder;
+        if (seen.add(holder)) {
+          DexProgramClass programClass = appInfo.definitionFor(holder).asProgramClass();
+          assert programClass != null : "Attempt to outline from library class";
+          assert programClass.originatesFromClassResource()
+              : "Attempt to outline from non-classfile input to classfile output";
+          classFileVersion = Math.max(classFileVersion, programClass.getClassFileVersion());
+        }
+      }
+    }
+    return classFileVersion;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
index 25b1d1b..bd85615 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
@@ -93,7 +93,7 @@
     List<DexEncodedField> switchMapFields = Arrays.stream(clazz.staticFields())
         .filter(this::maybeIsSwitchMap).collect(Collectors.toList());
     if (!switchMapFields.isEmpty()) {
-      IRCode initializer = clazz.getClassInitializer().buildIR(options);
+      IRCode initializer = clazz.getClassInitializer().buildIR(options, clazz.origin);
       switchMapFields.forEach(field -> extractSwitchMap(field, initializer));
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
index 53bc57e..b2100e2 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.SourceCode;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.function.Consumer;
 
@@ -37,7 +38,8 @@
   }
 
   @Override
-  public final IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+  public final IRCode buildIR(
+      DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
       throws ApiLevelException {
     return new IRBuilder(encodedMethod, sourceCode, options).build();
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index c0af336..25df235 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1343,7 +1343,7 @@
 
   private void handleProguardReflectiveBehavior(DexEncodedMethod method) {
     try {
-      IRCode code = method.buildIR(options);
+      IRCode code = method.buildIR(options, appInfo.originFor(method.method.holder));
       code.instructionIterator().forEachRemaining(this::handleProguardReflectiveBehavior);
     } catch (ApiLevelException e) {
       // Ignore this exception here. It will be hit again further in the pipeline when
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 5f5237b..91cab92 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -137,8 +137,7 @@
     parse(ImmutableList.of(new ProguardConfigurationSourceFile(path)));
   }
 
-  // package visible for testing
-  void parse(ProguardConfigurationSource source) {
+  public void parse(ProguardConfigurationSource source) {
     parse(ImmutableList.of(source));
   }
 
@@ -968,6 +967,11 @@
     private Path parseFileName() throws ProguardRuleParserException {
       TextPosition start = getPosition();
       skipWhitespace();
+
+      if (baseDirectory == null) {
+        throw parseError("Options with file names are not supported", start);
+      }
+
       String fileName = acceptString(character ->
           character != File.pathSeparatorChar
               && !Character.isWhitespace(character)
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceBytes.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceBytes.java
new file mode 100644
index 0000000..3f379a4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceBytes.java
@@ -0,0 +1,47 @@
+// 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.shaking;
+
+import com.android.tools.r8.origin.Origin;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+
+public class ProguardConfigurationSourceBytes implements ProguardConfigurationSource {
+  private final byte[] bytes;
+  private final Origin origin;
+
+  public ProguardConfigurationSourceBytes(byte[] bytes, Origin origin) {
+    this.bytes = bytes;
+    this.origin = origin;
+  }
+
+  public ProguardConfigurationSourceBytes(InputStream in, Origin origin) throws IOException {
+    this(ByteStreams.toByteArray(in), origin);
+  }
+
+  @Override
+  public String get() throws IOException {
+    return new String(bytes, StandardCharsets.UTF_8);
+  }
+
+  @Override
+  public Path getBaseDirectory() {
+    // Options with relative names (including -include) is not supported.
+    return null;
+  }
+
+  @Override
+  public String getName() {
+    return origin.toString();
+  }
+
+  @Override
+  public Origin getOrigin() {
+    return origin;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index d8dffea..1b026c0 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -589,5 +589,9 @@
         throw new CompilationError("Unsupported source file type", new PathOrigin(file));
       }
     }
+
+    public List<ProgramResourceProvider> getProgramResourceProviders() {
+      return programResourceProviders;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java b/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java
index 9fd91d9..99149ff 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import java.io.FileNotFoundException;
@@ -19,6 +20,10 @@
     this.origin = origin;
   }
 
+  public ExceptionDiagnostic(ResourceException e) {
+    this(e, e.getOrigin());
+  }
+
   @Override
   public Origin getOrigin() {
     return origin;
diff --git a/src/test/debugTestResourcesKotlin/KotlinInline.kt b/src/test/debugTestResourcesKotlin/KotlinInline.kt
index 7f914e4..4b55c57 100644
--- a/src/test/debugTestResourcesKotlin/KotlinInline.kt
+++ b/src/test/debugTestResourcesKotlin/KotlinInline.kt
@@ -47,6 +47,30 @@
         emptyMethod(-1)
     }
 
+    // Double inlining
+    fun testNestedInlining() {
+        val l1 = Int.MAX_VALUE
+        val l2 = Int.MIN_VALUE
+        inlinee1(l1, l2)
+    }
+    inline fun inlinee1(a: Int, b: Int) {
+        val c = a - 2
+        inlinee2(1) {
+            val left = a + b
+            val right = a - b
+            foo(left, right)
+        }
+        inlinee2(c) {
+            foo(b, a)
+        }
+    }
+
+    inline fun inlinee2(p: Int, block: () -> Unit) {
+        if (p > 0) {
+            block()
+        }
+    }
+
     companion object {
         @JvmStatic fun main(args: Array<String>) {
             println("Hello world!")
@@ -54,6 +78,7 @@
             instance.processObject(instance, instance::printObject)
             instance.invokeInlinedFunctions()
             instance.singleInline()
+            instance.testNestedInlining()
         }
     }
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
index 18c3436..4e07849 100644
--- a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
@@ -13,110 +13,287 @@
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.function.BiConsumer;
 import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.util.ASMifier;
 import org.objectweb.asm.util.TraceClassVisitor;
 
-@RunWith(Parameterized.class)
 public class CfFrontendExamplesTest extends TestBase {
 
-  static final Collection<Object[]> TESTS = Arrays.asList(
-    makeTest("arithmetic.Arithmetic"),
-    makeTest("arrayaccess.ArrayAccess"),
-    makeTest("barray.BArray"),
-    makeTest("bridge.BridgeMethod"),
-    makeTest("cse.CommonSubexpressionElimination"),
-    makeTest("constants.Constants"),
-    makeTest("controlflow.ControlFlow"),
-    makeTest("conversions.Conversions"),
-    makeTest("floating_point_annotations.FloatingPointValuedAnnotationTest"),
-    makeTest("filledarray.FilledArray"),
-    makeTest("hello.Hello"),
-    makeTest("ifstatements.IfStatements"),
-    makeTest("instancevariable.InstanceVariable"),
-    makeTest("instanceofstring.InstanceofString"),
-    makeTest("invoke.Invoke"),
-    makeTest("jumbostring.JumboString"),
-    makeTest("loadconst.LoadConst"),
-    makeTest("loop.UdpServer"),
-    makeTest("newarray.NewArray"),
-    makeTest("regalloc.RegAlloc"),
-    makeTest("returns.Returns"),
-    makeTest("staticfield.StaticField"),
-    makeTest("stringbuilding.StringBuilding"),
-    makeTest("switches.Switches"),
-    makeTest("sync.Sync"),
-    makeTest("throwing.Throwing"),
-    makeTest("trivial.Trivial"),
-    makeTest("trycatch.TryCatch"),
-    makeTest("nestedtrycatches.NestedTryCatches"),
-    makeTest("trycatchmany.TryCatchMany"),
-    makeTest("invokeempty.InvokeEmpty"),
-    makeTest("regress.Regress"),
-    makeTest("regress2.Regress2"),
-    makeTest("regress_37726195.Regress"),
-    makeTest("regress_37658666.Regress", CfFrontendExamplesTest::compareRegress37658666),
-    makeTest("regress_37875803.Regress"),
-    makeTest("regress_37955340.Regress"),
-    makeTest("regress_62300145.Regress"),
-    makeTest("regress_64881691.Regress"),
-    makeTest("regress_65104300.Regress"),
-    makeTest("regress_70703087.Test"),
-    makeTest("regress_70736958.Test"),
-    makeTest("regress_70737019.Test"),
-    makeTest("regress_72361252.Test"),
-    makeTest("memberrebinding2.Memberrebinding"),
-    makeTest("memberrebinding3.Memberrebinding"),
-    makeTest("minification.Minification"),
-    makeTest("enclosingmethod.Main"),
-    makeTest("enclosingmethod_proguarded.Main"),
-    makeTest("interfaceinlining.Main"),
-    makeTest("switchmaps.Switches")
-  );
-
-  private static Object[] makeTest(String className) {
-    return makeTest(className, null);
-  }
-
-  private static Object[] makeTest(String className, BiConsumer<byte[], byte[]> comparator) {
-    return new Object[] {className, comparator};
-  }
-
-  @Parameters(name = "{0}")
-  public static Collection<Object[]> data() {
-    return TESTS;
-  }
-
-  private static void compareRegress37658666(byte[] expectedBytes, byte[] actualBytes) {
-    // javac emits LDC(-0.0f) instead of the shorter FCONST_0 FNEG emitted by CfConstNumber.
-    String ldc = "mv.visitLdcInsn(new Float(\"-0.0\"));";
-    String constNeg = "mv.visitInsn(FCONST_0);\nmv.visitInsn(FNEG);";
-    assertEquals(
-        asmToString(expectedBytes).replace(ldc, constNeg),
-        asmToString(actualBytes));
-  }
-
-  private final Path inputJar;
-  private final BiConsumer<byte[], byte[]> comparator;
-
-  public CfFrontendExamplesTest(String clazz, BiConsumer<byte[], byte[]> comparator) {
-    this.comparator = comparator;
-    String pkg = clazz.substring(0, clazz.lastIndexOf('.'));
-    String suffix = "_debuginfo_all";
-    inputJar = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, pkg + suffix + JAR_EXTENSION);
+  @Test
+  public void testArithmetic() throws Exception {
+    runTest("arithmetic.Arithmetic");
   }
 
   @Test
-  public void test() throws Exception {
+  public void testArrayAccess() throws Exception {
+    runTest("arrayaccess.ArrayAccess");
+  }
+
+  @Test
+  public void testBArray() throws Exception {
+    runTest("barray.BArray");
+  }
+
+  @Test
+  public void testBridgeMethod() throws Exception {
+    runTest("bridge.BridgeMethod");
+  }
+
+  @Test
+  public void testCommonSubexpressionElimination() throws Exception {
+    runTest("cse.CommonSubexpressionElimination");
+  }
+
+  @Test
+  public void testConstants() throws Exception {
+    runTest("constants.Constants");
+  }
+
+  @Test
+  public void testControlFlow() throws Exception {
+    runTest("controlflow.ControlFlow");
+  }
+
+  @Test
+  public void testConversions() throws Exception {
+    runTest("conversions.Conversions");
+  }
+
+  @Test
+  public void testFloatingPointValuedAnnotation() throws Exception {
+    runTest("floating_point_annotations.FloatingPointValuedAnnotationTest");
+  }
+
+  @Test
+  public void testFilledArray() throws Exception {
+    runTest("filledarray.FilledArray");
+  }
+
+  @Test
+  public void testHello() throws Exception {
+    runTest("hello.Hello");
+  }
+
+  @Test
+  public void testIfStatements() throws Exception {
+    runTest("ifstatements.IfStatements");
+  }
+
+  @Test
+  public void testInstanceVariable() throws Exception {
+    runTest("instancevariable.InstanceVariable");
+  }
+
+  @Test
+  public void testInstanceofString() throws Exception {
+    runTest("instanceofstring.InstanceofString");
+  }
+
+  @Test
+  public void testInvoke() throws Exception {
+    runTest("invoke.Invoke");
+  }
+
+  @Test
+  public void testJumboString() throws Exception {
+    runTest("jumbostring.JumboString");
+  }
+
+  @Test
+  public void testLoadConst() throws Exception {
+    runTest("loadconst.LoadConst");
+  }
+
+  @Test
+  public void testUdpServer() throws Exception {
+    runTest("loop.UdpServer");
+  }
+
+  @Test
+  public void testNewArray() throws Exception {
+    runTest("newarray.NewArray");
+  }
+
+  @Test
+  public void testRegAlloc() throws Exception {
+    runTest("regalloc.RegAlloc");
+  }
+
+  @Test
+  public void testReturns() throws Exception {
+    runTest("returns.Returns");
+  }
+
+  @Test
+  public void testStaticField() throws Exception {
+    runTest("staticfield.StaticField");
+  }
+
+  @Test
+  public void testStringBuilding() throws Exception {
+    runTest("stringbuilding.StringBuilding");
+  }
+
+  @Test
+  public void testSwitches() throws Exception {
+    runTest("switches.Switches");
+  }
+
+  @Test
+  public void testSync() throws Exception {
+    runTest("sync.Sync");
+  }
+
+  @Test
+  public void testThrowing() throws Exception {
+    runTest("throwing.Throwing");
+  }
+
+  @Test
+  public void testTrivial() throws Exception {
+    runTest("trivial.Trivial");
+  }
+
+  @Test
+  public void testTryCatch() throws Exception {
+    runTest("trycatch.TryCatch");
+  }
+
+  @Test
+  public void testNestedTryCatches() throws Exception {
+    runTest("nestedtrycatches.NestedTryCatches");
+  }
+
+  @Test
+  public void testTryCatchMany() throws Exception {
+    runTest("trycatchmany.TryCatchMany");
+  }
+
+  @Test
+  public void testInvokeEmpty() throws Exception {
+    runTest("invokeempty.InvokeEmpty");
+  }
+
+  @Test
+  public void testRegress() throws Exception {
+    runTest("regress.Regress");
+  }
+
+  @Test
+  public void testRegress2() throws Exception {
+    runTest("regress2.Regress2");
+  }
+
+  @Test
+  public void testRegress37726195() throws Exception {
+    runTest("regress_37726195.Regress");
+  }
+
+  @Test
+  public void testRegress37658666() throws Exception {
+    runTest(
+        "regress_37658666.Regress",
+        (expectedBytes, actualBytes) -> {
+          // javac emits LDC(-0.0f) instead of the shorter FCONST_0 FNEG emitted by CfConstNumber.
+          String ldc = "mv.visitLdcInsn(new Float(\"-0.0\"));";
+          String constNeg = "mv.visitInsn(FCONST_0);\nmv.visitInsn(FNEG);";
+          assertEquals(asmToString(expectedBytes).replace(ldc, constNeg), asmToString(actualBytes));
+        });
+  }
+
+  @Test
+  public void testRegress37875803() throws Exception {
+    runTest("regress_37875803.Regress");
+  }
+
+  @Test
+  public void testRegress37955340() throws Exception {
+    runTest("regress_37955340.Regress");
+  }
+
+  @Test
+  public void testRegress62300145() throws Exception {
+    runTest("regress_62300145.Regress");
+  }
+
+  @Test
+  public void testRegress64881691() throws Exception {
+    runTest("regress_64881691.Regress");
+  }
+
+  @Test
+  public void testRegress65104300() throws Exception {
+    runTest("regress_65104300.Regress");
+  }
+
+  @Test
+  public void testRegress70703087() throws Exception {
+    runTest("regress_70703087.Test");
+  }
+
+  @Test
+  public void testRegress70736958() throws Exception {
+    runTest("regress_70736958.Test");
+  }
+
+  @Test
+  public void testRegress70737019() throws Exception {
+    runTest("regress_70737019.Test");
+  }
+
+  @Test
+  public void testRegress72361252() throws Exception {
+    runTest("regress_72361252.Test");
+  }
+
+  @Test
+  public void testMemberrebinding2() throws Exception {
+    runTest("memberrebinding2.Memberrebinding");
+  }
+
+  @Test
+  public void testMemberrebinding3() throws Exception {
+    runTest("memberrebinding3.Memberrebinding");
+  }
+
+  @Test
+  public void testMinification() throws Exception {
+    runTest("minification.Minification");
+  }
+
+  @Test
+  public void testEnclosingmethod() throws Exception {
+    runTest("enclosingmethod.Main");
+  }
+
+  @Test
+  public void testEnclosingmethodProguarded() throws Exception {
+    runTest("enclosingmethod_proguarded.Main");
+  }
+
+  @Test
+  public void testInterfaceInlining() throws Exception {
+    runTest("interfaceinlining.Main");
+  }
+
+  @Test
+  public void testSwitchmaps() throws Exception {
+    runTest("switchmaps.Switches");
+  }
+
+  private void runTest(String clazz) throws Exception {
+    runTest(clazz, null);
+  }
+
+  private void runTest(String clazz, BiConsumer<byte[], byte[]> comparator) throws Exception {
+    String pkg = clazz.substring(0, clazz.lastIndexOf('.'));
+    String suffix = "_debuginfo_all";
+    Path inputJar = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, pkg + suffix + JAR_EXTENSION);
     Path outputJar = temp.getRoot().toPath().resolve("output.jar");
     R8Command command =
         R8Command.builder()
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
index fc22895..2265c3e 100644
--- a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
+++ b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
@@ -3,14 +3,23 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.graph.invokesuper.Consumer;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.position.TextPosition;
+import com.android.tools.r8.position.TextRange;
 import com.android.tools.r8.utils.ListUtils;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
 
 // Helper to check that a particular error occurred.
-class DiagnosticsChecker implements DiagnosticsHandler {
+public class DiagnosticsChecker implements DiagnosticsHandler {
   public List<Diagnostic> errors = new ArrayList<>();
   public List<Diagnostic> warnings = new ArrayList<>();
   public List<Diagnostic> infos = new ArrayList<>();
@@ -51,4 +60,64 @@
       throw e;
     }
   }
+
+  public static Diagnostic checkDiagnostic(Diagnostic diagnostic, Consumer<Origin> originChecker,
+      int lineStart, int columnStart, String... messageParts) {
+    if (originChecker != null) {
+      originChecker.accept(diagnostic.getOrigin());
+    }
+    TextPosition position;
+    if (diagnostic.getPosition() instanceof TextRange) {
+      position = ((TextRange) diagnostic.getPosition()).getStart();
+    } else {
+      position = ((TextPosition) diagnostic.getPosition());
+    }
+    assertEquals(lineStart, position.getLine());
+    assertEquals(columnStart, position.getColumn());
+    for (String part : messageParts) {
+      assertTrue(diagnostic.getDiagnosticMessage() + " doesn't contain \"" + part + "\"",
+          diagnostic.getDiagnosticMessage().contains(part));
+    }
+    return diagnostic;
+  }
+
+  public static Diagnostic checkDiagnostic(Diagnostic diagnostic, Consumer<Origin> originChecker,
+      String... messageParts) {
+    if (originChecker != null) {
+      originChecker.accept(diagnostic.getOrigin());
+    }
+    assertEquals(diagnostic.getPosition(), Position.UNKNOWN);
+    for (String part : messageParts) {
+      assertTrue(diagnostic.getDiagnosticMessage() + " doesn't contain \"" + part + "\"",
+          diagnostic.getDiagnosticMessage().contains(part));
+    }
+    return diagnostic;
+  }
+
+  static class PathOriginChecker implements Consumer<Origin> {
+    private final Path path;
+    PathOriginChecker(Path path) {
+      this.path = path;
+    }
+
+    public void accept(Origin origin) {
+      if (path != null) {
+        assertEquals(path, ((PathOrigin) origin).getPath());
+      } else {
+        assertSame(Origin.unknown(), origin);
+      }
+    }
+  }
+
+  public static Diagnostic checkDiagnostic(Diagnostic diagnostic, Path path,
+      int lineStart, int columnStart, String... messageParts) {
+    return checkDiagnostic(diagnostic, new PathOriginChecker(path), lineStart, columnStart,
+        messageParts);
+  }
+
+  public static Diagnostic checkDiagnostics(List<Diagnostic> diagnostics, Path path,
+      int lineStart, int columnStart, String... messageParts) {
+    assertEquals(1, diagnostics.size());
+    return checkDiagnostic(diagnostics.get(0), path, lineStart, columnStart, messageParts);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/R8CFExamplesTests.java b/src/test/java/com/android/tools/r8/R8CFExamplesTests.java
new file mode 100644
index 0000000..377bfc0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/R8CFExamplesTests.java
@@ -0,0 +1,115 @@
+// 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;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.cf.LambdaTest;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.io.ByteStreams;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class R8CFExamplesTests extends TestBase {
+
+  private static final Path ART_TESTS_DIR = Paths.get(R8RunArtTestsTest.ART_TESTS_DIR, "dx");
+  private final TestMode testMode;
+  private final CompilationMode compilationMode;
+
+  public enum TestMode {
+    CF_SKIP_IR,
+    JAR_TO_IR,
+    CF_TO_IR,
+  }
+
+  @Parameters(name = "{0}:{1}")
+  public static List<Object[]> data() {
+    List<Object[]> data = new ArrayList<>();
+    for (TestMode testMode : TestMode.values()) {
+      for (CompilationMode compilationMode : CompilationMode.values()) {
+        data.add(new Object[] {testMode, compilationMode});
+      }
+    }
+    return data;
+  }
+
+  public R8CFExamplesTests(TestMode testMode, CompilationMode compilationMode) {
+    this.testMode = testMode;
+    this.compilationMode = compilationMode;
+  }
+
+  @Test
+  public void testConstMethodHandle() throws Exception {
+    Path testDirectory = ART_TESTS_DIR.resolve("979-const-method-handle/classes");
+    String classNames[] = {
+      "constmethodhandle.ConstTest", "Main",
+    };
+    runTest(writeInput(testDirectory, classNames), "Main");
+  }
+
+  @Test
+  public void testLambda() throws Exception {
+    runTest(LambdaTest.class);
+  }
+
+  private void runTest(Class<?> clazz) throws Exception {
+    runTest(writeInput(clazz), clazz.getName());
+  }
+
+  private Path writeInput(Class<?> clazz) throws Exception {
+    Path inputJar = temp.getRoot().toPath().resolve("input.jar");
+    ClassFileConsumer inputConsumer = new ClassFileConsumer.ArchiveConsumer(inputJar);
+    String descriptor = DescriptorUtils.javaTypeToDescriptor(clazz.getName());
+    inputConsumer.accept(ToolHelper.getClassAsBytes(clazz), descriptor, null);
+    inputConsumer.finished(null);
+    return inputJar;
+  }
+
+  private Path writeInput(Path testDirectory, String[] classNames) throws Exception {
+    Path inputJar = temp.getRoot().toPath().resolve("input.jar");
+    ClassFileConsumer inputConsumer = new ClassFileConsumer.ArchiveConsumer(inputJar);
+    for (String className : classNames) {
+      Path path = testDirectory.resolve(className.replace('.', '/') + ".class");
+      String descriptor = DescriptorUtils.javaTypeToDescriptor(className);
+      try (InputStream inputStream = new FileInputStream(path.toFile())) {
+        inputConsumer.accept(ByteStreams.toByteArray(inputStream), descriptor, null);
+      }
+    }
+    inputConsumer.finished(null);
+    return inputJar;
+  }
+
+  private void runTest(Path inputJar, String mainClass) throws Exception {
+    ProcessResult runInput = ToolHelper.runJava(inputJar, mainClass);
+    Assert.assertEquals(0, runInput.exitCode);
+    Path outputJar = runR8(inputJar, compilationMode, "output.jar");
+    ProcessResult runOutput = ToolHelper.runJava(outputJar, mainClass);
+    Assert.assertEquals(runInput.toString(), runOutput.toString());
+  }
+
+  private Path runR8(Path inputJar, CompilationMode mode, String outputName) throws Exception {
+    Path outputJar = temp.getRoot().toPath().resolve(outputName);
+    ToolHelper.runR8(
+        R8Command.builder()
+            .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+            .setMode(mode)
+            .addProgramFiles(inputJar)
+            .setOutput(outputJar, OutputMode.ClassFile)
+            .build(),
+        o -> {
+          o.skipIR = this.testMode == TestMode.CF_SKIP_IR;
+          o.enableCfFrontend = this.testMode != TestMode.JAR_TO_IR;
+        });
+    return outputJar;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 923398d..ac0955e 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -83,7 +83,7 @@
     D8_AFTER_R8CF
   }
 
-  private static final String ART_TESTS_DIR = "tests/2017-10-04/art";
+  public static final String ART_TESTS_DIR = "tests/2017-10-04/art";
   private static final String ART_LEGACY_TESTS_DIR = "tests/2016-12-19/art/";
   private static final String ART_TESTS_NATIVE_LIBRARY_DIR = "tests/2017-10-04/art/lib64";
   private static final String ART_LEGACY_TESTS_NATIVE_LIBRARY_DIR = "tests/2016-12-19/art/lib64";
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index 9d45f6a..b973818 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -33,6 +33,11 @@
     DX, JAVAC, JAVAC_ALL, JAVAC_NONE
   }
 
+  protected enum Frontend {
+    JAR,
+    CF
+  }
+
   protected enum Output {
     DEX,
     CF
@@ -45,8 +50,20 @@
 
   protected static String[] makeTest(
       Input input, CompilerUnderTest compiler, CompilationMode mode, String clazz, Output output) {
+    return makeTest(input, compiler, mode, clazz, Frontend.JAR, output);
+  }
+
+  protected static String[] makeTest(
+      Input input,
+      CompilerUnderTest compiler,
+      CompilationMode mode,
+      String clazz,
+      Frontend frontend,
+      Output output) {
     String pkg = clazz.substring(0, clazz.lastIndexOf('.'));
-    return new String[] {pkg, input.name(), compiler.name(), mode.name(), clazz, output.name()};
+    return new String[] {
+      pkg, input.name(), compiler.name(), mode.name(), clazz, frontend.name(), output.name()
+    };
   }
 
   @Rule
@@ -57,6 +74,7 @@
   private final CompilationMode mode;
   private final String pkg;
   private final String mainClass;
+  private final Frontend frontend;
   private final Output output;
 
   public R8RunExamplesCommon(
@@ -65,12 +83,14 @@
       String compiler,
       String mode,
       String mainClass,
+      String frontend,
       String output) {
     this.pkg = pkg;
     this.input = Input.valueOf(input);
     this.compiler = CompilerUnderTest.valueOf(compiler);
     this.mode = CompilationMode.valueOf(mode);
     this.mainClass = mainClass;
+    this.frontend = Frontend.valueOf(frontend);
     this.output = Output.valueOf(output);
   }
 
@@ -135,17 +155,24 @@
                 .build());
         break;
       }
-      case R8: {
-        R8Command command = addInputFile(R8Command.builder())
-            .setOutput(getOutputFile(), outputMode)
-            .setMode(mode)
-            .build();
-        ExceptionUtils.withR8CompilationHandler(command.getReporter(), () ->
-            ToolHelper.runR8(
-                command,
-                options -> options.lineNumberOptimization = LineNumberOptimization.OFF));
-        break;
-      }
+      case R8:
+        {
+          R8Command command =
+              addInputFile(R8Command.builder())
+                  .setOutput(getOutputFile(), outputMode)
+                  .setMode(mode)
+                  .build();
+          ExceptionUtils.withR8CompilationHandler(
+              command.getReporter(),
+              () ->
+                  ToolHelper.runR8(
+                      command,
+                      options -> {
+                        options.lineNumberOptimization = LineNumberOptimization.OFF;
+                        options.enableCfFrontend = frontend == Frontend.CF;
+                      }));
+          break;
+        }
       default:
         throw new Unreachable();
     }
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java
index 02c2d27..97c8624 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java
@@ -17,7 +17,7 @@
 @RunWith(Parameterized.class)
 public class R8RunExamplesKotlinTest extends R8RunExamplesCommon {
 
-  @Parameters(name = "{0}_{1}_{2}_{3}_{5}")
+  @Parameters(name = "{0}_{1}_{2}_{3}_{5}_{6}")
   public static Collection<String[]> data() {
     String[] tests = {
         "loops.LoopKt"
@@ -74,7 +74,8 @@
       String compiler,
       String mode,
       String mainClass,
+      String frontend,
       String output) {
-    super(pkg, input, compiler, mode, mainClass, output);
+    super(pkg, input, compiler, mode, mainClass, frontend, output);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index ba036dd..913278a 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -24,7 +24,7 @@
 
   private static final boolean ONLY_RUN_CF_TESTS = false;
 
-  @Parameters(name = "{0}_{1}_{2}_{3}_{5}")
+  @Parameters(name = "{0}_{1}_{2}_{3}_{5}_{6}")
   public static Collection<String[]> data() {
     String[] tests = {
         "arithmetic.Arithmetic",
@@ -99,6 +99,14 @@
       fullTestList.add(
           makeTest(
               Input.JAVAC_ALL, CompilerUnderTest.R8, CompilationMode.RELEASE, test, Output.CF));
+      fullTestList.add(
+          makeTest(
+              Input.JAVAC_ALL,
+              CompilerUnderTest.R8,
+              CompilationMode.RELEASE,
+              test,
+              Frontend.CF,
+              Output.CF));
     }
     return fullTestList;
   }
@@ -109,8 +117,9 @@
       String compiler,
       String mode,
       String mainClass,
+      String frontend,
       String output) {
-    super(pkg, input, compiler, mode, mainClass, output);
+    super(pkg, input, compiler, mode, mainClass, frontend, output);
   }
 
   @Override
@@ -134,7 +143,6 @@
   @Override
   protected Set<String> getFailingCompileCf() {
     return new ImmutableSet.Builder<String>()
-        .add("invoke.Invoke") // outline / CF->IR
         .build();
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index a29d673..96591d8 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -153,19 +153,26 @@
   protected Path jarTestClasses(Class... classes) throws IOException {
     Path jar = File.createTempFile("junit", ".jar", temp.getRoot()).toPath();
     try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar.toFile()))) {
-      for (Class clazz : classes) {
-        try (FileInputStream in =
-            new FileInputStream(ToolHelper.getClassFileForTestClass(clazz).toFile())) {
-          out.putNextEntry(new ZipEntry(ToolHelper.getJarEntryForTestClass(clazz)));
-          ByteStreams.copy(in, out);
-          out.closeEntry();
-        }
-      }
+      addTestClassesToJar(out, classes);
     }
     return jar;
   }
 
   /**
+   * Create a temporary JAR file containing the specified test classes.
+   */
+  protected void addTestClassesToJar(JarOutputStream out, Class... classes) throws IOException {
+    for (Class clazz : classes) {
+      try (FileInputStream in =
+          new FileInputStream(ToolHelper.getClassFileForTestClass(clazz).toFile())) {
+        out.putNextEntry(new ZipEntry(ToolHelper.getJarEntryForTestClass(clazz)));
+        ByteStreams.copy(in, out);
+        out.closeEntry();
+      }
+    }
+  }
+
+  /**
    * Create a temporary JAR file containing all test classes in a package.
    */
   protected Path jarTestClassesInPackage(Package pkg) throws IOException {
@@ -430,20 +437,6 @@
   }
 
   /**
-   * Run application on Art with the specified main class.
-   */
-  protected String runOnArt(AndroidApp app, String mainClass) throws IOException {
-    return runOnArtRaw(app, mainClass).stdout;
-  }
-
-  /**
-   * Run application on Art with the specified main class.
-   */
-  protected String runOnArt(AndroidApp app, Class mainClass) throws IOException {
-    return runOnArtRaw(app, mainClass).stdout;
-  }
-
-  /**
    * Run application on Art with the specified main class and provided arguments.
    */
   protected String runOnArt(AndroidApp app, Class mainClass, String... args) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/cf/BaseDefaultMethodTestRunner.java b/src/test/java/com/android/tools/r8/cf/BaseDefaultMethodTestRunner.java
index 414752f..be00acf 100644
--- a/src/test/java/com/android/tools/r8/cf/BaseDefaultMethodTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/BaseDefaultMethodTestRunner.java
@@ -46,8 +46,7 @@
     for (Class<?> c : CLASSES) {
       builder.addClassProgramData(ToolHelper.getClassAsBytes(c), Origin.unknown());
     }
-    // TODO(b/75997473): Enable inlining when supported
-    ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
+    ToolHelper.runR8(builder.build());
     ProcessResult runOutput = ToolHelper.runJava(out, CLASS.getCanonicalName());
     assertEquals(runInput.toString(), runOutput.toString());
   }
diff --git a/src/test/java/com/android/tools/r8/cf/BootstrapTest.java b/src/test/java/com/android/tools/r8/cf/BootstrapTest.java
index 0f92fb3..7fdad0d 100644
--- a/src/test/java/com/android/tools/r8/cf/BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/cf/BootstrapTest.java
@@ -56,10 +56,7 @@
 
     @Override
     public String toString() {
-      // TODO(mathiasr): Add pgMap to output when resource API (go/r8g/19460) has landed.
-      // Without resource API, comparing pgMaps will fail because R8 does not keep the resource
-      // indicating which Git revision R8 was compiled from.
-      return processResult.toString();
+      return processResult.toString() + "\n\n" + pgMap;
     }
   }
 
@@ -71,37 +68,50 @@
     assertEquals(0, runHello.exitCode);
 
     // Run r8.jar on hello.jar to ensure that r8.jar is a working compiler.
-    R8Result runInputR8 = runExternalR8(R8_STABLE_JAR, hello, "input", KEEP_HELLO);
+    R8Result runInputR8 = runExternalR8(R8_STABLE_JAR, hello, "input", KEEP_HELLO, "--debug");
     ProcessResult runHelloR8 = ToolHelper.runJava(runInputR8.outputJar, "hello.Hello");
     assertEquals(runHello.toString(), runHelloR8.toString());
 
+    compareR8(hello, runInputR8, CompilationMode.RELEASE, "r8-r8-rel", "--release", "output-rel");
+    compareR8(hello, runInputR8, CompilationMode.DEBUG, "r8-r8", "--debug", "output");
+  }
+
+  private void compareR8(
+      Path hello,
+      R8Result runInputR8,
+      CompilationMode internalMode,
+      String internalOutput,
+      String externalMode,
+      String externalOutput)
+      throws Exception {
     // Run R8 on r8.jar, and run the resulting compiler on hello.jar.
-    Path output = runR8(R8_STABLE_JAR, "r8-r8", KEEP_R8);
-    R8Result runR8R8 = runExternalR8(output, hello, "output", KEEP_HELLO);
+    Path output = runR8(R8_STABLE_JAR, internalOutput, KEEP_R8, internalMode);
+    R8Result runR8R8 = runExternalR8(output, hello, externalOutput, KEEP_HELLO, externalMode);
     // Check that the process outputs (exit code, stdout, stderr) are the same.
     assertEquals(runInputR8.toString(), runR8R8.toString());
     // Check that the output jars are the same.
     assertProgramsEqual(runInputR8.outputJar, runR8R8.outputJar);
   }
 
-  private Path runR8(Path inputJar, String outputFolder, String[] keepRules) throws Exception {
+  private Path runR8(Path inputJar, String outputFolder, String[] keepRules, CompilationMode mode)
+      throws Exception {
     Path outputPath = temp.newFolder(outputFolder).toPath();
     Path outputJar = outputPath.resolve("output.jar");
     Path pgConfigFile = outputPath.resolve("keep.rules");
     FileUtils.writeTextFile(pgConfigFile, keepRules);
     ToolHelper.runR8(
         R8Command.builder()
-            .setMode(CompilationMode.DEBUG)
+            .setMode(mode)
             .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
-            // TODO(mathiasr): Add resources to output when resource API (go/r8g/19460) has landed.
-            .setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(outputJar))
+            .setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(outputJar, true))
             .addProgramFiles(inputJar)
             .addProguardConfigurationFiles(pgConfigFile)
             .build());
     return outputJar;
   }
 
-  private R8Result runExternalR8(Path r8Jar, Path inputJar, String outputFolder, String[] keepRules)
+  private R8Result runExternalR8(
+      Path r8Jar, Path inputJar, String outputFolder, String[] keepRules, String mode)
       throws Exception {
     Path outputPath = temp.newFolder(outputFolder).toPath();
     Path pgConfigFile = outputPath.resolve("keep.rules");
@@ -120,7 +130,7 @@
             outputJar.toString(),
             "--pg-conf",
             pgConfigFile.toString(),
-            "--debug",
+            mode,
             "--pg-map-output",
             pgMapFile.toString());
     if (processResult.exitCode != 0) {
diff --git a/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java b/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
index fed7637..ad6a72b 100644
--- a/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
@@ -58,11 +58,6 @@
             .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
             .setProgramConsumer(consumer);
     input.accept(builder);
-    ToolHelper.runR8(
-        builder.build(),
-        o -> {
-          o.invalidDebugInfoFatal = true;
-          o.enableInlining = false;
-        });
+    ToolHelper.runR8(builder.build(), o -> o.invalidDebugInfoFatal = true);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java b/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
index f47bdc6..6c9d7b6 100644
--- a/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf;
 
-import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
@@ -12,8 +11,10 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.JarCode;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
@@ -22,18 +23,10 @@
 import java.nio.file.Paths;
 import java.util.Collections;
 import java.util.List;
-import java.util.ListIterator;
+import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.InsnList;
-import org.objectweb.asm.tree.InvokeDynamicInsnNode;
-import org.objectweb.asm.tree.MethodNode;
 
 public class LambdaTestRunner {
 
@@ -49,8 +42,10 @@
     // is not modified by the R8 compilation.
     // First, extract the InvokeDynamic instruction from the input class.
     byte[] inputClass = ToolHelper.getClassAsBytes(CLASS);
-    int opcode = Opcodes.INVOKEDYNAMIC;
-    InvokeDynamicInsnNode insnInput = findFirstInMethod(inputClass, opcode);
+    AndroidApp inputApp =
+        AndroidApp.builder().addClassProgramData(inputClass, Origin.unknown()).build();
+    CfInvokeDynamic insnInput = findFirstInMethod(inputApp);
+    Assert.assertNotNull("No CfInvokeDynamic found in input", insnInput);
     // Compile with R8 and extract the InvokeDynamic instruction from the output class.
     AndroidAppConsumers appBuilder = new AndroidAppConsumers();
     Path outPath = temp.getRoot().toPath().resolve("out.jar");
@@ -61,13 +56,11 @@
             .setProgramConsumer(appBuilder.wrapClassFileConsumer(new ArchiveConsumer(outPath)))
             .addClassProgramData(inputClass, Origin.unknown())
             .build());
-    AndroidApp app = appBuilder.build();
-    InvokeDynamicInsnNode insnOutput = findFirstInMethod(app, opcode);
+    AndroidApp outputApp = appBuilder.build();
+    CfInvokeDynamic insnOutput = findFirstInMethod(outputApp);
+    Assert.assertNotNull("No CfInvokeDynamic found in output", insnOutput);
     // Check that the InvokeDynamic instruction is not modified.
-    assertEquals(insnInput.name, insnOutput.name);
-    assertEquals(insnInput.desc, insnOutput.desc);
-    assertEquals(insnInput.bsm, insnOutput.bsm);
-    assertArrayEquals(insnInput.bsmArgs, insnOutput.bsmArgs);
+    assertEquals(print(insnInput), print(insnOutput));
     // Check that execution gives the same output.
     ProcessResult inputResult =
         ToolHelper.runJava(ToolHelper.getClassPathForTests(), CLASS.getName());
@@ -75,45 +68,24 @@
     assertEquals(inputResult.toString(), outputResult.toString());
   }
 
-  private InvokeDynamicInsnNode findFirstInMethod(AndroidApp app, int opcode) throws Exception {
+  private static CfInvokeDynamic findFirstInMethod(AndroidApp app) throws Exception {
     String returnType = "void";
-    DexInspector inspector = new DexInspector(app);
+    DexInspector inspector = new DexInspector(app, o -> o.enableCfFrontend = true);
     List<String> args = Collections.singletonList(String[].class.getTypeName());
     DexEncodedMethod method = inspector.clazz(CLASS).method(returnType, METHOD, args).getMethod();
-    JarCode jarCode = method.getCode().asJarCode();
-    MethodNode outputMethod = jarCode.getNode();
-    return (InvokeDynamicInsnNode) findFirstInstruction(outputMethod, opcode);
-  }
-
-  private InvokeDynamicInsnNode findFirstInMethod(byte[] clazz, int opcode) {
-    MethodNode[] method = {null};
-    new ClassReader(clazz)
-        .accept(
-            new ClassNode(Opcodes.ASM6) {
-              @Override
-              public MethodVisitor visitMethod(
-                  int access, String name, String desc, String signature, String[] exceptions) {
-                if (name.equals(METHOD)) {
-                  method[0] = new MethodNode(access, name, desc, signature, exceptions);
-                  return method[0];
-                } else {
-                  return null;
-                }
-              }
-            },
-            0);
-    return (InvokeDynamicInsnNode) findFirstInstruction(method[0], opcode);
-  }
-
-  private AbstractInsnNode findFirstInstruction(MethodNode node, int opcode) {
-    assert node != null;
-    InsnList asmInsns = node.instructions;
-    for (ListIterator<AbstractInsnNode> it = asmInsns.iterator(); it.hasNext(); ) {
-      AbstractInsnNode insn = it.next();
-      if (insn.getOpcode() == opcode) {
-        return insn;
+    CfCode code = method.getCode().asCfCode();
+    for (CfInstruction instruction : code.getInstructions()) {
+      if (instruction instanceof CfInvokeDynamic) {
+        return (CfInvokeDynamic) instruction;
       }
     }
-    throw new RuntimeException("Instruction not found");
+    return null;
   }
+
+  private static String print(CfInstruction instruction) {
+    CfPrinter printer = new CfPrinter();
+    instruction.print(printer);
+    return printer.toString();
+  }
+
 }
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
index ae3b95e..e43ffd7 100644
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
@@ -9,55 +9,91 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.ProgramConsumer;
-import com.android.tools.r8.R8;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.Reporter;
 import java.nio.file.Path;
+import java.util.ArrayList;
 import java.util.Arrays;
-import org.junit.Rule;
+import java.util.List;
+import org.junit.Assume;
 import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
-public class MethodHandleTestRunner {
+@RunWith(Parameterized.class)
+public class MethodHandleTestRunner extends TestBase {
   static final Class<?> CLASS = MethodHandleTest.class;
 
-  private boolean ldc = false;
-  private boolean minify = false;
+  enum LookupType {
+    DYNAMIC,
+    CONSTANT,
+  }
 
-  @Rule
-  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+  enum MinifyMode {
+    NONE,
+    MINIFY,
+  }
 
-  @Test
-  public void testMethodHandlesLookup() throws Exception {
-    // Run test with dynamic method lookups, i.e. using MethodHandles.lookup().find*()
-    ldc = false;
-    test();
+  enum Frontend {
+    JAR,
+    CF,
+  }
+
+  private CompilationMode compilationMode;
+  private LookupType lookupType;
+  private Frontend frontend;
+  private ProcessResult runInput;
+  private MinifyMode minifyMode;
+
+  @Parameters(name = "{0}_{1}_{2}_{3}")
+  public static List<String[]> data() {
+    List<String[]> res = new ArrayList<>();
+    for (LookupType lookupType : LookupType.values()) {
+      for (Frontend frontend : Frontend.values()) {
+        for (MinifyMode minifyMode : MinifyMode.values()) {
+          if (lookupType == LookupType.DYNAMIC && minifyMode == MinifyMode.MINIFY) {
+            // Skip because we don't keep the members looked up dynamically.
+            continue;
+          }
+          for (CompilationMode compilationMode : CompilationMode.values()) {
+            res.add(
+                new String[] {
+                  lookupType.name(), frontend.name(), minifyMode.name(), compilationMode.name()
+                });
+          }
+        }
+      }
+    }
+    return res;
+  }
+
+  public MethodHandleTestRunner(
+      String lookupType, String frontend, String minifyMode, String compilationMode) {
+    this.lookupType = LookupType.valueOf(lookupType);
+    this.frontend = Frontend.valueOf(frontend);
+    this.minifyMode = MinifyMode.valueOf(minifyMode);
+    this.compilationMode = CompilationMode.valueOf(compilationMode);
   }
 
   @Test
-  public void testLdcMethodHandle() throws Exception {
-    // Run test with LDC methods, i.e. without java.lang.invoke.MethodHandles
-    ldc = true;
-    test();
-  }
-
-  @Test
-  public void testMinify() throws Exception {
-    // Run test with LDC methods, i.e. without java.lang.invoke.MethodHandles
-    ldc = true;
-    ProcessResult runInput = runInput();
-    assertEquals(0, runInput.exitCode);
-    Path outCf = temp.getRoot().toPath().resolve("cf.jar");
-    build(new ClassFileConsumer.ArchiveConsumer(outCf), true);
-    ProcessResult runCf =
-        ToolHelper.runJava(outCf, CLASS.getCanonicalName(), ldc ? "error" : "exception");
-    assertEquals(runInput.toString(), runCf.toString());
+  public void test() throws Exception {
+    runInput();
+    runCf();
+    // TODO(mathiasr): Once we include a P runtime, change this to "P and above".
+    if (ToolHelper.getDexVm() == DexVm.ART_DEFAULT && ToolHelper.artSupported()) {
+      runDex();
+    }
   }
 
   private final Class[] inputClasses = {
@@ -70,25 +106,39 @@
     MethodHandleTest.F.class,
   };
 
-  private void test() throws Exception {
-    ProcessResult runInput = runInput();
-    Path outCf = temp.getRoot().toPath().resolve("cf.jar");
-    build(new ClassFileConsumer.ArchiveConsumer(outCf), false);
-    Path outDex = temp.getRoot().toPath().resolve("dex.zip");
-    build(new DexIndexedConsumer.ArchiveConsumer(outDex), false);
-
-    ProcessResult runCf =
-        ToolHelper.runJava(outCf, CLASS.getCanonicalName(), ldc ? "error" : "exception");
-    assertEquals(runInput.toString(), runCf.toString());
-    // TODO(mathiasr): Once we include a P runtime, change this to "P and above".
-    if (ToolHelper.getDexVm() != DexVm.ART_DEFAULT) {
-      return;
+  private void runInput() throws Exception {
+    Path out = temp.getRoot().toPath().resolve("input.jar");
+    ClassFileConsumer.ArchiveConsumer archiveConsumer = new ClassFileConsumer.ArchiveConsumer(out);
+    for (Class<?> c : inputClasses) {
+      archiveConsumer.accept(
+          getClassAsBytes(c), DescriptorUtils.javaTypeToDescriptor(c.getName()), null);
     }
+    archiveConsumer.finished(null);
+    String expected = lookupType == LookupType.CONSTANT ? "error" : "exception";
+    runInput = ToolHelper.runJava(out, CLASS.getName(), expected);
+    if (runInput.exitCode != 0) {
+      System.out.println(runInput);
+    }
+    assertEquals(0, runInput.exitCode);
+  }
+
+  private void runCf() throws Exception {
+    Path outCf = temp.getRoot().toPath().resolve("cf.jar");
+    build(new ClassFileConsumer.ArchiveConsumer(outCf));
+    String expected = lookupType == LookupType.CONSTANT ? "error" : "exception";
+    ProcessResult runCf = ToolHelper.runJava(outCf, CLASS.getCanonicalName(), expected);
+    assertEquals(runInput.toString(), runCf.toString());
+  }
+
+  private void runDex() throws Exception {
+    Path outDex = temp.getRoot().toPath().resolve("dex.zip");
+    build(new DexIndexedConsumer.ArchiveConsumer(outDex));
+    String expected = lookupType == LookupType.CONSTANT ? "pass" : "exception";
     ProcessResult runDex =
         ToolHelper.runArtRaw(
             outDex.toString(),
             CLASS.getCanonicalName(),
-            cmd -> cmd.appendProgramArgument(ldc ? "pass" : "exception"));
+            cmd -> cmd.appendProgramArgument(expected));
     // Only compare stdout and exitCode since dex2oat prints to stderr.
     if (runInput.exitCode != runDex.exitCode) {
       System.out.println(runDex.stderr);
@@ -97,51 +147,49 @@
     assertEquals(runInput.exitCode, runDex.exitCode);
   }
 
-  private void build(ProgramConsumer programConsumer, boolean minify) throws Exception {
+  private void build(ProgramConsumer programConsumer) throws Exception {
     // MethodHandle.invoke() only supported from Android O
     // ConstMethodHandle only supported from Android P
     AndroidApiLevel apiLevel = AndroidApiLevel.P;
-    Builder cfBuilder =
+    Builder command =
         R8Command.builder()
-            .setMode(CompilationMode.DEBUG)
+            .setMode(compilationMode)
             .addLibraryFiles(ToolHelper.getAndroidJar(apiLevel))
             .setProgramConsumer(programConsumer);
     if (!(programConsumer instanceof ClassFileConsumer)) {
-      cfBuilder.setMinApiLevel(apiLevel.getLevel());
+      command.setMinApiLevel(apiLevel.getLevel());
     }
     for (Class<?> c : inputClasses) {
       byte[] classAsBytes = getClassAsBytes(c);
-      cfBuilder.addClassProgramData(classAsBytes, Origin.unknown());
+      command.addClassProgramData(classAsBytes, Origin.unknown());
     }
-    if (minify) {
-      cfBuilder.addProguardConfiguration(
+    if (minifyMode == MinifyMode.MINIFY) {
+      command.addProguardConfiguration(
           Arrays.asList(
               "-keep public class com.android.tools.r8.cf.MethodHandleTest {",
               "  public static void main(...);",
               "}"),
           Origin.unknown());
     }
-    R8.run(cfBuilder.build());
-  }
-
-  private ProcessResult runInput() throws Exception {
-    Path out = temp.getRoot().toPath().resolve("input.jar");
-    ClassFileConsumer.ArchiveConsumer archiveConsumer = new ClassFileConsumer.ArchiveConsumer(out);
-    for (Class<?> c : inputClasses) {
-      archiveConsumer.accept(
-          getClassAsBytes(c), DescriptorUtils.javaTypeToDescriptor(c.getName()), null);
+    try {
+      ToolHelper.runR8(
+          command.build(), options -> options.enableCfFrontend = frontend == Frontend.CF);
+    } catch (CompilationError e) {
+      if (frontend == Frontend.CF && compilationMode == CompilationMode.DEBUG) {
+        // TODO(b/79725635): Investigate why these tests fail on the buildbot.
+        // Use a Reporter to extract origin info to standard error.
+        new Reporter(new DefaultDiagnosticsHandler()).error(e);
+        // Print the stack trace since this is not always printed by JUnit.
+        e.printStackTrace();
+        Assume.assumeNoException(
+            "TODO(b/79725635): Investigate why these tests fail on the buildbot.", e);
+      }
+      throw e;
     }
-    archiveConsumer.finished(null);
-    ProcessResult runInput = ToolHelper.runJava(out, CLASS.getName(), ldc ? "error" : "exception");
-    if (runInput.exitCode != 0) {
-      System.out.println(runInput);
-    }
-    assertEquals(0, runInput.exitCode);
-    return runInput;
   }
 
   private byte[] getClassAsBytes(Class<?> clazz) throws Exception {
-    if (ldc) {
+    if (lookupType == LookupType.CONSTANT) {
       if (clazz == MethodHandleTest.D.class) {
         return MethodHandleDump.dumpD();
       } else if (clazz == MethodHandleTest.class) {
diff --git a/src/test/java/com/android/tools/r8/code/ClassWithNativeMethodTest.java b/src/test/java/com/android/tools/r8/code/ClassWithNativeMethodTest.java
deleted file mode 100644
index 293485a..0000000
--- a/src/test/java/com/android/tools/r8/code/ClassWithNativeMethodTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-// 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.code;
-
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertThat;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.VmTestRunner;
-import com.android.tools.r8.jasmin.JasminBuilder;
-import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
-import java.nio.file.Path;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(VmTestRunner.class)
-public class ClassWithNativeMethodTest extends TestBase {
-
-  @Test
-  public void test() throws Exception {
-    JasminBuilder jasminBuilder = new JasminBuilder();
-
-    ClassBuilder cls = jasminBuilder.addClass("Main");
-    cls.addDefaultConstructor();
-    cls.addVirtualMethod("foo", ImmutableList.of("Ljava/lang/String;"), "V",
-        ".limit stack 3",
-        ".limit locals 2",
-        "getstatic java/lang/System/out Ljava/io/PrintStream;",
-        "aload_1",
-        "invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
-        "return");
-    cls.addNativeMethod("n1", ImmutableList.of("Ljava/lang/String;"), "V",
-        "return");
-    cls.addNativeMethod("n2", ImmutableList.of("Ljava/lang/String;"), "V",
-        "return");
-    cls.addNativeMethod("n3", ImmutableList.of("Ljava/lang/String;"), "V",
-        "return");
-    cls.addNativeMethod("n4", ImmutableList.of("Ljava/lang/String;"), "V",
-        "return");
-    cls.addMainMethod(
-        ".limit stack 4",
-        ".limit locals 2",
-        "new " + cls.name,
-        "dup",
-        "invokespecial " + cls.name + "/<init>()V",
-        "astore_0",
-        "aload_0",
-        "ldc \"foo\"",
-        "invokevirtual " + cls.name + "/foo(Ljava/lang/String;)V",
-        "return");
-
-    String mainClassName = cls.name;
-
-    Path outputDirectory = temp.newFolder().toPath();
-    jasminBuilder.writeClassFiles(outputDirectory);
-    ProcessResult javaResult = ToolHelper.runJava(outputDirectory, mainClassName);
-    // Native .o is not given. Expect to see JNI error.
-    assertNotEquals(0, javaResult.exitCode);
-    assertThat(javaResult.stderr, containsString("JNI"));
-
-    AndroidApp processedApp = compileWithD8(jasminBuilder.build());
-
-    DexInspector inspector = new DexInspector(processedApp);
-    ClassSubject mainSubject = inspector.clazz(cls.name);
-    MethodSubject nativeMethod =
-        mainSubject.method("void", "n1", ImmutableList.of("java.lang.String"));
-    assertThat(nativeMethod, isPresent());
-
-    ProcessResult artResult = runOnArtRaw(processedApp, mainClassName);
-    // ART can process main() even without native definitions.
-    assertEquals(0, artResult.exitCode);
-    assertThat(artResult.stdout, containsString("foo"));
-  }
-
-
-}
diff --git a/src/test/java/com/android/tools/r8/code/NativeMethodWithCodeTest.java b/src/test/java/com/android/tools/r8/code/NativeMethodWithCodeTest.java
new file mode 100644
index 0000000..83e711c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/code/NativeMethodWithCodeTest.java
@@ -0,0 +1,109 @@
+// 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.code;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+public class NativeMethodWithCodeTest extends TestBase {
+
+  // Test that D8 removes code from native methods (to match the behavior of dx).
+  // Note that the JVM rejects a class if it has a native method with a code attribute,
+  // but D8 has to handle that and cannot simply throw an error even though the JVM does.
+
+  @Test
+  public void test() throws Exception {
+    JasminBuilder jasminBuilder = new JasminBuilder();
+
+    ClassBuilder cls = jasminBuilder.addClass("Main");
+    cls.addDefaultConstructor();
+    cls.addVirtualMethod("foo", ImmutableList.of("Ljava/lang/String;"), "V",
+        ".limit stack 3",
+        ".limit locals 2",
+        "getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "aload_1",
+        "invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "return");
+    cls.addNativeMethodWithCode("n1", ImmutableList.of("Ljava/lang/String;"), "V",
+        "return");
+    cls.addNativeMethodWithCode("n2", ImmutableList.of("Ljava/lang/String;"), "V",
+        "return");
+    cls.addNativeMethodWithCode("n3", ImmutableList.of("Ljava/lang/String;"), "V",
+        "return");
+    cls.addNativeMethodWithCode("n4", ImmutableList.of("Ljava/lang/String;"), "V",
+        "return");
+    cls.addMainMethod(
+        ".limit stack 4",
+        ".limit locals 2",
+        "new " + cls.name,
+        "dup",
+        "invokespecial " + cls.name + "/<init>()V",
+        "astore_0",
+        "aload_0",
+        "ldc \"foo\"",
+        "invokevirtual " + cls.name + "/foo(Ljava/lang/String;)V",
+        "return");
+
+    String mainClassName = cls.name;
+
+    Path inputJar = temp.getRoot().toPath().resolve("input.jar");
+    jasminBuilder.writeJar(inputJar, null);
+    AndroidApp inputApp = AndroidApp.builder().addProgramFiles(inputJar).build();
+    // TODO(mathiasr): Consider making frontend read in code on native methods.
+    // If we do that, this assertNull should change to assertNotNull.
+    assertNull(getNativeMethod(mainClassName, inputApp).getMethod().getCode());
+
+    // JVM throws ClassFormatError because the input contains code on a native method. (JVM8§4.7.3)
+    ProcessResult javaResult = ToolHelper.runJava(inputJar, mainClassName);
+    assertNotEquals(0, javaResult.exitCode);
+    assertThat(javaResult.stderr, containsString("ClassFormatError"));
+
+    // DX strips code from native methods.
+    Path dxOutputDir = temp.newFolder().toPath();
+    Path dxOutputFile = dxOutputDir.resolve("classes.dex");
+    ToolHelper.runDexer(inputJar.toString(), dxOutputDir.toString());
+    AndroidApp dxApp = AndroidApp.builder().addProgramFiles(dxOutputFile).build();
+    assertNull(getNativeMethod(mainClassName, dxApp).getMethod().getCode());
+
+    // D8 should also strip code from native methods.
+    AndroidApp processedApp = compileWithD8(inputApp);
+    assertThat(getNativeMethod(mainClassName, processedApp), isPresent());
+    // TODO(mathiasr): Consider making D8 maintain code on native methods.
+    // If we do that, this assertNull should change to assertNotNull.
+    assertNull(getNativeMethod(mainClassName, processedApp).getMethod().getCode());
+
+    ProcessResult artResult = runOnArtRaw(processedApp, mainClassName);
+    assertEquals(0, artResult.exitCode);
+    assertThat(artResult.stdout, containsString("foo"));
+  }
+
+  private MethodSubject getNativeMethod(String mainClassName, AndroidApp processedApp)
+      throws IOException, ExecutionException {
+    DexInspector inspector = new DexInspector(processedApp);
+    ClassSubject mainSubject = inspector.clazz(mainClassName);
+    return mainSubject.method("void", "n1", ImmutableList.of("java.lang.String"));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index e8d21f2..25356ae 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -390,6 +390,14 @@
     return inspect(t -> t.checkLocal(localName));
   }
 
+  protected final JUnit3Wrapper.Command checkLocals(String... localNames) {
+    return inspect(t -> {
+      for (String str : localNames) {
+        t.checkLocal(str);
+      }
+    });
+  }
+
   protected final JUnit3Wrapper.Command checkLocal(String localName, Value expectedValue) {
     return inspect(t -> t.checkLocal(localName, expectedValue));
   }
@@ -398,6 +406,14 @@
     return inspect(t -> t.checkNoLocal(localName));
   }
 
+  protected final JUnit3Wrapper.Command checkNoLocals(String... localNames) {
+    return inspect(t -> {
+      for (String str : localNames) {
+        t.checkNoLocal(str);
+      }
+    });
+  }
+
   protected final JUnit3Wrapper.Command checkNoLocal() {
     return inspect(t -> {
       List<String> localNames = t.getLocalNames();
diff --git a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
index 913d39e..02e995f 100644
--- a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
@@ -10,57 +10,375 @@
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.function.Consumer;
 import java.util.stream.Stream;
 import org.junit.Assume;
+import org.junit.Ignore;
 import org.junit.Test;
 
 public class ExamplesDebugTest extends DebugTestBase {
 
-  private Path getExampleJar(String pkg) {
-    return Paths.get(
-        ToolHelper.EXAMPLES_BUILD_DIR, pkg + "_debuginfo_all" + FileUtils.JAR_EXTENSION);
+  private String clazzName;
+  private Path inputJar;
+
+  private Stream<DebuggeeState> input() throws Exception {
+    return streamDebugTest(new CfDebugTestConfig(inputJar), clazzName, ANDROID_FILTER);
   }
 
-  public DebugTestConfig cfConfig(String pkg) throws Exception {
-    return new CfDebugTestConfig(getExampleJar(pkg));
+  private Stream<DebuggeeState> d8() throws Exception {
+    D8DebugTestConfig config = new D8DebugTestConfig().compileAndAdd(temp, inputJar);
+    return streamDebugTest(config, clazzName, ANDROID_FILTER);
   }
 
-  public DebugTestConfig d8Config(String pkg) throws Exception {
-    return new D8DebugTestConfig().compileAndAdd(temp, getExampleJar(pkg));
+  private Stream<DebuggeeState> r8jar() throws Exception {
+    return streamDebugTest(getCfConfig("r8jar.jar", o -> {}), clazzName, ANDROID_FILTER);
   }
 
-  public DebugTestConfig r8DebugCfConfig(String pkg) throws Exception {
-    Path input = getExampleJar(pkg);
-    Path output = temp.newFolder().toPath().resolve("r8_debug_cf_output.jar");
+  private Stream<DebuggeeState> r8cf() throws Exception {
+    return streamDebugTest(
+        getCfConfig("r8cf.jar", options -> options.enableCfFrontend = true),
+        clazzName,
+        ANDROID_FILTER);
+  }
+
+  private DebugTestConfig getCfConfig(String outputName, Consumer<InternalOptions> optionsConsumer)
+      throws Exception {
+    Path input = inputJar;
+    Path output = temp.newFolder().toPath().resolve(outputName);
     ToolHelper.runR8(
         R8Command.builder()
             .addProgramFiles(input)
             .setMode(CompilationMode.DEBUG)
             .setOutput(output, OutputMode.ClassFile)
-            .build());
+            .build(),
+        optionsConsumer);
     return new CfDebugTestConfig(output);
   }
 
   @Test
   public void testArithmetic() throws Throwable {
+    testDebugging("arithmetic", "Arithmetic");
+  }
+
+  @Test
+  public void testArrayAccess() throws Exception {
+    testDebugging("arrayaccess", "ArrayAccess");
+  }
+
+  @Test
+  public void testBArray() throws Exception {
+    testDebugging("barray", "BArray");
+  }
+
+  @Test
+  public void testBridgeMethod() throws Exception {
+    // TODO(b/79671093): D8 has different line number info during stepping.
+    testDebuggingJvmOnly("bridge", "BridgeMethod");
+  }
+
+  @Test
+  public void testCommonSubexpressionElimination() throws Exception {
+    testDebugging("cse", "CommonSubexpressionElimination");
+  }
+
+  @Test
+  public void testConstants() throws Exception {
+    testDebugging("constants", "Constants");
+  }
+
+  @Test
+  public void testControlFlow() throws Exception {
+    testDebugging("controlflow", "ControlFlow");
+  }
+
+  @Test
+  public void testConversions() throws Exception {
+    testDebugging("conversions", "Conversions");
+  }
+
+  @Test
+  public void testFloatingPointValuedAnnotation() throws Exception {
+    // D8 has no source file.
+    testDebuggingJvmOnly("floating_point_annotations", "FloatingPointValuedAnnotationTest");
+  }
+
+  @Test
+  public void testFilledArray() throws Exception {
+    testDebugging("filledarray", "FilledArray");
+  }
+
+  @Test
+  public void testHello() throws Exception {
+    testDebugging("hello", "Hello");
+  }
+
+  @Test
+  public void testIfStatements() throws Exception {
+    testDebugging("ifstatements", "IfStatements");
+  }
+
+  @Test
+  public void testInstanceVariable() throws Exception {
+    // TODO(b/79671093): D8 has different line number info during stepping.
+    testDebuggingJvmOnly("instancevariable", "InstanceVariable");
+  }
+
+  @Test
+  public void testInstanceofString() throws Exception {
+    testDebugging("instanceofstring", "InstanceofString");
+  }
+
+  @Test
+  public void testInvoke() throws Exception {
+    // TODO(b/79671093): D8 has different line number info during stepping.
+    testDebuggingJvmOnly("invoke", "Invoke");
+  }
+
+  @Test
+  public void testJumboString() throws Exception {
+    testDebugging("jumbostring", "JumboString");
+  }
+
+  @Test
+  public void testLoadConst() throws Exception {
+    testDebugging("loadconst", "LoadConst");
+  }
+
+  @Test
+  public void testUdpServer() throws Exception {
+    // TODO(b/79671093): We don't match JVM's behavior on this example.
+    testDebuggingJvmOutputOnly("loop", "UdpServer");
+  }
+
+  @Test
+  public void testNewArray() throws Exception {
+    // TODO(b/79671093): D8 has different line number info during stepping.
+    testDebuggingJvmOnly("newarray", "NewArray");
+  }
+
+  @Test
+  public void testRegAlloc() throws Exception {
+    testDebugging("regalloc", "RegAlloc");
+  }
+
+  @Test
+  public void testReturns() throws Exception {
+    // TODO(b/79671093): D8 has different line number info during stepping.
+    testDebuggingJvmOnly("returns", "Returns");
+  }
+
+  @Test
+  public void testStaticField() throws Exception {
+    // TODO(b/79671093): D8 has different line number info during stepping.
+    testDebuggingJvmOnly("staticfield", "StaticField");
+  }
+
+  @Test
+  public void testStringBuilding() throws Exception {
+    testDebugging("stringbuilding", "StringBuilding");
+  }
+
+  @Test
+  public void testSwitches() throws Exception {
+    testDebugging("switches", "Switches");
+  }
+
+  @Test
+  public void testSync() throws Exception {
+    // D8 has two local variables with empty names.
+    testDebuggingJvmOnly("sync", "Sync");
+  }
+
+  @Test
+  public void testThrowing() throws Exception {
+    // TODO(b/79671093): We don't match JVM's behavior on this example.
+    testDebuggingJvmOutputOnly("throwing", "Throwing");
+  }
+
+  @Test
+  public void testTrivial() throws Exception {
+    testDebugging("trivial", "Trivial");
+  }
+
+  @Ignore("TODO(mathiasr): InvalidDebugInfoException in CfSourceCode")
+  @Test
+  public void testTryCatch() throws Exception {
+    // TODO(b/79671093): We don't match JVM's behavior on this example.
+    testDebuggingJvmOutputOnly("trycatch", "TryCatch");
+  }
+
+  @Test
+  public void testNestedTryCatches() throws Exception {
+    testDebugging("nestedtrycatches", "NestedTryCatches");
+  }
+
+  @Test
+  public void testTryCatchMany() throws Exception {
+    testDebugging("trycatchmany", "TryCatchMany");
+  }
+
+  @Test
+  public void testInvokeEmpty() throws Exception {
+    // TODO(b/79671093): D8 has different line number info during stepping.
+    testDebuggingJvmOnly("invokeempty", "InvokeEmpty");
+  }
+
+  @Test
+  public void testRegress() throws Exception {
+    testDebugging("regress", "Regress");
+  }
+
+  @Test
+  public void testRegress2() throws Exception {
+    // TODO(b/79671093): D8 has different line number info during stepping.
+    testDebuggingJvmOnly("regress2", "Regress2");
+  }
+
+  @Ignore("TODO(mathiasr): Different behavior CfSourceCode vs JarSourceCode")
+  @Test
+  public void testRegress37726195() throws Exception {
+    // TODO(b/79671093): We don't match JVM's behavior on this example.
+    testDebuggingJvmOutputOnly("regress_37726195", "Regress");
+  }
+
+  @Test
+  public void testRegress37658666() throws Exception {
+    // TODO(b/79671093): D8 has different line number info during stepping.
+    testDebuggingJvmOnly("regress_37658666", "Regress");
+  }
+
+  @Test
+  public void testRegress37875803() throws Exception {
+    testDebugging("regress_37875803", "Regress");
+  }
+
+  @Test
+  public void testRegress37955340() throws Exception {
+    // TODO(b/79671093): D8 has different line number info during stepping.
+    testDebuggingJvmOnly("regress_37955340", "Regress");
+  }
+
+  @Test
+  public void testRegress62300145() throws Exception {
+    // D8 has no source file.
+    testDebuggingJvmOnly("regress_62300145", "Regress");
+  }
+
+  @Test
+  public void testRegress64881691() throws Exception {
+    testDebugging("regress_64881691", "Regress");
+  }
+
+  @Test
+  public void testRegress65104300() throws Exception {
+    testDebugging("regress_65104300", "Regress");
+  }
+
+  @Ignore("TODO(b/79671093): This test seems to take forever")
+  @Test
+  public void testRegress70703087() throws Exception {
+    testDebugging("regress_70703087", "Test");
+  }
+
+  @Test
+  public void testRegress70736958() throws Exception {
+    // D8 has a local variable with empty name.
+    testDebuggingJvmOnly("regress_70736958", "Test");
+  }
+
+  @Ignore("TODO(mathiasr): Different behavior CfSourceCode vs JarSourceCode")
+  @Test
+  public void testRegress70737019() throws Exception {
+    // TODO(b/79671093): We don't match JVM's behavior on this example.
+    testDebuggingJvmOutputOnly("regress_70737019", "Test");
+  }
+
+  @Test
+  public void testRegress72361252() throws Exception {
+    // D8 output has variable with empty name.
+    testDebuggingJvmOnly("regress_72361252", "Test");
+  }
+
+  @Test
+  public void testMemberrebinding2() throws Exception {
+    // TODO(b/79671093): D8 has different line number info during stepping.
+    testDebuggingJvmOnly("memberrebinding2", "Memberrebinding");
+  }
+
+  @Test
+  public void testMemberrebinding3() throws Exception {
+    testDebugging("memberrebinding3", "Memberrebinding");
+  }
+
+  @Test
+  public void testMinification() throws Exception {
+    // TODO(b/79671093): D8 has different line number info during stepping.
+    testDebuggingJvmOnly("minification", "Minification");
+  }
+
+  @Test
+  public void testEnclosingmethod() throws Exception {
+    // TODO(b/79671093): D8 has different line number info during stepping.
+    testDebuggingJvmOnly("enclosingmethod", "Main");
+  }
+
+  @Test
+  public void testEnclosingmethod_proguarded() throws Exception {
+    // TODO(b/79671093): We don't match JVM's behavior on this example.
+    testDebuggingJvmOutputOnly("enclosingmethod_proguarded", "Main");
+  }
+
+  @Test
+  public void testInterfaceinlining() throws Exception {
+    testDebugging("interfaceinlining", "Main");
+  }
+
+  @Test
+  public void testSwitchmaps() throws Exception {
+    // TODO(b/79671093): D8 has different line number info during stepping.
+    testDebuggingJvmOnly("switchmaps", "Switches");
+  }
+
+  private void testDebugging(String pkg, String clazz) throws Exception {
+    init(pkg, clazz)
+        .add("Input", input())
+        .add("R8/CfSourceCode", r8cf())
+        .add("R8/JarSourceCode", r8jar())
+        .add("D8", d8())
+        .compare();
+  }
+
+  private void testDebuggingJvmOnly(String pkg, String clazz) throws Exception {
+    init(pkg, clazz)
+        .add("Input", input())
+        .add("R8/CfSourceCode", r8cf())
+        .add("R8/JarSourceCode", r8jar())
+        .compare();
+  }
+
+  private void testDebuggingJvmOutputOnly(String pkg, String clazz) throws Exception {
+    init(pkg, clazz)
+        .add("R8/CfSourceCode", r8cf())
+        .add("R8/JarSourceCode", r8jar())
+        .compare();
+  }
+
+  private DebugStreamComparator init(String pkg, String clazz) throws Exception {
     // See verifyStateLocation in DebugTestBase.
-    Assume.assumeTrue("Streaming on Dalvik DEX runtimes has some unknown interference issue",
+    Assume.assumeTrue(
+        "Streaming on Dalvik DEX runtimes has some unknown interference issue",
         ToolHelper.getDexVm().getVersion().isAtLeast(Version.V6_0_1));
-    Assume.assumeTrue("Skipping test " + testName.getMethodName()
+    Assume.assumeTrue(
+        "Skipping test "
+            + testName.getMethodName()
             + " because debug tests are not yet supported on Windows",
         !ToolHelper.isWindows());
-    String pkg = "arithmetic";
-    String clazzName = pkg + ".Arithmetic";
-    Stream<DebuggeeState> cf = streamDebugTest(cfConfig("arithmetic"), clazzName, ANDROID_FILTER);
-    Stream<DebuggeeState> d8 = streamDebugTest(d8Config("arithmetic"), clazzName, ANDROID_FILTER);
-    Stream<DebuggeeState> r8 =
-        streamDebugTest(r8DebugCfConfig("arithmetic"), clazzName, ANDROID_FILTER);
-    new DebugStreamComparator()
-        .add("CF", cf)
-        .add("D8", d8)
-        .add("R8/CF", r8)
-        .compare();
+    clazzName = pkg + "." + clazz;
+    inputJar =
+        Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, pkg + "_debuginfo_all" + FileUtils.JAR_EXTENSION);
+    return new DebugStreamComparator();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java b/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
index 843a0f8..bc390b9 100644
--- a/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
@@ -119,12 +119,6 @@
     } else {
       builder.addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()));
     }
-    // TODO(b/75997473): Enable inlining when supported by CF backend
-    ToolHelper.runR8(
-        builder.build(),
-        options -> {
-          options.enableInlining = false;
-          options.invalidDebugInfoFatal = true;
-        });
+    ToolHelper.runR8(builder.build(), options -> options.invalidDebugInfoFatal = true);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinDebugTestBase.java b/src/test/java/com/android/tools/r8/debug/KotlinDebugTestBase.java
index 5ddd98c..63bc007 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinDebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinDebugTestBase.java
@@ -4,9 +4,6 @@
 
 package com.android.tools.r8.debug;
 
-import com.android.tools.r8.ToolHelper;
-import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.List;
 import org.apache.harmony.jpda.tests.framework.jdwp.Frame.Variable;
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java b/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
index cdba21b..3837bb1 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
@@ -6,40 +6,46 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.ToolHelper;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import org.apache.harmony.jpda.tests.framework.jdwp.Value;
 import org.junit.Test;
 
-// TODO check double-depth inline (an inline in another inline)
 public class KotlinInlineTest extends KotlinDebugTestBase {
 
+  public static final String DEBUGGEE_CLASS = "KotlinInline";
+  public static final String SOURCE_FILE = "KotlinInline.kt";
+
   @Test
   public void testStepOverInline() throws Throwable {
     String methodName = "singleInline";
     runDebugTest(
         getD8Config(),
-        "KotlinInline",
-        breakpoint("KotlinInline", methodName),
+        DEBUGGEE_CLASS,
+        breakpoint(DEBUGGEE_CLASS, methodName),
         run(),
         inspect(s -> {
-          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(DEBUGGEE_CLASS, s.getClassName());
           assertEquals(methodName, s.getMethodName());
-          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(SOURCE_FILE, s.getSourceFile());
           assertEquals(41, s.getLineNumber());
           s.checkLocal("this");
         }),
         stepOver(),
         inspect(s -> {
-          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(DEBUGGEE_CLASS, s.getClassName());
           assertEquals(methodName, s.getMethodName());
-          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(SOURCE_FILE, s.getSourceFile());
           assertEquals(42, s.getLineNumber());
           s.checkLocal("this");
         }),
         kotlinStepOver(),
         inspect(s -> {
-          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(DEBUGGEE_CLASS, s.getClassName());
           assertEquals(methodName, s.getMethodName());
-          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(SOURCE_FILE, s.getSourceFile());
           assertEquals(43, s.getLineNumber());
           s.checkLocal("this");
         }),
@@ -51,29 +57,29 @@
     String methodName = "singleInline";
     runDebugTest(
         getD8Config(),
-        "KotlinInline",
-        breakpoint("KotlinInline", methodName),
+        DEBUGGEE_CLASS,
+        breakpoint(DEBUGGEE_CLASS, methodName),
         run(),
         inspect(s -> {
-          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(DEBUGGEE_CLASS, s.getClassName());
           assertEquals(methodName, s.getMethodName());
-          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(SOURCE_FILE, s.getSourceFile());
           assertEquals(41, s.getLineNumber());
           s.checkLocal("this");
         }),
         stepOver(),
         inspect(s -> {
-          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(DEBUGGEE_CLASS, s.getClassName());
           assertEquals(methodName, s.getMethodName());
-          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(SOURCE_FILE, s.getSourceFile());
           assertEquals(42, s.getLineNumber());
           s.checkLocal("this");
         }),
         stepInto(),
         inspect(s -> {
-          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(DEBUGGEE_CLASS, s.getClassName());
           assertEquals(methodName, s.getMethodName());
-          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(SOURCE_FILE, s.getSourceFile());
           // The actual line number (the one encoded in debug information) is different than the
           // source file one.
           // TODO(shertz) extract original line number from JSR-45's SMAP (only supported on
@@ -89,47 +95,65 @@
     String methodName = "singleInline";
     runDebugTest(
         getD8Config(),
-        "KotlinInline",
-        breakpoint("KotlinInline", methodName),
+        DEBUGGEE_CLASS,
+        breakpoint(DEBUGGEE_CLASS, methodName),
         run(),
         inspect(s -> {
-          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(DEBUGGEE_CLASS, s.getClassName());
           assertEquals(methodName, s.getMethodName());
-          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(SOURCE_FILE, s.getSourceFile());
           assertEquals(41, s.getLineNumber());
           s.checkLocal("this");
         }),
         stepOver(),
         inspect(s -> {
-          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(DEBUGGEE_CLASS, s.getClassName());
           assertEquals(methodName, s.getMethodName());
-          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(SOURCE_FILE, s.getSourceFile());
           assertEquals(42, s.getLineNumber());
           s.checkLocal("this");
         }),
         stepInto(),
         inspect(s -> {
-          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(DEBUGGEE_CLASS, s.getClassName());
           assertEquals(methodName, s.getMethodName());
         }),
         kotlinStepOut(),
         inspect(s -> {
-          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(DEBUGGEE_CLASS, s.getClassName());
           assertEquals(methodName, s.getMethodName());
-          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(SOURCE_FILE, s.getSourceFile());
           assertEquals(43, s.getLineNumber());
           s.checkLocal("this");
         }),
         run());
   }
 
+  private static String mangleFunctionNameFromInlineScope(String functionName) {
+    return "$i$f$" + functionName;
+  }
+
+  private static String mangleLambdaNameFromInlineScope(String functionName, int lambdaId) {
+    assert lambdaId > 0;
+    return "$i$a$" + lambdaId + "$" + functionName;
+  }
+
+  private static String mangleLvNameFromInlineScope(String lvName, int inlineDepth) {
+    assert inlineDepth > 0;
+    StringBuilder builder = new StringBuilder(lvName);
+    for (int i = 0; i < inlineDepth; ++i) {
+      builder.append("$iv");
+    }
+    return builder.toString();
+  }
+
   @Test
   public void testKotlinInline() throws Throwable {
     final String inliningMethodName = "invokeInlinedFunctions";
     runDebugTest(
         getD8Config(),
-        "KotlinInline",
-        breakpoint("KotlinInline", inliningMethodName),
+        DEBUGGEE_CLASS,
+        breakpoint(DEBUGGEE_CLASS, inliningMethodName),
         run(),
         inspect(s -> {
           assertEquals(inliningMethodName, s.getMethodName());
@@ -157,8 +181,8 @@
           s.checkLocal("this");
           s.checkLocal("inA", Value.createInt(1));
           // This is a "hidden" lv added by Kotlin (which is neither initialized nor used).
-          s.checkLocal("$i$f$inlinedA");
-          s.checkLocal("$i$a$1$inlinedA");
+          s.checkLocal(mangleFunctionNameFromInlineScope("inlinedA"));
+          s.checkLocal(mangleLambdaNameFromInlineScope("inlinedA", 1));
         }),
         stepInto(),
         inspect(s -> {
@@ -181,10 +205,144 @@
           s.checkLocal("this");
           s.checkLocal("inB", Value.createInt(2));
           // This is a "hidden" lv added by Kotlin (which is neither initialized nor used).
-          s.checkLocal("$i$f$inlinedB");
-          s.checkLocal("$i$a$1$inlinedB");
+          s.checkLocal(mangleFunctionNameFromInlineScope("inlinedB"));
+          s.checkLocal(mangleLambdaNameFromInlineScope("inlinedB", 1));
         }),
         run());
   }
 
+  @Test
+  public void testNestedInlining() throws Throwable {
+    // Count the number of lines in the source file. This is needed to check that inlined code
+    // refers to non-existing line numbers.
+    Path sourceFilePath = Paths.get(ToolHelper.TESTS_DIR, "debugTestResourcesKotlin", SOURCE_FILE);
+    assert sourceFilePath.toFile().exists();
+    final int maxLineNumber = Files.readAllLines(sourceFilePath).size();
+    final String inliningMethodName = "testNestedInlining";
+
+    // Local variables that represent the scope (start,end) of function's code that has been
+    // inlined.
+    final String inlinee1_inlineScope = mangleFunctionNameFromInlineScope("inlinee1");
+    final String inlinee2_inlineScope = mangleFunctionNameFromInlineScope("inlinee2");
+
+    // Local variables that represent the scope (start,end) of lambda's code that has been inlined.
+    final String inlinee2_lambda1_inlineScope = mangleLambdaNameFromInlineScope("inlinee2", 1);
+    final String inlinee2_lambda2_inlineScope = mangleLambdaNameFromInlineScope("inlinee2", 2);
+    final String c_mangledLvName = mangleLvNameFromInlineScope("c", 1);
+    final String left_mangledLvName = mangleLvNameFromInlineScope("left", 1);
+    final String right_mangledLvName = mangleLvNameFromInlineScope("right", 1);
+    final String p_mangledLvName = mangleLvNameFromInlineScope("p", 2);
+
+    runDebugTest(
+        getD8Config(),
+        DEBUGGEE_CLASS,
+        breakpoint(DEBUGGEE_CLASS, inliningMethodName),
+        run(),
+        inspect(s -> {
+          assertEquals(inliningMethodName, s.getMethodName());
+          assertEquals(52, s.getLineNumber());
+          s.checkLocal("this");
+        }),
+        checkLocal("this"),
+        checkNoLocals("l1", "l2"),
+        stepOver(),
+        checkLine(SOURCE_FILE, 53),
+        checkLocals("this", "l1"),
+        checkNoLocal("l2"),
+        stepOver(),
+        checkLine(SOURCE_FILE, 54),
+        checkLocals("this", "l1", "l2"),
+        stepInto(),
+        // We jumped into 1st inlinee but the current method is the same
+        checkMethod(DEBUGGEE_CLASS, inliningMethodName),
+        checkLocal(inlinee1_inlineScope),
+        inspect(state -> {
+          assertEquals(SOURCE_FILE, state.getSourceFile());
+          assertTrue(state.getLineNumber() > maxLineNumber);
+        }),
+        checkNoLocal(c_mangledLvName),
+        stepInto(),
+        checkLocal(c_mangledLvName),
+        stepInto(),
+        // We jumped into 2nd inlinee which is nested in the 1st inlinee
+        checkLocal(inlinee2_inlineScope),
+        checkLocal(inlinee1_inlineScope),
+        inspect(state -> {
+          assertEquals(SOURCE_FILE, state.getSourceFile());
+          assertTrue(state.getLineNumber() > maxLineNumber);
+        }),
+        // We must see the local variable "p" with a 2-level inline depth.
+        checkLocal(p_mangledLvName),
+        checkNoLocals(left_mangledLvName, right_mangledLvName),
+        // Enter the if block of inlinee2
+        stepInto(),
+        checkLocal(p_mangledLvName),
+        checkNoLocals(left_mangledLvName, right_mangledLvName),
+        // Enter the inlined lambda
+        stepInto(),
+        checkLocal(p_mangledLvName),
+        checkLocal(inlinee2_lambda1_inlineScope),
+        checkNoLocals(left_mangledLvName, right_mangledLvName),
+        stepInto(),
+        checkLocal(inlinee2_lambda1_inlineScope),
+        checkLocal(left_mangledLvName),
+        checkNoLocal(right_mangledLvName),
+        stepInto(),
+        checkLocals(left_mangledLvName, right_mangledLvName),
+        // Enter "foo"
+        stepInto(),
+        checkMethod(DEBUGGEE_CLASS, "foo"),
+        checkLine(SOURCE_FILE, 34),
+        stepOut(),
+        // We're back to the inline section, at the end of the lambda
+        inspect(state -> {
+          assertEquals(SOURCE_FILE, state.getSourceFile());
+          assertTrue(state.getLineNumber() > maxLineNumber);
+        }),
+        checkLocal(inlinee1_inlineScope),
+        checkLocal(inlinee2_inlineScope),
+        checkLocal(inlinee2_lambda1_inlineScope),
+        checkNoLocal(inlinee2_lambda2_inlineScope),
+        stepInto(),
+        // We're in inlinee2, after the call to the inlined lambda.
+        checkLocal(inlinee1_inlineScope),
+        checkLocal(inlinee2_inlineScope),
+        checkNoLocal(inlinee2_lambda1_inlineScope),
+        checkNoLocal(inlinee2_lambda2_inlineScope),
+        checkLocal(p_mangledLvName),
+        stepInto(),
+        // We're out of inlinee2
+        checkMethod(DEBUGGEE_CLASS, inliningMethodName),
+        checkLocal(inlinee1_inlineScope),
+        checkNoLocal(inlinee2_inlineScope),
+        checkNoLocal(inlinee2_lambda1_inlineScope),
+        // Enter the new call to "inlinee2"
+        stepInto(),
+        checkMethod(DEBUGGEE_CLASS, inliningMethodName),
+        checkLocal(inlinee1_inlineScope),
+        checkLocal(inlinee2_inlineScope),
+        checkNoLocal(inlinee2_lambda1_inlineScope),
+        checkNoLocal(inlinee2_lambda2_inlineScope),
+        checkNoLocal(p_mangledLvName),
+        stepInto(),
+        checkMethod(DEBUGGEE_CLASS, inliningMethodName),
+        checkLocal(inlinee1_inlineScope),
+        checkLocal(inlinee2_inlineScope),
+        checkNoLocal(inlinee2_lambda1_inlineScope),
+        checkNoLocal(inlinee2_lambda2_inlineScope),
+        checkNoLocal(p_mangledLvName),
+        // We enter the 2nd lambda
+        stepInto(),
+        checkMethod(DEBUGGEE_CLASS, inliningMethodName),
+        checkLocal(inlinee1_inlineScope),
+        checkLocal(inlinee2_inlineScope),
+        checkNoLocal(inlinee2_lambda1_inlineScope),
+        checkLocal(inlinee2_lambda2_inlineScope),
+        // Enter the call to "foo"
+        stepInto(),
+        checkMethod(DEBUGGEE_CLASS, "foo"),
+        checkLine(SOURCE_FILE, 34),
+        run());
+  }
+
 }
diff --git a/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java b/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java
new file mode 100644
index 0000000..3ea4017
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java
@@ -0,0 +1,77 @@
+// 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.debug;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.stream.Stream;
+import org.junit.Assume;
+import org.junit.Test;
+
+public class NopDebugTestRunner extends DebugTestBase {
+
+  private final Class<?> CLAZZ = NopTest.class;
+
+  @Test
+  public void testNop() throws Exception {
+    // R8 will dead-code eliminate "args = args" in NopTest.
+    // In debug mode the programmer should still be able to break on dead code,
+    // so R8 inserts a NOP in the output. Test that CfNop.buildIR() works.
+    runTwiceTest(writeInput(CLAZZ), CLAZZ.getName());
+  }
+
+  private Path writeInput(Class<?> clazz) throws Exception {
+    Path inputJar = temp.getRoot().toPath().resolve("input.jar");
+    ClassFileConsumer inputConsumer = new ClassFileConsumer.ArchiveConsumer(inputJar);
+    String descriptor = DescriptorUtils.javaTypeToDescriptor(clazz.getName());
+    inputConsumer.accept(ToolHelper.getClassAsBytes(clazz), descriptor, null);
+    inputConsumer.finished(null);
+    return inputJar;
+  }
+
+  private void runTwiceTest(Path inputJar, String mainClass) throws Exception {
+    Path output1 = runR8(inputJar, "output1.jar");
+    Path output2 = runR8(output1, "output2.jar");
+    stepOutput(mainClass, inputJar, output1, output2);
+  }
+
+  private Path runR8(Path inputJar, String outputName) throws Exception {
+    Path outputJar = temp.getRoot().toPath().resolve(outputName);
+    ToolHelper.runR8(
+        R8Command.builder()
+            .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+            .setMode(CompilationMode.DEBUG)
+            .addProgramFiles(inputJar)
+            .setOutput(outputJar, OutputMode.ClassFile)
+            .build(),
+        o -> o.enableCfFrontend = true);
+    return outputJar;
+  }
+
+  private void stepOutput(String mainClass, Path inputJar, Path output1, Path output2)
+      throws Exception {
+    Assume.assumeTrue(
+        "Skipping test "
+            + testName.getMethodName()
+            + " because debug tests are not yet supported on Windows",
+        !ToolHelper.isWindows());
+    new DebugStreamComparator()
+        .add("Input", streamDebugTest(mainClass, new CfDebugTestConfig(inputJar)))
+        .add("R8/CF", streamDebugTest(mainClass, new CfDebugTestConfig(output1)))
+        .add("R8/CF^2", streamDebugTest(mainClass, new CfDebugTestConfig(output2)))
+        .compare();
+  }
+
+  private Stream<DebuggeeState> streamDebugTest(String mainClass, DebugTestConfig config)
+      throws Exception {
+    return streamDebugTest(config, mainClass, ANDROID_FILTER);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/NopTest.java b/src/test/java/com/android/tools/r8/debug/NopTest.java
new file mode 100644
index 0000000..8d51961
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/NopTest.java
@@ -0,0 +1,12 @@
+// 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.debug;
+
+public class NopTest {
+
+  public static void main(String[] args) {
+    args = args;
+    System.out.println(args);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
index 108b474..103c07f 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
@@ -78,12 +78,6 @@
     } else {
       builder.addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()));
     }
-    // TODO(b/75997473): Enable inlining when supported by CF backend
-    ToolHelper.runR8(
-        builder.build(),
-        options -> {
-          options.enableInlining = false;
-          options.invalidDebugInfoFatal = true;
-        });
+    ToolHelper.runR8(builder.build(), options -> options.invalidDebugInfoFatal = true);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java b/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java
index f9541e5..4f2bdeb 100644
--- a/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java
@@ -115,12 +115,6 @@
     } else {
       builder.addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()));
     }
-    // TODO(b/75997473): Enable inlining when supported by CF backend
-    ToolHelper.runR8(
-        builder.build(),
-        options -> {
-          options.enableInlining = false;
-          options.invalidDebugInfoFatal = true;
-        });
+    ToolHelper.runR8(builder.build(), options -> options.invalidDebugInfoFatal = true);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
index 5150223..443a20e 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
@@ -31,6 +31,7 @@
 import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterInvoke;
 import com.android.tools.r8.ir.optimize.NonNullTracker;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.DexInspector;
@@ -57,7 +58,7 @@
     AppInfo appInfo = new AppInfo(dexApplication);
     DexInspector dexInspector = new DexInspector(appInfo.app);
     DexEncodedMethod foo = dexInspector.clazz(mainClass.getName()).method(signature).getMethod();
-    IRCode irCode = foo.buildIR(TEST_OPTIONS);
+    IRCode irCode = foo.buildIR(TEST_OPTIONS, Origin.unknown());
     NonNullTracker nonNullTracker = new NonNullTracker();
     nonNullTracker.addNonNull(irCode);
     TypeAnalysis analysis = new TypeAnalysis(appInfo, foo, irCode);
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
index ed15f7c..509b922 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
@@ -125,7 +125,7 @@
                 new MethodSignature("subtractConstants8bitRegisters", "int", ImmutableList.of()))
             .getMethod();
     try {
-      IRCode irCode = subtract.buildIR(TEST_OPTIONS);
+      IRCode irCode = subtract.buildIR(TEST_OPTIONS, Origin.unknown());
       TypeAnalysis analysis = new TypeAnalysis(appInfo, subtract, irCode);
       analysis.forEach((v, l) -> {
         assertEither(l, PRIMITIVE, NULL, TOP);
@@ -143,7 +143,7 @@
             .method(new MethodSignature("fibonacci", "int", ImmutableList.of("int")))
             .getMethod();
     try {
-      IRCode irCode = fib.buildIR(TEST_OPTIONS);
+      IRCode irCode = fib.buildIR(TEST_OPTIONS, Origin.unknown());
       TypeAnalysis analysis = new TypeAnalysis(appInfo, fib, irCode);
       analysis.forEach((v, l) -> {
         assertEither(l, PRIMITIVE, NULL);
@@ -161,7 +161,7 @@
             .method(new MethodSignature("test1", "int[]", ImmutableList.of()))
             .getMethod();
     try {
-      IRCode irCode = test1.buildIR(TEST_OPTIONS);
+      IRCode irCode = test1.buildIR(TEST_OPTIONS, Origin.unknown());
       TypeAnalysis analysis = new TypeAnalysis(appInfo, test1, irCode);
       Value array = null;
       InstructionIterator iterator = irCode.instructionIterator();
@@ -196,7 +196,7 @@
             .method(new MethodSignature("test4", "int[]", ImmutableList.of()))
             .getMethod();
     try {
-      IRCode irCode = test4.buildIR(TEST_OPTIONS);
+      IRCode irCode = test4.buildIR(TEST_OPTIONS, Origin.unknown());
       TypeAnalysis analysis = new TypeAnalysis(appInfo, test4, irCode);
       Value array = null;
       InstructionIterator iterator = irCode.instructionIterator();
@@ -231,7 +231,7 @@
             .method(new MethodSignature("loop2", "void", ImmutableList.of()))
             .getMethod();
     try {
-      IRCode irCode = loop2.buildIR(TEST_OPTIONS);
+      IRCode irCode = loop2.buildIR(TEST_OPTIONS, Origin.unknown());
       TypeAnalysis analysis = new TypeAnalysis(appInfo, loop2, irCode);
       analysis.forEach((v, l) -> {
         if (l.isClassTypeLatticeElement()) {
@@ -254,7 +254,7 @@
             .method(new MethodSignature("test2_throw", "int", ImmutableList.of()))
             .getMethod();
     try {
-      IRCode irCode = test2.buildIR(TEST_OPTIONS);
+      IRCode irCode = test2.buildIR(TEST_OPTIONS, Origin.unknown());
       TypeAnalysis analysis = new TypeAnalysis(appInfo, test2, irCode);
       analysis.forEach((v, l) -> {
         if (l.isClassTypeLatticeElement()) {
@@ -284,7 +284,7 @@
         CheckCast.class, new ClassTypeLatticeElement(test, true),
         NewInstance.class, new ClassTypeLatticeElement(test, false));
     try {
-      IRCode irCode = method.buildIR(TEST_OPTIONS);
+      IRCode irCode = method.buildIR(TEST_OPTIONS, Origin.unknown());
       TypeAnalysis analysis = new TypeAnalysis(appInfo, method, irCode);
       analysis.forEach((v, l) -> verifyTypeEnvironment(expectedLattices, v, l));
     } catch (ApiLevelException e) {
@@ -306,7 +306,7 @@
       InstanceOf.class, PRIMITIVE,
       StaticGet.class, new ClassTypeLatticeElement(test, true));
     try {
-      IRCode irCode = method.buildIR(TEST_OPTIONS);
+      IRCode irCode = method.buildIR(TEST_OPTIONS, Origin.unknown());
       TypeAnalysis analysis = new TypeAnalysis(appInfo, method, irCode);
       analysis.forEach((v, l) -> verifyTypeEnvironment(expectedLattices, v, l));
     } catch (ApiLevelException e) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
index b6109d4..fac2ca0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterInvoke;
 import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterNullCheck;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.InternalOptions;
@@ -47,7 +48,7 @@
     AppInfo appInfo = new AppInfo(dexApplication);
     DexInspector dexInspector = new DexInspector(appInfo.app);
     DexEncodedMethod foo = dexInspector.clazz(testClass.getName()).method(signature).getMethod();
-    IRCode irCode = foo.buildIR(TEST_OPTIONS);
+    IRCode irCode = foo.buildIR(TEST_OPTIONS, Origin.unknown());
     checkCountOfNonNull(irCode, 0);
 
     NonNullTracker nonNullTracker = new NonNullTracker();
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/B79405526.java b/src/test/java/com/android/tools/r8/ir/regalloc/B79405526.java
index f03e6f6..08058e5 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/B79405526.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/B79405526.java
@@ -5,12 +5,9 @@
 package com.android.tools.r8.ir.regalloc;
 
 import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertThat;
 
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import org.junit.Test;
@@ -22,13 +19,13 @@
     DexInspector inspector = new DexInspector(app);
     DexInspector.ClassSubject clazz = inspector.clazz(TestClass.class);
     assertThat(clazz, isPresent());
-    // TODO(christofferqa): Ensure runOnArt checks that there are no verification errors, and then
-    // use runOnArt instead of runOnArtRaw.
-    ToolHelper.ProcessResult d8Result = runOnArtRaw(app, TestClass.class.getCanonicalName());
-    assertThat(d8Result.stderr, not(containsString("Verification error")));
+    // Throws if a method in TestClass does not verify.
+    runOnArt(app, TestClass.class.getName());
   }
 
   private static class TestClass {
+    public static void main(String[] args) {}
+
     public void method() {
       Object x = this;
       TestClass y = this;
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index 674fba8..b50718d 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -5,6 +5,8 @@
 
 import static com.android.tools.r8.utils.DescriptorUtils.getPathFromDescriptor;
 
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
@@ -136,7 +138,11 @@
       return addMethod("public", name, argumentTypes, returnType, lines);
     }
 
-    public MethodSignature addNativeMethod(
+    /**
+     * Note that the JVM rejects native methods with code. This method is used to test that D8
+     * removes code from native methods.
+     */
+    public MethodSignature addNativeMethodWithCode(
         String name,
         List<String> argumentTypes,
         String returnType,
@@ -367,6 +373,19 @@
     return outputs;
   }
 
+  public void writeClassFiles(ClassFileConsumer consumer, DiagnosticsHandler handler)
+      throws Exception {
+    for (ClassBuilder clazz : classes) {
+      consumer.accept(compile(clazz), clazz.getDescriptor(), handler);
+    }
+  }
+
+  public void writeJar(Path output, DiagnosticsHandler handler) throws Exception {
+    ClassFileConsumer consumer = new ClassFileConsumer.ArchiveConsumer(output);
+    writeClassFiles(consumer, handler);
+    consumer.finished(handler);
+  }
+
   public DexApplication read() throws Exception {
     return read(new InternalOptions());
   }
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index d6b26f0..2ff2f71 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -619,7 +619,7 @@
                 DexAnnotationSet.empty(),
                 DexAnnotationSetRefList.empty(),
                 code);
-        IRCode ir = code.buildIR(method, options);
+        IRCode ir = code.buildIR(method, options, Origin.unknown());
         RegisterAllocator allocator = new LinearScanRegisterAllocator(ir, options);
         method.setCode(ir, allocator, options);
         directMethods[i] = method;
diff --git a/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java b/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
index 490162a..3429c2a 100644
--- a/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
+++ b/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
@@ -30,7 +29,6 @@
     builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class));
     builder.addProgramFiles(ToolHelper.getClassFileForTestClass(Utils.class));
     builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    builder.setMinApiLevel(AndroidApiLevel.O.getLevel());
     builder.setMode(CompilationMode.RELEASE);
     builder.addProguardConfiguration(
         ImmutableList.of(
diff --git a/src/test/java/com/android/tools/r8/movestringconstants/TestClass.java b/src/test/java/com/android/tools/r8/movestringconstants/TestClass.java
index bf13fc3..bf74250 100644
--- a/src/test/java/com/android/tools/r8/movestringconstants/TestClass.java
+++ b/src/test/java/com/android/tools/r8/movestringconstants/TestClass.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8.movestringconstants;
 
 public class TestClass {
+  public static void main(String[] args) {}
+
   static void foo(String arg1, String arg2, String arg3, String arg4) {
     Utils.check(arg1, "StringConstants::foo#1");
     Utils.check("", "StringConstants::foo#2");
diff --git a/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
index 3b2cf8c..e1fa3d0 100644
--- a/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
+++ b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
@@ -100,11 +100,6 @@
     for (Class<?> c : CLASSES) {
       builder.addClassProgramData(ToolHelper.getClassAsBytes(c), Origin.unknown());
     }
-    if (consumer instanceof ClassFileConsumer) {
-      // TODO(b/75997473): Enable inlining when supported by CF backend
-      ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
-    } else {
-      ToolHelper.runR8(builder.build());
-    }
+    ToolHelper.runR8(builder.build());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java b/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
index 9e062b3..c7c74e9 100644
--- a/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
+++ b/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
 import java.io.IOException;
@@ -119,13 +118,10 @@
             .addProgramFiles(inputJar)
             .setProgramConsumer(consumer)
             .addProguardConfigurationFiles(writeProguardRules(aggressive));
-    if (consumer instanceof ClassFileConsumer) {
-      // TODO(b/75997473): Enable inlining when supported by CF backend
-      ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
-    } else {
+    if (!(consumer instanceof ClassFileConsumer)) {
       builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
-      ToolHelper.runR8(builder.build());
     }
+    ToolHelper.runR8(builder.build());
   }
 
   private void buildAndRunProguard(String outName, boolean aggressive) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
index 4a7ba49..1f9c20f 100644
--- a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
+++ b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
@@ -31,7 +30,6 @@
     R8Command.Builder builder = R8Command.builder();
     builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class));
     builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    builder.setMinApiLevel(AndroidApiLevel.O.getLevel());
     builder.setMode(mode);
     builder.addProguardConfiguration(
         ImmutableList.of(
diff --git a/src/test/java/com/android/tools/r8/regress/b72485384/Main.java b/src/test/java/com/android/tools/r8/regress/b72485384/Main.java
index c569c5e..f492fcc 100644
--- a/src/test/java/com/android/tools/r8/regress/b72485384/Main.java
+++ b/src/test/java/com/android/tools/r8/regress/b72485384/Main.java
@@ -3,14 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.regress.b72485384;
 
-import com.google.common.base.Functions;
+import java.util.function.Function;
 
 public class Main {
 
   public static void main(String[] args) {
     GenericOuter<String> outer = new GenericOuter<>();
-    GenericOuter<String>.GenericInner<String> inner = outer
-        .makeInner(Functions.identity(), "Hello World!");
+    GenericOuter<String>.GenericInner<String> inner =
+        outer.makeInner(Function.identity(), "Hello World!");
     System.out.println(inner.innerGetter(inner));
     System.out.println(outer.outerGetter(inner));
   }
diff --git a/src/test/java/com/android/tools/r8/regress/b72485384/Regress72485384Test.java b/src/test/java/com/android/tools/r8/regress/b72485384/Regress72485384Test.java
index 3205434..ba65d92 100644
--- a/src/test/java/com/android/tools/r8/regress/b72485384/Regress72485384Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b72485384/Regress72485384Test.java
@@ -3,47 +3,70 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.regress.b72485384;
 
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.regress.b72485384.GenericOuter.GenericInner;
 import com.android.tools.r8.utils.AndroidApp;
 import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class Regress72485384Test extends TestBase {
+  @Rule public ExpectedException thrown = ExpectedException.none();
 
   @Parameters(name = "{0}")
-  public static List<String> getParameters() {
+  public static Collection<Object[]> getParameters() {
     String baseConfig =
         keepMainProguardConfiguration(Main.class)
             + "-keepattributes Signature,InnerClasses,EnclosingMethod ";
-    return ImmutableList.of(
-        baseConfig,
-        baseConfig + "-dontshrink",
-        baseConfig + "-dontshrink -dontobfuscate",
-        baseConfig + "-dontobfuscate",
-        "",
-        "-dontshrink",
-        "-keep class DoesNotExist -dontshrink"
-    );
+    return Arrays.asList(
+        new Object[][] {
+          {baseConfig, null},
+          {baseConfig + "-dontshrink", null},
+          {baseConfig + "-dontshrink -dontobfuscate", null},
+          {baseConfig + "-dontobfuscate", null},
+          {"", null},
+          {"-dontshrink", null},
+          {"-keep class DoesNotExist -dontshrink", "ClassNotFoundException"}
+        });
   }
 
   private final static List<Class> CLASSES = ImmutableList
       .of(GenericOuter.class, GenericInner.class, Main.class);
 
   private final String proguardConfig;
+  private final String expectedErrorMessage;
 
-  public Regress72485384Test(String proguardConfig) {
+  public Regress72485384Test(String proguardConfig, String expectedErrorMessage) {
     this.proguardConfig = proguardConfig;
+    this.expectedErrorMessage = expectedErrorMessage;
   }
 
   @Test
   public void testSignatureRewrite() throws Exception {
-    AndroidApp result = compileWithR8(CLASSES, proguardConfig);
-    runOnArt(result, Main.class.getCanonicalName());
+    AndroidApp app = compileWithR8(CLASSES, proguardConfig);
+
+    if (expectedErrorMessage == null) {
+      if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(ToolHelper.DexVm.Version.V6_0_1)) {
+        // Resolution of java.util.function.Function fails.
+        thrown.expect(AssertionError.class);
+      }
+
+      runOnArt(app, Main.class.getCanonicalName());
+    } else {
+      ToolHelper.ProcessResult result = runOnArtRaw(app, Main.class.getCanonicalName());
+      assertThat(result.stderr, containsString(expectedErrorMessage));
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTestRunner.java b/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTestRunner.java
index 3e75ac6..d1fc83e 100644
--- a/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTestRunner.java
+++ b/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTestRunner.java
@@ -69,11 +69,6 @@
     for (Class<?> c : CLASSES) {
       builder.addClassProgramData(ToolHelper.getClassAsBytes(c), Origin.unknown());
     }
-    if (consumer instanceof ClassFileConsumer) {
-      // TODO(b/75997473): Enable inlining when supported by CF.
-      ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
-    } else {
-      ToolHelper.runR8(builder.build());
-    }
+    ToolHelper.runR8(builder.build());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java
index 27f5321..474af4d 100644
--- a/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
 import java.io.IOException;
@@ -112,13 +111,10 @@
             .addProgramFiles(inputJar)
             .setProgramConsumer(consumer)
             .addProguardConfigurationFiles(writeProguardRules(aggressive));
-    if (consumer instanceof ClassFileConsumer) {
-      // TODO(b/75997473): Enable inlining when supported by CF backend
-      ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
-    } else {
+    if (!(consumer instanceof ClassFileConsumer)) {
       builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
-      ToolHelper.runR8(builder.build());
     }
+    ToolHelper.runR8(builder.build());
   }
 
   private void buildAndRunProguard(String outName, boolean aggressive) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
new file mode 100644
index 0000000..6ecdb09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
@@ -0,0 +1,201 @@
+// 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.shaking;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsChecker;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.ArchiveEntryOrigin;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.DexInspector;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.CharSource;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.jar.JarOutputStream;
+import java.util.zip.ZipEntry;
+import org.junit.Test;
+
+class A {
+  private static String buildClassName(String className) {
+    return A.class.getPackage().getName() + "." + className;
+  }
+
+  public static void main(String[] args) {
+    try {
+      Class bClass = Class.forName(buildClassName("B"));
+      System.out.println("YES");
+    } catch (ClassNotFoundException e) {
+      System.out.println("NO");
+    }
+  }
+}
+
+class B {
+
+}
+
+public class LibraryProvidedProguardRulesTest extends TestBase {
+  private void addTextJarEntry(JarOutputStream out, String name, String content) throws Exception {
+    out.putNextEntry(new ZipEntry(name));
+    ByteStreams.copy(
+        CharSource.wrap(content).asByteSource(StandardCharsets.UTF_8).openBufferedStream(), out);
+    out.closeEntry();
+  }
+
+  private AndroidApp runTest(List<String> rules, DiagnosticsHandler handler) throws Exception {
+    Path jar = temp.newFile("test.jar").toPath();
+    try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar.toFile()))) {
+      addTestClassesToJar(out, A.class, B.class);
+      for (int i =  0; i < rules.size(); i++) {
+        String name = "META-INF/proguard/jar" + (i == 0 ? "" : i) + ".rules";
+        addTextJarEntry(out, name, rules.get(i));
+      }
+    }
+
+    try {
+      R8Command command = (handler != null ? R8Command.builder(handler) : R8Command.builder())
+          .addProgramFiles(jar)
+          .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+          .build();
+      return ToolHelper.runR8(command);
+    } catch (CompilationFailedException e) {
+      assertNotNull(handler);
+      return null;
+    }
+  }
+
+  private AndroidApp runTest(String rules, DiagnosticsHandler handler) throws Exception {
+    return runTest(ImmutableList.of(rules), handler);
+  }
+
+
+  @Test
+  public void keepOnlyA() throws Exception {
+    AndroidApp app = runTest("-keep class " + A.class.getTypeName() +" {}", null);
+    DexInspector inspector = new DexInspector(app);
+    assertThat(inspector.clazz(A.class), isPresent());
+    assertThat(inspector.clazz(B.class), not(isPresent()));
+  }
+
+  @Test
+  public void keepOnlyB() throws Exception {
+    AndroidApp app = runTest("-keep class **B {}", null);
+    DexInspector inspector = new DexInspector(app);
+    assertThat(inspector.clazz(A.class), not(isPresent()));
+    assertThat(inspector.clazz(B.class), isPresent());
+  }
+
+  @Test
+  public void keepBoth() throws Exception {
+    AndroidApp app = runTest("-keep class ** {}", null);
+    DexInspector inspector = new DexInspector(app);
+    assertThat(inspector.clazz(A.class), isPresent());
+    assertThat(inspector.clazz(B.class), isPresent());
+  }
+
+  @Test
+  public void multipleFiles() throws Exception {
+    AndroidApp app = runTest(ImmutableList.of("-keep class **A {}", "-keep class **B {}"), null);
+    DexInspector inspector = new DexInspector(app);
+    assertThat(inspector.clazz(A.class), isPresent());
+    assertThat(inspector.clazz(B.class), isPresent());
+  }
+
+  private void checkOrigin(Origin origin) {
+    assertTrue(origin instanceof ArchiveEntryOrigin);
+    assertEquals(origin.part(), "META-INF/proguard/jar.rules");
+    assertTrue(origin.parent() instanceof PathOrigin);
+  }
+
+  @Test
+  public void syntaxError() throws Exception {
+    DiagnosticsChecker checker = new DiagnosticsChecker();
+    AndroidApp app = runTest("error", checker);
+    assertNull(app);
+    DiagnosticsChecker.checkDiagnostic(
+        checker.errors.get(0), this::checkOrigin, 1, 1, "Expected char '-'");
+  }
+
+  @Test
+  public void includeError() throws Exception {
+    DiagnosticsChecker checker = new DiagnosticsChecker();
+    AndroidApp app = runTest("-include other.rules", checker);
+    assertNull(app);
+    DiagnosticsChecker.checkDiagnostic(checker.errors.get(0), this::checkOrigin, 1, 10,
+        "Options with file names are not supported");
+  }
+
+  class TestProvider implements ProgramResourceProvider, DataResourceProvider {
+
+    @Override
+    public Collection<ProgramResource> getProgramResources() throws ResourceException {
+      byte[] bytes;
+      try {
+        bytes = ByteStreams.toByteArray(A.class.getResourceAsStream("A.class"));
+      } catch (IOException e) {
+        throw new ResourceException(Origin.unknown(), "Unexpected");
+      }
+      return ImmutableList.of(
+          ProgramResource.fromBytes(Origin.unknown(), Kind.CF, bytes,
+              Collections.singleton(DescriptorUtils.javaTypeToDescriptor(A.class.getTypeName()))));
+    }
+
+    @Override
+    public DataResourceProvider getDataResourceProvider() {
+      return this;
+    }
+
+    @Override
+    public void accept(Visitor visitor) throws ResourceException {
+      throw new ResourceException(Origin.unknown(), "Cannot provide data resources after all");
+    }
+  }
+
+  @Test
+  public void throwingDataResourceProvider() throws Exception {
+    DiagnosticsChecker checker = new DiagnosticsChecker();
+    try {
+      R8Command command = R8Command.builder(checker)
+          .addProgramResourceProvider(new TestProvider())
+          .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+          .build();
+      fail("Should not succeed");
+    } catch (CompilationFailedException e) {
+      DiagnosticsChecker.checkDiagnostic(
+          checker.errors.get(0),
+          origin -> assertSame(origin, Origin.unknown()),
+          "Cannot provide data resources after all");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index b7277cb..1012421 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -1718,6 +1718,7 @@
     assertEquals(0, handler.errors.size());
   }
 
+  // TODO(sgjesse): Change to use DiagnosticsChecker.
   private Diagnostic checkDiagnostic(List<Diagnostic> diagnostics, Path path, int lineStart,
       int columnStart, String... messageParts) {
     assertEquals(1, diagnostics.size());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java
index 8c67135..b77b29e 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java
@@ -21,7 +21,7 @@
   public static Collection<Object[]> data() {
     List<Object[]> parameters = new ArrayList<>();
     for (MinifyMode minify : MinifyMode.values()) {
-      // TODO(b/75997473): Add Frontend.JAR, Backend.CF when inlining is supported in CF backend.
+      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
       parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
       parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
     }
diff --git a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
index d2391ff..31abcf1 100644
--- a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
+++ b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
@@ -67,7 +68,7 @@
 
     DexEncodedMethod method = getMethod(originalApplication, methodSig);
     // Get the IR pre-optimization.
-    IRCode code = method.buildIR(new InternalOptions());
+    IRCode code = method.buildIR(new InternalOptions(), Origin.unknown());
 
     // Find the exit block and assert that the value is a phi merging the exceptional edge
     // with the normal edge.
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index 29676da..ace5c9b 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -136,6 +136,19 @@
             .read(app.getProguardMapOutputData()));
   }
 
+  public DexInspector(AndroidApp app, Consumer<InternalOptions> optionsConsumer)
+      throws IOException, ExecutionException {
+    this(
+        new ApplicationReader(app, runOptionsConsumer(optionsConsumer), new Timing("DexInspector"))
+            .read(app.getProguardMapOutputData()));
+  }
+
+  private static InternalOptions runOptionsConsumer(Consumer<InternalOptions> optionsConsumer) {
+    InternalOptions internalOptions = new InternalOptions();
+    optionsConsumer.accept(internalOptions);
+    return internalOptions;
+  }
+
   public DexInspector(AndroidApp app, Path proguardMap) throws IOException, ExecutionException {
     this(
         new ApplicationReader(app, new InternalOptions(), new Timing("DexInspector"))