Merge "Update unsplitting of arguments"
diff --git a/LIBRARY-LICENSE b/LIBRARY-LICENSE
index c19c9c3..12674e3 100644
--- a/LIBRARY-LICENSE
+++ b/LIBRARY-LICENSE
@@ -3,11 +3,11 @@
   copyrightHolder: The Guava Authors
   license: The Apache Software License, Version 2.0
   licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
-- artifact: com.googlecode.json-simple:json-simple:+
-  name: JSON.Simple
+- artifact: com.google.code.gson:gson:+
+  name: Gson
   license: The Apache Software License, Version 2.0
   licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
-  url: http://code.google.com/p/json-simple/
+  url: https://github.com/google/gson
 - artifact: it.unimi.dsi:fastutil:+
   name: fastutil
   license: Apache License, Version 2.0
diff --git a/build.gradle b/build.gradle
index 94c002f..8ee6b41 100644
--- a/build.gradle
+++ b/build.gradle
@@ -34,7 +34,7 @@
     fastutilVersion = '7.2.0'
     guavaVersion = '23.0'
     joptSimpleVersion = '4.6'
-    jsonSimpleVersion = '1.1'
+    gsonVersion = '2.7'
     junitVersion = '4.12'
     kotlinVersion = '1.2.30'
     protobufVersion = '3.0.0'
@@ -205,7 +205,7 @@
 
 dependencies {
     compile "net.sf.jopt-simple:jopt-simple:$joptSimpleVersion"
-    compile "com.googlecode.json-simple:json-simple:$jsonSimpleVersion"
+    compile "com.google.code.gson:gson:$gsonVersion"
     // Include all of guava when compiling the code, but exclude annotations that we don't
     // need from the packaging.
     compileOnly("com.google.guava:guava:$guavaVersion")
@@ -485,11 +485,11 @@
 
 static configureRelocations(ShadowJar task) {
     task.relocate('com.google.common', 'com.android.tools.r8.com.google.common')
+    task.relocate('com.google.gson', 'com.android.tools.r8.com.google.gson')
     task.relocate('com.google.thirdparty', 'com.android.tools.r8.com.google.thirdparty')
     task.relocate('joptsimple', 'com.android.tools.r8.joptsimple')
     task.relocate('org.apache.commons', 'com.android.tools.r8.org.apache.commons')
     task.relocate('org.objectweb.asm', 'com.android.tools.r8.org.objectweb.asm')
-    task.relocate('org.json.simple', 'com.android.tools.r8.org.json.simple')
     task.relocate('it.unimi.dsi.fastutil', 'com.android.tools.r8.it.unimi.dsi.fastutil')
 }
 
@@ -514,7 +514,7 @@
     classifier = null
     version = null
     manifest {
-        attributes 'Main-Class': 'com.android.tools.r8.R8'
+        attributes 'Main-Class': 'com.android.tools.r8.SwissArmyKnife'
     }
     // In order to build without dependencies, pass the exclude_deps property using:
     // gradle -Pexclude_deps R8
@@ -527,164 +527,27 @@
 }
 
 task D8(type: ShadowJar) {
-    from consolidatedLicense.outputs.files
-    exclude { path ->
-      path.getRelativePath().getPathString().startsWith("META-INF")
-    }
+    from R8.outputs.files
     baseName 'd8'
-    classifier = null
-    version = null
     manifest {
         attributes 'Main-Class': 'com.android.tools.r8.D8'
     }
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps D8
-    if (!project.hasProperty('exclude_deps')) {
-        from repackageSources.outputs.files
-        from repackageDeps.outputs.files
-    } else {
-        from sourceSets.main.output
-    }
 }
 
-task CompatDx(type: Jar) {
-    from sourceSets.main.output
+task CompatDx(type: ShadowJar) {
+    from R8.outputs.files
     baseName 'compatdx'
     manifest {
       attributes 'Main-Class': 'com.android.tools.r8.compatdx.CompatDx'
     }
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps CompatDx
-    if (!project.hasProperty('exclude_deps')) {
-        // Also include dependencies
-        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
-    }
 }
 
-task DexFileMerger(type: Jar) {
-    from sourceSets.main.output
-    baseName 'dexfilemerger'
-    manifest {
-      attributes 'Main-Class': 'com.android.tools.r8.dexfilemerger.DexFileMerger'
-    }
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps CompatDx
-    if (!project.hasProperty('exclude_deps')) {
-        // Also include dependencies
-        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
-    }
-}
-
-task DexSplitter(type: Jar) {
-    from sourceSets.main.output
-    baseName 'dexsplitter'
-    manifest {
-      attributes 'Main-Class': 'com.android.tools.r8.dexsplitter.DexSplitter'
-    }
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps CompatDx
-    if (!project.hasProperty('exclude_deps')) {
-        // Also include dependencies
-        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
-    }
-}
-
-task CompatProguard(type: Jar) {
-    from sourceSets.main.output
+task CompatProguard(type: ShadowJar) {
+    from R8.outputs.files
     baseName 'compatproguard'
     manifest {
         attributes 'Main-Class': 'com.android.tools.r8.compatproguard.CompatProguard'
     }
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps CompatProguard
-    if (!project.hasProperty('exclude_deps')) {
-        // Also include dependencies
-        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
-    }
-}
-
-task D8Logger(type: Jar) {
-    from sourceSets.main.output
-    baseName 'd8logger'
-    manifest {
-      attributes 'Main-Class': 'com.android.tools.r8.D8Logger'
-    }
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps D8Logger
-    if (!project.hasProperty('exclude_deps')) {
-        // Also include dependencies
-        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
-    }
-}
-
-task disasm(type: Jar) {
-    from sourceSets.main.output
-    baseName 'disasm'
-    manifest {
-        attributes 'Main-Class': 'com.android.tools.r8.Disassemble'
-    }
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps D8
-    if (!project.hasProperty('exclude_deps')) {
-        // Also include dependencies
-        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
-    }
-}
-
-task bisect(type: Jar) {
-    from sourceSets.main.output
-    baseName 'bisect'
-    manifest {
-      attributes 'Main-Class': 'com.android.tools.r8.bisect.Bisect'
-    }
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps R8
-    if (!project.hasProperty('exclude_deps')) {
-        // Also include dependencies
-        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
-    }
-}
-
-task DexSegments(type: Jar) {
-    from sourceSets.main.output
-    baseName 'dexsegments'
-    manifest {
-      attributes 'Main-Class': 'com.android.tools.r8.DexSegments'
-    }
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps DexSegments
-    if (!project.hasProperty('exclude_deps')) {
-        // Also include dependencies
-        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
-    }
-}
-
-task maindex(type: Jar) {
-    from sourceSets.main.output
-    baseName 'maindex'
-    manifest {
-      attributes 'Main-Class': 'com.android.tools.r8.GenerateMainDexList'
-    }
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps maindex
-    if (!project.hasProperty('exclude_deps')) {
-        // Also include dependencies
-        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
-    }
-}
-
-task ExtractMarker(type: Jar) {
-    from sourceSets.main.output
-    baseName 'extractmarker'
-    manifest {
-      attributes 'Main-Class': 'com.android.tools.r8.ExtractMarker'
-    }
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps ExtractMarker
-    if (!project.hasProperty('exclude_deps')) {
-        // Also include dependencies
-        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
-    }
 }
 
 task bspatch(type: Jar) {
@@ -694,7 +557,7 @@
         attributes 'Main-Class': 'com.android.tools.r8.dex.BSPatch'
     }
     // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps maindex
+    // gradle -Pexclude_deps bspatch
     if (!project.hasProperty('exclude_deps')) {
         // Also include dependencies
         from {
diff --git a/src/main/java/com/android/tools/r8/ApiLevelException.java b/src/main/java/com/android/tools/r8/ApiLevelException.java
index 14a530c..6d110a4 100644
--- a/src/main/java/com/android/tools/r8/ApiLevelException.java
+++ b/src/main/java/com/android/tools/r8/ApiLevelException.java
@@ -3,12 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.utils.AndroidApiLevel;
 
 /**
  * Exception to signal features that are not supported until a given API level.
  */
-public class ApiLevelException extends CompilationException {
+public class ApiLevelException extends CompilationError {
 
   public ApiLevelException(
       AndroidApiLevel minApiLevel, String unsupportedFeatures, String sourceString) {
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index aa99407..1db187e 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -155,6 +155,7 @@
       // Disable global optimizations.
       options.enableMinification = false;
       options.enableInlining = false;
+      options.enableClassInlining = false;
       options.outline.enabled = false;
 
       DexApplication app = new ApplicationReader(inputApp, options, timing).read(executor);
@@ -195,7 +196,7 @@
       InternalOptions options,
       Timing timing,
       ExecutorService executor)
-      throws IOException, ExecutionException, ApiLevelException {
+      throws IOException, ExecutionException {
     final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
 
     IRConverter converter = new IRConverter(appInfo, options, timing, printer);
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index bb7c37f..4c65134 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -333,6 +333,8 @@
     // Disable some of R8 optimizations.
     assert internal.enableInlining;
     internal.enableInlining = false;
+    assert internal.enableClassInlining;
+    internal.enableClassInlining = false;
     assert internal.enableSwitchMapRemoval;
     internal.enableSwitchMapRemoval = false;
     assert internal.outline.enabled;
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index e51e4d4..4f64416 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -32,6 +32,7 @@
       private Path outputPath = null;
       private Path proguardMapFile = null;
       private boolean useSmali = false;
+      private boolean allInfo = false;
 
       @Override
       Builder self() {
@@ -52,6 +53,11 @@
         return this;
       }
 
+      public Builder setAllInfo(boolean allInfo) {
+        this.allInfo = allInfo;
+        return this;
+      }
+
       public Builder setUseSmali(boolean useSmali) {
         this.useSmali = useSmali;
         return this;
@@ -67,6 +73,7 @@
             getAppBuilder().build(),
             getOutputPath(),
             proguardMapFile == null ? null : StringResource.fromFile(proguardMapFile),
+            allInfo,
             useSmali);
       }
     }
@@ -75,6 +82,7 @@
         "Usage: disasm [options] <input-files>",
         " where <input-files> are dex files",
         " and options are:",
+        "  --all                   # Include all information in disassembly.",
         "  --smali                 # Disassemble using smali syntax.",
         "  --pg-map <file>         # Proguard map <file> for mapping names.",
         "  --output                # Specify a file or directory to write to.",
@@ -82,6 +90,7 @@
         "  --help                  # Print this message."));
 
 
+    private final boolean allInfo;
     private final boolean useSmali;
 
     public static Builder builder() {
@@ -103,6 +112,8 @@
           builder.setPrintHelp(true);
         } else if (arg.equals("--version")) {
           builder.setPrintVersion(true);
+          } else if (arg.equals("--all")) {
+          builder.setAllInfo(true);
         } else if (arg.equals("--smali")) {
           builder.setUseSmali(true);
         } else if (arg.equals("--pg-map")) {
@@ -121,10 +132,12 @@
     }
 
     private DisassembleCommand(
-        AndroidApp inputApp, Path outputPath, StringResource proguardMap, boolean useSmali) {
+        AndroidApp inputApp, Path outputPath, StringResource proguardMap,
+        boolean allInfo, boolean useSmali) {
       super(inputApp);
       this.outputPath = outputPath;
       this.proguardMap = proguardMap;
+      this.allInfo = allInfo;
       this.useSmali = useSmali;
     }
 
@@ -132,6 +145,7 @@
       super(printHelp, printVersion);
       outputPath = null;
       proguardMap = null;
+      allInfo = false;
       useSmali = false;
     }
 
@@ -177,7 +191,7 @@
           new ApplicationReader(app, options, timing).read(command.proguardMap, executor);
       DexByteCodeWriter writer = command.useSmali()
           ? new SmaliWriter(application, options)
-          : new AssemblyWriter(application, options);
+          : new AssemblyWriter(application, options, command.allInfo);
       if (command.getOutputPath() != null) {
         writer.write(command.getOutputPath());
       } else {
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/ProgramResourceProvider.java b/src/main/java/com/android/tools/r8/ProgramResourceProvider.java
index 0dcc619..76d340c 100644
--- a/src/main/java/com/android/tools/r8/ProgramResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/ProgramResourceProvider.java
@@ -9,4 +9,8 @@
 public interface ProgramResourceProvider {
 
   Collection<ProgramResource> getProgramResources() throws ResourceException;
+
+  default DataResourceProvider getDataResourceProvider() {
+    return null;
+  }
 }
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 919e982..dafa35a 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:
  *
@@ -228,18 +232,12 @@
           new EnsureNonDexProgramResourceProvider(programProvider));
     }
 
-    public Builder addDataResourceProvider(DataResourceProvider dataResourceProvider) {
-      assert dataResourceProvider != null;
-      getAppBuilder().addDataResourceProvider(dataResourceProvider);
-      return self();
-    }
-
     @Override
     protected InternalProgramOutputPathConsumer createProgramOutputConsumer(
         Path path,
         OutputMode mode,
         boolean consumeDataResources) {
-      return super.createProgramOutputConsumer(path, mode, true);
+      return super.createProgramOutputConsumer(path, mode, false);
     }
 
     @Override
@@ -288,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();
       }
@@ -375,6 +406,11 @@
       }
       return resources;
     }
+
+    @Override
+    public DataResourceProvider getDataResourceProvider() {
+      return provider.getDataResourceProvider();
+    }
   }
 
   // Internal state to verify parsing properties not enforced by the builder.
@@ -665,6 +701,7 @@
     if (internal.debug) {
       // TODO(zerny): Should we support inlining in debug mode? b/62937285
       internal.enableInlining = false;
+      internal.enableClassInlining = false;
       // TODO(zerny): Should we support outlining in debug mode? b/62937285
       internal.outline.enabled = false;
     }
diff --git a/src/main/java/com/android/tools/r8/SwissArmyKnife.java b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
new file mode 100644
index 0000000..0e35847
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
@@ -0,0 +1,83 @@
+// 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.bisect.Bisect;
+import com.android.tools.r8.compatdx.CompatDx;
+import com.android.tools.r8.compatproguard.CompatProguard;
+import com.android.tools.r8.dexfilemerger.DexFileMerger;
+import com.android.tools.r8.dexsplitter.DexSplitter;
+import java.util.Arrays;
+
+/**
+ * Common entry point to everything in the R8 project.
+ *
+ * <p>This class is used as the main class in {@code r8.jar}. It checks the first command-line
+ * argument to find the tool to run, or runs {@link R8} if the first argument is not a recognized
+ * tool name.
+ *
+ * <p>The set of tools recognized by this class is defined by a switch statement in {@link
+ * SwissArmyKnife#main(String[])}.
+ */
+public class SwissArmyKnife {
+
+  public static void main(String[] args) throws Exception {
+    if (args.length == 0) {
+      runDefault(args);
+      return;
+    }
+    switch (args[0]) {
+      case "r8":
+        R8.main(shift(args));
+        break;
+      case "d8":
+        D8.main(shift(args));
+        break;
+      case "compatdx":
+        CompatDx.main(shift(args));
+        break;
+      case "dexfilemerger":
+        DexFileMerger.main(shift(args));
+        break;
+      case "dexsplitter":
+        DexSplitter.main(shift(args));
+        break;
+      case "compatproguard":
+        CompatProguard.main(shift(args));
+        break;
+      case "d8logger":
+        D8Logger.main(shift(args));
+        break;
+      case "disasm":
+        Disassemble.main(shift(args));
+        break;
+      case "bisect":
+        Bisect.main(shift(args));
+        break;
+      case "dexsegments":
+        DexSegments.main(shift(args));
+        break;
+      case "maindex":
+        GenerateMainDexList.main(shift(args));
+        break;
+      case "extractmarker":
+        ExtractMarker.main(shift(args));
+        break;
+      case "jardiff":
+        JarDiff.main(shift(args));
+        break;
+      default:
+        runDefault(args);
+        break;
+    }
+  }
+
+  private static void runDefault(String[] args) {
+    R8.main(args);
+  }
+
+  private static String[] shift(String[] args) {
+    return Arrays.copyOfRange(args, 1, args.length);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 0712db2..98ed5e9 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.2.15-dev";
+  public static final String LABEL = "1.2.17-dev";
 
   private Version() {
   }
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..3122404 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
@@ -7,6 +7,9 @@
 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 +39,16 @@
   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) {
+    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..ebbaf21 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
@@ -7,6 +7,9 @@
 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 +40,15 @@
   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) {
+    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..6d2a3d5 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
@@ -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;
 
@@ -25,4 +28,30 @@
   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);
+
+  /** 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..5dfef0c 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
@@ -7,9 +7,18 @@
 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 +83,72 @@
         throw new Unreachable("unknown CfInvoke opcode " + opcode);
     }
   }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    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..015b8a3 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
@@ -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;
 
@@ -41,4 +44,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[] 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..a30c6e6 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,160 @@
   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
+          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/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java
index a346020..bad4e47 100644
--- a/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
@@ -71,7 +70,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     builder.addConstMethodHandle(AA, (DexMethodHandle) BBBB);
   }
 
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..84c090a 100644
--- a/src/main/java/com/android/tools/r8/code/ConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/code/ConstMethodType.java
@@ -3,11 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.errors.InternalCompilerError;
 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 +56,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)) {
@@ -65,7 +70,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     builder.addConstMethodType(AA, (DexProto) BBBB);
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/FilledNewArray.java b/src/main/java/com/android/tools/r8/code/FilledNewArray.java
index 8aaf5b9..4f2ebb6 100644
--- a/src/main/java/com/android/tools/r8/code/FilledNewArray.java
+++ b/src/main/java/com/android/tools/r8/code/FilledNewArray.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -42,7 +41,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     builder.addInvokeNewArray(getType(), A, new int[]{C, D, E, F, G});
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java b/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java
index 2d39d50..0ee63b0 100644
--- a/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java
+++ b/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -42,7 +41,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     builder.addInvokeRangeNewArray(getType(), AA, CCCC);
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/Format10t.java b/src/main/java/com/android/tools/r8/code/Format10t.java
index 4c61fa3..8365a28 100644
--- a/src/main/java/com/android/tools/r8/code/Format10t.java
+++ b/src/main/java/com/android/tools/r8/code/Format10t.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.code;
 
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
@@ -53,7 +54,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format10x.java b/src/main/java/com/android/tools/r8/code/Format10x.java
index 8eed571..0b497fb 100644
--- a/src/main/java/com/android/tools/r8/code/Format10x.java
+++ b/src/main/java/com/android/tools/r8/code/Format10x.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.code;
 
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
@@ -45,7 +46,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format11n.java b/src/main/java/com/android/tools/r8/code/Format11n.java
index f2b8ac2..4d9a01b 100644
--- a/src/main/java/com/android/tools/r8/code/Format11n.java
+++ b/src/main/java/com/android/tools/r8/code/Format11n.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
@@ -58,7 +59,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format11x.java b/src/main/java/com/android/tools/r8/code/Format11x.java
index 318bf04..dd5a76b 100644
--- a/src/main/java/com/android/tools/r8/code/Format11x.java
+++ b/src/main/java/com/android/tools/r8/code/Format11x.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
@@ -53,7 +54,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format12x.java b/src/main/java/com/android/tools/r8/code/Format12x.java
index ac90623..07ec8e2 100644
--- a/src/main/java/com/android/tools/r8/code/Format12x.java
+++ b/src/main/java/com/android/tools/r8/code/Format12x.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
@@ -57,7 +58,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format20t.java b/src/main/java/com/android/tools/r8/code/Format20t.java
index f145476..ee32757 100644
--- a/src/main/java/com/android/tools/r8/code/Format20t.java
+++ b/src/main/java/com/android/tools/r8/code/Format20t.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.code;
 
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
@@ -53,7 +54,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format21c.java b/src/main/java/com/android/tools/r8/code/Format21c.java
index 2ae2808..faa9d58 100644
--- a/src/main/java/com/android/tools/r8/code/Format21c.java
+++ b/src/main/java/com/android/tools/r8/code/Format21c.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -62,8 +63,9 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    BBBB.collectIndexedItems(indexedItems);
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
+    BBBB.collectIndexedItems(indexedItems, method, instructionOffset);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format21h.java b/src/main/java/com/android/tools/r8/code/Format21h.java
index 221dc9e..074a0a0 100644
--- a/src/main/java/com/android/tools/r8/code/Format21h.java
+++ b/src/main/java/com/android/tools/r8/code/Format21h.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import java.nio.ShortBuffer;
 
@@ -48,7 +49,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format21s.java b/src/main/java/com/android/tools/r8/code/Format21s.java
index a9d7dc1..48f457c 100644
--- a/src/main/java/com/android/tools/r8/code/Format21s.java
+++ b/src/main/java/com/android/tools/r8/code/Format21s.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.StringUtils;
@@ -60,7 +61,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format21t.java b/src/main/java/com/android/tools/r8/code/Format21t.java
index 9f15752..40b7227 100644
--- a/src/main/java/com/android/tools/r8/code/Format21t.java
+++ b/src/main/java/com/android/tools/r8/code/Format21t.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.ir.code.If.Type;
 import com.android.tools.r8.ir.code.ValueType;
@@ -76,7 +77,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format22b.java b/src/main/java/com/android/tools/r8/code/Format22b.java
index 3952eb7..86a51b6 100644
--- a/src/main/java/com/android/tools/r8/code/Format22b.java
+++ b/src/main/java/com/android/tools/r8/code/Format22b.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.StringUtils;
@@ -65,7 +66,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format22c.java b/src/main/java/com/android/tools/r8/code/Format22c.java
index 6f8d6c6..16082a4 100644
--- a/src/main/java/com/android/tools/r8/code/Format22c.java
+++ b/src/main/java/com/android/tools/r8/code/Format22c.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -66,8 +67,9 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    CCCC.collectIndexedItems(indexedItems);
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
+    CCCC.collectIndexedItems(indexedItems, method, instructionOffset);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format22s.java b/src/main/java/com/android/tools/r8/code/Format22s.java
index 25c51d6..9666b21 100644
--- a/src/main/java/com/android/tools/r8/code/Format22s.java
+++ b/src/main/java/com/android/tools/r8/code/Format22s.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.StringUtils;
@@ -65,7 +66,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format22t.java b/src/main/java/com/android/tools/r8/code/Format22t.java
index 9bf727c..ae05622 100644
--- a/src/main/java/com/android/tools/r8/code/Format22t.java
+++ b/src/main/java/com/android/tools/r8/code/Format22t.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.ir.code.If.Type;
 import com.android.tools.r8.ir.code.ValueType;
@@ -82,7 +83,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format22x.java b/src/main/java/com/android/tools/r8/code/Format22x.java
index 5c1b2c4..d30f023 100644
--- a/src/main/java/com/android/tools/r8/code/Format22x.java
+++ b/src/main/java/com/android/tools/r8/code/Format22x.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
@@ -59,7 +60,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format23x.java b/src/main/java/com/android/tools/r8/code/Format23x.java
index 739b66f..02722db 100644
--- a/src/main/java/com/android/tools/r8/code/Format23x.java
+++ b/src/main/java/com/android/tools/r8/code/Format23x.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
@@ -63,7 +64,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format30t.java b/src/main/java/com/android/tools/r8/code/Format30t.java
index bfcf0d3..5ef18eb 100644
--- a/src/main/java/com/android/tools/r8/code/Format30t.java
+++ b/src/main/java/com/android/tools/r8/code/Format30t.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.code;
 
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
@@ -52,7 +53,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format31c.java b/src/main/java/com/android/tools/r8/code/Format31c.java
index ead4920..3019b9b 100644
--- a/src/main/java/com/android/tools/r8/code/Format31c.java
+++ b/src/main/java/com/android/tools/r8/code/Format31c.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.dex.Constants.U8BIT_MAX;
 
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
@@ -58,8 +59,9 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    BBBBBBBB.collectIndexedItems(indexedItems);
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
+    BBBBBBBB.collectIndexedItems(indexedItems, method, instructionOffset);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format31i.java b/src/main/java/com/android/tools/r8/code/Format31i.java
index 5e55ca4..192b28d 100644
--- a/src/main/java/com/android/tools/r8/code/Format31i.java
+++ b/src/main/java/com/android/tools/r8/code/Format31i.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
@@ -53,7 +54,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format31t.java b/src/main/java/com/android/tools/r8/code/Format31t.java
index d650fed..19de81f 100644
--- a/src/main/java/com/android/tools/r8/code/Format31t.java
+++ b/src/main/java/com/android/tools/r8/code/Format31t.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
@@ -68,7 +69,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format32x.java b/src/main/java/com/android/tools/r8/code/Format32x.java
index a31a494..4c70702 100644
--- a/src/main/java/com/android/tools/r8/code/Format32x.java
+++ b/src/main/java/com/android/tools/r8/code/Format32x.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.dex.Constants.U16BIT_MAX;
 
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
@@ -61,7 +62,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format35c.java b/src/main/java/com/android/tools/r8/code/Format35c.java
index b1f3f49..f8c68c3 100644
--- a/src/main/java/com/android/tools/r8/code/Format35c.java
+++ b/src/main/java/com/android/tools/r8/code/Format35c.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -110,8 +111,9 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    BBBB.collectIndexedItems(indexedItems);
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
+    BBBB.collectIndexedItems(indexedItems, method, instructionOffset);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format3rc.java b/src/main/java/com/android/tools/r8/code/Format3rc.java
index 4362b4e..77d3571 100644
--- a/src/main/java/com/android/tools/r8/code/Format3rc.java
+++ b/src/main/java/com/android/tools/r8/code/Format3rc.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -88,8 +89,9 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    BBBB.collectIndexedItems(indexedItems);
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
+    BBBB.collectIndexedItems(indexedItems, method, instructionOffset);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format45cc.java b/src/main/java/com/android/tools/r8/code/Format45cc.java
index f5123cf..fc88461 100644
--- a/src/main/java/com/android/tools/r8/code/Format45cc.java
+++ b/src/main/java/com/android/tools/r8/code/Format45cc.java
@@ -87,9 +87,10 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    BBBB.collectIndexedItems(indexedItems);
-    HHHH.collectIndexedItems(indexedItems);
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
+    BBBB.collectIndexedItems(indexedItems, method, instructionOffset);
+    HHHH.collectIndexedItems(indexedItems, method, instructionOffset);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format4rcc.java b/src/main/java/com/android/tools/r8/code/Format4rcc.java
index 0ec2780..c139b03 100644
--- a/src/main/java/com/android/tools/r8/code/Format4rcc.java
+++ b/src/main/java/com/android/tools/r8/code/Format4rcc.java
@@ -93,9 +93,10 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    BBBB.collectIndexedItems(indexedItems);
-    HHHH.collectIndexedItems(indexedItems);
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
+    BBBB.collectIndexedItems(indexedItems, method, instructionOffset);
+    HHHH.collectIndexedItems(indexedItems, method, instructionOffset);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format51l.java b/src/main/java/com/android/tools/r8/code/Format51l.java
index 0e9ad93..008e724 100644
--- a/src/main/java/com/android/tools/r8/code/Format51l.java
+++ b/src/main/java/com/android/tools/r8/code/Format51l.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
@@ -53,7 +54,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index 154622b..c8b5154 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.graph.DexCallSite;
@@ -184,7 +183,7 @@
     return NO_TARGETS;
   }
 
-  public abstract void buildIR(IRBuilder builder) throws ApiLevelException;
+  public abstract void buildIR(IRBuilder builder);
 
   public DexCallSite getCallSite() {
     return null;
@@ -239,7 +238,8 @@
 
   public abstract void write(ShortBuffer buffer, ObjectToOffsetMapping mapping);
 
-  public abstract void collectIndexedItems(IndexedItemCollection indexedItems);
+  public abstract void collectIndexedItems(
+      IndexedItemCollection indexedItems, DexMethod method, int instructionOffset);
 
   public boolean equals(Instruction other, BiPredicate<IndexedDexItem, IndexedDexItem> equality) {
     // In the default case, there is nothing to substitute.
diff --git a/src/main/java/com/android/tools/r8/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/code/InvokeDirect.java
index 06b919b..08d6826 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeDirect.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
@@ -51,7 +50,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     builder.addInvokeRegisters(Type.DIRECT, getMethod(), getProto(), A, new int[]{C, D, E, F, G});
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeDirectRange.java b/src/main/java/com/android/tools/r8/code/InvokeDirectRange.java
index e2b129d..8f72a2b 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeDirectRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeDirectRange.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.graph.UseRegistry;
@@ -50,7 +49,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     builder.addInvokeRange(Type.DIRECT, getMethod(), getProto(), AA, CCCC);
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/code/InvokeInterface.java
index c0d912a..b98af41 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeInterface.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
@@ -51,7 +50,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     builder.addInvokeRegisters(
         Type.INTERFACE, getMethod(), getProto(), A, new int[] {C, D, E, F, G});
   }
diff --git a/src/main/java/com/android/tools/r8/code/InvokeInterfaceRange.java b/src/main/java/com/android/tools/r8/code/InvokeInterfaceRange.java
index c63c41b..e4d63b4 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeInterfaceRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeInterfaceRange.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.graph.UseRegistry;
@@ -50,7 +49,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     builder.addInvokeRange(Type.INTERFACE, getMethod(), getProto(), AA, CCCC);
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/code/InvokePolymorphic.java
index 891dcca..d5ecd11 100644
--- a/src/main/java/com/android/tools/r8/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/code/InvokePolymorphic.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
@@ -30,7 +29,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     builder.addInvokeRegisters(
         Type.POLYMORPHIC, getMethod(), getProto(), A, new int[] {C, D, E, F, G});
   }
diff --git a/src/main/java/com/android/tools/r8/code/InvokePolymorphicRange.java b/src/main/java/com/android/tools/r8/code/InvokePolymorphicRange.java
index 3969a8c..45eb6e8 100644
--- a/src/main/java/com/android/tools/r8/code/InvokePolymorphicRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokePolymorphicRange.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
@@ -53,7 +52,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     builder.addInvokeRange(Type.POLYMORPHIC, getMethod(), getProto(), AA, CCCC);
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/code/InvokeStatic.java
index 6c0a724..73cb887 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeStatic.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.graph.UseRegistry;
@@ -50,7 +49,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     builder.addInvokeRegisters(Type.STATIC, getMethod(), getProto(), A, new int[]{C, D, E, F, G});
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeStaticRange.java b/src/main/java/com/android/tools/r8/code/InvokeStaticRange.java
index 912cd4b..6fd3f42 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeStaticRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeStaticRange.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.graph.UseRegistry;
@@ -50,7 +49,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     builder.addInvokeRange(Type.STATIC, getMethod(), getProto(), AA, CCCC);
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/code/InvokeSuper.java
index 10ed497..9575ed9 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeSuper.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
@@ -51,7 +50,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     builder.addInvokeRegisters(Type.SUPER, getMethod(), getProto(), A, new int[]{C, D, E, F, G});
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeSuperRange.java b/src/main/java/com/android/tools/r8/code/InvokeSuperRange.java
index 8933527..63c6318 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeSuperRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeSuperRange.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.graph.UseRegistry;
@@ -50,7 +49,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     builder.addInvokeRange(Type.SUPER, getMethod(), getProto(), AA, CCCC);
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/code/InvokeVirtual.java
index 132c564..003debf 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeVirtual.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.graph.UseRegistry;
@@ -50,7 +49,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     builder.addInvokeRegisters(Type.VIRTUAL, getMethod(), getProto(), A, new int[]{C, D, E, F, G});
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java b/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
index 00cb861..64e9b6d 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.graph.UseRegistry;
@@ -50,7 +49,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     builder.addInvokeRange(Type.VIRTUAL, getMethod(), getProto(), AA, CCCC);
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 7dec61a..a478860 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -114,7 +114,7 @@
       classReader.readSources();
       ThreadUtils.awaitFutures(futures);
       classReader.initializeLazyClassCollection(builder);
-      builder.replaceDataResourceProviders(inputApp.getDataResourceProviders());
+      builder.addProgramResourceProviders(inputApp.getProgramResourceProviders());
     } catch (ResourceException e) {
       throw options.reporter.fatalError(new StringDiagnostic(e.getMessage(), e.getOrigin()));
     } finally {
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index b8d17ce..127ec43 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -3,20 +3,19 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.dex;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.DataDirectoryResource;
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.DataResourceConsumer;
 import com.android.tools.r8.DataResourceProvider;
 import com.android.tools.r8.DataResourceProvider.Visitor;
 import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.ProgramResourceProvider;
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationDirectory;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexDebugInfo;
@@ -29,6 +28,7 @@
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.ProguardMapSupplier;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -42,9 +42,11 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
+import java.util.stream.Collectors;
 
 public class ApplicationWriter {
 
@@ -103,7 +105,7 @@
     }
 
     @Override
-    public boolean add(DexAnnotationSetRefList annotationSetRefList) {
+    public boolean add(ParameterAnnotationsList parameterAnnotationsList) {
       return true;
     }
 
@@ -297,7 +299,14 @@
     }
     DataResourceConsumer dataResourceConsumer = options.dataResourceConsumer;
     if (dataResourceConsumer != null) {
-      for (DataResourceProvider dataResourceProvider : application.dataResourceProviders) {
+
+      List<DataResourceProvider> dataResourceProviders = application.programResourceProviders
+          .stream()
+          .map(ProgramResourceProvider::getDataResourceProvider)
+          .filter(Objects::nonNull)
+          .collect(Collectors.toList());
+
+      for (DataResourceProvider dataResourceProvider : dataResourceProviders) {
         try {
           dataResourceProvider.accept(new Visitor() {
             @Override
@@ -419,8 +428,7 @@
     }
   }
 
-  private byte[] writeDexFile(ObjectToOffsetMapping mapping)
-      throws ApiLevelException {
+  private byte[] writeDexFile(ObjectToOffsetMapping mapping) {
     FileWriter fileWriter = new FileWriter(mapping, application, options, namingLens);
     // Collect the non-fixed sections.
     fileWriter.collect();
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 8042a1a..d762646 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexCode;
@@ -56,6 +55,7 @@
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
@@ -340,11 +340,11 @@
     return result;
   }
 
-  private DexAnnotationSetRefList annotationSetRefListAt(int offset) {
-    return (DexAnnotationSetRefList) cacheAt(offset, this::parseAnnotationSetRefList);
+  private ParameterAnnotationsList annotationSetRefListAt(int offset) {
+    return (ParameterAnnotationsList) cacheAt(offset, this::parseAnnotationSetRefList);
   }
 
-  private DexAnnotationSetRefList parseAnnotationSetRefList() {
+  private ParameterAnnotationsList parseAnnotationSetRefList() {
     int size = dexReader.getUint();
     int[] annotationOffsets = new int[size];
     for (int i = 0; i < size; i++) {
@@ -354,7 +354,7 @@
     for (int i = 0; i < size; i++) {
       values[i] = annotationSetAt(annotationOffsets[i]);
     }
-    return new DexAnnotationSetRefList(values);
+    return new ParameterAnnotationsList(values);
   }
 
   private DexParameterAnnotation[] parseParameterAnnotations(int size) {
@@ -373,7 +373,8 @@
       DexMethod method = indexedItems.getMethod(methodIndices[i]);
       result[i] = new DexParameterAnnotation(
           method,
-          annotationSetRefListAt(annotationOffsets[i]));
+          annotationSetRefListAt(annotationOffsets[i])
+              .withParameterCount(method.proto.parameters.size()));
     }
     dexReader.position(saved);
     return result;
@@ -589,8 +590,8 @@
     int methodIndex = 0;
     MemberAnnotationIterator<DexMethod, DexAnnotationSet> annotationIterator =
         new MemberAnnotationIterator<>(annotations, DexAnnotationSet::empty);
-    MemberAnnotationIterator<DexMethod, DexAnnotationSetRefList> parameterAnnotationsIterator =
-        new MemberAnnotationIterator<>(parameters, DexAnnotationSetRefList::empty);
+    MemberAnnotationIterator<DexMethod, ParameterAnnotationsList> parameterAnnotationsIterator =
+        new MemberAnnotationIterator<>(parameters, ParameterAnnotationsList::empty);
     for (int i = 0; i < size; i++) {
       methodIndex += dexReader.getUleb128();
       MethodAccessFlags accessFlags = MethodAccessFlags.fromDexAccessFlags(dexReader.getUleb128());
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index ced33f6..b73f8a4 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.graph.DexAnnotationDirectory;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
@@ -40,6 +39,7 @@
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.KeyedDexItem;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.PresortedComparable;
 import com.android.tools.r8.graph.ProgramClassVisitor;
 import com.android.tools.r8.logging.Log;
@@ -51,7 +51,6 @@
 import com.android.tools.r8.utils.DexVersion;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.LebUtils;
-import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
@@ -135,7 +134,7 @@
     return this;
   }
 
-  public byte[] generate() throws ApiLevelException {
+  public byte[] generate() {
     // Check restrictions on interface methods.
     checkInterfaceMethods();
 
@@ -203,7 +202,7 @@
     return Arrays.copyOf(dest.asArray(), layout.getEndOfFile());
   }
 
-  private void checkInterfaceMethods() throws ApiLevelException {
+  private void checkInterfaceMethods() {
     for (DexProgramClass clazz : mapping.getClasses()) {
       if (clazz.isInterface()) {
         for (DexEncodedMethod method : clazz.directMethods()) {
@@ -222,7 +221,7 @@
   //  -- starting with N interfaces may also have public or private
   //     static methods, as well as public non-abstract (default)
   //     and private instance methods.
-  private void checkInterfaceMethod(DexEncodedMethod method) throws ApiLevelException {
+  private void checkInterfaceMethod(DexEncodedMethod method) {
     if (application.dexItemFactory.isClassConstructor(method.method)) {
       return; // Class constructor is always OK.
     }
@@ -295,16 +294,16 @@
     }
   }
 
-  private <T extends IndexedDexItem> void writeFixedSectionItems(Collection<T> items, int offset,
-      ThrowingConsumer<T, ApiLevelException> writer) throws ApiLevelException {
+  private <T extends IndexedDexItem> void writeFixedSectionItems(
+      Collection<T> items, int offset, Consumer<T> writer) {
     assert dest.position() == offset;
     for (T item : items) {
       writer.accept(item);
     }
   }
 
-  private void writeFixedSectionItems(DexProgramClass[] items, int offset,
-      ThrowingConsumer<DexProgramClass, ApiLevelException> writer) throws ApiLevelException {
+  private void writeFixedSectionItems(
+      DexProgramClass[] items, int offset, Consumer<DexProgramClass> writer) {
     assert dest.position() == offset;
     for (DexProgramClass item : items) {
       writer.accept(item);
@@ -510,12 +509,17 @@
     }
   }
 
-  private void writeAnnotationSetRefList(DexAnnotationSetRefList setRefList) {
-    assert !setRefList.isEmpty();
-    mixedSectionOffsets.setOffsetFor(setRefList, dest.align(4));
-    dest.putInt(setRefList.values.length);
-    for (DexAnnotationSet set : setRefList.values) {
-      dest.putInt(mixedSectionOffsets.getOffsetFor(set));
+  private void writeAnnotationSetRefList(ParameterAnnotationsList parameterAnnotationsList) {
+    assert !parameterAnnotationsList.isEmpty();
+    mixedSectionOffsets.setOffsetFor(parameterAnnotationsList, dest.align(4));
+    dest.putInt(parameterAnnotationsList.countNonMissing());
+    for (int i = 0; i < parameterAnnotationsList.size(); i++) {
+      if (parameterAnnotationsList.isMissing(i)) {
+        // b/62300145: Maintain broken ParameterAnnotations attribute by only outputting the
+        // non-missing annotation lists.
+        continue;
+      }
+      dest.putInt(mixedSectionOffsets.getOffsetFor(parameterAnnotationsList.get(i)));
     }
   }
 
@@ -541,7 +545,7 @@
     writeMemberAnnotations(methodAnnotations,
         item -> mixedSectionOffsets.getOffsetFor(item.annotations));
     writeMemberAnnotations(parameterAnnotations,
-        item -> mixedSectionOffsets.getOffsetFor(item.parameterAnnotations));
+        item -> mixedSectionOffsets.getOffsetFor(item.parameterAnnotationsList));
   }
 
   private void writeEncodedFields(DexEncodedField[] fields) {
@@ -605,7 +609,7 @@
     }
   }
 
-  private void writeMethodHandle(DexMethodHandle methodHandle) throws ApiLevelException {
+  private void writeMethodHandle(DexMethodHandle methodHandle) {
     checkThatInvokeCustomIsAllowed();
     MethodHandleType methodHandleDexType;
     switch (methodHandle.type) {
@@ -631,7 +635,7 @@
     dest.putShort((short) 0); // unused
   }
 
-  private void writeCallSite(DexCallSite callSite) throws ApiLevelException {
+  private void writeCallSite(DexCallSite callSite) {
     checkThatInvokeCustomIsAllowed();
     assert dest.isAligned(4);
     dest.putInt(mixedSectionOffsets.getOffsetFor(callSite.getEncodedArray()));
@@ -990,7 +994,7 @@
     private final Reference2IntMap<DexString> stringData = createReference2IntMap();
     private final Object2IntMap<DexAnnotation> annotations = createObject2IntMap();
     private final Object2IntMap<DexAnnotationSet> annotationSets = createObject2IntMap();
-    private final Object2IntMap<DexAnnotationSetRefList> annotationSetRefLists
+    private final Object2IntMap<ParameterAnnotationsList> annotationSetRefLists
         = createObject2IntMap();
     private final Object2IntMap<DexAnnotationDirectory> annotationDirectories
         = createObject2IntMap();
@@ -1072,7 +1076,7 @@
     }
 
     @Override
-    public boolean add(DexAnnotationSetRefList annotationSetRefList) {
+    public boolean add(ParameterAnnotationsList annotationSetRefList) {
       if (annotationSetRefList.isEmpty()) {
         return false;
       }
@@ -1120,7 +1124,7 @@
       return annotationSets.keySet();
     }
 
-    public Collection<DexAnnotationSetRefList> getAnnotationSetRefLists() {
+    public Collection<ParameterAnnotationsList> getAnnotationSetRefLists() {
       return annotationSetRefLists.keySet();
     }
 
@@ -1200,7 +1204,7 @@
       return lookup(annotationSet, annotationSets);
     }
 
-    public int getOffsetFor(DexAnnotationSetRefList annotationSetRefList) {
+    public int getOffsetFor(ParameterAnnotationsList annotationSetRefList) {
       if (annotationSetRefList.isEmpty()) {
         return 0;
       }
@@ -1261,7 +1265,7 @@
       setOffsetFor(encodedArray, offset, encodedArrays);
     }
 
-    void setOffsetFor(DexAnnotationSetRefList annotationSetRefList, int offset) {
+    void setOffsetFor(ParameterAnnotationsList annotationSetRefList, int offset) {
       assert offset != 0 && !annotationSetRefList.isEmpty();
       setOffsetFor(annotationSetRefList, offset, annotationSetRefLists);
     }
@@ -1291,7 +1295,7 @@
     }
   }
 
-  private void checkThatInvokeCustomIsAllowed() throws ApiLevelException {
+  private void checkThatInvokeCustomIsAllowed() {
     if (!options.canUseInvokeCustom()) {
       throw new ApiLevelException(
           AndroidApiLevel.O,
diff --git a/src/main/java/com/android/tools/r8/dex/Marker.java b/src/main/java/com/android/tools/r8/dex/Marker.java
index c9af221..b36f30c 100644
--- a/src/main/java/com/android/tools/r8/dex/Marker.java
+++ b/src/main/java/com/android/tools/r8/dex/Marker.java
@@ -3,11 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.dex;
 
-import java.util.Map;
-import java.util.TreeMap;
-import org.json.simple.JSONObject;
-import org.json.simple.parser.JSONParser;
-import org.json.simple.parser.ParseException;
+import com.android.tools.r8.graph.DexString;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+import java.util.Comparator;
+import java.util.Map.Entry;
 
 /**
  * Abstraction for hidden dex marker intended for the main dex file.
@@ -20,26 +22,22 @@
 
   public enum Tool {D8, R8}
 
-  private static final String kPrefix = "~~";
-  private static final String kD8prefix = kPrefix + Tool.D8 + "{";
-  private static final String kR8prefix = kPrefix + Tool.R8 + "{";
+  private static final char PREFIX_CHAR = '~';
+  private static final String PREFIX = "~~";
+  private static final String D8_PREFIX = PREFIX + Tool.D8 + "{";
+  private static final String R8_PREFIX = PREFIX + Tool.R8 + "{";
 
-  private final TreeMap<String, Object> content;
+  private final JsonObject jsonObject;
   private final Tool tool;
 
   public Marker(Tool tool) {
     this.tool = tool;
-    this.content = new TreeMap<>();
+    jsonObject = new JsonObject();
   }
 
-  private Marker(Tool tool, JSONObject object) {
+  private Marker(Tool tool, JsonObject jsonObject) {
     this.tool = tool;
-    content = new TreeMap<>();
-    // This loop is necessary to make the type checker to shut up.
-    for (Object e : object.entrySet()) {
-      Map.Entry<?,?> entry = (Map.Entry<?,?>) e;
-      content.put(String.valueOf(entry.getKey()), entry.getValue());
-    }
+    this.jsonObject = jsonObject;
   }
 
   public Tool getTool() {
@@ -55,91 +53,84 @@
   }
 
   public String getVersion() {
-    return (String) content.get(VERSION);
+    return jsonObject.get(VERSION).getAsString();
   }
 
   public Marker setVersion(String version) {
-    internalPut(VERSION, version);
+    assert !jsonObject.has(VERSION);
+    jsonObject.addProperty(VERSION, version);
     return this;
   }
 
   public Long getMinApi() {
-    return (Long) content.get(MIN_API);
+    return jsonObject.get(MIN_API).getAsLong();
   }
 
   public Marker setMinApi(long minApi) {
-    internalPut(MIN_API, minApi);
+    assert !jsonObject.has(MIN_API);
+    jsonObject.addProperty(MIN_API, minApi);
     return this;
   }
 
   public String getSha1() {
-    return (String) content.get(SHA1);
+    return jsonObject.get(SHA1).getAsString();
   }
 
   public Marker setSha1(String sha1) {
-    internalPut(SHA1, sha1);
-    return this;
-  }
-
-  private Marker internalPut(String key, Object value) {
-    assert (key != null) && (value != null);
-    assert !content.containsKey(key);
-    content.put(key, value);
+    assert !jsonObject.has(SHA1);
+    jsonObject.addProperty(SHA1, sha1);
     return this;
   }
 
   @Override
   public String toString() {
-    // The JSONObject does not support a predictable sorted serialization of the object.
-    // Therefore, a TreeMap is used and iteration is over the keySet.
-    StringBuffer sb = new StringBuffer(kPrefix + tool);
-    boolean first = true;
-    sb.append('{');
-    for (String key : content.keySet()) {
-      if (first) {
-        first = false;
-      } else {
-        sb.append(',');
-      }
-      sb.append(JSONObject.toString(key, content.get(key)));
-    }
-    sb.append('}');
-    return sb.toString();
+    // In order to make printing of markers deterministic we sort the entries by key.
+    final JsonObject sortedJson = new JsonObject();
+    jsonObject.entrySet()
+        .stream()
+        .sorted(Comparator.comparing(Entry::getKey))
+        .forEach(entry -> sortedJson.add(entry.getKey(), entry.getValue()));
+    return PREFIX + tool + sortedJson;
   }
 
   @Override
   public boolean equals(Object obj) {
     if (obj instanceof Marker) {
       Marker other = (Marker) obj;
-      return (tool == other.tool) && content.equals(other.content);
+      return (tool == other.tool) && jsonObject.equals(other.jsonObject);
     }
     return false;
   }
 
   @Override
   public int hashCode() {
-    return tool.hashCode() + 3 * content.hashCode();
+    return tool.hashCode() + 3 * jsonObject.hashCode();
   }
 
   // Try to parse str as a marker.
   // Returns null if parsing fails.
-  public static Marker parse(String str) {
-    if (str.startsWith(kD8prefix)) {
-      return internalParse(Tool.D8, str.substring(kD8prefix.length() - 1));
-    }
-    if (str.startsWith(kR8prefix)) {
-      return internalParse(Tool.R8, str.substring(kR8prefix.length() - 1));
+  public static Marker parse(DexString dexString) {
+    if (dexString.size > 2
+        && dexString.content[0] == PREFIX_CHAR
+        && dexString.content[1] == PREFIX_CHAR) {
+      String str = dexString.toString();
+      if (str.startsWith(D8_PREFIX)) {
+        return internalParse(Tool.D8, str.substring(D8_PREFIX.length() - 1));
+      }
+      if (str.startsWith(R8_PREFIX)) {
+        return internalParse(Tool.R8, str.substring(R8_PREFIX.length() - 1));
+      }
     }
     return null;
   }
 
   private static Marker internalParse(Tool tool, String str) {
     try {
-      Object result =  new JSONParser().parse(str);
-      if (result instanceof JSONObject) {
-        return new Marker(tool, (JSONObject) result);
+      JsonElement result =  new JsonParser().parse(str);
+      if (result.isJsonObject()) {
+        return new Marker(tool, result.getAsJsonObject());
       }
-    } catch (ParseException e) {
+    } catch (JsonSyntaxException e) {
       // Fall through.
     }
     return null;
diff --git a/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java b/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
index eb7ae98..dee12fc 100644
--- a/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
+++ b/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
@@ -6,13 +6,13 @@
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationDirectory;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexDebugInfo;
 import com.android.tools.r8.graph.DexEncodedArray;
 import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 
 /**
  * Collection of the various components of the mixed section of a dex file.
@@ -86,7 +86,7 @@
    *
    * @return true if the item was not added before
    */
-  public abstract boolean add(DexAnnotationSetRefList annotationSetRefList);
+  public abstract boolean add(ParameterAnnotationsList annotationSetRefList);
 
   /**
    * Adds the given annotation to the collection.
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..7daef42 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -3,14 +3,21 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.errors.Unreachable;
+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;
 import com.google.common.collect.ImmutableMap.Builder;
+import com.google.common.collect.Sets;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 
@@ -62,6 +69,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);
   }
@@ -469,6 +481,40 @@
     return result;
   }
 
+  public boolean canTriggerStaticInitializer(DexType clazz) {
+    Set<DexType> knownInterfaces = Sets.newIdentityHashSet();
+
+    // Process superclass chain.
+    while (clazz != null && clazz != dexItemFactory.objectType) {
+      DexClass definition = definitionFor(clazz);
+      if (definition == null || definition.hasClassInitializer()) {
+        return true; // Assume it *may* trigger if we didn't find the definition.
+      }
+      knownInterfaces.addAll(Arrays.asList(definition.interfaces.values));
+      clazz = definition.superType;
+    }
+
+    // Process interfaces.
+    Queue<DexType> queue = new ArrayDeque<>(knownInterfaces);
+    while (!queue.isEmpty()) {
+      DexType iface = queue.remove();
+      DexClass definition = definitionFor(iface);
+      if (definition == null || definition.hasClassInitializer()) {
+        return true; // Assume it *may* trigger if we didn't find the definition.
+      }
+      if (!definition.accessFlags.isInterface()) {
+        throw new Unreachable(iface.toSourceString() + " is expected to be an interface");
+      }
+
+      for (DexType superIface : definition.interfaces.values) {
+        if (knownInterfaces.add(superIface)) {
+          queue.add(superIface);
+        }
+      }
+    }
+    return false;
+  }
+
   public interface ResolutionResult {
 
     DexEncodedMethod asResultOfResolve();
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 50ff578..63a2958 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -4,13 +4,21 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.utils.InternalOptions;
 import java.io.PrintStream;
 
 public class AssemblyWriter extends DexByteCodeWriter {
 
-  public AssemblyWriter(DexApplication application, InternalOptions options) {
+  private final boolean writeAllClassInfo;
+  private final boolean writeFields;
+  private final boolean writeAnnotations;
+
+  public AssemblyWriter(DexApplication application, InternalOptions options, boolean allInfo) {
     super(application, options);
+    this.writeAllClassInfo = allInfo;
+    this.writeFields = allInfo;
+    this.writeAnnotations = allInfo;
   }
 
   @Override
@@ -28,12 +36,43 @@
     }
     ps.println("# Bytecode for");
     ps.println("# Class: '" + clazzName + "'");
+    if (writeAllClassInfo) {
+      writeAnnotations(clazz.annotations, ps);
+      ps.println("# Flags: '" + clazz.accessFlags + "'");
+      if (clazz.superType != application.dexItemFactory.objectType) {
+        ps.println("# Extends: '" + clazz.superType.toSourceString() + "'");
+      }
+      for (DexType value : clazz.interfaces.values) {
+        ps.println("# Implements: '" + value.toSourceString() + "'");
+      }
+    }
     ps.println();
   }
 
   @Override
+  void writeFieldsHeader(DexProgramClass clazz, PrintStream ps) {
+    if (writeFields) {
+      ps.println("#");
+      ps.println("# Fields:");
+      ps.println("#");
+    }
+  }
+
+  @Override
   void writeField(DexEncodedField field, PrintStream ps) {
-    // Not implemented, yet.
+    if (writeFields) {
+      ClassNameMapper naming = application.getProguardMap();
+      FieldSignature fieldSignature = naming != null
+          ? naming.originalSignatureOf(field.field)
+          : FieldSignature.fromDexField(field.field);
+      writeAnnotations(field.annotations, ps);
+      ps.println(fieldSignature);
+    }
+  }
+
+  @Override
+  void writeFieldsFooter(DexProgramClass clazz, PrintStream ps) {
+    ps.println();
   }
 
   @Override
@@ -44,6 +83,7 @@
         : method.method.name.toString();
     ps.println("#");
     ps.println("# Method: '" + methodName + "':");
+    writeAnnotations(method.annotations, ps);
     ps.println("#");
     ps.println();
     Code code = method.getCode();
@@ -52,6 +92,18 @@
     }
   }
 
+  private void writeAnnotations(DexAnnotationSet annotations, PrintStream ps) {
+    if (writeAnnotations) {
+      if (!annotations.isEmpty()) {
+        ps.println("# Annotations:");
+        for (DexAnnotation annotation : annotations.annotations) {
+          ps.print("#   ");
+          ps.println(annotation);
+        }
+      }
+    }
+  }
+
   @Override
   void writeClassFooter(DexProgramClass clazz, PrintStream ps) {
 
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..1fd4a73 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -3,30 +3,23 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfLabel;
 import com.android.tools.r8.cf.code.CfPosition;
 import com.android.tools.r8.cf.code.CfReturnVoid;
-import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.cf.code.CfTryCatch;
 import com.android.tools.r8.errors.Unimplemented;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.Throw;
-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;
 import java.util.List;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
@@ -120,6 +113,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,34 +199,40 @@
   }
 
   @Override
-  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
-      throws ApiLevelException {
-    if (instructions.size() == 2
-        && instructions.get(0) instanceof CfConstNull
-        && instructions.get(1) instanceof CfThrow) {
-      BasicBlock block = new BasicBlock();
-      block.setNumber(1);
-      Value nullValue = new Value(0, ValueType.OBJECT, null);
-      block.add(new ConstNumber(nullValue, 0L));
-      block.add(new Throw(nullValue));
-      block.close(null);
-      for (Instruction insn : block.getInstructions()) {
-        insn.setPosition(Position.none());
-      }
-      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");
+  public IRCode buildIR(
+      DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
+    return internalBuild(encodedMethod, appInfo, options, null, null, origin);
   }
 
   @Override
   public IRCode buildInliningIR(
       DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
       InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
-      Position callerPosition)
-      throws ApiLevelException {
-    throw new Unimplemented("Converting Java class-file bytecode to IR not yet supported");
+      Position callerPosition,
+      Origin origin) {
+    assert valueNumberGenerator != null;
+    assert callerPosition != null;
+    return internalBuild(
+        encodedMethod, appInfo, options, valueNumberGenerator, callerPosition, origin);
+  }
+
+  private IRCode internalBuild(
+      DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
+      InternalOptions options,
+      ValueNumberGenerator generator,
+      Position callerPosition,
+      Origin origin) {
+    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, appInfo, source, options)
+            : new IRBuilder(encodedMethod, appInfo, 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 52217a3..60a5ef5 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.Unreachable;
@@ -12,19 +11,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)
-      throws ApiLevelException;
+  public abstract IRCode buildIR(
+      DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin);
 
   public IRCode buildInliningIR(
       DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
       InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
-      Position callerPosition)
-      throws ApiLevelException {
+      Position callerPosition,
+      Origin origin) {
     throw new Unreachable("Unexpected attempt to build IR graph for inlining from: "
         + getClass().getCanonicalName());
   }
@@ -52,14 +53,24 @@
     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()");
   }
 
+  public LazyCfCode asLazyCfCode() {
+    throw new Unreachable(getClass().getCanonicalName() + ".asLazyCfCode()");
+  }
+
   public DexCode asDexCode() {
     throw new Unreachable(getClass().getCanonicalName() + ".asDexCode()");
   }
@@ -73,7 +84,8 @@
   }
 
   @Override
-  void collectIndexedItems(IndexedItemCollection collection) {
+  void collectIndexedItems(IndexedItemCollection collection,
+      DexMethod method, int instructionOffset) {
     throw new Unreachable();
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index d762340..73ac7fb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -55,8 +55,9 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    annotation.collectIndexedItems(indexedItems);
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
+    annotation.collectIndexedItems(indexedItems, method, instructionOffset);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
index db55cd6..fa5d8a8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
@@ -33,7 +33,7 @@
       if (!method.annotations.isEmpty()) {
         methodAnnotations.add(method);
       }
-      if (!method.parameterAnnotations.isEmpty()) {
+      if (!method.parameterAnnotationsList.isEmpty()) {
         parameterAnnotations.add(method);
       }
     }
@@ -98,7 +98,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection collection) {
+  public void collectIndexedItems(IndexedItemCollection collection,
+      DexMethod method, int instructionOffset) {
     throw new Unreachable();
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationElement.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationElement.java
index c55ea05..040d256 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationElement.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationElement.java
@@ -39,9 +39,10 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    name.collectIndexedItems(indexedItems);
-    value.collectIndexedItems(indexedItems);
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
+    name.collectIndexedItems(indexedItems, method, instructionOffset);
+    value.collectIndexedItems(indexedItems, method, instructionOffset);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
index 93e103d..d093a20 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
@@ -5,7 +5,9 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.function.Predicate;
 
 public class DexAnnotationSet extends CachedHashValueDexItem {
 
@@ -39,7 +41,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     collectAll(indexedItems, annotations);
   }
 
@@ -114,4 +117,30 @@
     extendedArray[annotations.length] = newAnnotation;
     return new DexAnnotationSet(extendedArray);
   }
+
+  public DexAnnotationSet keepIf(Predicate<DexAnnotation> filter) {
+    ArrayList<DexAnnotation> filtered = null;
+    for (int i = 0; i < annotations.length; i++) {
+      DexAnnotation annotation = annotations[i];
+      if (filter.test(annotation)) {
+        if (filtered != null) {
+          filtered.add(annotation);
+        }
+      } else {
+        if (filtered == null) {
+          filtered = new ArrayList<>(annotations.length);
+          for (int j = 0; j < i; j++) {
+            filtered.add(annotations[j]);
+          }
+        }
+      }
+    }
+    if (filtered == null) {
+      return this;
+    } else if (filtered.isEmpty()) {
+      return DexAnnotationSet.empty();
+    } else {
+      return new DexAnnotationSet(filtered.toArray(new DexAnnotation[filtered.size()]));
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationSetRefList.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationSetRefList.java
deleted file mode 100644
index e6b4a2c..0000000
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSetRefList.java
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graph;
-
-import com.android.tools.r8.dex.IndexedItemCollection;
-import com.android.tools.r8.dex.MixedSectionCollection;
-import java.util.Arrays;
-
-public class DexAnnotationSetRefList extends DexItem {
-
-  private static final DexAnnotationSetRefList theEmptyTypeList = new DexAnnotationSetRefList();
-
-  public final DexAnnotationSet[] values;
-  private final int missingParameterAnnotations;
-
-  public static DexAnnotationSetRefList empty() {
-    return theEmptyTypeList;
-  }
-
-  private DexAnnotationSetRefList() {
-    this.values = new DexAnnotationSet[0];
-    this.missingParameterAnnotations = 0;
-  }
-
-  public DexAnnotationSetRefList(DexAnnotationSet[] values) {
-    this(values, 0);
-  }
-
-  public DexAnnotationSetRefList(DexAnnotationSet[] values, int missingParameterAnnotations) {
-    assert values != null && values.length > 0;
-    this.values = values;
-    this.missingParameterAnnotations = missingParameterAnnotations;
-  }
-
-  @Override
-  public int hashCode() {
-    return Arrays.hashCode(values);
-  }
-
-  @Override
-  public boolean equals(Object other) {
-    if (this == other) {
-      return true;
-    }
-    if (other instanceof DexAnnotationSetRefList) {
-      return Arrays.equals(values, ((DexAnnotationSetRefList) other).values);
-    }
-    return false;
-  }
-
-  @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    collectAll(indexedItems, values);
-  }
-
-  @Override
-  void collectMixedSectionItems(MixedSectionCollection mixedItems) {
-    // Collect values first so that the annotation sets have sorted themselves before adding this.
-    collectAll(mixedItems, values);
-    mixedItems.add(this);
-  }
-
-  public boolean isEmpty() {
-    return values.length == 0;
-  }
-
-  public int getMissingParameterAnnotations() {
-    return missingParameterAnnotations;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index e908b64..ead547f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -6,7 +6,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.ProgramResourceProvider;
 import com.android.tools.r8.dex.ApplicationReader.ProgramClassConflictResolver;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -27,7 +27,7 @@
   // Maps type into class, may be used concurrently.
   final ProgramClassCollection programClasses;
 
-  public final ImmutableList<DataResourceProvider> dataResourceProviders;
+  public final ImmutableList<ProgramResourceProvider> programResourceProviders;
 
   public final ImmutableSet<DexType> mainDexList;
   public final String deadCode;
@@ -47,7 +47,7 @@
   DexApplication(
       ClassNameMapper proguardMap,
       ProgramClassCollection programClasses,
-      ImmutableList<DataResourceProvider> dataResourceProviders,
+      ImmutableList<ProgramResourceProvider> programResourceProviders,
       ImmutableSet<DexType> mainDexList,
       String deadCode,
       DexItemFactory dexItemFactory,
@@ -56,7 +56,7 @@
     assert programClasses != null;
     this.proguardMap = proguardMap;
     this.programClasses = programClasses;
-    this.dataResourceProviders = dataResourceProviders;
+    this.programResourceProviders = programResourceProviders;
     this.mainDexList = mainDexList;
     this.deadCode = deadCode;
     this.dexItemFactory = dexItemFactory;
@@ -118,7 +118,7 @@
 
     final List<DexProgramClass> programClasses;
 
-    final List<DataResourceProvider> dataResourceProviders = new ArrayList<>();
+    final List<ProgramResourceProvider> programResourceProviders = new ArrayList<>();
 
     public final DexItemFactory dexItemFactory;
     ClassNameMapper proguardMap;
@@ -141,7 +141,7 @@
 
     public Builder(DexApplication application) {
       programClasses = application.programClasses.getAllClasses();
-      replaceDataResourceProviders(application.dataResourceProviders);
+      addProgramResourceProviders(application.programResourceProviders);
       proguardMap = application.getProguardMap();
       timing = application.timing;
       highestSortingString = application.highestSortingString;
@@ -164,11 +164,10 @@
       return self();
     }
 
-    public synchronized T replaceDataResourceProviders(
-        List<DataResourceProvider> dataResourceProviders) {
-      this.dataResourceProviders.clear();
-      if (dataResourceProviders != null) {
-        this.dataResourceProviders.addAll(dataResourceProviders);
+    public synchronized T addProgramResourceProviders(
+        List<ProgramResourceProvider> programResourceProviders) {
+      if (programResourceProviders != null) {
+        this.programResourceProviders.addAll(programResourceProviders);
       }
       return self();
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java b/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
index 025a502..21900a01 100644
--- a/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
@@ -82,8 +82,12 @@
 
   private void writeClass(DexProgramClass clazz, PrintStream ps) {
     writeClassHeader(clazz, ps);
+    writeFieldsHeader(clazz, ps);
     clazz.forEachField(field -> writeField(field, ps));
+    writeFieldsFooter(clazz, ps);
+    writeMethodsHeader(clazz, ps);
     clazz.forEachMethod(method -> writeMethod(method, ps));
+    writeMethodsFooter(clazz, ps);
     writeClassFooter(clazz, ps);
   }
 
@@ -91,9 +95,25 @@
 
   abstract void writeClassHeader(DexProgramClass clazz, PrintStream ps);
 
+  void writeFieldsHeader(DexProgramClass clazz, PrintStream ps) {
+    // Do nothing.
+  }
+
   abstract void writeField(DexEncodedField field, PrintStream ps);
 
+  void writeFieldsFooter(DexProgramClass clazz, PrintStream ps) {
+    // Do nothing.
+  }
+
+  void writeMethodsHeader(DexProgramClass clazz, PrintStream ps) {
+    // Do nothing.
+  }
+
   abstract void writeMethod(DexEncodedMethod method, PrintStream ps);
 
+  void writeMethodsFooter(DexProgramClass clazz, PrintStream ps) {
+    // Do nothing.
+  }
+
   abstract void writeClassFooter(DexProgramClass clazz, PrintStream ps);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexCallSite.java b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
index c193134..e690317 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCallSite.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
@@ -21,7 +21,7 @@
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.tree.InvokeDynamicInsnNode;
 
-public final class DexCallSite extends IndexedDexItem {
+public final class DexCallSite extends IndexedDexItem implements Comparable<DexCallSite> {
 
   public final DexString methodName;
   public final DexProto methodProto;
@@ -31,6 +31,11 @@
 
   private DexEncodedArray encodedArray = null;
 
+  // Only used for sorting for deterministic output. This is the method and the instruction
+  // offset where this DexCallSite ends up in the output.
+  private DexMethod method;
+  private int instructionOffset;
+
   DexCallSite(
       DexString methodName,
       DexProto methodProto,
@@ -110,13 +115,18 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
+    assert method != null;
     if (indexedItems.addCallSite(this)) {
-      methodName.collectIndexedItems(indexedItems);
-      methodProto.collectIndexedItems(indexedItems);
-      bootstrapMethod.collectIndexedItems(indexedItems);
+      assert this.method == null;
+      this.method = method;
+      this.instructionOffset = instructionOffset;
+      methodName.collectIndexedItems(indexedItems, method, instructionOffset);
+      methodProto.collectIndexedItems(indexedItems, method, instructionOffset);
+      bootstrapMethod.collectIndexedItems(indexedItems, method, instructionOffset);
       for (DexValue arg : bootstrapArgs) {
-        arg.collectIndexedItems(indexedItems);
+        arg.collectIndexedItems(indexedItems, method, instructionOffset);
       }
     }
   }
@@ -141,6 +151,17 @@
     return new HashBuilder().build();
   }
 
+  @Override
+  public int compareTo(DexCallSite other) {
+    assert method != null && other.method != null;
+    int methodCompare = method.slowCompareTo(other.method);
+    if (methodCompare != 0) {
+      return methodCompare;
+    }
+    assert (instructionOffset - other.instructionOffset) != 0;
+    return instructionOffset - other.instructionOffset;
+  }
+
   private final class HashBuilder {
     private ByteArrayOutputStream bytes;
     private ObjectOutputStream out;
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 7b09cab..23139c5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -159,21 +159,13 @@
       for (DexAnnotation annotation : method.annotations.annotations) {
         consumer.accept(annotation);
       }
-      for (DexAnnotationSet parameterAnnotations : method.parameterAnnotations.values) {
-        for (DexAnnotation annotation : parameterAnnotations.annotations) {
-          consumer.accept(annotation);
-        }
-      }
+      method.parameterAnnotationsList.forEachAnnotation(consumer);
     }
     for (DexEncodedMethod method : virtualMethods()) {
       for (DexAnnotation annotation : method.annotations.annotations) {
         consumer.accept(annotation);
       }
-      for (DexAnnotationSet parameterAnnotations : method.parameterAnnotations.values) {
-        for (DexAnnotation annotation : parameterAnnotations.annotations) {
-          consumer.accept(annotation);
-        }
-      }
+      method.parameterAnnotationsList.forEachAnnotation(consumer);
     }
     for (DexEncodedField field : instanceFields()) {
       for (DexAnnotation annotation : field.annotations.annotations) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
index 4308dd4..27bb976 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
@@ -50,7 +50,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     throw new Unreachable();
   }
 
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 50117c7..b1b5f8a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.ReturnVoid;
 import com.android.tools.r8.code.SwitchPayload;
@@ -17,6 +16,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,29 +163,31 @@
   }
 
   @Override
-  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
-      throws ApiLevelException {
+  public IRCode buildIR(
+      DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
     DexSourceCode source =
         new DexSourceCode(
             this, encodedMethod, null, options.lineNumberOptimization == LineNumberOptimization.ON);
-    IRBuilder builder = new IRBuilder(encodedMethod, source, options);
+    IRBuilder builder = new IRBuilder(encodedMethod, appInfo, source, options);
     return builder.build();
   }
 
   @Override
   public IRCode buildInliningIR(
       DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
       InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
-      Position callerPosition)
-      throws ApiLevelException {
+      Position callerPosition,
+      Origin origin) {
     DexSourceCode source =
         new DexSourceCode(
             this,
             encodedMethod,
             callerPosition,
             options.lineNumberOptimization == LineNumberOptimization.ON);
-    IRBuilder builder = new IRBuilder(encodedMethod, source, options, valueNumberGenerator);
+    IRBuilder builder =
+        new IRBuilder(encodedMethod, appInfo, source, options, valueNumberGenerator);
     return builder.build();
   }
 
@@ -336,9 +338,11 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
+    assert instructionOffset == -1;
     for (Instruction insn : instructions) {
-      insn.collectIndexedItems(indexedItems);
+      insn.collectIndexedItems(indexedItems, method, insn.getOffset());
     }
     if (debugInfo != null) {
       debugInfo.collectIndexedItems(indexedItems);
@@ -417,7 +421,8 @@
     }
 
     @Override
-    void collectIndexedItems(IndexedItemCollection indexedItems) {
+    void collectIndexedItems(IndexedItemCollection indexedItems,
+        DexMethod method, int instructionOffset) {
       // Intentionally left empty.
     }
 
@@ -462,7 +467,8 @@
     }
 
     @Override
-    public void collectIndexedItems(IndexedItemCollection indexedItems) {
+    public void collectIndexedItems(IndexedItemCollection indexedItems,
+        DexMethod method, int instructionOffset) {
       collectAll(indexedItems, pairs);
     }
 
@@ -503,8 +509,9 @@
       }
 
       @Override
-      public void collectIndexedItems(IndexedItemCollection indexedItems) {
-        type.collectIndexedItems(indexedItems);
+      public void collectIndexedItems(IndexedItemCollection indexedItems,
+          DexMethod method, int instructionOffset) {
+        type.collectIndexedItems(indexedItems, method, instructionOffset);
       }
 
       @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
index 35108bd..1264a26 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -13,7 +13,8 @@
 abstract public class DexDebugEvent extends DexItem {
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection collection) {
+  public void collectIndexedItems(IndexedItemCollection collection,
+      DexMethod method, int instructionOffset) {
     // Empty by default.
   }
 
@@ -212,15 +213,16 @@
     }
 
     @Override
-    public void collectIndexedItems(IndexedItemCollection collection) {
+    public void collectIndexedItems(IndexedItemCollection collection,
+        DexMethod method, int instructionOffset) {
       if (name != null) {
-        name.collectIndexedItems(collection);
+        name.collectIndexedItems(collection, method, instructionOffset);
       }
       if (type != null) {
-        type.collectIndexedItems(collection);
+        type.collectIndexedItems(collection, method, instructionOffset);
       }
       if (signature != null) {
-        signature.collectIndexedItems(collection);
+        signature.collectIndexedItems(collection, method, instructionOffset);
       }
     }
 
@@ -351,8 +353,9 @@
     }
 
     @Override
-    public void collectIndexedItems(IndexedItemCollection collection) {
-      fileName.collectIndexedItems(collection);
+    public void collectIndexedItems(IndexedItemCollection collection,
+        DexMethod method, int instructionOffset) {
+      fileName.collectIndexedItems(collection, method, instructionOffset);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
index 4b8c4b8..9c27243 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
@@ -55,7 +55,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection collection) {
+  public void collectIndexedItems(IndexedItemCollection collection,
+      DexMethod method, int instructionOffset) {
     collectAll(collection, parameters);
     collectAll(collection, events);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
index a8f41d6..c47e72b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
@@ -22,8 +22,9 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    type.collectIndexedItems(indexedItems);
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
+    type.collectIndexedItems(indexedItems, method, instructionOffset);
     collectAll(indexedItems, elements);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedArray.java b/src/main/java/com/android/tools/r8/graph/DexEncodedArray.java
index 8d7eba9..fac96c9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedArray.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedArray.java
@@ -16,7 +16,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     collectAll(indexedItems, values);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index a28dc82..7083b9f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -29,11 +29,12 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    field.collectIndexedItems(indexedItems);
-    annotations.collectIndexedItems(indexedItems);
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
+    field.collectIndexedItems(indexedItems, method, instructionOffset);
+    annotations.collectIndexedItems(indexedItems, method, instructionOffset);
     if (accessFlags.isStatic()) {
-      getStaticValue().collectIndexedItems(indexedItems);
+      getStaticValue().collectIndexedItems(indexedItems, method, instructionOffset);
     }
   }
 
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 d437180..0914e7b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -9,7 +9,6 @@
 import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_SUBCLASS;
 import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_NOT_INLINING_CANDIDATE;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfThrow;
@@ -42,10 +41,14 @@
 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 com.google.common.collect.ImmutableMap;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.function.Consumer;
 
 public class DexEncodedMethod extends KeyedDexItem<DexMethod> implements ResolutionResult {
@@ -92,7 +95,7 @@
   public final DexMethod method;
   public final MethodAccessFlags accessFlags;
   public DexAnnotationSet annotations;
-  public DexAnnotationSetRefList parameterAnnotations;
+  public ParameterAnnotationsList parameterAnnotationsList;
   private Code code;
   private CompilationState compilationState = CompilationState.NOT_PROCESSED;
   private OptimizationInfo optimizationInfo = DefaultOptimizationInfo.DEFAULT;
@@ -101,12 +104,12 @@
       DexMethod method,
       MethodAccessFlags accessFlags,
       DexAnnotationSet annotations,
-      DexAnnotationSetRefList parameterAnnotations,
+      ParameterAnnotationsList parameterAnnotationsList,
       Code code) {
     this.method = method;
     this.accessFlags = accessFlags;
     this.annotations = annotations;
-    this.parameterAnnotations = parameterAnnotations;
+    this.parameterAnnotationsList = parameterAnnotationsList;
     this.code = code;
     assert code == null || !accessFlags.isAbstract();
   }
@@ -227,20 +230,23 @@
     compilationState = CompilationState.NOT_PROCESSED;
   }
 
-  public IRCode buildIR(InternalOptions options) throws ApiLevelException {
-    return code == null ? null : code.buildIR(this, options);
+  public IRCode buildIR(AppInfo appInfo, InternalOptions options, Origin origin) {
+    return code == null ? null : code.buildIR(this, appInfo, options, origin);
   }
 
   public IRCode buildInliningIRForTesting(
-      InternalOptions options, ValueNumberGenerator valueNumberGenerator)
-      throws ApiLevelException {
-    return buildInliningIR(options, valueNumberGenerator, null);
+      InternalOptions options, ValueNumberGenerator valueNumberGenerator) {
+    return buildInliningIR(null, options, valueNumberGenerator, null, Origin.unknown());
   }
 
   public IRCode buildInliningIR(
-      InternalOptions options, ValueNumberGenerator valueNumberGenerator, Position callerPosition)
-      throws ApiLevelException {
-    return code.buildInliningIR(this, options, valueNumberGenerator, callerPosition);
+      AppInfo appInfo,
+      InternalOptions options,
+      ValueNumberGenerator valueNumberGenerator,
+      Position callerPosition,
+      Origin origin) {
+    return code.buildInliningIR(
+        this, appInfo, options, valueNumberGenerator, callerPosition, origin);
   }
 
   public void setCode(Code code) {
@@ -261,13 +267,14 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    method.collectIndexedItems(indexedItems);
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
+    this.method.collectIndexedItems(indexedItems);
     if (code != null) {
-      code.collectIndexedItems(indexedItems);
+      code.collectIndexedItems(indexedItems, this.method);
     }
     annotations.collectIndexedItems(indexedItems);
-    parameterAnnotations.collectIndexedItems(indexedItems);
+    parameterAnnotationsList.collectIndexedItems(indexedItems);
   }
 
   @Override
@@ -276,7 +283,7 @@
       code.collectMixedSectionItems(mixedItems);
     }
     annotations.collectMixedSectionItems(mixedItems);
-    parameterAnnotations.collectMixedSectionItems(mixedItems);
+    parameterAnnotationsList.collectMixedSectionItems(mixedItems);
   }
 
   public Code getCode() {
@@ -532,7 +539,7 @@
   }
 
   public boolean hasAnnotation() {
-    return !annotations.isEmpty() || !parameterAnnotations.isEmpty();
+    return !annotations.isEmpty() || !parameterAnnotationsList.isEmpty();
   }
 
   public void registerCodeReferences(UseRegistry registry) {
@@ -559,6 +566,8 @@
     private boolean useIdentifierNameString = false;
     private boolean checksNullReceiverBeforeAnySideEffect = false;
     private boolean triggersClassInitBeforeAnySideEffect = false;
+    private Set<DexField> receiverOnlyUsedForReadingFields = null;
+    private Map<DexField, Integer> onlyInitializesFieldsWithNoOtherSideEffects = null;
 
     private OptimizationInfo() {
       // Intentionally left empty.
@@ -595,6 +604,15 @@
       return returnsConstant;
     }
 
+    public boolean isReceiverOnlyUsedForReadingFields(Set<DexField> fields) {
+      return receiverOnlyUsedForReadingFields != null &&
+          fields.containsAll(receiverOnlyUsedForReadingFields);
+    }
+
+    public Map<DexField, Integer> onlyInitializesFieldsWithNoOtherSideEffects() {
+      return onlyInitializesFieldsWithNoOtherSideEffects;
+    }
+
     public long getReturnedConstant() {
       assert returnsConstant();
       return returnedConstant;
@@ -636,6 +654,19 @@
       returnedConstant = value;
     }
 
+    private void markReceiverOnlyUsedForReadingFields(Set<DexField> fields) {
+      receiverOnlyUsedForReadingFields = fields;
+    }
+
+    private void markOnlyInitializesFieldsWithNoOtherSideEffects(Map<DexField, Integer> mapping) {
+      if (mapping == null) {
+        onlyInitializesFieldsWithNoOtherSideEffects = null;
+      } else {
+        onlyInitializesFieldsWithNoOtherSideEffects = mapping.isEmpty()
+            ? Collections.emptyMap() : ImmutableMap.copyOf(mapping);
+      }
+    }
+
     private void markForceInline() {
       forceInline = true;
     }
@@ -693,6 +724,15 @@
     ensureMutableOI().markReturnsConstant(value);
   }
 
+  synchronized public void markReceiverOnlyUsedForReadingFields(Set<DexField> fields) {
+    ensureMutableOI().markReceiverOnlyUsedForReadingFields(fields);
+  }
+
+  synchronized public void markOnlyInitializesFieldsWithNoOtherSideEffects(
+      Map<DexField, Integer> mapping) {
+    ensureMutableOI().markOnlyInitializesFieldsWithNoOtherSideEffects(mapping);
+  }
+
   synchronized public void markForceInline() {
     ensureMutableOI().markForceInline();
   }
@@ -722,7 +762,7 @@
     private DexMethod method;
     private final MethodAccessFlags accessFlags;
     private final DexAnnotationSet annotations;
-    private final DexAnnotationSetRefList parameterAnnotations;
+    private final ParameterAnnotationsList parameterAnnotations;
     private Code code;
     private CompilationState compilationState = CompilationState.NOT_PROCESSED;
     private OptimizationInfo optimizationInfo = DefaultOptimizationInfo.DEFAULT;
@@ -732,7 +772,7 @@
       method = from.method;
       accessFlags = from.accessFlags.copy();
       annotations = from.annotations;
-      parameterAnnotations = from.parameterAnnotations;
+      parameterAnnotations = from.parameterAnnotationsList;
       code = from.code;
       compilationState = from.compilationState;
       optimizationInfo = from.optimizationInfo.copy();
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index ab0d654..98c523d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -48,11 +48,13 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     if (indexedItems.addField(this)) {
-      clazz.collectIndexedItems(indexedItems);
-      type.collectIndexedItems(indexedItems);
-      indexedItems.getRenamedName(this).collectIndexedItems(indexedItems);
+      clazz.collectIndexedItems(indexedItems, method, instructionOffset);
+      type.collectIndexedItems(indexedItems, method, instructionOffset);
+      indexedItems.getRenamedName(this).collectIndexedItems(
+          indexedItems, method, instructionOffset);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexItem.java b/src/main/java/com/android/tools/r8/graph/DexItem.java
index 88665e9..3dc5558 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItem.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItem.java
@@ -38,7 +38,16 @@
     }
   }
 
-  abstract void collectIndexedItems(IndexedItemCollection collection);
+  abstract void collectIndexedItems(IndexedItemCollection collection,
+      DexMethod method, int instructionOffset);
+
+  public void collectIndexedItems(IndexedItemCollection collection) {
+    collectIndexedItems(collection, null, -1);
+  }
+
+  public void collectIndexedItems(IndexedItemCollection collection, DexMethod method) {
+    collectIndexedItems(collection, method, -1);
+  }
 
   abstract void collectMixedSectionItems(MixedSectionCollection collection);
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemBasedString.java b/src/main/java/com/android/tools/r8/graph/DexItemBasedString.java
index 14fc568..36f0907 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemBasedString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemBasedString.java
@@ -40,7 +40,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     // This instance should not exist when collecting indexed items.
     // {@link com.android.tools.r8.naming.IdentifierMinifier} will replace this with an appropriate
     // {@link DexString}.
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 6b99724..86ca12a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -40,9 +40,9 @@
   private final ConcurrentHashMap<DexField, DexField> fields = new ConcurrentHashMap<>();
   private final ConcurrentHashMap<DexProto, DexProto> protos = new ConcurrentHashMap<>();
   private final ConcurrentHashMap<DexMethod, DexMethod> methods = new ConcurrentHashMap<>();
-  private final ConcurrentHashMap<DexCallSite, DexCallSite> callSites = new ConcurrentHashMap<>();
   private final ConcurrentHashMap<DexMethodHandle, DexMethodHandle> methodHandles =
       new ConcurrentHashMap<>();
+  private final List<DexCallSite> callSites = new ArrayList<>();
 
   // DexDebugEvent Canonicalization.
   private final Int2ObjectMap<AdvanceLine> advanceLines = new Int2ObjectOpenHashMap<>();
@@ -115,6 +115,7 @@
   public final DexString valueOfMethodName = createString("valueOf");
 
   public final DexString getClassMethodName = createString("getClass");
+  public final DexString finalizeMethodName = createString("finalize");
   public final DexString ordinalMethodName = createString("ordinal");
   public final DexString desiredAssertionStatusMethodName = createString("desiredAssertionStatus");
   public final DexString forNameMethodName = createString("forName");
@@ -125,6 +126,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 +212,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
@@ -234,6 +238,7 @@
   public final DexType callSiteType = createType("Ljava/lang/invoke/CallSite;");
   public final DexType lookupType = createType("Ljava/lang/invoke/MethodHandles$Lookup;");
   public final DexType serializableType = createType("Ljava/io/Serializable;");
+  public final DexType comparableType = createType("Ljava/lang/Comparable;");
 
   public final DexMethod metafactoryMethod =
       createMethod(
@@ -322,12 +327,15 @@
 
     public final DexMethod getClass;
     public final DexMethod constructor;
+    public final DexMethod finalize;
 
     private ObjectMethods() {
       getClass = createMethod(objectDescriptor, getClassMethodName, classDescriptor,
           DexString.EMPTY_ARRAY);
       constructor = createMethod(objectDescriptor,
           constructorMethodName, voidType.descriptor, DexString.EMPTY_ARRAY);
+      finalize = createMethod(objectDescriptor,
+          finalizeMethodName, voidType.descriptor, DexString.EMPTY_ARRAY);
     }
   }
 
@@ -471,6 +479,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);
@@ -510,7 +587,7 @@
   public synchronized Marker extractMarker() {
     // This is slow but it is not needed for any production code yet.
     for (DexString dexString : strings.keySet()) {
-      Marker result = Marker.parse(dexString.toString());
+      Marker result = Marker.parse(dexString);
       if (result != null) {
         return result;
       }
@@ -524,7 +601,7 @@
     // This is slow but it is not needed for any production code yet.
     List<Marker> markers = new ArrayList<>();
     for (DexString dexString : strings.keySet()) {
-      Marker marker = Marker.parse(dexString.toString());
+      Marker marker = Marker.parse(dexString);
       if (marker != null) {
         markers.add(marker);
       }
@@ -611,9 +688,13 @@
   public DexCallSite createCallSite(
       DexString methodName, DexProto methodProto,
       DexMethodHandle bootstrapMethod, List<DexValue> bootstrapArgs) {
+    // Call sites are never equal and therefore we do not canonicalize.
     assert !sorted;
     DexCallSite callSite = new DexCallSite(methodName, methodProto, bootstrapMethod, bootstrapArgs);
-    return canonicalize(callSites, callSite);
+    synchronized (callSites) {
+      callSites.add(callSite);
+    }
+    return callSite;
   }
 
   public DexMethod createMethod(DexString clazzDescriptor, DexString name,
@@ -727,7 +808,7 @@
     new ArrayList<>(types.values()).forEach(f);
   }
 
-  public void forAllCallSites(Consumer<DexCallSite> f) {
-    new ArrayList<>(callSites.values()).forEach(f);
+  synchronized public void forAllCallSites(Consumer<DexCallSite> f) {
+    new ArrayList<>(callSites).forEach(f);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
index 0fac5e8..5e25fa9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
@@ -55,7 +55,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     throw new Unreachable();
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java
index 6f959c7..6cc2fb4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java
@@ -17,9 +17,10 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    item.collectIndexedItems(indexedItems);
-    annotations.collectIndexedItems(indexedItems);
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
+    item.collectIndexedItems(indexedItems, method, instructionOffset);
+    annotations.collectIndexedItems(indexedItems, method, instructionOffset);
   }
 
   @Override
@@ -59,9 +60,9 @@
   }
 
   public static class DexParameterAnnotation extends
-      DexMemberAnnotation<DexMethod, DexAnnotationSetRefList> {
+      DexMemberAnnotation<DexMethod, ParameterAnnotationsList> {
 
-    public DexParameterAnnotation(DexMethod item, DexAnnotationSetRefList annotations) {
+    public DexParameterAnnotation(DexMethod item, ParameterAnnotationsList annotations) {
       super(item, annotations);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index 9c3e9a5..f1acdac 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -40,11 +40,13 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     if (indexedItems.addMethod(this)) {
-      holder.collectIndexedItems(indexedItems);
-      proto.collectIndexedItems(indexedItems);
-      indexedItems.getRenamedName(this).collectIndexedItems(indexedItems);
+      holder.collectIndexedItems(indexedItems, method, instructionOffset);
+      proto.collectIndexedItems(indexedItems, method, instructionOffset);
+      indexedItems.getRenamedName(this).collectIndexedItems(
+          indexedItems, method, instructionOffset);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
index a9e3b8e..06ea28d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
@@ -206,9 +206,10 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     if (indexedItems.addMethodHandle(this)) {
-      fieldOrMethod.collectIndexedItems(indexedItems);
+      fieldOrMethod.collectIndexedItems(indexedItems, method, instructionOffset);
     }
   }
 
@@ -289,7 +290,7 @@
 
   @Override
   public int compareTo(DexMethodHandle other) {
-    return sortedCompareTo(other.getSortedIndex());
+    return slowCompareTo(other);
   }
 
   public Handle toAsmHandle(NamingLens lens) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 1473191..c1b843f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -111,22 +111,23 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     if (indexedItems.addClass(this)) {
-      type.collectIndexedItems(indexedItems);
+      type.collectIndexedItems(indexedItems, method, instructionOffset);
       if (superType != null) {
-        superType.collectIndexedItems(indexedItems);
+        superType.collectIndexedItems(indexedItems, method, instructionOffset);
       } else {
         assert type.toDescriptorString().equals("Ljava/lang/Object;");
       }
       if (sourceFile != null) {
-        sourceFile.collectIndexedItems(indexedItems);
+        sourceFile.collectIndexedItems(indexedItems, method, instructionOffset);
       }
       if (annotations != null) {
-        annotations.collectIndexedItems(indexedItems);
+        annotations.collectIndexedItems(indexedItems, method, instructionOffset);
       }
       if (interfaces != null) {
-        interfaces.collectIndexedItems(indexedItems);
+        interfaces.collectIndexedItems(indexedItems, method, instructionOffset);
       }
       if (getEnclosingMethod() != null) {
         getEnclosingMethod().collectIndexedItems(indexedItems);
diff --git a/src/main/java/com/android/tools/r8/graph/DexProto.java b/src/main/java/com/android/tools/r8/graph/DexProto.java
index 7acab73..17f6efa 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProto.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProto.java
@@ -42,11 +42,12 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     if (indexedItems.addProto(this)) {
-      shorty.collectIndexedItems(indexedItems);
-      returnType.collectIndexedItems(indexedItems);
-      parameters.collectIndexedItems(indexedItems);
+      shorty.collectIndexedItems(indexedItems, method, instructionOffset);
+      returnType.collectIndexedItems(indexedItems, method, instructionOffset);
+      parameters.collectIndexedItems(indexedItems, method, instructionOffset);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index 3127422..12587f4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -139,7 +139,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     indexedItems.addString(this);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 3d0a177..d178e4a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -261,9 +261,11 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection collection) {
+  public void collectIndexedItems(IndexedItemCollection collection,
+      DexMethod method, int instructionOffset) {
     if (collection.addType(this)) {
-      collection.getRenamedDescriptor(this).collectIndexedItems(collection);
+      collection.getRenamedDescriptor(this).collectIndexedItems(collection, method,
+          instructionOffset);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeList.java b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
index 5de71a1..8df5030 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeList.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
@@ -34,9 +34,10 @@
   }
 
   @Override
-  void collectIndexedItems(IndexedItemCollection indexedItems) {
+  void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset) {
     for (DexType type : values) {
-      type.collectIndexedItems(indexedItems);
+      type.collectIndexedItems(indexedItems, method, instructionOffset);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index 342ea2a..ed99d25 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -153,7 +153,8 @@
     }
 
     @Override
-    public void collectIndexedItems(IndexedItemCollection indexedItems) {
+    public void collectIndexedItems(IndexedItemCollection indexedItems,
+        DexMethod method, int instructionOffset) {
       throw new Unreachable();
     }
 
@@ -206,7 +207,8 @@
   static private abstract class SimpleDexValue extends DexValue {
 
     @Override
-    public void collectIndexedItems(IndexedItemCollection indexedItems) {
+    public void collectIndexedItems(IndexedItemCollection indexedItems,
+        DexMethod method, int instructionOffset) {
       // Intentionally left empty
     }
 
@@ -662,8 +664,9 @@
     }
 
     @Override
-    public void collectIndexedItems(IndexedItemCollection indexedItems) {
-      value.collectIndexedItems(indexedItems);
+    public void collectIndexedItems(IndexedItemCollection indexedItems,
+        DexMethod method, int instructionOffset) {
+      value.collectIndexedItems(indexedItems, method, instructionOffset);
     }
 
     @Override
@@ -727,8 +730,9 @@
     }
 
     @Override
-    public void collectIndexedItems(IndexedItemCollection indexedItems) {
-      value.collectIndexedItems(indexedItems);
+    public void collectIndexedItems(IndexedItemCollection indexedItems,
+        DexMethod method, int instructionOffset) {
+      value.collectIndexedItems(indexedItems, method, instructionOffset);
     }
   }
 
@@ -744,8 +748,9 @@
     }
 
     @Override
-    public void collectIndexedItems(IndexedItemCollection indexedItems) {
-      value.collectIndexedItems(indexedItems);
+    public void collectIndexedItems(IndexedItemCollection indexedItems,
+        DexMethod method, int instructionOffset) {
+      value.collectIndexedItems(indexedItems, method, instructionOffset);
     }
   }
 
@@ -761,8 +766,9 @@
     }
 
     @Override
-    public void collectIndexedItems(IndexedItemCollection indexedItems) {
-      value.collectIndexedItems(indexedItems);
+    public void collectIndexedItems(IndexedItemCollection indexedItems,
+        DexMethod method, int instructionOffset) {
+      value.collectIndexedItems(indexedItems, method, instructionOffset);
     }
   }
 
@@ -778,8 +784,9 @@
     }
 
     @Override
-    public void collectIndexedItems(IndexedItemCollection indexedItems) {
-      value.collectIndexedItems(indexedItems);
+    public void collectIndexedItems(IndexedItemCollection indexedItems,
+        DexMethod method, int instructionOffset) {
+      value.collectIndexedItems(indexedItems, method, instructionOffset);
     }
   }
 
@@ -795,8 +802,9 @@
     }
 
     @Override
-    public void collectIndexedItems(IndexedItemCollection indexedItems) {
-      value.collectIndexedItems(indexedItems);
+    public void collectIndexedItems(IndexedItemCollection indexedItems,
+        DexMethod method, int instructionOffset) {
+      value.collectIndexedItems(indexedItems, method, instructionOffset);
     }
   }
 
@@ -813,7 +821,8 @@
     }
 
     @Override
-    public void collectIndexedItems(IndexedItemCollection indexedItems) {
+    public void collectIndexedItems(IndexedItemCollection indexedItems,
+        DexMethod method, int instructionOffset) {
       collectAll(indexedItems, values);
     }
 
@@ -875,8 +884,9 @@
     }
 
     @Override
-    public void collectIndexedItems(IndexedItemCollection indexedItems) {
-      value.collectIndexedItems(indexedItems);
+    public void collectIndexedItems(IndexedItemCollection indexedItems,
+        DexMethod method, int instructionOffset) {
+      value.collectIndexedItems(indexedItems, method, instructionOffset);
     }
 
     @Override
@@ -1041,8 +1051,9 @@
     }
 
     @Override
-    public void collectIndexedItems(IndexedItemCollection indexedItems) {
-      value.collectIndexedItems(indexedItems);
+    public void collectIndexedItems(IndexedItemCollection indexedItems,
+        DexMethod method, int instructionOffset) {
+      value.collectIndexedItems(indexedItems, method, instructionOffset);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index a28d8ef..8d5f0d0 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -6,7 +6,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.ProgramResourceProvider;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.ProgramClassCollection;
 import com.android.tools.r8.utils.Timing;
@@ -25,12 +25,12 @@
 
   private DirectMappedDexApplication(ClassNameMapper proguardMap,
       ProgramClassCollection programClasses,
-      ImmutableList<DataResourceProvider> dataResourceProviders,
+      ImmutableList<ProgramResourceProvider> programResourceProviders,
       ImmutableMap<DexType, DexLibraryClass> libraryClasses,
       ImmutableSet<DexType> mainDexList, String deadCode,
       DexItemFactory dexItemFactory, DexString highestSortingString,
       Timing timing) {
-    super(proguardMap, programClasses, dataResourceProviders, mainDexList, deadCode,
+    super(proguardMap, programClasses, programResourceProviders, mainDexList, deadCode,
         dexItemFactory, highestSortingString, timing);
     this.libraryClasses = libraryClasses;
   }
@@ -119,7 +119,7 @@
           proguardMap,
           ProgramClassCollection.create(
               programClasses, ProgramClassCollection::resolveClassConflictImpl),
-          ImmutableList.copyOf(dataResourceProviders),
+          ImmutableList.copyOf(programResourceProviders),
           libraryClasses.stream().collect(ImmutableMap.toImmutableMap(c -> c.type, c -> c)),
           ImmutableSet.copyOf(mainDexList),
           deadCode,
diff --git a/src/main/java/com/android/tools/r8/graph/IndexedDexItem.java b/src/main/java/com/android/tools/r8/graph/IndexedDexItem.java
index e45a7c7..0ee9b42 100644
--- a/src/main/java/com/android/tools/r8/graph/IndexedDexItem.java
+++ b/src/main/java/com/android/tools/r8/graph/IndexedDexItem.java
@@ -15,7 +15,8 @@
   private int sortedIndex = SORTED_INDEX_UNKNOWN; // assigned globally after reading.
 
   @Override
-  public abstract void collectIndexedItems(IndexedItemCollection indexedItems);
+  public abstract void collectIndexedItems(IndexedItemCollection indexedItems,
+      DexMethod method, int instructionOffset);
 
   @Override
   void collectMixedSectionItems(MixedSectionCollection mixedItems) {
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index a1125af..02f0801 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -3,57 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import static org.objectweb.asm.ClassReader.EXPAND_FRAMES;
 import static org.objectweb.asm.ClassReader.SKIP_FRAMES;
 import static org.objectweb.asm.Opcodes.ACC_DEPRECATED;
 import static org.objectweb.asm.Opcodes.ASM6;
 
 import com.android.tools.r8.ProgramResource.Kind;
-import com.android.tools.r8.cf.code.CfArithmeticBinop;
-import com.android.tools.r8.cf.code.CfArrayLength;
-import com.android.tools.r8.cf.code.CfArrayLoad;
-import com.android.tools.r8.cf.code.CfArrayStore;
-import com.android.tools.r8.cf.code.CfCheckCast;
-import com.android.tools.r8.cf.code.CfCmp;
-import com.android.tools.r8.cf.code.CfConstClass;
-import com.android.tools.r8.cf.code.CfConstMethodHandle;
-import com.android.tools.r8.cf.code.CfConstMethodType;
-import com.android.tools.r8.cf.code.CfConstNull;
-import com.android.tools.r8.cf.code.CfConstNumber;
-import com.android.tools.r8.cf.code.CfConstString;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
-import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
-import com.android.tools.r8.cf.code.CfGoto;
-import com.android.tools.r8.cf.code.CfIf;
-import com.android.tools.r8.cf.code.CfIfCmp;
-import com.android.tools.r8.cf.code.CfIinc;
-import com.android.tools.r8.cf.code.CfInstanceOf;
-import com.android.tools.r8.cf.code.CfInstruction;
-import com.android.tools.r8.cf.code.CfInvoke;
-import com.android.tools.r8.cf.code.CfInvokeDynamic;
-import com.android.tools.r8.cf.code.CfLabel;
-import com.android.tools.r8.cf.code.CfLoad;
-import com.android.tools.r8.cf.code.CfLogicalBinop;
-import com.android.tools.r8.cf.code.CfMonitor;
-import com.android.tools.r8.cf.code.CfMultiANewArray;
-import com.android.tools.r8.cf.code.CfNeg;
-import com.android.tools.r8.cf.code.CfNew;
-import com.android.tools.r8.cf.code.CfNewArray;
-import com.android.tools.r8.cf.code.CfNop;
-import com.android.tools.r8.cf.code.CfNumberConversion;
-import com.android.tools.r8.cf.code.CfPosition;
-import com.android.tools.r8.cf.code.CfReturn;
-import com.android.tools.r8.cf.code.CfReturnVoid;
-import com.android.tools.r8.cf.code.CfStackInstruction;
-import com.android.tools.r8.cf.code.CfStore;
-import com.android.tools.r8.cf.code.CfSwitch;
-import com.android.tools.r8.cf.code.CfThrow;
-import com.android.tools.r8.cf.code.CfTryCatch;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
 import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
 import com.android.tools.r8.graph.DexValue.DexValueBoolean;
@@ -68,26 +25,15 @@
 import com.android.tools.r8.graph.DexValue.DexValueShort;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.DexValue.DexValueType;
-import com.android.tools.r8.graph.JarCode.ReparseContext;
-import com.android.tools.r8.ir.code.If;
-import com.android.tools.r8.ir.code.MemberType;
-import com.android.tools.r8.ir.code.Monitor;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
-import java.util.IdentityHashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
@@ -96,10 +42,8 @@
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.FieldVisitor;
-import org.objectweb.asm.Handle;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
 import org.objectweb.asm.TypePath;
 
@@ -144,12 +88,9 @@
     input.reset();
 
     ClassReader reader = new ClassReader(input);
-    int flags = SKIP_FRAMES;
-    if (application.options.enableCfFrontend) {
-      flags = EXPAND_FRAMES;
-    }
     reader.accept(
-        new CreateDexClassVisitor(origin, classKind, reader.b, application, classConsumer), flags);
+        new CreateDexClassVisitor(origin, classKind, reader.b, application, classConsumer),
+        SKIP_FRAMES);
   }
 
   private static int cleanAccessFlags(int access) {
@@ -316,9 +257,6 @@
     @Override
     public MethodVisitor visitMethod(
         int access, String name, String desc, String signature, String[] exceptions) {
-      if (application.options.enableCfFrontend) {
-        return new CfCreateMethodVisitor(access, name, desc, signature, exceptions, this);
-      }
       return new CreateMethodVisitor(access, name, desc, signature, exceptions, this);
     }
 
@@ -502,15 +440,13 @@
 
   private static class CreateMethodVisitor extends MethodVisitor {
 
-    private final int access;
     private final String name;
-    private final String desc;
     final CreateDexClassVisitor parent;
     private final int parameterCount;
     private List<DexAnnotation> annotations = null;
     private DexValue defaultAnnotation = null;
     private int fakeParameterAnnotations = 0;
-    private List<List<DexAnnotation>> parameterAnnotations = null;
+    private List<List<DexAnnotation>> parameterAnnotationsLists = null;
     private List<DexValue> parameterNames = null;
     private List<DexValue> parameterFlags = null;
     final DexMethod method;
@@ -520,9 +456,7 @@
     public CreateMethodVisitor(int access, String name, String desc, String signature,
         String[] exceptions, CreateDexClassVisitor parent) {
       super(ASM6);
-      this.access = access;
       this.name = name;
-      this.desc = desc;
       this.parent = parent;
       this.method = parent.application.getMethod(parent.type, name, desc);
       this.flags = createMethodAccessFlags(name, access);
@@ -574,21 +508,21 @@
         // We can iterate through all the parameters twice. Once for visible and once for
         // invisible parameter annotations. We only record the number of fake parameter
         // annotations once.
-        if (parameterAnnotations == null) {
+        if (parameterAnnotationsLists == null) {
           fakeParameterAnnotations++;
         }
         return null;
       }
-      if (parameterAnnotations == null) {
+      if (parameterAnnotationsLists == null) {
         int adjustedParameterCount = parameterCount - fakeParameterAnnotations;
-        parameterAnnotations = new ArrayList<>(adjustedParameterCount);
+        parameterAnnotationsLists = new ArrayList<>(adjustedParameterCount);
         for (int i = 0; i < adjustedParameterCount; i++) {
-          parameterAnnotations.add(new ArrayList<>());
+          parameterAnnotationsLists.add(new ArrayList<>());
         }
       }
       assert mv == null;
       return createAnnotationVisitor(desc, visible,
-          parameterAnnotations.get(parameter - fakeParameterAnnotations), parent.application);
+          parameterAnnotationsLists.get(parameter - fakeParameterAnnotations), parent.application);
     }
 
     @Override
@@ -626,9 +560,12 @@
 
     @Override
     public void visitCode() {
-      assert !flags.isAbstract() && !flags.isNative();
-      if (parent.classKind == ClassKind.PROGRAM) {
-        code = new JarCode(method, parent.origin, parent.context, parent.application);
+      if (!flags.isAbstract() && !flags.isNative() && parent.classKind == ClassKind.PROGRAM) {
+        if (parent.application.options.enableCfFrontend) {
+          code = new LazyCfCode(method, parent.origin, parent.context, parent.application);
+        } else {
+          code = new JarCode(method, parent.origin, parent.context, parent.application);
+        }
       }
     }
 
@@ -636,15 +573,15 @@
     public void visitEnd() {
       assert flags.isAbstract() || flags.isNative() || parent.classKind != ClassKind.PROGRAM
           || code != null;
-      DexAnnotationSetRefList parameterAnnotationSets;
-      if (parameterAnnotations == null) {
-        parameterAnnotationSets = DexAnnotationSetRefList.empty();
+      ParameterAnnotationsList annotationsList;
+      if (parameterAnnotationsLists == null) {
+        annotationsList = ParameterAnnotationsList.empty();
       } else {
-        DexAnnotationSet[] sets = new DexAnnotationSet[parameterAnnotations.size()];
-        for (int i = 0; i < parameterAnnotations.size(); i++) {
-          sets[i] = createAnnotationSet(parameterAnnotations.get(i));
+        DexAnnotationSet[] sets = new DexAnnotationSet[parameterAnnotationsLists.size()];
+        for (int i = 0; i < parameterAnnotationsLists.size(); i++) {
+          sets[i] = createAnnotationSet(parameterAnnotationsLists.get(i));
         }
-        parameterAnnotationSets = new DexAnnotationSetRefList(sets, fakeParameterAnnotations);
+        annotationsList = new ParameterAnnotationsList(sets, fakeParameterAnnotations);
       }
       InternalOptions internalOptions = parent.application.options;
       if (parameterNames != null && internalOptions.canUseParameterNameAnnotations()) {
@@ -659,7 +596,7 @@
             parent.application.getFactory()));
       }
       DexEncodedMethod dexMethod = new DexEncodedMethod(method, flags,
-          createAnnotationSet(annotations), parameterAnnotationSets, code);
+          createAnnotationSet(annotations), annotationsList, code);
       if (flags.isStatic() || flags.isConstructor() || flags.isPrivate()) {
         parent.directMethods.add(dexMethod);
       } else {
@@ -682,646 +619,6 @@
     }
   }
 
-  private static class CfCreateMethodVisitor extends CreateMethodVisitor {
-
-    private final JarApplicationReader application;
-    private final DexItemFactory factory;
-    private int maxStack;
-    private int maxLocals;
-    private List<CfInstruction> instructions;
-    private List<CfTryCatch> tryCatchRanges;
-    private List<LocalVariableInfo> localVariables;
-    private Map<Label, CfLabel> labelMap;
-
-    public CfCreateMethodVisitor(
-        int access,
-        String name,
-        String desc,
-        String signature,
-        String[] exceptions,
-        CreateDexClassVisitor parent) {
-      super(access, name, desc, signature, exceptions, parent);
-      this.application = parent.application;
-      this.factory = application.getFactory();
-    }
-
-    @Override
-    public void visitCode() {
-      assert !flags.isAbstract() && !flags.isNative();
-      maxStack = 0;
-      maxLocals = 0;
-      instructions = new ArrayList<>();
-      tryCatchRanges = new ArrayList<>();
-      localVariables = new ArrayList<>();
-      labelMap = new IdentityHashMap<>();
-    }
-
-    @Override
-    public void visitEnd() {
-      if (!flags.isAbstract() && !flags.isNative() && parent.classKind == ClassKind.PROGRAM) {
-        code =
-            new CfCode(method, maxStack, maxLocals, instructions, tryCatchRanges, localVariables);
-      }
-      super.visitEnd();
-    }
-
-    @Override
-    public void visitFrame(
-        int frameType, int nLocals, Object[] localTypes, int nStack, Object[] stackTypes) {
-      assert frameType == Opcodes.F_NEW;
-      Int2ReferenceSortedMap<FrameType> parsedLocals = parseLocals(nLocals, localTypes);
-      List<FrameType> parsedStack = parseStack(nStack, stackTypes);
-      instructions.add(new CfFrame(parsedLocals, parsedStack));
-    }
-
-    private Int2ReferenceSortedMap<FrameType> parseLocals(int typeCount, Object[] asmTypes) {
-      Int2ReferenceSortedMap<FrameType> types = new Int2ReferenceAVLTreeMap<>();
-      int i = 0;
-      for (int j = 0; j < typeCount; j++) {
-        Object localType = asmTypes[j];
-        FrameType value = getFrameType(localType);
-        types.put(i++, value);
-        if (value.isWide()) {
-          i++;
-        }
-      }
-      return types;
-    }
-
-    private List<FrameType> parseStack(int nStack, Object[] stackTypes) {
-      List<FrameType> dexStack = new ArrayList<>(nStack);
-      for (int i = 0; i < nStack; i++) {
-        dexStack.add(getFrameType(stackTypes[i]));
-      }
-      return dexStack;
-    }
-
-    private FrameType getFrameType(Object localType) {
-      if (localType instanceof Label) {
-        return FrameType.uninitializedNew(getLabel((Label) localType));
-      } else if (localType == Opcodes.UNINITIALIZED_THIS) {
-        return FrameType.uninitializedThis();
-      } else if (localType == null || localType == Opcodes.TOP) {
-        return FrameType.top();
-      } else {
-        return FrameType.initialized(parseAsmType(localType));
-      }
-    }
-
-    private CfLabel getLabel(Label label) {
-      return labelMap.computeIfAbsent(label, l -> new CfLabel());
-    }
-
-    private DexType parseAsmType(Object local) {
-      assert local != null && local != Opcodes.TOP;
-      if (local == Opcodes.INTEGER) {
-        return factory.intType;
-      } else if (local == Opcodes.FLOAT) {
-        return factory.floatType;
-      } else if (local == Opcodes.LONG) {
-        return factory.longType;
-      } else if (local == Opcodes.DOUBLE) {
-        return factory.doubleType;
-      } else if (local == Opcodes.NULL) {
-        return DexItemFactory.nullValueType;
-      } else if (local instanceof String) {
-        return createTypeFromInternalType((String) local);
-      } else {
-        throw new Unreachable("Unexpected ASM type: " + local);
-      }
-    }
-
-    private DexType createTypeFromInternalType(String local) {
-      assert local.indexOf('.') == -1;
-      return factory.createType("L" + local + ";");
-    }
-
-    @Override
-    public void visitInsn(int opcode) {
-      switch (opcode) {
-        case Opcodes.NOP:
-          instructions.add(new CfNop());
-          break;
-        case Opcodes.ACONST_NULL:
-          instructions.add(new CfConstNull());
-          break;
-        case Opcodes.ICONST_M1:
-        case Opcodes.ICONST_0:
-        case Opcodes.ICONST_1:
-        case Opcodes.ICONST_2:
-        case Opcodes.ICONST_3:
-        case Opcodes.ICONST_4:
-        case Opcodes.ICONST_5:
-          instructions.add(new CfConstNumber(opcode - Opcodes.ICONST_0, ValueType.INT));
-          break;
-        case Opcodes.LCONST_0:
-        case Opcodes.LCONST_1:
-          instructions.add(new CfConstNumber(opcode - Opcodes.LCONST_0, ValueType.LONG));
-          break;
-        case Opcodes.FCONST_0:
-        case Opcodes.FCONST_1:
-        case Opcodes.FCONST_2:
-          instructions.add(
-              new CfConstNumber(
-                  Float.floatToRawIntBits(opcode - Opcodes.FCONST_0), ValueType.FLOAT));
-          break;
-        case Opcodes.DCONST_0:
-        case Opcodes.DCONST_1:
-          instructions.add(
-              new CfConstNumber(
-                  Double.doubleToRawLongBits(opcode - Opcodes.DCONST_0), ValueType.DOUBLE));
-          break;
-        case Opcodes.IALOAD:
-        case Opcodes.LALOAD:
-        case Opcodes.FALOAD:
-        case Opcodes.DALOAD:
-        case Opcodes.AALOAD:
-        case Opcodes.BALOAD:
-        case Opcodes.CALOAD:
-        case Opcodes.SALOAD:
-          instructions.add(new CfArrayLoad(getMemberTypeForOpcode(opcode)));
-          break;
-        case Opcodes.IASTORE:
-        case Opcodes.LASTORE:
-        case Opcodes.FASTORE:
-        case Opcodes.DASTORE:
-        case Opcodes.AASTORE:
-        case Opcodes.BASTORE:
-        case Opcodes.CASTORE:
-        case Opcodes.SASTORE:
-          instructions.add(new CfArrayStore(getMemberTypeForOpcode(opcode)));
-          break;
-        case Opcodes.POP:
-        case Opcodes.POP2:
-        case Opcodes.DUP:
-        case Opcodes.DUP_X1:
-        case Opcodes.DUP_X2:
-        case Opcodes.DUP2:
-        case Opcodes.DUP2_X1:
-        case Opcodes.DUP2_X2:
-        case Opcodes.SWAP:
-          instructions.add(CfStackInstruction.fromAsm(opcode));
-          break;
-        case Opcodes.IADD:
-        case Opcodes.LADD:
-        case Opcodes.FADD:
-        case Opcodes.DADD:
-        case Opcodes.ISUB:
-        case Opcodes.LSUB:
-        case Opcodes.FSUB:
-        case Opcodes.DSUB:
-        case Opcodes.IMUL:
-        case Opcodes.LMUL:
-        case Opcodes.FMUL:
-        case Opcodes.DMUL:
-        case Opcodes.IDIV:
-        case Opcodes.LDIV:
-        case Opcodes.FDIV:
-        case Opcodes.DDIV:
-        case Opcodes.IREM:
-        case Opcodes.LREM:
-        case Opcodes.FREM:
-        case Opcodes.DREM:
-          instructions.add(CfArithmeticBinop.fromAsm(opcode));
-          break;
-        case Opcodes.INEG:
-        case Opcodes.LNEG:
-        case Opcodes.FNEG:
-        case Opcodes.DNEG:
-          instructions.add(CfNeg.fromAsm(opcode));
-          break;
-        case Opcodes.ISHL:
-        case Opcodes.LSHL:
-        case Opcodes.ISHR:
-        case Opcodes.LSHR:
-        case Opcodes.IUSHR:
-        case Opcodes.LUSHR:
-        case Opcodes.IAND:
-        case Opcodes.LAND:
-        case Opcodes.IOR:
-        case Opcodes.LOR:
-        case Opcodes.IXOR:
-        case Opcodes.LXOR:
-          instructions.add(CfLogicalBinop.fromAsm(opcode));
-          break;
-        case Opcodes.I2L:
-        case Opcodes.I2F:
-        case Opcodes.I2D:
-        case Opcodes.L2I:
-        case Opcodes.L2F:
-        case Opcodes.L2D:
-        case Opcodes.F2I:
-        case Opcodes.F2L:
-        case Opcodes.F2D:
-        case Opcodes.D2I:
-        case Opcodes.D2L:
-        case Opcodes.D2F:
-        case Opcodes.I2B:
-        case Opcodes.I2C:
-        case Opcodes.I2S:
-          instructions.add(CfNumberConversion.fromAsm(opcode));
-          break;
-        case Opcodes.LCMP:
-        case Opcodes.FCMPL:
-        case Opcodes.FCMPG:
-        case Opcodes.DCMPL:
-        case Opcodes.DCMPG:
-          instructions.add(CfCmp.fromAsm(opcode));
-          break;
-        case Opcodes.IRETURN:
-          instructions.add(new CfReturn(ValueType.INT));
-          break;
-        case Opcodes.LRETURN:
-          instructions.add(new CfReturn(ValueType.LONG));
-          break;
-        case Opcodes.FRETURN:
-          instructions.add(new CfReturn(ValueType.FLOAT));
-          break;
-        case Opcodes.DRETURN:
-          instructions.add(new CfReturn(ValueType.DOUBLE));
-          break;
-        case Opcodes.ARETURN:
-          instructions.add(new CfReturn(ValueType.OBJECT));
-          break;
-        case Opcodes.RETURN:
-          instructions.add(new CfReturnVoid());
-          break;
-        case Opcodes.ARRAYLENGTH:
-          instructions.add(new CfArrayLength());
-          break;
-        case Opcodes.ATHROW:
-          instructions.add(new CfThrow());
-          break;
-        case Opcodes.MONITORENTER:
-          instructions.add(new CfMonitor(Monitor.Type.ENTER));
-          break;
-        case Opcodes.MONITOREXIT:
-          instructions.add(new CfMonitor(Monitor.Type.EXIT));
-          break;
-        default:
-          throw new Unreachable("Unknown instruction");
-      }
-    }
-
-    private DexType opType(int opcode, DexItemFactory factory) {
-      switch (opcode) {
-        case Opcodes.IADD:
-        case Opcodes.ISUB:
-        case Opcodes.IMUL:
-        case Opcodes.IDIV:
-        case Opcodes.IREM:
-        case Opcodes.INEG:
-        case Opcodes.ISHL:
-        case Opcodes.ISHR:
-        case Opcodes.IUSHR:
-          return factory.intType;
-        case Opcodes.LADD:
-        case Opcodes.LSUB:
-        case Opcodes.LMUL:
-        case Opcodes.LDIV:
-        case Opcodes.LREM:
-        case Opcodes.LNEG:
-        case Opcodes.LSHL:
-        case Opcodes.LSHR:
-        case Opcodes.LUSHR:
-          return factory.longType;
-        case Opcodes.FADD:
-        case Opcodes.FSUB:
-        case Opcodes.FMUL:
-        case Opcodes.FDIV:
-        case Opcodes.FREM:
-        case Opcodes.FNEG:
-          return factory.floatType;
-        case Opcodes.DADD:
-        case Opcodes.DSUB:
-        case Opcodes.DMUL:
-        case Opcodes.DDIV:
-        case Opcodes.DREM:
-        case Opcodes.DNEG:
-          return factory.doubleType;
-        default:
-          throw new Unreachable("Unexpected opcode " + opcode);
-      }
-    }
-
-    private static MemberType getMemberTypeForOpcode(int opcode) {
-      switch (opcode) {
-        case Opcodes.IALOAD:
-        case Opcodes.IASTORE:
-          return MemberType.INT;
-        case Opcodes.FALOAD:
-        case Opcodes.FASTORE:
-          return MemberType.FLOAT;
-        case Opcodes.LALOAD:
-        case Opcodes.LASTORE:
-          return MemberType.LONG;
-        case Opcodes.DALOAD:
-        case Opcodes.DASTORE:
-          return MemberType.DOUBLE;
-        case Opcodes.AALOAD:
-        case Opcodes.AASTORE:
-          return MemberType.OBJECT;
-        case Opcodes.BALOAD:
-        case Opcodes.BASTORE:
-          return MemberType.BOOLEAN; // TODO: Distinguish byte and boolean.
-        case Opcodes.CALOAD:
-        case Opcodes.CASTORE:
-          return MemberType.CHAR;
-        case Opcodes.SALOAD:
-        case Opcodes.SASTORE:
-          return MemberType.SHORT;
-        default:
-          throw new Unreachable("Unexpected array opcode " + opcode);
-      }
-    }
-
-    @Override
-    public void visitIntInsn(int opcode, int operand) {
-      switch (opcode) {
-        case Opcodes.SIPUSH:
-        case Opcodes.BIPUSH:
-          instructions.add(new CfConstNumber(operand, ValueType.INT));
-          break;
-        case Opcodes.NEWARRAY:
-          instructions.add(
-              new CfNewArray(factory.createArrayType(1, arrayTypeDesc(operand, factory))));
-          break;
-        default:
-          throw new Unreachable("Unexpected int opcode " + opcode);
-      }
-    }
-
-    private static DexType arrayTypeDesc(int arrayTypeCode, DexItemFactory factory) {
-      switch (arrayTypeCode) {
-        case Opcodes.T_BOOLEAN:
-          return factory.booleanType;
-        case Opcodes.T_CHAR:
-          return factory.charType;
-        case Opcodes.T_FLOAT:
-          return factory.floatType;
-        case Opcodes.T_DOUBLE:
-          return factory.doubleType;
-        case Opcodes.T_BYTE:
-          return factory.byteType;
-        case Opcodes.T_SHORT:
-          return factory.shortType;
-        case Opcodes.T_INT:
-          return factory.intType;
-        case Opcodes.T_LONG:
-          return factory.longType;
-        default:
-          throw new Unreachable("Unexpected array-type code " + arrayTypeCode);
-      }
-    }
-
-    @Override
-    public void visitVarInsn(int opcode, int var) {
-      ValueType type;
-      switch (opcode) {
-        case Opcodes.ILOAD:
-        case Opcodes.ISTORE:
-          type = ValueType.INT;
-          break;
-        case Opcodes.FLOAD:
-        case Opcodes.FSTORE:
-          type = ValueType.FLOAT;
-          break;
-        case Opcodes.LLOAD:
-        case Opcodes.LSTORE:
-          type = ValueType.LONG;
-          break;
-        case Opcodes.DLOAD:
-        case Opcodes.DSTORE:
-          type = ValueType.DOUBLE;
-          break;
-        case Opcodes.ALOAD:
-        case Opcodes.ASTORE:
-          type = ValueType.OBJECT;
-          break;
-        case Opcodes.RET:
-          throw new Unreachable("RET should be handled by the ASM jsr inliner");
-        default:
-          throw new Unreachable("Unexpected VarInsn opcode: " + opcode);
-      }
-      if (Opcodes.ILOAD <= opcode && opcode <= Opcodes.ALOAD) {
-        instructions.add(new CfLoad(type, var));
-      } else {
-        instructions.add(new CfStore(type, var));
-      }
-    }
-
-    @Override
-    public void visitTypeInsn(int opcode, String typeName) {
-      DexType type = createTypeFromInternalType(typeName);
-      switch (opcode) {
-        case Opcodes.NEW:
-          instructions.add(new CfNew(type));
-          break;
-        case Opcodes.ANEWARRAY:
-          instructions.add(new CfNewArray(factory.createArrayType(1, type)));
-          break;
-        case Opcodes.CHECKCAST:
-          instructions.add(new CfCheckCast(type));
-          break;
-        case Opcodes.INSTANCEOF:
-          instructions.add(new CfInstanceOf(type));
-          break;
-        default:
-          throw new Unreachable("Unexpected TypeInsn opcode: " + opcode);
-      }
-    }
-
-    @Override
-    public void visitFieldInsn(int opcode, String owner, String name, String desc) {
-      DexField field =
-          factory.createField(createTypeFromInternalType(owner), factory.createType(desc), name);
-      // TODO(mathiasr): Don't require CfFieldInstruction::declaringField. It is needed for proper
-      // renaming in the backend, but it is not available here in the frontend.
-      instructions.add(new CfFieldInstruction(opcode, field, field));
-    }
-
-    @Override
-    public void visitMethodInsn(int opcode, String owner, String name, String desc) {
-      visitMethodInsn(opcode, owner, name, desc, false);
-    }
-
-    @Override
-    public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
-      DexMethod method = application.getMethod(owner, name, desc);
-      instructions.add(new CfInvoke(opcode, method, itf));
-    }
-
-    @Override
-    public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
-      DexCallSite callSite =
-          DexCallSite.fromAsmInvokeDynamic(application, parent.type, name, desc, bsm, bsmArgs);
-      instructions.add(new CfInvokeDynamic(callSite));
-    }
-
-    @Override
-    public void visitJumpInsn(int opcode, Label label) {
-      CfLabel target = getLabel(label);
-      if (Opcodes.IFEQ <= opcode && opcode <= Opcodes.IF_ACMPNE) {
-        if (opcode <= Opcodes.IFLE) {
-          // IFEQ, IFNE, IFLT, IFGE, IFGT, or IFLE.
-          instructions.add(new CfIf(ifType(opcode), ValueType.INT, target));
-        } else {
-          // IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, or
-          // IF_ACMPNE.
-          ValueType valueType;
-          if (opcode <= Opcodes.IF_ICMPLE) {
-            valueType = ValueType.INT;
-          } else {
-            valueType = ValueType.OBJECT;
-          }
-          instructions.add(new CfIfCmp(ifType(opcode), valueType, target));
-        }
-      } else {
-        // GOTO, JSR, IFNULL or IFNONNULL.
-        switch (opcode) {
-          case Opcodes.GOTO:
-            instructions.add(new CfGoto(target));
-            break;
-          case Opcodes.IFNULL:
-          case Opcodes.IFNONNULL:
-            If.Type type = opcode == Opcodes.IFNULL ? If.Type.EQ : If.Type.NE;
-            instructions.add(new CfIf(type, ValueType.OBJECT, target));
-            break;
-          case Opcodes.JSR:
-            throw new Unreachable("JSR should be handled by the ASM jsr inliner");
-          default:
-            throw new Unreachable("Unexpected JumpInsn opcode: " + opcode);
-        }
-      }
-    }
-
-    private static If.Type ifType(int opcode) {
-      switch (opcode) {
-        case Opcodes.IFEQ:
-        case Opcodes.IF_ICMPEQ:
-        case Opcodes.IF_ACMPEQ:
-          return If.Type.EQ;
-        case Opcodes.IFNE:
-        case Opcodes.IF_ICMPNE:
-        case Opcodes.IF_ACMPNE:
-          return If.Type.NE;
-        case Opcodes.IFLT:
-        case Opcodes.IF_ICMPLT:
-          return If.Type.LT;
-        case Opcodes.IFGE:
-        case Opcodes.IF_ICMPGE:
-          return If.Type.GE;
-        case Opcodes.IFGT:
-        case Opcodes.IF_ICMPGT:
-          return If.Type.GT;
-        case Opcodes.IFLE:
-        case Opcodes.IF_ICMPLE:
-          return If.Type.LE;
-        default:
-          throw new Unreachable("Unexpected If instruction opcode: " + opcode);
-      }
-    }
-
-    @Override
-    public void visitLabel(Label label) {
-      instructions.add(getLabel(label));
-    }
-
-    @Override
-    public void visitLdcInsn(Object cst) {
-      if (cst instanceof Type) {
-        Type type = (Type) cst;
-        if (type.getSort() == Type.METHOD) {
-          DexProto proto = application.getProto(type.getDescriptor());
-          instructions.add(new CfConstMethodType(proto));
-        } else {
-          instructions.add(new CfConstClass(factory.createType(type.getDescriptor())));
-        }
-      } else if (cst instanceof String) {
-        instructions.add(new CfConstString(factory.createString((String) cst)));
-      } else if (cst instanceof Long) {
-        instructions.add(new CfConstNumber((Long) cst, ValueType.LONG));
-      } else if (cst instanceof Double) {
-        long l = Double.doubleToRawLongBits((Double) cst);
-        instructions.add(new CfConstNumber(l, ValueType.DOUBLE));
-      } else if (cst instanceof Integer) {
-        instructions.add(new CfConstNumber((Integer) cst, ValueType.INT));
-      } else if (cst instanceof Float) {
-        long i = Float.floatToRawIntBits((Float) cst);
-        instructions.add(new CfConstNumber(i, ValueType.FLOAT));
-      } else if (cst instanceof Handle) {
-        instructions.add(
-            new CfConstMethodHandle(
-                DexMethodHandle.fromAsmHandle((Handle) cst, application, parent.type)));
-      } else {
-        throw new CompilationError("Unsupported constant: " + cst.toString());
-      }
-    }
-
-    @Override
-    public void visitIincInsn(int var, int increment) {
-      instructions.add(new CfIinc(var, increment));
-    }
-
-    @Override
-    public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
-      assert max == min + labels.length - 1;
-      ArrayList<CfLabel> targets = new ArrayList<>(labels.length);
-      for (Label label : labels) {
-        targets.add(getLabel(label));
-      }
-      instructions.add(new CfSwitch(CfSwitch.Kind.TABLE, getLabel(dflt), new int[] {min}, targets));
-    }
-
-    @Override
-    public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
-      ArrayList<CfLabel> targets = new ArrayList<>(labels.length);
-      for (Label label : labels) {
-        targets.add(getLabel(label));
-      }
-      instructions.add(new CfSwitch(CfSwitch.Kind.LOOKUP, getLabel(dflt), keys, targets));
-    }
-
-    @Override
-    public void visitMultiANewArrayInsn(String desc, int dims) {
-      instructions.add(new CfMultiANewArray(factory.createType(desc), dims));
-    }
-
-    @Override
-    public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
-      List<DexType> guards =
-          Collections.singletonList(
-              type == null ? DexItemFactory.catchAllType : createTypeFromInternalType(type));
-      List<CfLabel> targets = Collections.singletonList(getLabel(handler));
-      tryCatchRanges.add(new CfTryCatch(getLabel(start), getLabel(end), guards, targets));
-    }
-
-    @Override
-    public void visitLocalVariable(
-        String name, String desc, String signature, Label start, Label end, int index) {
-      DebugLocalInfo debugLocalInfo =
-          new DebugLocalInfo(
-              factory.createString(name),
-              factory.createType(desc),
-              signature == null ? null : factory.createString(signature));
-      localVariables.add(
-          new LocalVariableInfo(index, debugLocalInfo, getLabel(start), getLabel(end)));
-    }
-
-    @Override
-    public void visitLineNumber(int line, Label start) {
-      instructions.add(new CfPosition(getLabel(start), new Position(line, null, method, null)));
-    }
-
-    @Override
-    public void visitMaxs(int maxStack, int maxLocals) {
-      assert maxStack >= 0;
-      assert maxLocals >= 0;
-      this.maxStack = maxStack;
-      this.maxLocals = maxLocals;
-    }
-  }
-
   private static class CreateAnnotationVisitor extends AnnotationVisitor {
 
     private final JarApplicationReader application;
@@ -1466,4 +763,14 @@
       return getDexValueArray(value);
     }
   }
+
+  public static class ReparseContext {
+
+    // This will hold the content of the whole class. Once all the methods of the class are swapped
+    // from this to the actual JarCode, no other references would be left and the content can be
+    // GC'd.
+    public byte[] classCache;
+    public DexProgramClass owner;
+    public final List<Code> codeList = new ArrayList<>();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 55ae757..640172f 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -3,8 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
+import com.android.tools.r8.graph.JarClassFileReader.ReparseContext;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
@@ -17,9 +17,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import java.io.PrintWriter;
 import java.io.StringWriter;
-import java.util.ArrayList;
 import java.util.Iterator;
-import java.util.List;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.MethodVisitor;
@@ -39,16 +37,6 @@
     node.accept(visitor);
   }
 
-  public static class ReparseContext {
-
-    // This will hold the content of the whole class. Once all the methods of the class are swapped
-    // from this to the actual JarCode, no other references would be left and the content can be
-    // GC'd.
-    public byte[] classCache;
-    public DexProgramClass owner;
-    private final List<JarCode> codeList = new ArrayList<>();
-  }
-
   private final DexMethod method;
   private final Origin origin;
   private MethodNode node;
@@ -115,49 +103,50 @@
   }
 
   @Override
-  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
-      throws ApiLevelException {
+  public IRCode buildIR(
+      DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
     triggerDelayedParsingIfNeccessary();
     return options.debug
-        ? internalBuildWithLocals(encodedMethod, options, null, null)
-        : internalBuild(encodedMethod, options, null, null);
+        ? internalBuildWithLocals(encodedMethod, appInfo, options, null, null)
+        : internalBuild(encodedMethod, appInfo, options, null, null);
   }
 
   @Override
   public IRCode buildInliningIR(
       DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
       InternalOptions options,
       ValueNumberGenerator generator,
-      Position callerPosition)
-      throws ApiLevelException {
+      Position callerPosition,
+      Origin origin) {
     assert generator != null;
     triggerDelayedParsingIfNeccessary();
     return options.debug
-        ? internalBuildWithLocals(encodedMethod, options, generator, callerPosition)
-        : internalBuild(encodedMethod, options, generator, callerPosition);
+        ? internalBuildWithLocals(encodedMethod, appInfo, options, generator, callerPosition)
+        : internalBuild(encodedMethod, appInfo, options, generator, callerPosition);
   }
 
   private IRCode internalBuildWithLocals(
       DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
       InternalOptions options,
       ValueNumberGenerator generator,
-      Position callerPosition)
-      throws ApiLevelException {
+      Position callerPosition) {
     try {
-      return internalBuild(encodedMethod, options, generator, callerPosition);
+      return internalBuild(encodedMethod, appInfo, options, generator, callerPosition);
     } catch (InvalidDebugInfoException e) {
       options.warningInvalidDebugInfo(encodedMethod, origin, e);
       node.localVariables.clear();
-      return internalBuild(encodedMethod, options, generator, callerPosition);
+      return internalBuild(encodedMethod, appInfo, options, generator, callerPosition);
     }
   }
 
   private IRCode internalBuild(
       DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
       InternalOptions options,
       ValueNumberGenerator generator,
-      Position callerPosition)
-      throws ApiLevelException {
+      Position callerPosition) {
     if (!options.debug) {
       node.localVariables.clear();
     }
@@ -165,8 +154,8 @@
         method.getHolder(), node, application, encodedMethod.method, callerPosition);
     IRBuilder builder =
         (generator == null)
-            ? new IRBuilder(encodedMethod, source, options)
-            : new IRBuilder(encodedMethod, source, options, generator);
+            ? new IRBuilder(encodedMethod, appInfo, source, options)
+            : new IRBuilder(encodedMethod, appInfo, source, options, generator);
     return builder.build();
   }
 
@@ -196,15 +185,44 @@
   }
 
   private void triggerDelayedParsingIfNeccessary() {
-    if (context != null) {
-      // The SecondVistor is in charge of setting the context to null.
-      DexProgramClass owner = context.owner;
-      new ClassReader(context.classCache).accept(new SecondVisitor(context, application),
-          ClassReader.SKIP_FRAMES);
-      assert verifyNoReparseContext(owner);
+    if (this.context != null) {
+      // The SecondVisitor is in charge of setting this.context to null.
+      ReparseContext context = this.context;
+      parseCode(context, false);
+      if (hasJsr(context)) {
+        System.out.println("JarCode: JSR encountered; reparse using JSRInlinerAdapter");
+        parseCode(context, true);
+        assert !hasJsr(context);
+      }
+      assert verifyNoReparseContext(context.owner);
     }
   }
 
+  private void parseCode(ReparseContext context, boolean useJsrInliner) {
+    SecondVisitor classVisitor = new SecondVisitor(context, application, useJsrInliner);
+    new ClassReader(context.classCache).accept(classVisitor, ClassReader.SKIP_FRAMES);
+  }
+
+  private boolean hasJsr(ReparseContext context) {
+    for (Code code : context.codeList) {
+      if (hasJsr(code.asJarCode().node)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private boolean hasJsr(MethodNode node) {
+    Iterator<AbstractInsnNode> it = node.instructions.iterator();
+    while (it.hasNext()) {
+      int opcode = it.next().getOpcode();
+      if (opcode == Opcodes.JSR || opcode == Opcodes.RET) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   /**
    * Fills the MethodNodes of all the methods in the class and removes the ReparseContext.
    */
@@ -212,22 +230,28 @@
 
     private final ReparseContext context;
     private final JarApplicationReader application;
+    private final boolean useJsrInliner;
     private int methodIndex = 0;
 
-    public SecondVisitor(ReparseContext context, JarApplicationReader application) {
+    public SecondVisitor(
+        ReparseContext context, JarApplicationReader application, boolean useJsrInliner) {
       super(Opcodes.ASM6);
       this.context = context;
       this.application = application;
+      this.useJsrInliner = useJsrInliner;
     }
 
     @Override
     public MethodVisitor visitMethod(int access, String name, String desc, String signature,
         String[] exceptions) {
-      MethodNode node = new JSRInlinerAdapter(null, access, name, desc, signature, exceptions);
+      MethodNode node =
+          useJsrInliner
+              ? new JSRInlinerAdapter(null, access, name, desc, signature, exceptions)
+              : new MethodNode(Opcodes.ASM6, access, name, desc, signature, exceptions);
       JarCode code = null;
       MethodAccessFlags flags = JarClassFileReader.createMethodAccessFlags(name, access);
       if (!flags.isAbstract() && !flags.isNative()) {
-        code = context.codeList.get(methodIndex++);
+        code = context.codeList.get(methodIndex++).asJarCode();
         assert code.method == application.getMethod(context.owner.type, name, desc);
       }
       if (code != null) {
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
new file mode 100644
index 0000000..8ecc1ab
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -0,0 +1,851 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.cf.code.CfArithmeticBinop;
+import com.android.tools.r8.cf.code.CfArrayLength;
+import com.android.tools.r8.cf.code.CfArrayLoad;
+import com.android.tools.r8.cf.code.CfArrayStore;
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfCmp;
+import com.android.tools.r8.cf.code.CfConstClass;
+import com.android.tools.r8.cf.code.CfConstMethodHandle;
+import com.android.tools.r8.cf.code.CfConstMethodType;
+import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.cf.code.CfGoto;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfIfCmp;
+import com.android.tools.r8.cf.code.CfIinc;
+import com.android.tools.r8.cf.code.CfInstanceOf;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfLogicalBinop;
+import com.android.tools.r8.cf.code.CfMonitor;
+import com.android.tools.r8.cf.code.CfMultiANewArray;
+import com.android.tools.r8.cf.code.CfNeg;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfNewArray;
+import com.android.tools.r8.cf.code.CfNop;
+import com.android.tools.r8.cf.code.CfNumberConversion;
+import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.cf.code.CfSwitch;
+import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
+import com.android.tools.r8.graph.JarClassFileReader.ReparseContext;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.Monitor;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueNumberGenerator;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.InternalOptions;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.JSRInlinerAdapter;
+
+public class LazyCfCode extends Code {
+
+  private static class JsrEncountered extends RuntimeException {
+    public JsrEncountered(String s) {
+      super(s);
+    }
+  }
+
+  public LazyCfCode(
+      DexMethod method, Origin origin, ReparseContext context, JarApplicationReader application) {
+
+    this.method = method;
+    this.origin = origin;
+    this.context = context;
+    this.application = application;
+    context.codeList.add(this);
+  }
+
+  private final DexMethod method;
+  private final Origin origin;
+  private final JarApplicationReader application;
+  private CfCode code;
+  private ReparseContext context;
+
+  @Override
+  public boolean isCfCode() {
+    return true;
+  }
+
+  @Override
+  public LazyCfCode asLazyCfCode() {
+    return this;
+  }
+
+  @Override
+  public CfCode asCfCode() {
+    if (code == null) {
+      ReparseContext context = this.context;
+      assert context != null;
+      // The ClassCodeVisitor is in charge of setting this.context to null.
+      try {
+        parseCode(context, false);
+      } catch (JsrEncountered e) {
+        System.out.println("LazyCfCode: JSR encountered; reparse using JSRInlinerAdapter");
+        for (Code code : context.codeList) {
+          code.asLazyCfCode().code = null;
+          code.asLazyCfCode().context = context;
+        }
+        try {
+          parseCode(context, true);
+        } catch (JsrEncountered e1) {
+          throw new Unreachable(e1);
+        }
+      }
+      assert verifyNoReparseContext(context.owner);
+    }
+    assert code != null;
+    return code;
+  }
+
+  public void parseCode(ReparseContext context, boolean useJsrInliner) {
+    ClassCodeVisitor classVisitor = new ClassCodeVisitor(context, application, useJsrInliner);
+    new ClassReader(context.classCache).accept(classVisitor, ClassReader.EXPAND_FRAMES);
+  }
+
+  private void setCode(CfCode code) {
+    assert this.code == null;
+    assert this.context != null;
+    this.code = code;
+    this.context = null;
+  }
+
+  @Override
+  protected int computeHashCode() {
+    throw new Unimplemented();
+  }
+
+  @Override
+  protected boolean computeEquals(Object other) {
+    throw new Unimplemented();
+  }
+
+  @Override
+  public boolean isEmptyVoidMethod() {
+    return asCfCode().isEmptyVoidMethod();
+  }
+
+  @Override
+  public int estimatedSizeForInlining() {
+    return asCfCode().estimatedSizeForInlining();
+  }
+
+  @Override
+  public boolean estimatedSizeForInliningAtMost(int threshold) {
+    return asCfCode().estimatedSizeForInliningAtMost(threshold);
+  }
+
+  @Override
+  public IRCode buildIR(
+      DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
+    return asCfCode().buildIR(encodedMethod, appInfo, options, origin);
+  }
+
+  @Override
+  public IRCode buildInliningIR(
+      DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
+      InternalOptions options,
+      ValueNumberGenerator valueNumberGenerator,
+      Position callerPosition,
+      Origin origin) {
+    return asCfCode().buildInliningIR(
+        encodedMethod, appInfo, options, valueNumberGenerator, callerPosition, origin);
+  }
+
+  @Override
+  public void registerCodeReferences(UseRegistry registry) {
+    asCfCode().registerCodeReferences(registry);
+  }
+
+  @Override
+  public String toString() {
+    return asCfCode().toString();
+  }
+
+  @Override
+  public String toString(DexEncodedMethod method, ClassNameMapper naming) {
+    return null;
+  }
+
+  private static class ClassCodeVisitor extends ClassVisitor {
+
+    private final ReparseContext context;
+    private final JarApplicationReader application;
+    private int methodIndex = 0;
+    private boolean usrJsrInliner;
+
+    ClassCodeVisitor(
+        ReparseContext context, JarApplicationReader application, boolean useJsrInliner) {
+      super(Opcodes.ASM6);
+      this.context = context;
+      this.application = application;
+      this.usrJsrInliner = useJsrInliner;
+    }
+
+    @Override
+    public MethodVisitor visitMethod(
+        int access, String name, String desc, String signature, String[] exceptions) {
+      MethodAccessFlags flags = JarClassFileReader.createMethodAccessFlags(name, access);
+      if (!flags.isAbstract() && !flags.isNative()) {
+        LazyCfCode code = context.codeList.get(methodIndex++).asLazyCfCode();
+        DexMethod method = application.getMethod(context.owner.type, name, desc);
+        assert code.method == method;
+        MethodCodeVisitor methodVisitor = new MethodCodeVisitor(application, code);
+        if (!usrJsrInliner) {
+          return methodVisitor;
+        }
+        return new JSRInlinerAdapter(methodVisitor, access, name, desc, signature, exceptions);
+      }
+      return null;
+    }
+  }
+
+  private static class MethodCodeVisitor extends MethodVisitor {
+    private final JarApplicationReader application;
+    private final DexItemFactory factory;
+    private int maxStack;
+    private int maxLocals;
+    private List<CfInstruction> instructions;
+    private List<CfTryCatch> tryCatchRanges;
+    private List<LocalVariableInfo> localVariables;
+    private final Map<DebugLocalInfo, DebugLocalInfo> canonicalDebugLocalInfo = new HashMap<>();
+    private Map<Label, CfLabel> labelMap;
+    private final LazyCfCode code;
+    private DexMethod method;
+
+    MethodCodeVisitor(JarApplicationReader application, LazyCfCode code) {
+      super(Opcodes.ASM6);
+      this.application = application;
+      this.factory = application.getFactory();
+      this.method = code.method;
+      this.code = code;
+    }
+
+    @Override
+    public void visitCode() {
+      maxStack = 0;
+      maxLocals = 0;
+      instructions = new ArrayList<>();
+      tryCatchRanges = new ArrayList<>();
+      localVariables = new ArrayList<>();
+      labelMap = new IdentityHashMap<>();
+    }
+
+    @Override
+    public void visitEnd() {
+      code.setCode(
+          new CfCode(method, maxStack, maxLocals, instructions, tryCatchRanges, localVariables));
+    }
+
+    @Override
+    public void visitFrame(
+        int frameType, int nLocals, Object[] localTypes, int nStack, Object[] stackTypes) {
+      assert frameType == Opcodes.F_NEW;
+      Int2ReferenceSortedMap<FrameType> parsedLocals = parseLocals(nLocals, localTypes);
+      List<FrameType> parsedStack = parseStack(nStack, stackTypes);
+      instructions.add(new CfFrame(parsedLocals, parsedStack));
+    }
+
+    private Int2ReferenceSortedMap<FrameType> parseLocals(int typeCount, Object[] asmTypes) {
+      Int2ReferenceSortedMap<FrameType> types = new Int2ReferenceAVLTreeMap<>();
+      int i = 0;
+      for (int j = 0; j < typeCount; j++) {
+        Object localType = asmTypes[j];
+        FrameType value = getFrameType(localType);
+        types.put(i++, value);
+        if (value.isWide()) {
+          i++;
+        }
+      }
+      return types;
+    }
+
+    private List<FrameType> parseStack(int nStack, Object[] stackTypes) {
+      List<FrameType> dexStack = new ArrayList<>(nStack);
+      for (int i = 0; i < nStack; i++) {
+        dexStack.add(getFrameType(stackTypes[i]));
+      }
+      return dexStack;
+    }
+
+    private FrameType getFrameType(Object localType) {
+      if (localType instanceof Label) {
+        return FrameType.uninitializedNew(getLabel((Label) localType));
+      } else if (localType == Opcodes.UNINITIALIZED_THIS) {
+        return FrameType.uninitializedThis();
+      } else if (localType == null || localType == Opcodes.TOP) {
+        return FrameType.top();
+      } else {
+        return FrameType.initialized(parseAsmType(localType));
+      }
+    }
+
+    private CfLabel getLabel(Label label) {
+      return labelMap.computeIfAbsent(label, l -> new CfLabel());
+    }
+
+    private DexType parseAsmType(Object local) {
+      assert local != null && local != Opcodes.TOP;
+      if (local == Opcodes.INTEGER) {
+        return factory.intType;
+      } else if (local == Opcodes.FLOAT) {
+        return factory.floatType;
+      } else if (local == Opcodes.LONG) {
+        return factory.longType;
+      } else if (local == Opcodes.DOUBLE) {
+        return factory.doubleType;
+      } else if (local == Opcodes.NULL) {
+        return DexItemFactory.nullValueType;
+      } else if (local instanceof String) {
+        return createTypeFromInternalType((String) local);
+      } else {
+        throw new Unreachable("Unexpected ASM type: " + local);
+      }
+    }
+
+    private DexType createTypeFromInternalType(String local) {
+      assert local.indexOf('.') == -1;
+      return factory.createType(Type.getObjectType(local).getDescriptor());
+    }
+
+    @Override
+    public void visitInsn(int opcode) {
+      switch (opcode) {
+        case Opcodes.NOP:
+          instructions.add(new CfNop());
+          break;
+        case Opcodes.ACONST_NULL:
+          instructions.add(new CfConstNull());
+          break;
+        case Opcodes.ICONST_M1:
+        case Opcodes.ICONST_0:
+        case Opcodes.ICONST_1:
+        case Opcodes.ICONST_2:
+        case Opcodes.ICONST_3:
+        case Opcodes.ICONST_4:
+        case Opcodes.ICONST_5:
+          instructions.add(new CfConstNumber(opcode - Opcodes.ICONST_0, ValueType.INT));
+          break;
+        case Opcodes.LCONST_0:
+        case Opcodes.LCONST_1:
+          instructions.add(new CfConstNumber(opcode - Opcodes.LCONST_0, ValueType.LONG));
+          break;
+        case Opcodes.FCONST_0:
+        case Opcodes.FCONST_1:
+        case Opcodes.FCONST_2:
+          instructions.add(
+              new CfConstNumber(
+                  Float.floatToRawIntBits(opcode - Opcodes.FCONST_0), ValueType.FLOAT));
+          break;
+        case Opcodes.DCONST_0:
+        case Opcodes.DCONST_1:
+          instructions.add(
+              new CfConstNumber(
+                  Double.doubleToRawLongBits(opcode - Opcodes.DCONST_0), ValueType.DOUBLE));
+          break;
+        case Opcodes.IALOAD:
+        case Opcodes.LALOAD:
+        case Opcodes.FALOAD:
+        case Opcodes.DALOAD:
+        case Opcodes.AALOAD:
+        case Opcodes.BALOAD:
+        case Opcodes.CALOAD:
+        case Opcodes.SALOAD:
+          instructions.add(new CfArrayLoad(getMemberTypeForOpcode(opcode)));
+          break;
+        case Opcodes.IASTORE:
+        case Opcodes.LASTORE:
+        case Opcodes.FASTORE:
+        case Opcodes.DASTORE:
+        case Opcodes.AASTORE:
+        case Opcodes.BASTORE:
+        case Opcodes.CASTORE:
+        case Opcodes.SASTORE:
+          instructions.add(new CfArrayStore(getMemberTypeForOpcode(opcode)));
+          break;
+        case Opcodes.POP:
+        case Opcodes.POP2:
+        case Opcodes.DUP:
+        case Opcodes.DUP_X1:
+        case Opcodes.DUP_X2:
+        case Opcodes.DUP2:
+        case Opcodes.DUP2_X1:
+        case Opcodes.DUP2_X2:
+        case Opcodes.SWAP:
+          instructions.add(CfStackInstruction.fromAsm(opcode));
+          break;
+        case Opcodes.IADD:
+        case Opcodes.LADD:
+        case Opcodes.FADD:
+        case Opcodes.DADD:
+        case Opcodes.ISUB:
+        case Opcodes.LSUB:
+        case Opcodes.FSUB:
+        case Opcodes.DSUB:
+        case Opcodes.IMUL:
+        case Opcodes.LMUL:
+        case Opcodes.FMUL:
+        case Opcodes.DMUL:
+        case Opcodes.IDIV:
+        case Opcodes.LDIV:
+        case Opcodes.FDIV:
+        case Opcodes.DDIV:
+        case Opcodes.IREM:
+        case Opcodes.LREM:
+        case Opcodes.FREM:
+        case Opcodes.DREM:
+          instructions.add(CfArithmeticBinop.fromAsm(opcode));
+          break;
+        case Opcodes.INEG:
+        case Opcodes.LNEG:
+        case Opcodes.FNEG:
+        case Opcodes.DNEG:
+          instructions.add(CfNeg.fromAsm(opcode));
+          break;
+        case Opcodes.ISHL:
+        case Opcodes.LSHL:
+        case Opcodes.ISHR:
+        case Opcodes.LSHR:
+        case Opcodes.IUSHR:
+        case Opcodes.LUSHR:
+        case Opcodes.IAND:
+        case Opcodes.LAND:
+        case Opcodes.IOR:
+        case Opcodes.LOR:
+        case Opcodes.IXOR:
+        case Opcodes.LXOR:
+          instructions.add(CfLogicalBinop.fromAsm(opcode));
+          break;
+        case Opcodes.I2L:
+        case Opcodes.I2F:
+        case Opcodes.I2D:
+        case Opcodes.L2I:
+        case Opcodes.L2F:
+        case Opcodes.L2D:
+        case Opcodes.F2I:
+        case Opcodes.F2L:
+        case Opcodes.F2D:
+        case Opcodes.D2I:
+        case Opcodes.D2L:
+        case Opcodes.D2F:
+        case Opcodes.I2B:
+        case Opcodes.I2C:
+        case Opcodes.I2S:
+          instructions.add(CfNumberConversion.fromAsm(opcode));
+          break;
+        case Opcodes.LCMP:
+        case Opcodes.FCMPL:
+        case Opcodes.FCMPG:
+        case Opcodes.DCMPL:
+        case Opcodes.DCMPG:
+          instructions.add(CfCmp.fromAsm(opcode));
+          break;
+        case Opcodes.IRETURN:
+          instructions.add(new CfReturn(ValueType.INT));
+          break;
+        case Opcodes.LRETURN:
+          instructions.add(new CfReturn(ValueType.LONG));
+          break;
+        case Opcodes.FRETURN:
+          instructions.add(new CfReturn(ValueType.FLOAT));
+          break;
+        case Opcodes.DRETURN:
+          instructions.add(new CfReturn(ValueType.DOUBLE));
+          break;
+        case Opcodes.ARETURN:
+          instructions.add(new CfReturn(ValueType.OBJECT));
+          break;
+        case Opcodes.RETURN:
+          instructions.add(new CfReturnVoid());
+          break;
+        case Opcodes.ARRAYLENGTH:
+          instructions.add(new CfArrayLength());
+          break;
+        case Opcodes.ATHROW:
+          instructions.add(new CfThrow());
+          break;
+        case Opcodes.MONITORENTER:
+          instructions.add(new CfMonitor(Monitor.Type.ENTER));
+          break;
+        case Opcodes.MONITOREXIT:
+          instructions.add(new CfMonitor(Monitor.Type.EXIT));
+          break;
+        default:
+          throw new Unreachable("Unknown instruction");
+      }
+    }
+
+    private static MemberType getMemberTypeForOpcode(int opcode) {
+      switch (opcode) {
+        case Opcodes.IALOAD:
+        case Opcodes.IASTORE:
+          return MemberType.INT;
+        case Opcodes.FALOAD:
+        case Opcodes.FASTORE:
+          return MemberType.FLOAT;
+        case Opcodes.LALOAD:
+        case Opcodes.LASTORE:
+          return MemberType.LONG;
+        case Opcodes.DALOAD:
+        case Opcodes.DASTORE:
+          return MemberType.DOUBLE;
+        case Opcodes.AALOAD:
+        case Opcodes.AASTORE:
+          return MemberType.OBJECT;
+        case Opcodes.BALOAD:
+        case Opcodes.BASTORE:
+          return MemberType.BOOLEAN; // TODO: Distinguish byte and boolean.
+        case Opcodes.CALOAD:
+        case Opcodes.CASTORE:
+          return MemberType.CHAR;
+        case Opcodes.SALOAD:
+        case Opcodes.SASTORE:
+          return MemberType.SHORT;
+        default:
+          throw new Unreachable("Unexpected array opcode " + opcode);
+      }
+    }
+
+    @Override
+    public void visitIntInsn(int opcode, int operand) {
+      switch (opcode) {
+        case Opcodes.SIPUSH:
+        case Opcodes.BIPUSH:
+          instructions.add(new CfConstNumber(operand, ValueType.INT));
+          break;
+        case Opcodes.NEWARRAY:
+          instructions.add(
+              new CfNewArray(factory.createArrayType(1, arrayTypeDesc(operand, factory))));
+          break;
+        default:
+          throw new Unreachable("Unexpected int opcode " + opcode);
+      }
+    }
+
+    private static DexType arrayTypeDesc(int arrayTypeCode, DexItemFactory factory) {
+      switch (arrayTypeCode) {
+        case Opcodes.T_BOOLEAN:
+          return factory.booleanType;
+        case Opcodes.T_CHAR:
+          return factory.charType;
+        case Opcodes.T_FLOAT:
+          return factory.floatType;
+        case Opcodes.T_DOUBLE:
+          return factory.doubleType;
+        case Opcodes.T_BYTE:
+          return factory.byteType;
+        case Opcodes.T_SHORT:
+          return factory.shortType;
+        case Opcodes.T_INT:
+          return factory.intType;
+        case Opcodes.T_LONG:
+          return factory.longType;
+        default:
+          throw new Unreachable("Unexpected array-type code " + arrayTypeCode);
+      }
+    }
+
+    @Override
+    public void visitVarInsn(int opcode, int var) {
+      ValueType type;
+      switch (opcode) {
+        case Opcodes.ILOAD:
+        case Opcodes.ISTORE:
+          type = ValueType.INT;
+          break;
+        case Opcodes.FLOAD:
+        case Opcodes.FSTORE:
+          type = ValueType.FLOAT;
+          break;
+        case Opcodes.LLOAD:
+        case Opcodes.LSTORE:
+          type = ValueType.LONG;
+          break;
+        case Opcodes.DLOAD:
+        case Opcodes.DSTORE:
+          type = ValueType.DOUBLE;
+          break;
+        case Opcodes.ALOAD:
+        case Opcodes.ASTORE:
+          type = ValueType.OBJECT;
+          break;
+        case Opcodes.RET:
+          throw new JsrEncountered("RET should be handled by the ASM jsr inliner");
+        default:
+          throw new Unreachable("Unexpected VarInsn opcode: " + opcode);
+      }
+      if (Opcodes.ILOAD <= opcode && opcode <= Opcodes.ALOAD) {
+        instructions.add(new CfLoad(type, var));
+      } else {
+        instructions.add(new CfStore(type, var));
+      }
+    }
+
+    @Override
+    public void visitTypeInsn(int opcode, String typeName) {
+      DexType type = factory.createType(Type.getObjectType(typeName).getDescriptor());
+      switch (opcode) {
+        case Opcodes.NEW:
+          instructions.add(new CfNew(type));
+          break;
+        case Opcodes.ANEWARRAY:
+          instructions.add(new CfNewArray(factory.createArrayType(1, type)));
+          break;
+        case Opcodes.CHECKCAST:
+          instructions.add(new CfCheckCast(type));
+          break;
+        case Opcodes.INSTANCEOF:
+          instructions.add(new CfInstanceOf(type));
+          break;
+        default:
+          throw new Unreachable("Unexpected TypeInsn opcode: " + opcode);
+      }
+    }
+
+    @Override
+    public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+      DexField field =
+          factory.createField(createTypeFromInternalType(owner), factory.createType(desc), name);
+      // TODO(mathiasr): Don't require CfFieldInstruction::declaringField. It is needed for proper
+      // renaming in the backend, but it is not available here in the frontend.
+      instructions.add(new CfFieldInstruction(opcode, field, field));
+    }
+
+    @Override
+    public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+      visitMethodInsn(opcode, owner, name, desc, false);
+    }
+
+    @Override
+    public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
+      DexMethod method = application.getMethod(owner, name, desc);
+      instructions.add(new CfInvoke(opcode, method, itf));
+    }
+
+    @Override
+    public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
+      DexCallSite callSite =
+          DexCallSite.fromAsmInvokeDynamic(application, method.holder, name, desc, bsm, bsmArgs);
+      instructions.add(new CfInvokeDynamic(callSite));
+    }
+
+    @Override
+    public void visitJumpInsn(int opcode, Label label) {
+      CfLabel target = getLabel(label);
+      if (Opcodes.IFEQ <= opcode && opcode <= Opcodes.IF_ACMPNE) {
+        if (opcode <= Opcodes.IFLE) {
+          // IFEQ, IFNE, IFLT, IFGE, IFGT, or IFLE.
+          instructions.add(new CfIf(ifType(opcode), ValueType.INT, target));
+        } else {
+          // IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, or
+          // IF_ACMPNE.
+          ValueType valueType;
+          if (opcode <= Opcodes.IF_ICMPLE) {
+            valueType = ValueType.INT;
+          } else {
+            valueType = ValueType.OBJECT;
+          }
+          instructions.add(new CfIfCmp(ifType(opcode), valueType, target));
+        }
+      } else {
+        // GOTO, JSR, IFNULL or IFNONNULL.
+        switch (opcode) {
+          case Opcodes.GOTO:
+            instructions.add(new CfGoto(target));
+            break;
+          case Opcodes.IFNULL:
+          case Opcodes.IFNONNULL:
+            If.Type type = opcode == Opcodes.IFNULL ? If.Type.EQ : If.Type.NE;
+            instructions.add(new CfIf(type, ValueType.OBJECT, target));
+            break;
+          case Opcodes.JSR:
+            throw new JsrEncountered("JSR should be handled by the ASM jsr inliner");
+          default:
+            throw new Unreachable("Unexpected JumpInsn opcode: " + opcode);
+        }
+      }
+    }
+
+    private static If.Type ifType(int opcode) {
+      switch (opcode) {
+        case Opcodes.IFEQ:
+        case Opcodes.IF_ICMPEQ:
+        case Opcodes.IF_ACMPEQ:
+          return If.Type.EQ;
+        case Opcodes.IFNE:
+        case Opcodes.IF_ICMPNE:
+        case Opcodes.IF_ACMPNE:
+          return If.Type.NE;
+        case Opcodes.IFLT:
+        case Opcodes.IF_ICMPLT:
+          return If.Type.LT;
+        case Opcodes.IFGE:
+        case Opcodes.IF_ICMPGE:
+          return If.Type.GE;
+        case Opcodes.IFGT:
+        case Opcodes.IF_ICMPGT:
+          return If.Type.GT;
+        case Opcodes.IFLE:
+        case Opcodes.IF_ICMPLE:
+          return If.Type.LE;
+        default:
+          throw new Unreachable("Unexpected If instruction opcode: " + opcode);
+      }
+    }
+
+    @Override
+    public void visitLabel(Label label) {
+      instructions.add(getLabel(label));
+    }
+
+    @Override
+    public void visitLdcInsn(Object cst) {
+      if (cst instanceof Type) {
+        Type type = (Type) cst;
+        if (type.getSort() == Type.METHOD) {
+          DexProto proto = application.getProto(type.getDescriptor());
+          instructions.add(new CfConstMethodType(proto));
+        } else {
+          instructions.add(new CfConstClass(factory.createType(type.getDescriptor())));
+        }
+      } else if (cst instanceof String) {
+        instructions.add(new CfConstString(factory.createString((String) cst)));
+      } else if (cst instanceof Long) {
+        instructions.add(new CfConstNumber((Long) cst, ValueType.LONG));
+      } else if (cst instanceof Double) {
+        long l = Double.doubleToRawLongBits((Double) cst);
+        instructions.add(new CfConstNumber(l, ValueType.DOUBLE));
+      } else if (cst instanceof Integer) {
+        instructions.add(new CfConstNumber((Integer) cst, ValueType.INT));
+      } else if (cst instanceof Float) {
+        long i = Float.floatToRawIntBits((Float) cst);
+        instructions.add(new CfConstNumber(i, ValueType.FLOAT));
+      } else if (cst instanceof Handle) {
+        instructions.add(
+            new CfConstMethodHandle(
+                DexMethodHandle.fromAsmHandle((Handle) cst, application, method.holder)));
+      } else {
+        throw new CompilationError("Unsupported constant: " + cst.toString());
+      }
+    }
+
+    @Override
+    public void visitIincInsn(int var, int increment) {
+      instructions.add(new CfIinc(var, increment));
+    }
+
+    @Override
+    public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
+      assert max == min + labels.length - 1;
+      ArrayList<CfLabel> targets = new ArrayList<>(labels.length);
+      for (Label label : labels) {
+        targets.add(getLabel(label));
+      }
+      instructions.add(new CfSwitch(CfSwitch.Kind.TABLE, getLabel(dflt), new int[] {min}, targets));
+    }
+
+    @Override
+    public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+      ArrayList<CfLabel> targets = new ArrayList<>(labels.length);
+      for (Label label : labels) {
+        targets.add(getLabel(label));
+      }
+      instructions.add(new CfSwitch(CfSwitch.Kind.LOOKUP, getLabel(dflt), keys, targets));
+    }
+
+    @Override
+    public void visitMultiANewArrayInsn(String desc, int dims) {
+      instructions.add(new CfMultiANewArray(factory.createType(desc), dims));
+    }
+
+    @Override
+    public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+      List<DexType> guards =
+          Collections.singletonList(
+              type == null ? DexItemFactory.catchAllType : createTypeFromInternalType(type));
+      List<CfLabel> targets = Collections.singletonList(getLabel(handler));
+      tryCatchRanges.add(new CfTryCatch(getLabel(start), getLabel(end), guards, targets));
+    }
+
+    @Override
+    public void visitLocalVariable(
+        String name, String desc, String signature, Label start, Label end, int index) {
+      DebugLocalInfo debugLocalInfo =
+          canonicalize(
+              new DebugLocalInfo(
+                  factory.createString(name),
+                  factory.createType(desc),
+                  signature == null ? null : factory.createString(signature)));
+      localVariables.add(
+          new LocalVariableInfo(index, debugLocalInfo, getLabel(start), getLabel(end)));
+    }
+
+    private DebugLocalInfo canonicalize(DebugLocalInfo debugLocalInfo) {
+      return canonicalDebugLocalInfo.computeIfAbsent(debugLocalInfo, o -> debugLocalInfo);
+    }
+
+    @Override
+    public void visitLineNumber(int line, Label start) {
+      instructions.add(new CfPosition(getLabel(start), new Position(line, null, method, null)));
+    }
+
+    @Override
+    public void visitMaxs(int maxStack, int maxLocals) {
+      assert maxStack >= 0;
+      assert maxLocals >= 0;
+      this.maxStack = maxStack;
+      this.maxLocals = maxLocals;
+    }
+  }
+
+  private static boolean verifyNoReparseContext(DexProgramClass owner) {
+    for (DexEncodedMethod method : owner.virtualMethods()) {
+      Code code = method.getCode();
+      assert code == null || !(code instanceof LazyCfCode) || ((LazyCfCode) code).context == null;
+    }
+    for (DexEncodedMethod method : owner.directMethods()) {
+      Code code = method.getCode();
+      assert code == null || !(code instanceof LazyCfCode) || ((LazyCfCode) code).context == null;
+    }
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index 6eb9ce5..b648e41 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -6,7 +6,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.ProgramResourceProvider;
 import com.android.tools.r8.dex.ApplicationReader.ProgramClassConflictResolver;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.ClasspathClassCollection;
@@ -28,13 +28,13 @@
    */
   private LazyLoadedDexApplication(ClassNameMapper proguardMap,
       ProgramClassCollection programClasses,
-      ImmutableList<DataResourceProvider> dataResourceProviders,
+      ImmutableList<ProgramResourceProvider> programResourceProviders,
       ClasspathClassCollection classpathClasses,
       LibraryClassCollection libraryClasses,
       ImmutableSet<DexType> mainDexList, String deadCode,
       DexItemFactory dexItemFactory, DexString highestSortingString,
       Timing timing) {
-    super(proguardMap, programClasses, dataResourceProviders, mainDexList, deadCode,
+    super(proguardMap, programClasses, programResourceProviders, mainDexList, deadCode,
         dexItemFactory, highestSortingString, timing);
     this.classpathClasses = classpathClasses;
     this.libraryClasses = libraryClasses;
@@ -122,7 +122,7 @@
       return new LazyLoadedDexApplication(
           proguardMap,
           ProgramClassCollection.create(programClasses, resolver),
-          ImmutableList.copyOf(dataResourceProviders),
+          ImmutableList.copyOf(programResourceProviders),
           classpathClasses,
           libraryClasses,
           ImmutableSet.copyOf(mainDexList),
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
index a8b431c..5cbdf26 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
@@ -52,16 +52,13 @@
     assert methodHandles != null;
 
     this.classes = sortClasses(application, classes);
-    this.protos = createMap(protos, true, this::failOnOverflow);
-    this.types = createMap(types, true, this::failOnOverflow);
-    this.methods = createMap(methods, true, this::failOnOverflow);
-    this.fields = createMap(fields, true, this::failOnOverflow);
-    this.strings = createMap(strings, true, this::setFirstJumboString);
-    // No need to sort CallSite, they will be written in data section in the callSites order,
-    // consequently offset of call site used into the call site section will be in ascending order.
-    this.callSites = createMap(callSites, false, this::failOnOverflow);
-    // No need to sort method handle
-    this.methodHandles = createMap(methodHandles, false, this::failOnOverflow);
+    this.protos = createMap(protos, this::failOnOverflow);
+    this.types = createMap(types, this::failOnOverflow);
+    this.methods = createMap(methods, this::failOnOverflow);
+    this.fields = createMap(fields, this::failOnOverflow);
+    this.strings = createMap(strings, this::setFirstJumboString);
+    this.callSites = createMap(callSites, this::failOnOverflow);
+    this.methodHandles = createMap(methodHandles, this::failOnOverflow);
   }
 
   private void setFirstJumboString(DexString string) {
@@ -74,13 +71,13 @@
   }
 
   private <T extends IndexedDexItem> Reference2IntMap<T> createMap(Collection<T> items,
-      boolean sort, Consumer<T> onUInt16Overflow) {
+      Consumer<T> onUInt16Overflow) {
     if (items.isEmpty()) {
       return null;
     }
     Reference2IntMap<T> map = new Reference2IntLinkedOpenHashMap<>(items.size());
     map.defaultReturnValue(NOT_FOUND);
-    Collection<T> sorted = sort ? items.stream().sorted().collect(Collectors.toList()) : items;
+    Collection<T> sorted = items.stream().sorted().collect(Collectors.toList());
     int index = 0;
     for (T item : sorted) {
       if (index == Constants.U16BIT_MAX + 1) {
diff --git a/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java b/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
new file mode 100644
index 0000000..23d5a38
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
@@ -0,0 +1,184 @@
+// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.dex.MixedSectionCollection;
+import java.util.Arrays;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * List of parameter annotations.
+ *
+ * <p>Due to a javac bug that went unfixed for multiple Java versions, the JVM specification does
+ * not require that the number of entries in the ParameterAnnotations attribute of a method matches
+ * the number of parameters in the method prototype; the number of ParameterAnnotations entries may
+ * be less than the number of prototype parameters for methods on inner classes.
+ *
+ * <p>There are two ways of accessing the parameter annotations:
+ *
+ * <ul>
+ *   <li>Using {@link ParameterAnnotationsList#forEachAnnotation(Consumer)}
+ *   <li>Using {@link ParameterAnnotationsList#size()}, {@link
+ *       ParameterAnnotationsList#isMissing(int)} and {@link ParameterAnnotationsList#get(int)}
+ * </ul>
+ *
+ * <p>The {@link ParameterAnnotationsList#forEachAnnotation(Consumer)} method visits all the {@link
+ * DexAnnotation}s specified in the ParameterAnnotations attribute. In contrast, the {@link
+ * ParameterAnnotationsList#size()} and {@link ParameterAnnotationsList#get(int)} methods may be
+ * used to access the annotations on individual parameters; these methods automatically shift
+ * parameter annotations up to mitigate the javac bug. The {@link
+ * ParameterAnnotationsList#isMissing(int)} accessor is used to determine whether a given parameter
+ * is missing in the ParameterAnnotations attribute.
+ */
+public class ParameterAnnotationsList extends DexItem {
+
+  private static final ParameterAnnotationsList EMPTY_PARAMETER_ANNOTATIONS_LIST =
+      new ParameterAnnotationsList();
+
+  private final DexAnnotationSet[] values;
+  private final int missingParameterAnnotations;
+
+  public static ParameterAnnotationsList empty() {
+    return EMPTY_PARAMETER_ANNOTATIONS_LIST;
+  }
+
+  private ParameterAnnotationsList() {
+    this.values = new DexAnnotationSet[0];
+    this.missingParameterAnnotations = 0;
+  }
+
+  public ParameterAnnotationsList(DexAnnotationSet[] values) {
+    this(values, 0);
+  }
+
+  public ParameterAnnotationsList(DexAnnotationSet[] values, int missingParameterAnnotations) {
+    assert values != null && values.length > 0;
+    this.values = values;
+    this.missingParameterAnnotations = missingParameterAnnotations;
+  }
+
+  @Override
+  public int hashCode() {
+    return Arrays.hashCode(values);
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (this == other) {
+      return true;
+    }
+    if (other instanceof ParameterAnnotationsList) {
+      return Arrays.equals(values, ((ParameterAnnotationsList) other).values);
+    }
+    return false;
+  }
+
+  @Override
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
+    collectAll(indexedItems, values);
+  }
+
+  @Override
+  void collectMixedSectionItems(MixedSectionCollection mixedItems) {
+    // Collect values first so that the annotation sets have sorted themselves before adding this.
+    collectAll(mixedItems, values);
+    mixedItems.add(this);
+  }
+
+  public boolean isEmpty() {
+    return values.length == 0;
+  }
+
+  /** Iterate over the {@link DexAnnotation}s of all parameters. */
+  public void forEachAnnotation(Consumer<DexAnnotation> consumer) {
+    for (DexAnnotationSet parameterAnnotations : values) {
+      for (DexAnnotation annotation : parameterAnnotations.annotations) {
+        consumer.accept(annotation);
+      }
+    }
+  }
+
+  /**
+   * Return the number of parameters in the method prototype, or zero if the method's parameters
+   * have no annotations.
+   */
+  public int size() {
+    return missingParameterAnnotations + values.length;
+  }
+
+  /**
+   * Return the number of parameters specified in the ParameterAnnotations attribute, that is, the
+   * number of parameters for which {@link ParameterAnnotationsList#isMissing(int)} returns false.
+   */
+  public int countNonMissing() {
+    return values.length;
+  }
+
+  /**
+   * Return true if the ParameterAnnotations attribute is missing an entry for this parameter. This
+   * is sometimes the case for the first parameter in a method on an inner class.
+   *
+   * @param i Index of the parameter in the method prototype.
+   */
+  public boolean isMissing(int i) {
+    assert i >= 0;
+    return i < missingParameterAnnotations;
+  }
+
+  /**
+   * Return the annotations on the {@code i}th parameter (indexed according to the method
+   * prototype). If the parameter's annotation list is missing, or {@code i} is not less than the
+   * number of parameters (see {@link ParameterAnnotationsList#isMissing(int)}), {@link
+   * DexAnnotationSet#empty()} is returned.
+   *
+   * @param i Index of the parameter in the method prototype.
+   */
+  public DexAnnotationSet get(int i) {
+    assert i >= 0;
+    int adjustedIndex = i - missingParameterAnnotations;
+    return (0 <= adjustedIndex && adjustedIndex < values.length)
+        ? values[adjustedIndex]
+        : DexAnnotationSet.empty();
+  }
+
+  /** Return a ParameterAnnotationsList extended to the given number of parameters. */
+  public ParameterAnnotationsList withParameterCount(int parameterCount) {
+    assert parameterCount >= size();
+    if (this == EMPTY_PARAMETER_ANNOTATIONS_LIST || parameterCount == size()) {
+      return this;
+    }
+    return new ParameterAnnotationsList(values, parameterCount - values.length);
+  }
+
+  /**
+   * Return a new ParameterAnnotationsList that keeps only the annotations matched by {@code
+   * filter}.
+   */
+  public ParameterAnnotationsList keepIf(Predicate<DexAnnotation> filter) {
+    DexAnnotationSet[] filtered = null;
+    boolean allEmpty = true;
+    for (int i = 0; i < values.length; i++) {
+      DexAnnotationSet updated = values[i].keepIf(filter);
+      if (updated != values[i]) {
+        if (filtered == null) {
+          filtered = values.clone();
+        }
+        filtered[i] = updated;
+      }
+      if (!updated.isEmpty()) {
+        allEmpty = false;
+      }
+    }
+    if (filtered == null) {
+      return this;
+    }
+    if (allEmpty) {
+      return ParameterAnnotationsList.empty();
+    }
+    return new ParameterAnnotationsList(filtered, missingParameterAnnotations);
+  }
+}
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/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index bb13495..1ec23d0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -358,6 +358,15 @@
     return true;
   }
 
+  public boolean hasCatchHandlers() {
+    for (BasicBlock block : blocks) {
+      if (block.hasCatchHandlers()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   private boolean consistentDefUseChains() {
     Set<Value> values = new HashSet<>();
 
@@ -578,16 +587,23 @@
   }
 
   public List<Value> collectArguments() {
+    return collectArguments(false);
+  }
+
+  public List<Value> collectArguments(boolean ignoreReceiver) {
     final List<Value> arguments = new ArrayList<>();
     Iterator<Instruction> iterator = blocks.get(0).iterator();
     while (iterator.hasNext()) {
       Instruction instruction = iterator.next();
       if (instruction.isArgument()) {
-        arguments.add(instruction.asArgument().outValue());
+        Value out = instruction.asArgument().outValue();
+        if (!ignoreReceiver || !out.isThis()) {
+          arguments.add(out);
+        }
       }
     }
     assert arguments.size()
-        == method.method.getArity() + (method.accessFlags.isStatic() ? 0 : 1);
+        == method.method.getArity() + ((method.accessFlags.isStatic() || ignoreReceiver) ? 0 : 1);
     return arguments;
   }
 
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/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index e9504cd..819bb4b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -119,8 +119,19 @@
     assert !needsRangedInvoke(builder);
     int i = 0;
     for (Value value : arguments()) {
-      int register = builder.allocatedRegister(value, getNumber());
-      assert register <= Constants.U4BIT_MAX;
+      // If one of the arguments to the invoke instruction is an argument of the enclosing method
+      // that has been spilled at this location, then we need to take the argument from its
+      // original input register (because the register allocator never inserts moves from an
+      // argument register to a spill register). Note that this is only a problem if an argument
+      // has been spilled to a register that is not the argument's original register.
+      //
+      // For simplicity, we just use the original input register for all arguments if the register
+      // fits in 4 bits.
+      int register = builder.argumentOrAllocateRegister(value, getNumber());
+      if (register + value.requiredRegisters() - 1 > Constants.U4BIT_MAX) {
+        register = builder.allocatedRegister(value, getNumber());
+      }
+      assert register + value.requiredRegisters() - 1 <= Constants.U4BIT_MAX;
       for (int j = 0; j < value.requiredRegisters(); j++) {
         assert i < 5;
         registers[i++] = register++;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index d280dfe..81d4cdc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -131,6 +131,6 @@
 
   @Override
   public InlineAction computeInlining(InliningOracle decider, DexType invocationContext) {
-    return decider.computeForInvokePolymorpic(this, invocationContext);
+    return decider.computeForInvokePolymorphic(this, invocationContext);
   }
 }
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..e68e3d5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -0,0 +1,625 @@
+// 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.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) {
+    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/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 0d87723..31ee706 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.conversion;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.code.FillArrayData;
 import com.android.tools.r8.code.FillArrayDataPayload;
 import com.android.tools.r8.code.FilledNewArray;
@@ -173,8 +172,7 @@
 
   @Override
   public void buildInstruction(
-      IRBuilder builder, int instructionIndex, boolean firstBlockInstruction)
-      throws ApiLevelException {
+      IRBuilder builder, int instructionIndex, boolean firstBlockInstruction) {
     updateCurrentCatchHandlers(instructionIndex);
     updateDebugPosition(instructionIndex, builder);
     currentDexInstruction = code.instructions[instructionIndex];
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..702218e 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
@@ -8,11 +8,13 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexCallSite;
 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 +115,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 +246,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.
@@ -261,18 +296,15 @@
   private final LinkedList<BasicBlock> blocks = new LinkedList<>();
 
   private BasicBlock currentBlock = null;
-
   private final List<BasicBlock.Pair> needGotoToCatchBlocks = new ArrayList<>();
-
   final private ValueNumberGenerator valueNumberGenerator;
-
   private final DexEncodedMethod method;
+  private final AppInfo appInfo;
 
   // Source code to build IR from. Null if already built.
   private SourceCode source;
 
-  boolean throwingInstructionInCurrentBlock = false;
-
+  private boolean throwingInstructionInCurrentBlock = false;
   private final InternalOptions options;
 
   // Pending local reads.
@@ -281,16 +313,17 @@
 
   private int nextBlockNumber = 0;
 
-  public IRBuilder(DexEncodedMethod method, SourceCode source, InternalOptions options) {
-    this(method, source, options, new ValueNumberGenerator());
+  public IRBuilder(DexEncodedMethod method, AppInfo appInfo,
+      SourceCode source, InternalOptions options) {
+    this(method, appInfo, source, options, new ValueNumberGenerator());
   }
 
   public IRBuilder(
-      DexEncodedMethod method,
-      SourceCode source,
+      DexEncodedMethod method, AppInfo appInfo, SourceCode source,
       InternalOptions options, ValueNumberGenerator valueNumberGenerator) {
     assert source != null;
     this.method = method;
+    this.appInfo = appInfo;
     this.source = source;
     this.valueNumberGenerator = valueNumberGenerator;
     this.options = options;
@@ -304,6 +337,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()) {
@@ -320,7 +357,7 @@
    *
    * @return The list of basic blocks. First block is the main entry.
    */
-  public IRCode build() throws ApiLevelException {
+  public IRCode build() {
     assert source != null;
     source.setUp();
 
@@ -479,7 +516,7 @@
     return true;
   }
 
-  private void processWorklist() throws ApiLevelException {
+  private void processWorklist() {
     for (WorklistItem item = ssaWorklist.poll(); item != null; item = ssaWorklist.poll()) {
       if (item.block.isFilled()) {
         continue;
@@ -615,6 +652,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.
@@ -797,8 +835,7 @@
     add(instruction);
   }
 
-  public void addConstMethodHandle(int dest, DexMethodHandle methodHandle)
-      throws ApiLevelException {
+  public void addConstMethodHandle(int dest, DexMethodHandle methodHandle) {
     if (!options.canUseConstantMethodHandle()) {
       throw new ApiLevelException(
           AndroidApiLevel.P,
@@ -810,8 +847,7 @@
     add(instruction);
   }
 
-  public void addConstMethodType(int dest, DexProto methodType)
-      throws ApiLevelException {
+  public void addConstMethodType(int dest, DexProto methodType) {
     if (!options.canUseConstantMethodType()) {
       throw new ApiLevelException(
           AndroidApiLevel.P,
@@ -1002,9 +1038,8 @@
   }
 
   public void addInvoke(
-      Type type, DexItem item, DexProto callSiteProto, List<Value> arguments, boolean itf)
-      throws ApiLevelException {
-    if (type == Invoke.Type.POLYMORPHIC) {
+      Type type, DexItem item, DexProto callSiteProto, List<Value> arguments, boolean itf) {
+    if (type == Type.POLYMORPHIC) {
       assert item instanceof DexMethod;
       if (!options.canUseInvokePolymorphic()) {
         throw new ApiLevelException(
@@ -1019,11 +1054,23 @@
             null /* sourceString */);
       }
     }
+    if (appInfo != null && type == Type.VIRTUAL) {
+      // If an invoke-virtual targets a private method in the current class overriding will
+      // not apply (see jvm spec on method resolution 5.4.3.3 and overriding 5.4.5) and
+      // therefore we use an invoke-direct instead. We need to do this as the Android Runtime
+      // will not allow invoke-virtual of a private method.
+      DexMethod invocationMethod = (DexMethod) item;
+      if (invocationMethod.holder == method.method.holder) {
+        DexEncodedMethod directTarget = appInfo.lookupDirectTarget(invocationMethod);
+        if (directTarget != null && invocationMethod.holder == directTarget.method.holder) {
+          type = Type.DIRECT;
+        }
+      }
+    }
     add(Invoke.create(type, item, callSiteProto, null, arguments, itf));
   }
 
-  public void addInvoke(Type type, DexItem item, DexProto callSiteProto, List<Value> arguments)
-      throws ApiLevelException {
+  public void addInvoke(Type type, DexItem item, DexProto callSiteProto, List<Value> arguments) {
     addInvoke(type, item, callSiteProto, arguments, false);
   }
 
@@ -1032,8 +1079,7 @@
       DexItem item,
       DexProto callSiteProto,
       List<ValueType> types,
-      List<Integer> registers)
-      throws ApiLevelException {
+      List<Integer> registers) {
     addInvoke(type, item, callSiteProto, types, registers, false);
   }
 
@@ -1043,8 +1089,7 @@
       DexProto callSiteProto,
       List<ValueType> types,
       List<Integer> registers,
-      boolean itf)
-      throws ApiLevelException {
+      boolean itf) {
     assert types.size() == registers.size();
     List<Value> arguments = new ArrayList<>(types.size());
     for (int i = 0; i < types.size(); i++) {
@@ -1112,8 +1157,7 @@
       DexMethod method,
       DexProto callSiteProto,
       int argumentRegisterCount,
-      int[] argumentRegisters)
-      throws ApiLevelException {
+      int[] argumentRegisters) {
     // The value of argumentRegisterCount is the number of registers - not the number of values,
     // but it is an upper bound on the number of arguments.
     List<Value> arguments = new ArrayList<>(argumentRegisterCount);
@@ -1140,8 +1184,7 @@
     addInvoke(type, method, callSiteProto, arguments);
   }
 
-  public void addInvokeNewArray(DexType type, int argumentCount, int[] argumentRegisters)
-      throws ApiLevelException {
+  public void addInvokeNewArray(DexType type, int argumentCount, int[] argumentRegisters) {
     String descriptor = type.descriptor.toString();
     assert descriptor.charAt(0) == '[';
     assert descriptor.length() >= 2;
@@ -1160,7 +1203,7 @@
     addInvoke(Invoke.Type.NEW_ARRAY, type, null, arguments);
   }
 
-  public void addMultiNewArray(DexType type, int dest, int[] dimensions) throws ApiLevelException {
+  public void addMultiNewArray(DexType type, int dest, int[] dimensions) {
     assert isGeneratingClassFiles();
     List<Value> arguments = new ArrayList<>(dimensions.length);
     for (int dimension : dimensions) {
@@ -1175,8 +1218,7 @@
       DexMethod method,
       DexProto callSiteProto,
       int argumentCount,
-      int firstArgumentRegister)
-      throws ApiLevelException {
+      int firstArgumentRegister) {
     // The value of argumentCount is the number of registers - not the number of values, but it
     // is an upper bound on the number of arguments.
     List<Value> arguments = new ArrayList<>(argumentCount);
@@ -1203,8 +1245,7 @@
     addInvoke(type, method, callSiteProto, arguments);
   }
 
-  public void addInvokeRangeNewArray(DexType type, int argumentCount, int firstArgumentRegister)
-      throws ApiLevelException {
+  public void addInvokeRangeNewArray(DexType type, int argumentCount, int firstArgumentRegister) {
     String descriptor = type.descriptor.toString();
     assert descriptor.charAt(0) == '[';
     assert descriptor.length() >= 2;
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..889e5d5 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
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.ExcludeDexResources;
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.IncludeAllResources;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
@@ -46,6 +45,7 @@
 import com.android.tools.r8.ir.optimize.NonNullTracker;
 import com.android.tools.r8.ir.optimize.Outliner;
 import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
+import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
 import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -87,6 +87,7 @@
   private final LambdaRewriter lambdaRewriter;
   private final InterfaceMethodRewriter interfaceMethodRewriter;
   private final LambdaMerger lambdaMerger;
+  private final ClassInliner classInliner;
   private final InternalOptions options;
   private final CfgPrinter printer;
   private final GraphLense graphLense;
@@ -160,6 +161,9 @@
       this.identifierNameStringMarker = null;
       this.devirtualizer = null;
     }
+    this.classInliner =
+        (options.enableClassInlining && options.enableInlining && inliner != null)
+            ? new ClassInliner(appInfo.dexItemFactory) : null;
   }
 
   /**
@@ -228,7 +232,7 @@
     }
   }
 
-  private void synthesizeLambdaClasses(Builder<?> builder) throws ApiLevelException {
+  private void synthesizeLambdaClasses(Builder<?> builder) {
     if (lambdaRewriter != null) {
       lambdaRewriter.adjustAccessibility();
       lambdaRewriter.synthesizeLambdaClasses(builder);
@@ -236,15 +240,14 @@
   }
 
   private void desugarInterfaceMethods(
-      Builder<?> builder, InterfaceMethodRewriter.Flavor includeAllResources)
-      throws ApiLevelException {
+      Builder<?> builder, InterfaceMethodRewriter.Flavor includeAllResources) {
     if (interfaceMethodRewriter != null) {
       interfaceMethodRewriter.desugarInterfaceMethods(builder, includeAllResources);
     }
   }
 
   public DexApplication convertToDex(DexApplication application, ExecutorService executor)
-      throws ExecutionException, ApiLevelException {
+      throws ExecutionException {
     removeLambdaDeserializationMethods();
 
     timing.begin("IR conversion");
@@ -342,7 +345,7 @@
     ThreadUtils.awaitFutures(futures);
   }
 
-  void convertMethodToDex(DexEncodedMethod method) throws ApiLevelException {
+  void convertMethodToDex(DexEncodedMethod method) {
     assert options.isGeneratingDex();
     if (method.getCode() != null) {
       boolean matchesMethodFilter = options.methodMatchesFilter(method);
@@ -357,8 +360,7 @@
     }
   }
 
-  public DexApplication optimize(DexApplication application)
-      throws ExecutionException, ApiLevelException {
+  public DexApplication optimize(DexApplication application) throws ExecutionException {
     ExecutorService executor = Executors.newSingleThreadExecutor();
     try {
       return optimize(application, executor);
@@ -367,9 +369,8 @@
     }
   }
 
-  public DexApplication optimize(DexApplication application,
-      ExecutorService executorService)
-      throws ExecutionException, ApiLevelException {
+  public DexApplication optimize(DexApplication application, ExecutorService executorService)
+      throws ExecutionException {
     removeLambdaDeserializationMethods();
     collectLambdaMergingCandidates(application);
 
@@ -452,9 +453,12 @@
     }
   }
 
-  private void finalizeLambdaMerging(DexApplication application,
-      OptimizationFeedback directFeedback, Builder<?> builder, ExecutorService executorService)
-      throws ExecutionException, ApiLevelException {
+  private void finalizeLambdaMerging(
+      DexApplication application,
+      OptimizationFeedback directFeedback,
+      Builder<?> builder,
+      ExecutorService executorService)
+      throws ExecutionException {
     if (lambdaMerger != null) {
       lambdaMerger.applyLambdaClassMapping(
           application, this, directFeedback, builder, executorService);
@@ -507,7 +511,7 @@
     return result;
   }
 
-  private DexProgramClass prepareOutlining() throws ApiLevelException {
+  private DexProgramClass prepareOutlining() {
     if (!outliner.selectMethodsForOutlining()) {
       return null;
     }
@@ -516,7 +520,7 @@
     return outlineClass;
   }
 
-  public void optimizeSynthesizedClass(DexProgramClass clazz) throws ApiLevelException {
+  public void optimizeSynthesizedClass(DexProgramClass clazz) {
     try {
       codeRewriter.enterCachedClass(clazz);
       // Process the generated class, but don't apply any outlining.
@@ -526,7 +530,7 @@
     }
   }
 
-  public void optimizeSynthesizedMethod(DexEncodedMethod method) throws ApiLevelException {
+  public void optimizeSynthesizedMethod(DexEncodedMethod method) {
     // Process the generated method, but don't apply any outlining.
     processMethod(method, ignoreOptimizationFeedback, x -> false, CallSiteInformation.empty(),
         Outliner::noProcessing);
@@ -536,12 +540,12 @@
     return options.useSmaliSyntax ? method.toSmaliString(null) : method.codeToString();
   }
 
-  public void processMethod(DexEncodedMethod method,
+  public void processMethod(
+      DexEncodedMethod method,
       OptimizationFeedback feedback,
       Predicate<DexEncodedMethod> isProcessedConcurrently,
       CallSiteInformation callSiteInformation,
-      BiConsumer<IRCode, DexEncodedMethod> outlineHandler)
-      throws ApiLevelException {
+      BiConsumer<IRCode, DexEncodedMethod> outlineHandler) {
     Code code = method.getCode();
     boolean matchesMethodFilter = options.methodMatchesFilter(method);
     if (code != null && matchesMethodFilter) {
@@ -560,12 +564,12 @@
     }
   }
 
-  private void rewriteCode(DexEncodedMethod method,
+  private void rewriteCode(
+      DexEncodedMethod method,
       OptimizationFeedback feedback,
       Predicate<DexEncodedMethod> isProcessedConcurrently,
       CallSiteInformation callSiteInformation,
-      BiConsumer<IRCode, DexEncodedMethod> outlineHandler)
-      throws ApiLevelException {
+      BiConsumer<IRCode, DexEncodedMethod> outlineHandler) {
     if (options.verbose) {
       options.reporter.info(
           new StringDiagnostic("Processing: " + method.toSourceString()));
@@ -578,7 +582,7 @@
       feedback.markProcessed(method, Constraint.NEVER);
       return;
     }
-    IRCode code = method.buildIR(options);
+    IRCode code = method.buildIR(appInfo, options, appInfo.originFor(method.method.holder));
     if (code == null) {
       feedback.markProcessed(method, Constraint.NEVER);
       return;
@@ -686,6 +690,15 @@
       assert code.isConsistentSSA();
     }
 
+    if (classInliner != null) {
+      assert options.enableInlining && inliner != null;
+      classInliner.processMethodCode(
+          appInfo.withSubtyping(), method, code, isProcessedConcurrently,
+          methodsToInline -> inliner.performForcedInlining(method, code, methodsToInline)
+      );
+      assert code.isConsistentSSA();
+    }
+
     if (options.outline.enabled) {
       outlineHandler.accept(code, method);
       assert code.isConsistentSSA();
@@ -705,6 +718,12 @@
       assert code.isConsistentSSA();
     }
 
+    // Analysis must be done after method is rewritten by logArgumentTypes()
+    codeRewriter.identifyReceiverOnlyUsedForReadingFields(method, code, feedback);
+    if (method.isInstanceInitializer()) {
+      codeRewriter.identifyOnlyInitializesFieldsWithNoOtherSideEffects(method, code, feedback);
+    }
+
     printMethod(code, "Optimized IR (SSA)");
     finalizeIR(method, code, feedback);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 46cc8e9..b5f0a05 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
@@ -29,7 +28,6 @@
 import com.android.tools.r8.ir.conversion.JarState.Local;
 import com.android.tools.r8.ir.conversion.JarState.Slot;
 import com.android.tools.r8.logging.Log;
-import com.android.tools.r8.utils.ThrowingBiConsumer;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
@@ -44,6 +42,7 @@
 import java.util.List;
 import java.util.Queue;
 import java.util.Set;
+import java.util.function.BiConsumer;
 import org.objectweb.asm.Handle;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
@@ -478,8 +477,7 @@
 
   @Override
   public void buildInstruction(
-      IRBuilder builder, int instructionIndex, boolean firstBlockInstruction)
-      throws ApiLevelException {
+      IRBuilder builder, int instructionIndex, boolean firstBlockInstruction) {
     if (instructionIndex == EXCEPTIONAL_SYNC_EXIT_OFFSET) {
       buildExceptionalPostlude(builder);
       return;
@@ -1816,7 +1814,7 @@
 
   // IR instruction building procedures.
 
-  private void build(AbstractInsnNode insn, IRBuilder builder) throws ApiLevelException {
+  private void build(AbstractInsnNode insn, IRBuilder builder) {
     switch (insn.getType()) {
       case AbstractInsnNode.INSN:
         build((InsnNode) insn, builder);
@@ -2540,7 +2538,7 @@
     }
   }
 
-  private void build(MethodInsnNode insn, IRBuilder builder) throws ApiLevelException {
+  private void build(MethodInsnNode insn, IRBuilder builder) {
     // Resolve the target method of the invoke.
     DexMethod method = application.getMethod(insn.owner, insn.name, insn.desc);
 
@@ -2619,8 +2617,7 @@
       Type methodOwner,
       boolean addImplicitReceiver,
       IRBuilder builder,
-      ThrowingBiConsumer<List<ValueType>, List<Integer>, ApiLevelException> creator)
-      throws ApiLevelException {
+      BiConsumer<List<ValueType>, List<Integer>> creator) {
 
     // Build the argument list of the form [owner, param1, ..., paramN].
     // The arguments are in reverse order on the stack, so we pop off the parameters here.
@@ -2657,7 +2654,7 @@
     registers.add(slot.register);
   }
 
-  private void build(InvokeDynamicInsnNode insn, IRBuilder builder) throws ApiLevelException {
+  private void build(InvokeDynamicInsnNode insn, IRBuilder builder) {
     DexCallSite callSite = DexCallSite.fromAsmInvokeDynamic(insn, application, clazz);
 
     buildInvoke(insn.desc, null /* Not needed */,
@@ -2716,7 +2713,7 @@
     // Intentionally empty.
   }
 
-  private void build(LdcInsnNode insn, IRBuilder builder) throws ApiLevelException {
+  private void build(LdcInsnNode insn, IRBuilder builder) {
     if (insn.cst instanceof Type) {
       Type type = (Type) insn.cst;
       if (type.getSort() == Type.METHOD) {
@@ -2781,7 +2778,7 @@
     builder.addSwitch(index, keys, fallthroughOffset, labelOffsets);
   }
 
-  private void build(MultiANewArrayInsnNode insn, IRBuilder builder) throws ApiLevelException {
+  private void build(MultiANewArrayInsnNode insn, IRBuilder builder) {
     // Type of the full array.
     Type arrayType = application.getAsmObjectType(insn.desc);
     DexType dexArrayType = application.getType(arrayType);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
index b2c5cfd..26ca6b2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
@@ -5,7 +5,10 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import java.util.Map;
+import java.util.Set;
 
 public interface OptimizationFeedback {
   void methodReturnsArgument(DexEncodedMethod method, int argument);
@@ -15,4 +18,7 @@
   void markProcessed(DexEncodedMethod method, Constraint state);
   void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
   void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
+  void markReceiverOnlyUsedForReadingFields(DexEncodedMethod method, Set<DexField> fields);
+  void markOnlyInitializesFieldsWithNoOtherSideEffects(
+      DexEncodedMethod method, Map<DexField, Integer> mapping);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
index ac1972b..645df64 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
@@ -5,7 +5,10 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import java.util.Map;
+import java.util.Set;
 
 public class OptimizationFeedbackDirect implements OptimizationFeedback {
 
@@ -43,4 +46,15 @@
   public void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {
     method.markTriggerClassInitBeforeAnySideEffect(mark);
   }
+
+  @Override
+  public void markReceiverOnlyUsedForReadingFields(DexEncodedMethod method, Set<DexField> fields) {
+    method.markReceiverOnlyUsedForReadingFields(fields);
+  }
+
+  @Override
+  public void markOnlyInitializesFieldsWithNoOtherSideEffects(DexEncodedMethod method,
+      Map<DexField, Integer> mapping) {
+    method.markOnlyInitializesFieldsWithNoOtherSideEffects(mapping);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
index 80cb04f..97fe052 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
@@ -5,7 +5,10 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import java.util.Map;
+import java.util.Set;
 
 public class OptimizationFeedbackIgnore implements OptimizationFeedback {
 
@@ -29,4 +32,13 @@
 
   @Override
   public void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {}
+
+  @Override
+  public void markReceiverOnlyUsedForReadingFields(DexEncodedMethod method, Set<DexField> fields) {
+  }
+
+  @Override
+  public void markOnlyInitializesFieldsWithNoOtherSideEffects(DexEncodedMethod method,
+      Map<DexField, Integer> mapping) {
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
index cd8efa7..e95790a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.conversion;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.Position;
@@ -48,8 +47,8 @@
   // Delegates for IR building.
   void buildPrelude(IRBuilder builder);
 
-  void buildInstruction(IRBuilder builder, int instructionIndex, boolean firstBlockInstruction)
-      throws ApiLevelException;
+  void buildInstruction(IRBuilder builder, int instructionIndex, boolean firstBlockInstruction);
+
   void buildPostlude(IRBuilder builder);
 
   // Helper to resolve switch payloads and build switch instructions (dex code only).
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 7befc69..c75456d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -111,7 +111,7 @@
     // Some debuggers (like IntelliJ) automatically skip synthetic methods on single step.
     newFlags.setSynthetic();
     return new DexEncodedMethod(newMethod, newFlags,
-        defaultMethod.annotations, defaultMethod.parameterAnnotations,
+        defaultMethod.annotations, defaultMethod.parameterAnnotationsList,
         new SynthesizedCode(new ForwardMethodSourceCode(
             clazz.type, method.proto, /* static method */ null,
             rewriter.defaultAsMethodOfCompanionClass(method),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 8add960..73c5118 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.desugar;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.DexApplication.Builder;
@@ -325,7 +324,7 @@
    * Move static and default interface methods to companion classes,
    * add missing methods to forward to moved default methods implementation.
    */
-  public void desugarInterfaceMethods(Builder<?> builder, Flavor flavour) throws ApiLevelException {
+  public void desugarInterfaceMethods(Builder<?> builder, Flavor flavour) {
     // Process all classes first. Add missing forwarding methods to
     // replace desugared default interface methods.
     forwardingMethods.addAll(processClasses(builder, flavour));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 898a2b2..1e8d57a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -70,7 +70,7 @@
             || (companionMethod.getArity() == dexCode.getDebugInfo().parameters.length);
 
         companionMethods.add(new DexEncodedMethod(companionMethod,
-            newFlags, virtual.annotations, virtual.parameterAnnotations, code));
+            newFlags, virtual.annotations, virtual.parameterAnnotationsList, code));
 
         // Make the method abstract.
         virtual.accessFlags.setAbstract();
@@ -106,7 +106,7 @@
             + "either be public or private in " + iface.origin;
         companionMethods.add(new DexEncodedMethod(
             rewriter.staticAsMethodOfCompanionClass(direct.method), newFlags,
-            direct.annotations, direct.parameterAnnotations, direct.getCode()));
+            direct.annotations, direct.parameterAnnotationsList, direct.getCode()));
 
       } else {
         if (originalFlags.isPrivate()) {
@@ -129,7 +129,7 @@
               || (companionMethod.getArity() == dexCode.getDebugInfo().parameters.length);
 
           companionMethods.add(new DexEncodedMethod(companionMethod,
-              newFlags, direct.annotations, direct.parameterAnnotations, code));
+              newFlags, direct.annotations, direct.parameterAnnotationsList, code));
 
         } else {
           // Since there are no interface constructors at this point,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 82958da..81407bd 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -4,13 +4,11 @@
 
 package com.android.tools.r8.ir.desugar;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -27,6 +25,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueNull;
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.origin.SynthesizedOrigin;
@@ -171,7 +170,7 @@
             MethodAccessFlags.fromSharedAccessFlags(
                 Constants.ACC_PUBLIC | Constants.ACC_FINAL, false),
             DexAnnotationSet.empty(),
-            DexAnnotationSetRefList.empty(),
+            ParameterAnnotationsList.empty(),
             new SynthesizedCode(new LambdaMainMethodSourceCode(this, mainMethod)));
 
     // Synthesize bridge methods.
@@ -187,7 +186,7 @@
                       | Constants.ACC_BRIDGE,
                   false),
               DexAnnotationSet.empty(),
-              DexAnnotationSetRefList.empty(),
+              ParameterAnnotationsList.empty(),
               new SynthesizedCode(
                   new LambdaBridgeMethodSourceCode(this, mainMethod, bridgeMethod)));
     }
@@ -208,7 +207,7 @@
                     | Constants.ACC_SYNTHETIC,
                 true),
             DexAnnotationSet.empty(),
-            DexAnnotationSetRefList.empty(),
+            ParameterAnnotationsList.empty(),
             new SynthesizedCode(new LambdaConstructorSourceCode(this)));
 
     // Class constructor for stateless lambda classes.
@@ -219,7 +218,7 @@
               MethodAccessFlags.fromSharedAccessFlags(
                   Constants.ACC_SYNTHETIC | Constants.ACC_STATIC, true),
               DexAnnotationSet.empty(),
-              DexAnnotationSetRefList.empty(),
+              ParameterAnnotationsList.empty(),
               new SynthesizedCode(new LambdaClassConstructorSourceCode(this)));
     }
     return methods;
@@ -425,7 +424,7 @@
     }
 
     // Ensure access of the referenced symbol(s).
-    abstract boolean ensureAccessibility() throws ApiLevelException;
+    abstract boolean ensureAccessibility();
 
     DexClass definitionFor(DexType type) {
       return rewriter.converter.appInfo.app.definitionFor(type);
@@ -492,7 +491,7 @@
           // relax its accessibility without making it virtual.
           DexEncodedMethod newMethod = new DexEncodedMethod(
               callTarget, encodedMethod.accessFlags, encodedMethod.annotations,
-              encodedMethod.parameterAnnotations, encodedMethod.getCode());
+              encodedMethod.parameterAnnotationsList, encodedMethod.getCode());
           // TODO(ager): Should we give the new first parameter an actual name? Maybe 'this'?
           encodedMethod.accessFlags.setStatic();
           encodedMethod.accessFlags.unsetPrivate();
@@ -520,7 +519,7 @@
     }
 
     @Override
-    boolean ensureAccessibility() throws ApiLevelException {
+    boolean ensureAccessibility() {
       // Create a static accessor with proper accessibility.
       DexProgramClass accessorClass = programDefinitionFor(callTarget.holder);
       assert accessorClass != null;
@@ -532,7 +531,7 @@
               Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC,
               false);
       DexEncodedMethod accessorEncodedMethod = new DexEncodedMethod(
-          callTarget, accessorFlags, DexAnnotationSet.empty(), DexAnnotationSetRefList.empty(),
+          callTarget, accessorFlags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(),
           new SynthesizedCode(new AccessorMethodSourceCode(LambdaClass.this)));
       accessorClass.setDirectMethods(appendMethod(
           accessorClass.directMethods(), accessorEncodedMethod));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
index 5ea5d0e..45c08fb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
@@ -126,7 +126,10 @@
 
       // `a` is primitive and `b` is a supertype of the boxed type `a`.
       DexType boxedPrimitiveType = getBoxedForPrimitiveType(a);
-      if (b == boxedPrimitiveType || b == factory.objectType) {
+      if (b == boxedPrimitiveType ||
+          b == factory.objectType ||
+          b == factory.serializableType ||
+          b == factory.comparableType) {
         return true;
       }
       return boxedPrimitiveType != factory.boxedCharType
@@ -337,6 +340,8 @@
       DexType boxedFromType = getBoxedForPrimitiveType(fromType);
       if (toType == boxedFromType ||
           toType == factory().objectType ||
+          toType == factory().serializableType ||
+          toType == factory().comparableType ||
           (boxedFromType != factory().booleanType &&
               boxedFromType != factory().charType &&
               toType == factory().boxedNumberType)) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 11ff65b..d6b99a7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.desugar;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexApplication.Builder;
@@ -162,7 +161,7 @@
    * Adjust accessibility of referenced application symbols or
    * creates necessary accessors.
    */
-  public void adjustAccessibility() throws ApiLevelException {
+  public void adjustAccessibility() {
     // For each lambda class perform necessary adjustment of the
     // referenced symbols to make them accessible. This can result in
     // method access relaxation or creation of accessor method.
@@ -172,7 +171,7 @@
   }
 
   /** Generates lambda classes and adds them to the builder. */
-  public void synthesizeLambdaClasses(Builder<?> builder) throws ApiLevelException {
+  public void synthesizeLambdaClasses(Builder<?> builder) {
     for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
       DexProgramClass synthesizedClass = lambdaClass.synthesizeLambdaClass();
       converter.optimizeSynthesizedClass(synthesizedClass);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 85a3ac6..e5c8962 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -47,10 +47,13 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.If.Type;
+import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.InvokeVirtual;
@@ -733,6 +736,122 @@
     }
   }
 
+  public void identifyReceiverOnlyUsedForReadingFields(
+      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+    if (!method.isNonAbstractVirtualMethod()) {
+      return;
+    }
+
+    feedback.markReceiverOnlyUsedForReadingFields(method, null);
+
+    Value instance = code.getThis();
+    if (instance.numberOfPhiUsers() > 0) {
+      return;
+    }
+
+    Set<DexField> fields = Sets.newIdentityHashSet();
+    for (Instruction insn : instance.uniqueUsers()) {
+      if (!insn.isInstanceGet()) {
+        return;
+      }
+      InstanceGet get = insn.asInstanceGet();
+      if (get.dest() == instance || get.object() != instance) {
+        return;
+      }
+      fields.add(get.getField());
+    }
+    feedback.markReceiverOnlyUsedForReadingFields(method, fields);
+  }
+
+  public void identifyOnlyInitializesFieldsWithNoOtherSideEffects(
+      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+    assert method.isInstanceInitializer();
+
+    feedback.markOnlyInitializesFieldsWithNoOtherSideEffects(method, null);
+
+    if (code.hasCatchHandlers()) {
+      return;
+    }
+
+    List<Value> args = code.collectArguments(true /* not interested in receiver */);
+    Map<DexField, Integer> mapping = new IdentityHashMap<>();
+    Value receiver = code.getThis();
+
+    InstructionIterator it = code.instructionIterator();
+    while (it.hasNext()) {
+      Instruction instruction = it.next();
+
+      // Mark an argument.
+      if (instruction.isArgument()) {
+        continue;
+      }
+
+      // Allow super call to java.lang.Object.<init>() on 'this'.
+      if (instruction.isInvokeDirect()) {
+        InvokeDirect invokedDirect = instruction.asInvokeDirect();
+        if (invokedDirect.getInvokedMethod() != dexItemFactory.objectMethods.constructor ||
+            invokedDirect.getReceiver() != receiver) {
+          return;
+        }
+        continue;
+      }
+
+      // Allow final return.
+      if (instruction.isReturn()) {
+        continue;
+      }
+
+      // Allow assignment to this class' fields. If the assigned value is an argument
+      // reference update the mep. Otherwise just allow assigning any value, since all
+      // invalid values should be filtered out at the definitions.
+      if (instruction.isInstancePut()) {
+        InstancePut instancePut = instruction.asInstancePut();
+        DexField field = instancePut.getField();
+        if (instancePut.object() != receiver) {
+          return;
+        }
+
+        Value value = instancePut.value();
+        if (value.isArgument() && !value.isThis()) {
+          assert (args.contains(value));
+          mapping.put(field, args.indexOf(value));
+        } else {
+          mapping.remove(field);
+        }
+        continue;
+      }
+
+      // Allow non-throwing constants.
+      if (instruction.isConstInstruction()) {
+        if (instruction.instructionTypeCanThrow()) {
+          return;
+        }
+        continue;
+      }
+
+      // Allow goto instructions jumping to the next block.
+      if (instruction.isGoto()) {
+        if (instruction.getBlock().getNumber() + 1 !=
+            instruction.asGoto().getTarget().getNumber()) {
+          return;
+        }
+        continue;
+      }
+
+      // Allow binary and unary instructions if they don't throw.
+      if (instruction.isBinop() || instruction.isUnop()) {
+        if (instruction.instructionTypeCanThrow()) {
+          return;
+        }
+        continue;
+      }
+
+      return;
+    }
+
+    feedback.markOnlyInitializesFieldsWithNoOtherSideEffects(method, mapping);
+  }
+
   /**
    * An enum used to classify instructions according to a particular effect that they produce.
    *
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
new file mode 100644
index 0000000..845e17b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -0,0 +1,349 @@
+// 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.optimize;
+
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokePolymorphic;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.conversion.CallSiteInformation;
+import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.logging.Log;
+import java.util.ListIterator;
+import java.util.function.Predicate;
+
+final class DefaultInliningOracle implements InliningOracle, InliningStrategy {
+
+  private final Inliner inliner;
+  private final DexEncodedMethod method;
+  private final IRCode code;
+  private final TypeEnvironment typeEnvironment;
+  private final CallSiteInformation callSiteInformation;
+  private final Predicate<DexEncodedMethod> isProcessedConcurrently;
+  private final InliningInfo info;
+  private final int inliningInstructionLimit;
+  private int instructionAllowance;
+
+  DefaultInliningOracle(
+      Inliner inliner,
+      DexEncodedMethod method,
+      IRCode code,
+      TypeEnvironment typeEnvironment,
+      CallSiteInformation callSiteInformation,
+      Predicate<DexEncodedMethod> isProcessedConcurrently,
+      int inliningInstructionLimit,
+      int inliningInstructionAllowance) {
+    this.inliner = inliner;
+    this.method = method;
+    this.code = code;
+    this.typeEnvironment = typeEnvironment;
+    this.callSiteInformation = callSiteInformation;
+    this.isProcessedConcurrently = isProcessedConcurrently;
+    info = Log.ENABLED ? new InliningInfo(method) : null;
+    this.inliningInstructionLimit = inliningInstructionLimit;
+    this.instructionAllowance = inliningInstructionAllowance;
+  }
+
+  @Override
+  public void finish() {
+    if (Log.ENABLED) {
+      Log.debug(getClass(), info.toString());
+    }
+  }
+
+  private DexEncodedMethod validateCandidate(InvokeMethod invoke, DexType invocationContext) {
+    DexEncodedMethod candidate =
+        invoke.computeSingleTarget(inliner.appInfo, typeEnvironment, invocationContext);
+    if ((candidate == null)
+        || (candidate.getCode() == null)
+        || inliner.appInfo.definitionFor(candidate.method.getHolder()).isLibraryClass()) {
+      if (info != null) {
+        info.exclude(invoke, "No inlinee");
+      }
+      return null;
+    }
+    // Ignore the implicit receiver argument.
+    int numberOfArguments =
+        invoke.arguments().size() - (invoke.isInvokeMethodWithReceiver() ? 1 : 0);
+    if (numberOfArguments != candidate.method.getArity()) {
+      if (info != null) {
+        info.exclude(invoke, "Argument number mismatch");
+      }
+      return null;
+    }
+    return candidate;
+  }
+
+  private Reason computeInliningReason(DexEncodedMethod target) {
+    if (target.getOptimizationInfo().forceInline()) {
+      return Reason.FORCE;
+    }
+    if (inliner.appInfo.hasLiveness()
+        && inliner.appInfo.withLiveness().alwaysInline.contains(target)) {
+      return Reason.ALWAYS;
+    }
+    if (callSiteInformation.hasSingleCallSite(target)) {
+      return Reason.SINGLE_CALLER;
+    }
+    if (isDoubleInliningTarget(target)) {
+      return Reason.DUAL_CALLER;
+    }
+    return Reason.SIMPLE;
+  }
+
+  private boolean canInlineStaticInvoke(DexEncodedMethod method, DexEncodedMethod target) {
+    // Only proceed with inlining a static invoke if:
+    // - the holder for the target equals the holder for the method, or
+    // - the target method always triggers class initialization of its holder before any other side
+    //   effect (hence preserving class initialization semantics).
+    // - there is no non-trivial class initializer.
+    DexType targetHolder = target.method.getHolder();
+    if (method.method.getHolder() == targetHolder) {
+      return true;
+    }
+    DexClass clazz = inliner.appInfo.definitionFor(targetHolder);
+    assert clazz != null;
+    if (target.getOptimizationInfo().triggersClassInitBeforeAnySideEffect()) {
+      return true;
+    }
+    return classInitializationHasNoSideffects(targetHolder);
+  }
+
+  /**
+   * Check for class initializer side effects when loading this class, as inlining might remove the
+   * load operation.
+   * <p>
+   * See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-5.html#jvms-5.5.
+   * <p>
+   * For simplicity, we are conservative and consider all interfaces, not only the ones with default
+   * methods.
+   */
+  private boolean classInitializationHasNoSideffects(DexType classToCheck) {
+    DexClass clazz = inliner.appInfo.definitionFor(classToCheck);
+    if ((clazz == null)
+        || clazz.hasNonTrivialClassInitializer()
+        || clazz.defaultValuesForStaticFieldsMayTriggerAllocation()) {
+      return false;
+    }
+    for (DexType iface : clazz.interfaces.values) {
+      if (!classInitializationHasNoSideffects(iface)) {
+        return false;
+      }
+    }
+    return clazz.superType == null || classInitializationHasNoSideffects(clazz.superType);
+  }
+
+  private synchronized boolean isDoubleInliningTarget(DexEncodedMethod candidate) {
+    // 10 is found from measuring.
+    return inliner.isDoubleInliningTarget(callSiteInformation, candidate)
+        && candidate.getCode().estimatedSizeForInliningAtMost(10);
+  }
+
+  private boolean passesInliningConstraints(InvokeMethod invoke, DexEncodedMethod candidate,
+      Reason reason) {
+    if (method == candidate) {
+      // Cannot handle recursive inlining at this point.
+      // Force inlined method should never be recursive.
+      assert !candidate.getOptimizationInfo().forceInline();
+      if (info != null) {
+        info.exclude(invoke, "direct recursion");
+      }
+      return false;
+    }
+
+    if (reason != Reason.FORCE && isProcessedConcurrently.test(candidate)) {
+      if (info != null) {
+        info.exclude(invoke, "is processed in parallel");
+      }
+      return false;
+    }
+
+    // Abort inlining attempt if method -> target access is not right.
+    if (!inliner.hasInliningAccess(method, candidate)) {
+      if (info != null) {
+        info.exclude(invoke, "target does not have right access");
+      }
+      return false;
+    }
+
+    DexClass holder = inliner.appInfo.definitionFor(candidate.method.getHolder());
+    if (holder.isInterface()) {
+      // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception at
+      // runtime.
+      if (info != null) {
+        info.exclude(invoke, "Do not inline target if method holder is an interface class");
+      }
+      return false;
+    }
+
+    if (holder.isLibraryClass()) {
+      // Library functions should not be inlined.
+      return false;
+    }
+
+    // Don't inline if target is synchronized.
+    if (candidate.accessFlags.isSynchronized()) {
+      if (info != null) {
+        info.exclude(invoke, "target is synchronized");
+      }
+      return false;
+    }
+
+    // Attempt to inline a candidate that is only called twice.
+    if ((reason == Reason.DUAL_CALLER) && (inliner.doubleInlining(method, candidate) == null)) {
+      if (info != null) {
+        info.exclude(invoke, "target is not ready for double inlining");
+      }
+      return false;
+    }
+
+    if (reason == Reason.SIMPLE) {
+      // If we are looking for a simple method, only inline if actually simple.
+      Code code = candidate.getCode();
+      if (!code.estimatedSizeForInliningAtMost(inliningInstructionLimit)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Override
+  public InlineAction computeForInvokeWithReceiver(
+      InvokeMethodWithReceiver invoke, DexType invocationContext) {
+    DexEncodedMethod candidate = validateCandidate(invoke, invocationContext);
+    if (candidate == null || inliner.isBlackListed(candidate.method)) {
+      return null;
+    }
+
+    // We can only inline an instance method call if we preserve the null check semantic (which
+    // would throw NullPointerException if the receiver is null). Therefore we can inline only if
+    // one of the following conditions is true:
+    // * the candidate inlinee checks null receiver before any side effect
+    // * the receiver is known to be non-null
+    boolean receiverIsNeverNull =
+        !typeEnvironment.getLatticeElement(invoke.getReceiver()).isNullable();
+    if (!receiverIsNeverNull
+        && !candidate.getOptimizationInfo().checksNullReceiverBeforeAnySideEffect()) {
+      if (info != null) {
+        info.exclude(invoke, "receiver for candidate can be null");
+      }
+      return null;
+    }
+
+    Reason reason = computeInliningReason(candidate);
+    if (!candidate.isInliningCandidate(method, reason, inliner.appInfo)) {
+      // Abort inlining attempt if the single target is not an inlining candidate.
+      if (info != null) {
+        info.exclude(invoke, "target is not identified for inlining");
+      }
+      return null;
+    }
+
+    if (!passesInliningConstraints(invoke, candidate, reason)) {
+      return null;
+    }
+
+    if (info != null) {
+      info.include(invoke.getType(), candidate);
+    }
+    return new InlineAction(candidate, invoke, reason);
+  }
+
+  @Override
+  public InlineAction computeForInvokeStatic(InvokeStatic invoke, DexType invocationContext) {
+    DexEncodedMethod candidate = validateCandidate(invoke, invocationContext);
+    if (candidate == null || inliner.isBlackListed(candidate.method)) {
+      return null;
+    }
+
+    Reason reason = computeInliningReason(candidate);
+    // Determine if this should be inlined no matter how big it is.
+    if (!candidate.isInliningCandidate(method, reason, inliner.appInfo)) {
+      // Abort inlining attempt if the single target is not an inlining candidate.
+      if (info != null) {
+        info.exclude(invoke, "target is not identified for inlining");
+      }
+      return null;
+    }
+
+    // Abort inlining attempt if we can not guarantee class for static target has been initialized.
+    if (!canInlineStaticInvoke(method, candidate)) {
+      if (info != null) {
+        info.exclude(invoke, "target is static but we cannot guarantee class has been initialized");
+      }
+      return null;
+    }
+
+    if (!passesInliningConstraints(invoke, candidate, reason)) {
+      return null;
+    }
+
+    if (info != null) {
+      info.include(invoke.getType(), candidate);
+    }
+    return new InlineAction(candidate, invoke, reason);
+  }
+
+  @Override
+  public InlineAction computeForInvokePolymorphic(
+      InvokePolymorphic invoke, DexType invocationContext) {
+    // TODO: No inlining of invoke polymorphic for now.
+    if (info != null) {
+      info.exclude(invoke, "inlining through invoke signature polymorpic is not supported");
+    }
+    return null;
+  }
+
+  @Override
+  public void ensureMethodProcessed(DexEncodedMethod target, IRCode inlinee) {
+    if (!target.isProcessed()) {
+      if (Log.ENABLED) {
+        Log.verbose(getClass(), "Forcing extra inline on " + target.toSourceString());
+      }
+      inliner.performInlining(
+          target, inlinee, typeEnvironment, isProcessedConcurrently, callSiteInformation);
+    }
+  }
+
+  @Override
+  public boolean exceededAllowance() {
+    return instructionAllowance < 0;
+  }
+
+  @Override
+  public void markInlined(IRCode inlinee) {
+    instructionAllowance -= inliner.numberOfInstructions(inlinee);
+  }
+
+  @Override
+  public ListIterator<BasicBlock> updateTypeInformationIfNeeded(IRCode inlinee,
+      ListIterator<BasicBlock> blockIterator, BasicBlock block, BasicBlock invokeSuccessor) {
+    if (inliner.options.enableNonNullTracking) {
+      // Move the cursor back to where the inlinee blocks are added.
+      blockIterator = code.blocks.listIterator(code.blocks.indexOf(block));
+      // Kick off the tracker to add non-null IRs only to the inlinee blocks.
+      new NonNullTracker()
+          .addNonNullInPart(code, blockIterator, inlinee.blocks::contains);
+      // Move the cursor forward to where the inlinee blocks end.
+      blockIterator = code.blocks.listIterator(code.blocks.indexOf(invokeSuccessor));
+    }
+    // Update type env for inlined blocks.
+    typeEnvironment.analyzeBlocks(inlinee.topologicallySortedBlocks());
+    // TODO(b/69964136): need a test where refined env in inlinee affects the caller.
+    return blockIterator;
+  }
+
+  @Override
+  public DexType getReceiverTypeIfKnown(InvokeMethod invoke) {
+    return null; // Maybe improve later.
+  }
+}
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..1b68f7d 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
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -40,7 +39,7 @@
     this.options = options;
   }
 
-  public AppInfoWithLiveness run() throws ApiLevelException {
+  public AppInfoWithLiveness run() {
     for (DexProgramClass clazz : appInfo.classes()) {
       processClasses(clazz);
     }
@@ -50,13 +49,13 @@
     return appInfo;
   }
 
-  private void processClasses(DexProgramClass clazz) throws ApiLevelException {
+  private void processClasses(DexProgramClass clazz) {
     // Enum classes are flagged as such. Also, for library classes, the ordinals are not known.
     if (!clazz.accessFlags.isEnum() || clazz.isLibraryClass() || !clazz.hasClassInitializer()) {
       return;
     }
     DexEncodedMethod initializer = clazz.getClassInitializer();
-    IRCode code = initializer.getCode().buildIR(initializer, options);
+    IRCode code = initializer.getCode().buildIR(initializer, appInfo, 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/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
new file mode 100644
index 0000000..86d40d9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -0,0 +1,85 @@
+// 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.optimize;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokePolymorphic;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import java.util.ListIterator;
+import java.util.Map;
+
+final class ForcedInliningOracle implements InliningOracle, InliningStrategy {
+  private final DexEncodedMethod method;
+  private final Map<InvokeMethodWithReceiver, Inliner.InliningInfo> invokesToInline;
+
+  ForcedInliningOracle(DexEncodedMethod method,
+      Map<InvokeMethodWithReceiver, Inliner.InliningInfo> invokesToInline) {
+    this.method = method;
+    this.invokesToInline = invokesToInline;
+  }
+
+  @Override
+  public void finish() {
+  }
+
+  @Override
+  public InlineAction computeForInvokeWithReceiver(
+      InvokeMethodWithReceiver invoke, DexType invocationContext) {
+    Inliner.InliningInfo info = invokesToInline.get(invoke);
+    if (info == null) {
+      return null;
+    }
+
+    assert method != info.target;
+    return new InlineAction(info.target, invoke, Reason.FORCE);
+  }
+
+  @Override
+  public InlineAction computeForInvokeStatic(
+      InvokeStatic invoke, DexType invocationContext) {
+    return null; // Not yet supported.
+  }
+
+  @Override
+  public InlineAction computeForInvokePolymorphic(
+      InvokePolymorphic invoke, DexType invocationContext) {
+    return null; // Not yet supported.
+  }
+
+  @Override
+  public void ensureMethodProcessed(DexEncodedMethod target, IRCode inlinee) {
+    assert target.isProcessed();
+  }
+
+  @Override
+  public ListIterator<BasicBlock> updateTypeInformationIfNeeded(IRCode inlinee,
+      ListIterator<BasicBlock> blockIterator, BasicBlock block, BasicBlock invokeSuccessor) {
+    return blockIterator;
+  }
+
+  @Override
+  public boolean exceededAllowance() {
+    return false; // Never exceeds allowance.
+  }
+
+  @Override
+  public void markInlined(IRCode inlinee) {
+  }
+
+  @Override
+  public DexType getReceiverTypeIfKnown(InvokeMethod invoke) {
+    assert invoke.isInvokeMethodWithReceiver();
+    Inliner.InliningInfo info = invokesToInline.get(invoke.asInvokeMethodWithReceiver());
+    assert info != null;
+    return info.receiverType;
+  }
+}
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..d4cb8a8 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
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexClass;
@@ -21,6 +20,7 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
@@ -28,7 +28,7 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 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;
@@ -42,10 +42,11 @@
 import java.util.stream.Collectors;
 
 public class Inliner {
+  private static final int INITIAL_INLINING_INSTRUCTION_ALLOWANCE = 1500;
 
   protected final AppInfoWithLiveness appInfo;
   private final GraphLense graphLense;
-  private final InternalOptions options;
+  final InternalOptions options;
 
   // State for inlining methods which are known to be called twice.
   private boolean applyDoubleInlining = false;
@@ -149,8 +150,8 @@
     return target;
   }
 
-  public synchronized void processDoubleInlineCallers(IRConverter converter,
-      OptimizationFeedback feedback) throws ApiLevelException {
+  public synchronized void processDoubleInlineCallers(
+      IRConverter converter, OptimizationFeedback feedback) {
     if (doubleInlineCallers.size() > 0) {
       applyDoubleInlining = true;
       List<DexEncodedMethod> methods = doubleInlineCallers
@@ -265,10 +266,10 @@
         AppInfoWithSubtyping appInfo,
         GraphLense graphLense,
         InternalOptions options,
-        Position callerPosition)
-        throws ApiLevelException {
+        Position callerPosition) {
       // 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(appInfo, options, generator, callerPosition, origin);
       if (!target.isProcessed()) {
         new LensCodeRewriter(graphLense, appInfo).rewrite(code, target);
       }
@@ -276,7 +277,7 @@
     }
   }
 
-  private int numberOfInstructions(IRCode code) {
+  final int numberOfInstructions(IRCode code) {
     int numOfInstructions = 0;
     for (BasicBlock block : code.blocks) {
       numOfInstructions += block.getInstructions().size();
@@ -362,36 +363,61 @@
     return true;
   }
 
+  public static class InliningInfo {
+    public final DexEncodedMethod target;
+    public final DexType receiverType; // null, if unknown
+
+    public InliningInfo(DexEncodedMethod target, DexType receiverType) {
+      this.target = target;
+      this.receiverType = receiverType;
+    }
+  }
+
+  public void performForcedInlining(
+      DexEncodedMethod method,
+      IRCode code,
+      Map<InvokeMethodWithReceiver, InliningInfo> invokesToInline) {
+
+    ForcedInliningOracle oracle = new ForcedInliningOracle(method, invokesToInline);
+    performInliningImpl(oracle, oracle, method, code);
+  }
+
   public void performInlining(
       DexEncodedMethod method,
       IRCode code,
       TypeEnvironment typeEnvironment,
       Predicate<DexEncodedMethod> isProcessedConcurrently,
-      CallSiteInformation callSiteInformation)
-      throws ApiLevelException {
-    int instruction_allowance = 1500;
-    instruction_allowance -= numberOfInstructions(code);
-    if (instruction_allowance < 0) {
-      return;
-    }
-    InliningOracle oracle =
-        new InliningOracle(
+      CallSiteInformation callSiteInformation) {
+
+    DefaultInliningOracle oracle =
+        new DefaultInliningOracle(
             this,
             method,
+            code,
             typeEnvironment,
             callSiteInformation,
             isProcessedConcurrently,
-            options.inliningInstructionLimit);
+            options.inliningInstructionLimit,
+            INITIAL_INLINING_INSTRUCTION_ALLOWANCE - numberOfInstructions(code));
+
+    performInliningImpl(oracle, oracle, method, code);
+  }
+
+  private void performInliningImpl(
+      InliningStrategy strategy, InliningOracle oracle, DexEncodedMethod method, IRCode code) {
+    if (strategy.exceededAllowance()) {
+      return;
+    }
 
     List<BasicBlock> blocksToRemove = new ArrayList<>();
     ListIterator<BasicBlock> blockIterator = code.listIterator();
-    while (blockIterator.hasNext() && (instruction_allowance >= 0)) {
+    while (blockIterator.hasNext() && !strategy.exceededAllowance()) {
       BasicBlock block = blockIterator.next();
       if (blocksToRemove.contains(block)) {
         continue;
       }
       InstructionListIterator iterator = block.listIterator();
-      while (iterator.hasNext() && (instruction_allowance >= 0)) {
+      while (iterator.hasNext() && !strategy.exceededAllowance()) {
         Instruction current = iterator.next();
         if (current.isInvokeMethod()) {
           InvokeMethod invoke = current.asInvokeMethod();
@@ -414,49 +440,27 @@
               if (block.hasCatchHandlers() && inlinee.computeNormalExitBlocks().isEmpty()) {
                 continue;
               }
+
               // If this code did not go through the full pipeline, apply inlining to make sure
               // that force inline targets get processed.
-              if (!target.isProcessed()) {
-                assert result.reason == Reason.FORCE;
-                if (Log.ENABLED) {
-                  Log.verbose(getClass(), "Forcing extra inline on " + target.toSourceString());
-                }
-                performInlining(
-                    target, inlinee, typeEnvironment, isProcessedConcurrently, callSiteInformation);
-              }
+              strategy.ensureMethodProcessed(target, inlinee);
+
               // Make sure constructor inlining is legal.
               assert !target.isClassInitializer();
               if (target.isInstanceInitializer()
                   && !legalConstructorInline(method, invoke, inlinee)) {
                 continue;
               }
-              DexType downcast = null;
-              if (invoke.isInvokeMethodWithReceiver()) {
-                // If the invoke has a receiver but the declared method holder is different
-                // from the computed target holder, inlining requires a downcast of the receiver.
-                if (target.method.getHolder() != invoke.getInvokedMethod().getHolder()) {
-                  downcast = result.target.method.getHolder();
-                }
-              }
+              DexType downcast = createDowncastIfNeeded(strategy, invoke, target);
               // Inline the inlinee code in place of the invoke instruction
               // Back up before the invoke instruction.
               iterator.previous();
-              instruction_allowance -= numberOfInstructions(inlinee);
-              if (instruction_allowance >= 0 || result.ignoreInstructionBudget()) {
+              strategy.markInlined(inlinee);
+              if (!strategy.exceededAllowance() || result.ignoreInstructionBudget()) {
                 BasicBlock invokeSuccessor =
                     iterator.inlineInvoke(code, inlinee, blockIterator, blocksToRemove, downcast);
-                if (options.enableNonNullTracking) {
-                  // Move the cursor back to where the inlinee blocks are added.
-                  blockIterator = code.blocks.listIterator(code.blocks.indexOf(block));
-                  // Kick off the tracker to add non-null IRs only to the inlinee blocks.
-                  new NonNullTracker()
-                      .addNonNullInPart(code, blockIterator, inlinee.blocks::contains);
-                  // Move the cursor forward to where the inlinee blocks end.
-                  blockIterator = code.blocks.listIterator(code.blocks.indexOf(invokeSuccessor));
-                }
-                // Update type env for inlined blocks.
-                typeEnvironment.analyzeBlocks(inlinee.topologicallySortedBlocks());
-                // TODO(b/69964136): need a test where refined env in inlinee affects the caller.
+                blockIterator = strategy.
+                    updateTypeInformationIfNeeded(inlinee, blockIterator, block, invokeSuccessor);
 
                 // If we inlined the invoke from a bridge method, it is no longer a bridge method.
                 if (method.accessFlags.isBridge()) {
@@ -479,4 +483,22 @@
     code.removeAllTrivialPhis();
     assert code.isConsistentSSA();
   }
+
+  private DexType createDowncastIfNeeded(
+      InliningStrategy strategy, InvokeMethod invoke, DexEncodedMethod target) {
+    if (invoke.isInvokeMethodWithReceiver()) {
+      // If the invoke has a receiver but the actual type of the receiver is different
+      // from the computed target holder, inlining requires a downcast of the receiver.
+      DexType assumedReceiverType = strategy.getReceiverTypeIfKnown(invoke);
+      if (assumedReceiverType == null) {
+        // In case we don't know exact type of the receiver we use declared
+        // method holder as a fallback.
+        assumedReceiverType = invoke.getInvokedMethod().getHolder();
+      }
+      if (assumedReceiverType != target.method.getHolder()) {
+        return target.method.getHolder();
+      }
+    }
+    return null;
+  }
 }
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..08f8045 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
@@ -1,297 +1,28 @@
 // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
+
 package com.android.tools.r8.ir.optimize;
 
-import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
-import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokePolymorphic;
 import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.conversion.CallSiteInformation;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
-import com.android.tools.r8.logging.Log;
-import java.util.function.Predicate;
 
 /**
- * The InliningOracle contains information needed for when inlining
- * other methods into @method.
+ * The InliningOracle contains information needed for when inlining other methods into @method.
  */
-public class InliningOracle {
+public interface InliningOracle {
 
-  private final Inliner inliner;
-  private final DexEncodedMethod method;
-  private final TypeEnvironment typeEnvironment;
-  private final CallSiteInformation callSiteInformation;
-  private final Predicate<DexEncodedMethod> isProcessedConcurrently;
-  private final InliningInfo info;
-  private final int inliningInstructionLimit;
+  void finish();
 
-  InliningOracle(
-      Inliner inliner,
-      DexEncodedMethod method,
-      TypeEnvironment typeEnvironment,
-      CallSiteInformation callSiteInformation,
-      Predicate<DexEncodedMethod> isProcessedConcurrently,
-      int inliningInstructionLimit) {
-    this.inliner = inliner;
-    this.method = method;
-    this.typeEnvironment = typeEnvironment;
-    this.callSiteInformation = callSiteInformation;
-    this.isProcessedConcurrently = isProcessedConcurrently;
-    info = Log.ENABLED ? new InliningInfo(method) : null;
-    this.inliningInstructionLimit = inliningInstructionLimit;
-  }
+  InlineAction computeForInvokeWithReceiver(
+      InvokeMethodWithReceiver invoke, DexType invocationContext);
 
-  void finish() {
-    if (Log.ENABLED) {
-      Log.debug(getClass(), info.toString());
-    }
-  }
+  InlineAction computeForInvokeStatic(
+      InvokeStatic invoke, DexType invocationContext);
 
-  private DexEncodedMethod validateCandidate(InvokeMethod invoke, DexType invocationContext) {
-    DexEncodedMethod candidate =
-        invoke.computeSingleTarget(inliner.appInfo, typeEnvironment, invocationContext);
-    if ((candidate == null)
-        || (candidate.getCode() == null)
-        || inliner.appInfo.definitionFor(candidate.method.getHolder()).isLibraryClass()) {
-      if (info != null) {
-        info.exclude(invoke, "No inlinee");
-      }
-      return null;
-    }
-    // Ignore the implicit receiver argument.
-    int numberOfArguments =
-        invoke.arguments().size() - (invoke.isInvokeMethodWithReceiver() ? 1 : 0);
-    if (numberOfArguments != candidate.method.getArity()) {
-      if (info != null) {
-        info.exclude(invoke, "Argument number mismatch");
-      }
-      return null;
-    }
-    return candidate;
-  }
-
-  private Reason computeInliningReason(DexEncodedMethod target) {
-    if (target.getOptimizationInfo().forceInline()) {
-      return Reason.FORCE;
-    }
-    if (inliner.appInfo.hasLiveness()
-        && inliner.appInfo.withLiveness().alwaysInline.contains(target)) {
-      return Reason.ALWAYS;
-    }
-    if (callSiteInformation.hasSingleCallSite(target)) {
-      return Reason.SINGLE_CALLER;
-    }
-    if (isDoubleInliningTarget(target)) {
-      return Reason.DUAL_CALLER;
-    }
-    return Reason.SIMPLE;
-  }
-
-  private boolean canInlineStaticInvoke(DexEncodedMethod method, DexEncodedMethod target) {
-    // Only proceed with inlining a static invoke if:
-    // - the holder for the target equals the holder for the method, or
-    // - the target method always triggers class initialization of its holder before any other side
-    //   effect (hence preserving class initialization semantics).
-    // - there is no non-trivial class initializer.
-    DexType targetHolder = target.method.getHolder();
-    if (method.method.getHolder() == targetHolder) {
-      return true;
-    }
-    DexClass clazz = inliner.appInfo.definitionFor(targetHolder);
-    assert clazz != null;
-    if (target.getOptimizationInfo().triggersClassInitBeforeAnySideEffect()) {
-      return true;
-    }
-    return classInitializationHasNoSideffects(targetHolder);
-  }
-
-  /**
-   * Check for class initializer side effects when loading this class, as inlining might remove the
-   * load operation.
-   * <p>
-   * See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-5.html#jvms-5.5.
-   * <p>
-   * For simplicity, we are conservative and consider all interfaces, not only the ones with default
-   * methods.
-   */
-  private boolean classInitializationHasNoSideffects(DexType classToCheck) {
-    DexClass clazz = inliner.appInfo.definitionFor(classToCheck);
-    if ((clazz == null)
-        || clazz.hasNonTrivialClassInitializer()
-        || clazz.defaultValuesForStaticFieldsMayTriggerAllocation()) {
-      return false;
-    }
-    for (DexType iface : clazz.interfaces.values) {
-      if (!classInitializationHasNoSideffects(iface)) {
-        return false;
-      }
-    }
-    return clazz.superType == null || classInitializationHasNoSideffects(clazz.superType);
-  }
-
-  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);
-  }
-
-  private boolean passesInliningConstraints(InvokeMethod invoke, DexEncodedMethod candidate,
-      Reason reason) {
-    if (method == candidate) {
-      // Cannot handle recursive inlining at this point.
-      // Force inlined method should never be recursive.
-      assert !candidate.getOptimizationInfo().forceInline();
-      if (info != null) {
-        info.exclude(invoke, "direct recursion");
-      }
-      return false;
-    }
-
-    if (reason != Reason.FORCE && isProcessedConcurrently.test(candidate)) {
-      if (info != null) {
-        info.exclude(invoke, "is processed in parallel");
-      }
-      return false;
-    }
-
-    // Abort inlining attempt if method -> target access is not right.
-    if (!inliner.hasInliningAccess(method, candidate)) {
-      if (info != null) {
-        info.exclude(invoke, "target does not have right access");
-      }
-      return false;
-    }
-
-    DexClass holder = inliner.appInfo.definitionFor(candidate.method.getHolder());
-    if (holder.isInterface()) {
-      // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception at
-      // runtime.
-      if (info != null) {
-        info.exclude(invoke, "Do not inline target if method holder is an interface class");
-      }
-      return false;
-    }
-
-    if (holder.isLibraryClass()) {
-      // Library functions should not be inlined.
-      return false;
-    }
-
-    // Don't inline if target is synchronized.
-    if (candidate.accessFlags.isSynchronized()) {
-      if (info != null) {
-        info.exclude(invoke, "target is synchronized");
-      }
-      return false;
-    }
-
-    // Attempt to inline a candidate that is only called twice.
-    if ((reason == Reason.DUAL_CALLER) && (inliner.doubleInlining(method, candidate) == null)) {
-      if (info != null) {
-        info.exclude(invoke, "target is not ready for double inlining");
-      }
-      return false;
-    }
-
-    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) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  public InlineAction computeForInvokeWithReceiver(
-      InvokeMethodWithReceiver invoke, DexType invocationContext) {
-    DexEncodedMethod candidate = validateCandidate(invoke, invocationContext);
-    if (candidate == null || inliner.isBlackListed(candidate.method)) {
-      return null;
-    }
-
-    // We can only inline an instance method call if we preserve the null check semantic (which
-    // would throw NullPointerException if the receiver is null). Therefore we can inline only if
-    // one of the following conditions is true:
-    // * the candidate inlinee checks null receiver before any side effect
-    // * the receiver is known to be non-null
-    boolean receiverIsNeverNull =
-        !typeEnvironment.getLatticeElement(invoke.getReceiver()).isNullable();
-    if (!receiverIsNeverNull
-        && !candidate.getOptimizationInfo().checksNullReceiverBeforeAnySideEffect()) {
-      if (info != null) {
-        info.exclude(invoke, "receiver for candidate can be null");
-      }
-      return null;
-    }
-
-    Reason reason = computeInliningReason(candidate);
-    if (!candidate.isInliningCandidate(method, reason, inliner.appInfo)) {
-      // Abort inlining attempt if the single target is not an inlining candidate.
-      if (info != null) {
-        info.exclude(invoke, "target is not identified for inlining");
-      }
-      return null;
-    }
-
-    if (!passesInliningConstraints(invoke, candidate, reason)) {
-      return null;
-    }
-
-    if (info != null) {
-      info.include(invoke.getType(), candidate);
-    }
-    return new InlineAction(candidate, invoke, reason);
-  }
-
-  public InlineAction computeForInvokeStatic(InvokeStatic invoke, DexType invocationContext) {
-    DexEncodedMethod candidate = validateCandidate(invoke, invocationContext);
-    if (candidate == null || inliner.isBlackListed(candidate.method)) {
-      return null;
-    }
-
-    Reason reason = computeInliningReason(candidate);
-    // Determine if this should be inlined no matter how big it is.
-    if (!candidate.isInliningCandidate(method, reason, inliner.appInfo)) {
-      // Abort inlining attempt if the single target is not an inlining candidate.
-      if (info != null) {
-        info.exclude(invoke, "target is not identified for inlining");
-      }
-      return null;
-    }
-
-    // Abort inlining attempt if we can not guarantee class for static target has been initialized.
-    if (!canInlineStaticInvoke(method, candidate)) {
-      if (info != null) {
-        info.exclude(invoke, "target is static but we cannot guarantee class has been initialized");
-      }
-      return null;
-    }
-
-    if (!passesInliningConstraints(invoke, candidate, reason)) {
-      return null;
-    }
-
-    if (info != null) {
-      info.include(invoke.getType(), candidate);
-    }
-    return new InlineAction(candidate, invoke, reason);
-  }
-
-  public InlineAction computeForInvokePolymorpic(
-      InvokePolymorphic invoke, DexType invocationContext) {
-    // TODO: No inlining of invoke polymorphic for now.
-    if (info != null) {
-      info.exclude(invoke, "inlining through invoke signature polymorpic is not supported");
-    }
-    return null;
-  }
+  InlineAction computeForInvokePolymorphic(
+      InvokePolymorphic invoke, DexType invocationContext);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
new file mode 100644
index 0000000..7b88b38
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
@@ -0,0 +1,25 @@
+// 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.optimize;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import java.util.ListIterator;
+
+interface InliningStrategy {
+  boolean exceededAllowance();
+
+  void markInlined(IRCode inlinee);
+
+  void ensureMethodProcessed(DexEncodedMethod target, IRCode inlinee);
+
+  ListIterator<BasicBlock> updateTypeInformationIfNeeded(IRCode inlinee,
+      ListIterator<BasicBlock> blockIterator, BasicBlock block, BasicBlock invokeSuccessor);
+
+  DexType getReceiverTypeIfKnown(InvokeMethod invoke);
+}
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..bd53adf 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
@@ -4,14 +4,13 @@
 
 package com.android.tools.r8.ir.optimize;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -22,6 +21,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.Add;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -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,10 +1003,10 @@
     }
 
     @Override
-    public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
-        throws ApiLevelException {
+    public IRCode buildIR(
+        DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
       OutlineSourceCode source = new OutlineSourceCode(outline);
-      IRBuilder builder = new IRBuilder(encodedMethod, source, options);
+      IRBuilder builder = new IRBuilder(encodedMethod, appInfo, source, options);
       return builder.build();
     }
 
@@ -1056,7 +1057,7 @@
       DexString methodName = dexItemFactory.createString(OutlineOptions.METHOD_PREFIX + count);
       DexMethod method = outline.buildMethod(type, methodName);
       direct[count] = new DexEncodedMethod(method, methodAccess, DexAnnotationSet.empty(),
-          DexAnnotationSetRefList.empty(), new OutlineCode(outline));
+          ParameterAnnotationsList.empty(), new OutlineCode(outline));
       generatedOutlines.put(outline, method);
       count++;
     }
@@ -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..a586355 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
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
@@ -75,7 +74,7 @@
     intArrayType = appInfo.dexItemFactory.createType("[I");
   }
 
-  public AppInfoWithLiveness run() throws ApiLevelException {
+  public AppInfoWithLiveness run() {
     for (DexProgramClass clazz : appInfo.classes()) {
       processClasses(clazz);
     }
@@ -85,7 +84,7 @@
     return appInfo;
   }
 
-  private void processClasses(DexProgramClass clazz) throws ApiLevelException {
+  private void processClasses(DexProgramClass clazz) {
     // Switchmap classes are synthetic and have a class initializer.
     if (!clazz.accessFlags.isSynthetic() && !clazz.hasClassInitializer()) {
       return;
@@ -93,7 +92,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(appInfo, options, clazz.origin);
       switchMapFields.forEach(field -> extractSwitchMap(field, initializer));
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
new file mode 100644
index 0000000..9ff44eb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -0,0 +1,384 @@
+// 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.optimize.classinliner;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+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.IRCode;
+import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.google.common.collect.Streams;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+public final class ClassInliner {
+  private final DexItemFactory factory;
+  private final ConcurrentHashMap<DexType, Boolean> knownClasses = new ConcurrentHashMap<>();
+
+  private static final Map<DexField, Integer> NO_MAPPING = new IdentityHashMap<>();
+
+  public interface InlinerAction {
+    void inline(Map<InvokeMethodWithReceiver, InliningInfo> methods);
+  }
+
+  public ClassInliner(DexItemFactory factory) {
+    this.factory = factory;
+  }
+
+  // Process method code and inline eligible class instantiations, in short:
+  //
+  // - collect all 'new-instance' instructions in the original code. Note that class
+  // inlining, if happens, mutates code and can add 'new-instance' instructions.
+  // Processing them as well is possible, but does not seem to have much value.
+  //
+  // - for each 'new-instance' we check if it is eligible for inlining, i.e:
+  //     -> the class of the new instance is 'eligible' (see computeClassEligible(...))
+  //     -> the instance is initialized with 'eligible' constructor (see
+  //        onlyInitializesFieldsWithNoOtherSideEffects flag in method's optimization
+  //        info); eligible constructor also defines a set of instance fields directly
+  //        initialized with parameter values, called field initialization mapping below
+  //     -> has only 'eligible' uses, i.e:
+  //          * it is a receiver of a field read if the field is present in the
+  //            field initialization mapping
+  //          * it is a receiver of virtual or interface call with single target being
+  //            a method only reading fields in the current field initialization mapping
+  //
+  // - inline eligible 'new-instance' instructions, i.e:
+  //     -> force inline methods called on the instance (which may introduce additional
+  //        instance field reads, but only for fields present in the current field
+  //        initialization mapping)
+  //     -> replace instance field reads with appropriate values passed to the constructor
+  //        according to field initialization mapping
+  //     -> remove constructor call
+  //     -> remove 'new-instance' instructions
+  //
+  // For example:
+  //
+  // Original code:
+  //   class C {
+  //     static class L {
+  //       final int x;
+  //       L(int x) {
+  //         this.x = x;
+  //       }
+  //       int getX() {
+  //         return x;
+  //       }
+  //     }
+  //     static int method1() {
+  //       return new L(1).x;
+  //     }
+  //     static int method2() {
+  //       return new L(1).getX();
+  //     }
+  //   }
+  //
+  // Code after class C is 'inlined':
+  //   class C {
+  //     static int method1() {
+  //       return 1;
+  //     }
+  //     static int method2() {
+  //       return 1;
+  //     }
+  //   }
+  //
+  public final void processMethodCode(
+      AppInfoWithSubtyping appInfo,
+      DexEncodedMethod method,
+      IRCode code,
+      Predicate<DexEncodedMethod> isProcessedConcurrently,
+      InlinerAction inliner) {
+
+    // Collect all the new-instance instructions in the code before inlining.
+    List<NewInstance> newInstances = Streams.stream(code.instructionIterator())
+        .filter(Instruction::isNewInstance)
+        .map(Instruction::asNewInstance)
+        .collect(Collectors.toList());
+
+    nextNewInstance:
+    for (NewInstance newInstance : newInstances) {
+      Value eligibleInstance = newInstance.outValue();
+      if (eligibleInstance == null) {
+        continue;
+      }
+
+      DexType eligibleClass = newInstance.clazz;
+      if (!isClassEligible(appInfo, eligibleClass)) {
+        continue;
+      }
+
+      // No Phi users.
+      if (eligibleInstance.numberOfPhiUsers() > 0) {
+        continue;
+      }
+
+      Set<Instruction> uniqueUsers = eligibleInstance.uniqueUsers();
+
+      // Find an initializer invocation.
+      InvokeDirect eligibleInitCall = null;
+      Map<DexField, Integer> mappings = null;
+      for (Instruction user : uniqueUsers) {
+        if (!user.isInvokeDirect()) {
+          continue;
+        }
+
+        InvokeDirect candidate = user.asInvokeDirect();
+        DexMethod candidateInit = candidate.getInvokedMethod();
+        if (factory.isConstructor(candidateInit) &&
+            candidate.inValues().lastIndexOf(eligibleInstance) == 0) {
+
+          if (candidateInit.holder != eligibleClass) {
+            // Inlined constructor call? We won't get field initialization mapping in this
+            // case, but since we only support eligible classes extending java.lang.Object,
+            // it's safe to assume an empty mapping.
+            if (candidateInit.holder == factory.objectType) {
+              mappings = Collections.emptyMap();
+            }
+
+          } else {
+            // Is it a call to an *eligible* constructor?
+            mappings = getConstructorFieldMappings(appInfo, candidateInit, isProcessedConcurrently);
+          }
+
+          eligibleInitCall = candidate;
+          break;
+        }
+      }
+
+      if (mappings == null) {
+        continue;
+      }
+
+      // Check all regular users.
+      Map<InvokeMethodWithReceiver, InliningInfo> methodCalls = new IdentityHashMap<>();
+
+      for (Instruction user : uniqueUsers) {
+        if (user == eligibleInitCall) {
+          continue /* next user */;
+        }
+
+        if (user.isInstanceGet()) {
+          InstanceGet instanceGet = user.asInstanceGet();
+          if (mappings.containsKey(instanceGet.getField())) {
+            continue /* next user */;
+          }
+
+          // Not replaceable field read.
+          continue nextNewInstance;
+        }
+
+        if (user.isInvokeVirtual() || user.isInvokeInterface()) {
+          InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver();
+          if (invoke.inValues().lastIndexOf(eligibleInstance) > 0) {
+            continue nextNewInstance; // Instance must only be passes as a receiver.
+          }
+
+          DexEncodedMethod singleTarget =
+              findSingleTarget(appInfo, invoke, eligibleClass);
+          if (singleTarget == null) {
+            continue nextNewInstance;
+          }
+          if (isProcessedConcurrently.test(singleTarget)) {
+            continue nextNewInstance;
+          }
+          if (method == singleTarget) {
+            continue nextNewInstance; // Don't inline itself.
+          }
+
+          if (!singleTarget.getOptimizationInfo()
+              .isReceiverOnlyUsedForReadingFields(mappings.keySet())) {
+            continue nextNewInstance; // Target must be trivial.
+          }
+
+          if (!singleTarget.isInliningCandidate(method, Reason.SIMPLE, appInfo)) {
+            // We won't be able to inline it here.
+
+            // Note that there may be some false negatives here since the method may
+            // reference private fields of its class which are supposed to be replaced
+            // with arguments after inlining. We should try and improve it later.
+
+            // Using -allowaccessmodification mitigates this.
+            continue nextNewInstance;
+          }
+
+          methodCalls.put(invoke, new InliningInfo(singleTarget, eligibleClass));
+          continue /* next user */;
+        }
+
+        continue nextNewInstance; // Unsupported user.
+      }
+
+      // Force-inline of method invocation if any.
+      inlineAllCalls(inliner, methodCalls);
+      assert assertOnlyConstructorAndFieldReads(eligibleInstance, eligibleInitCall, mappings);
+
+      // Replace all field reads with arguments passed to the constructor.
+      patchFieldReads(eligibleInstance, eligibleInitCall, mappings);
+      assert assertOnlyConstructor(eligibleInstance, eligibleInitCall);
+
+      // Remove constructor call and new-instance instructions.
+      removeInstruction(eligibleInitCall);
+      removeInstruction(newInstance);
+      code.removeAllTrivialPhis();
+    }
+  }
+
+  private void inlineAllCalls(
+      InlinerAction inliner, Map<InvokeMethodWithReceiver, InliningInfo> methodCalls) {
+    if (!methodCalls.isEmpty()) {
+      inliner.inline(methodCalls);
+    }
+  }
+
+  private void patchFieldReads(
+      Value instance, InvokeDirect invokeMethod, Map<DexField, Integer> mappings) {
+    for (Instruction user : instance.uniqueUsers()) {
+      if (!user.isInstanceGet()) {
+        continue;
+      }
+      InstanceGet fieldRead = user.asInstanceGet();
+
+      // Replace the field read with
+      assert mappings.containsKey(fieldRead.getField());
+      Value arg = invokeMethod.inValues().get(1 + mappings.get(fieldRead.getField()));
+      assert arg != null;
+      Value value = fieldRead.outValue();
+      if (value != null) {
+        value.replaceUsers(arg);
+        assert value.numberOfAllUsers() == 0;
+      }
+
+      // Remove instruction.
+      removeInstruction(fieldRead);
+    }
+  }
+
+  private void removeInstruction(Instruction instruction) {
+    instruction.inValues().forEach(v -> v.removeUser(instruction));
+    instruction.getBlock().removeInstruction(instruction);
+  }
+
+  private boolean assertOnlyConstructorAndFieldReads(
+      Value instance, InvokeDirect invokeMethod, Map<DexField, Integer> mappings) {
+    for (Instruction user : instance.uniqueUsers()) {
+      if (user != invokeMethod &&
+          !(user.isInstanceGet() && mappings.containsKey(user.asFieldInstruction().getField()))) {
+        throw new Unreachable("Not all calls are inlined!");
+      }
+    }
+    return true;
+  }
+
+  private boolean assertOnlyConstructor(Value instance, InvokeDirect invokeMethod) {
+    for (Instruction user : instance.uniqueUsers()) {
+      if (user != invokeMethod) {
+        throw new Unreachable("Not all field reads are substituted!");
+      }
+    }
+    return true;
+  }
+
+  private DexEncodedMethod findSingleTarget(
+      AppInfo appInfo, InvokeMethodWithReceiver invoke, DexType instanceType) {
+
+    // We don't use computeSingleTarget(...) on invoke since it sometimes fails to
+    // find the single target, while this code may be more successful since we exactly
+    // know what is the actual type of the receiver.
+
+    // Note that we also intentionally limit ourselves to methods directly defined in
+    // the instance's class. This may be improved later.
+
+    DexClass clazz = appInfo.definitionFor(instanceType);
+    if (clazz != null) {
+      DexMethod callee = invoke.getInvokedMethod();
+      for (DexEncodedMethod candidate : clazz.virtualMethods()) {
+        if (candidate.method.name == callee.name && candidate.method.proto == callee.proto) {
+          return candidate;
+        }
+      }
+    }
+    return null;
+  }
+
+  private Map<DexField, Integer> getConstructorFieldMappings(
+      AppInfo appInfo, DexMethod init, Predicate<DexEncodedMethod> isProcessedConcurrently) {
+    assert isClassEligible(appInfo, init.holder);
+
+    DexEncodedMethod definition = appInfo.definitionFor(init);
+    if (definition == null) {
+      return NO_MAPPING;
+    }
+
+    if (isProcessedConcurrently.test(definition)) {
+      return NO_MAPPING;
+    }
+
+    if (definition.accessFlags.isAbstract() || definition.accessFlags.isNative()) {
+      return NO_MAPPING;
+    }
+
+    return definition.getOptimizationInfo().onlyInitializesFieldsWithNoOtherSideEffects();
+  }
+
+  private boolean isClassEligible(AppInfo appInfo, DexType clazz) {
+    Boolean eligible = knownClasses.get(clazz);
+    if (eligible == null) {
+      Boolean computed = computeClassEligible(appInfo, clazz);
+      Boolean existing = knownClasses.putIfAbsent(clazz, computed);
+      assert existing == null || existing == computed;
+      eligible = existing == null ? computed : existing;
+    }
+    return eligible;
+  }
+
+  // Class is eligible for this optimization. Eligibility implementation:
+  //   - not an abstract or interface
+  //   - directly extends java.lang.Object
+  //   - does not declare finalizer
+  //   - does not trigger any static initializers
+  private boolean computeClassEligible(AppInfo appInfo, DexType clazz) {
+    DexClass definition = appInfo.definitionFor(clazz);
+    if (definition == null || definition.isLibraryClass() ||
+        definition.accessFlags.isAbstract() || definition.accessFlags.isInterface()) {
+      return false;
+    }
+
+    // Must directly extend Object.
+    if (definition.superType != factory.objectType) {
+      return false;
+    }
+
+    // Class must not define finalizer.
+    for (DexEncodedMethod method : definition.virtualMethods()) {
+      if (method.method.name == factory.finalizeMethodName &&
+          method.method.proto == factory.objectMethods.finalize.proto) {
+        return false;
+      }
+    }
+
+    // Check for static initializers in this class or any of interfaces it implements.
+    return !appInfo.canTriggerStaticInitializer(clazz);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index 03daf44..d1b56f2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.optimize.lambda;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
@@ -187,9 +186,13 @@
     }
   }
 
-  public final void applyLambdaClassMapping(DexApplication app,
-      IRConverter converter, OptimizationFeedback feedback, Builder<?> builder,
-      ExecutorService executorService) throws ExecutionException, ApiLevelException {
+  public final void applyLambdaClassMapping(
+      DexApplication app,
+      IRConverter converter,
+      OptimizationFeedback feedback,
+      Builder<?> builder,
+      ExecutorService executorService)
+      throws ExecutionException {
     if (lambdas.isEmpty()) {
       return;
     }
@@ -294,8 +297,7 @@
     }
   }
 
-  private void rewriteLambdaReferences(
-      IRConverter converter, OptimizationFeedback feedback) throws ApiLevelException {
+  private void rewriteLambdaReferences(IRConverter converter, OptimizationFeedback feedback) {
     List<DexEncodedMethod> methods =
         methodsToReprocess
             .stream()
@@ -327,12 +329,12 @@
 
     for (DexEncodedMethod method : clazz.directMethods()) {
       lambdaInvalidator.accept(method.annotations);
-      lambdaInvalidator.accept(method.parameterAnnotations);
+      lambdaInvalidator.accept(method.parameterAnnotationsList);
       lambdaInvalidator.accept(method.method, clazz.type);
     }
     for (DexEncodedMethod method : clazz.virtualMethods()) {
       lambdaInvalidator.accept(method.annotations);
-      lambdaInvalidator.accept(method.parameterAnnotations);
+      lambdaInvalidator.accept(method.parameterAnnotationsList);
       lambdaInvalidator.accept(method.method, clazz.type);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaTypeVisitor.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaTypeVisitor.java
index 4f56306..05c71e6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaTypeVisitor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaTypeVisitor.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexEncodedAnnotation;
 import com.android.tools.r8.graph.DexField;
@@ -24,6 +23,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
 import com.android.tools.r8.graph.DexValue.DexValueMethodType;
 import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
@@ -116,10 +116,8 @@
     }
   }
 
-  void accept(DexAnnotationSetRefList annotationSetRefList) {
-    for (DexAnnotationSet annotationSet : annotationSetRefList.values) {
-      accept(annotationSet);
-    }
+  void accept(ParameterAnnotationsList parameterAnnotationsList) {
+    parameterAnnotationsList.forEachAnnotation(this::accept);
   }
 
   private void accept(DexAnnotation annotation) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
index d441a00..1fe0317 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -19,6 +18,7 @@
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroupClassBuilder;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
@@ -113,7 +113,7 @@
             factory.createMethod(group.getGroupClassType(), methodProto, methodName),
             accessFlags,
             isMainMethod ? id.mainMethodAnnotations : DexAnnotationSet.empty(),
-            isMainMethod ? id.mainMethodParamAnnotations : DexAnnotationSetRefList.empty(),
+            isMainMethod ? id.mainMethodParamAnnotations : ParameterAnnotationsList.empty(),
             new SynthesizedCode(
                 new KotlinLambdaVirtualMethodSourceCode(factory, group.getGroupClassType(),
                     methodProto, group.getLambdaIdField(factory), implMethods))));
@@ -159,7 +159,7 @@
         factory.createMethod(groupClassType, initializerProto, factory.constructorMethodName),
         CONSTRUCTOR_FLAGS_RELAXED,  // always create access-relaxed constructor.
         DexAnnotationSet.empty(),
-        DexAnnotationSetRefList.empty(),
+        ParameterAnnotationsList.empty(),
         new SynthesizedCode(createInstanceInitializerSourceCode(groupClassType, initializerProto)));
 
     // Static class initializer for stateless lambdas.
@@ -170,7 +170,7 @@
               factory.classConstructorMethodName),
           CLASS_INITIALIZER_FLAGS,
           DexAnnotationSet.empty(),
-          DexAnnotationSetRefList.empty(),
+          ParameterAnnotationsList.empty(),
           new SynthesizedCode(new ClassInitializerSourceCode(factory, group)));
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java
index 0540bef..996099b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java
@@ -5,13 +5,13 @@
 package com.android.tools.r8.ir.optimize.lambda.kotlin;
 
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroupId;
 
@@ -41,7 +41,7 @@
   final DexString mainMethodName;
   final DexProto mainMethodProto;
   final DexAnnotationSet mainMethodAnnotations;
-  final DexAnnotationSetRefList mainMethodParamAnnotations;
+  final ParameterAnnotationsList mainMethodParamAnnotations;
 
   final EnclosingMethodAttribute enclosing;
 
@@ -61,7 +61,7 @@
     this.mainMethodName = mainMethod.method.name;
     this.mainMethodProto = mainMethod.method.proto;
     this.mainMethodAnnotations = mainMethod.annotations;
-    this.mainMethodParamAnnotations = mainMethod.parameterAnnotations;
+    this.mainMethodParamAnnotations = mainMethod.parameterAnnotationsList;
     this.innerClassAccess = inner != null ? inner.getAccess() : MISSING_INNER_CLASS_ATTRIBUTE;
     this.enclosing = enclosing;
     this.hash = computeHashCode();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
index f5698aa..42e0bb1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
@@ -191,7 +191,7 @@
       throw new LambdaStructureError("unexpected method annotations [" +
           method.annotations.toSmaliString() + "] on " + method.method.toSourceString());
     }
-    if (!method.parameterAnnotations.isEmpty()) {
+    if (!method.parameterAnnotationsList.isEmpty()) {
       throw new LambdaStructureError("unexpected method parameters annotations [" +
           method.annotations.toSmaliString() + "] on " + method.method.toSourceString());
     }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index fd334e9..bb27c08 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -390,14 +390,10 @@
         // Compute the final change in locals and insert it before nextInstruction.
         boolean localsChanged = !ending.isEmpty() || !starting.isEmpty();
         if (localsChanged) {
-          boolean skipChange =
-              nextInstruction == nextInstruction.getBlock().exit() && nextInstruction.isGoto();
-          if (!skipChange) {
-            DebugLocalsChange change = createLocalsChange(ending, starting);
-            if (change != null) {
-              // Insert the DebugLocalsChange instruction before nextInstruction.
-              instructionIterator.add(change);
-            }
+          DebugLocalsChange change = createLocalsChange(ending, starting);
+          if (change != null) {
+            // Insert the DebugLocalsChange instruction before nextInstruction.
+            instructionIterator.add(change);
           }
           // Create new maps for the next DebugLocalsChange instruction.
           ending = new Int2ReferenceOpenHashMap<>();
@@ -1152,6 +1148,10 @@
   }
 
   private int getSpillRegister(LiveIntervals intervals) {
+    if (intervals.isArgumentInterval()) {
+      return intervals.getSplitParent().getRegister();
+    }
+
     int register = maxRegisterNumber + 1;
     increaseCapacity(maxRegisterNumber + intervals.requiredRegisters());
     assert registersAreFree(register, intervals.getType().isWide());
@@ -1287,9 +1287,10 @@
     return false;
   }
 
-  private boolean longOverlappingLong(int register1, int register2) {
-    return register1 == register2 || register1 == (register2 + 1)
-        || (register1 + 1) == register2 || (register1 + 1) == (register2 + 1);
+  // Check if the two longs are half-overlapping, that is first register of one is the second
+  // register of the other.
+  private boolean longHalfOverlappingLong(int register1, int register2) {
+    return register1 == (register2 + 1) || (register1 + 1) == register2;
   }
 
   private boolean isLongResultOverlappingLongOperands(
@@ -1305,7 +1306,8 @@
     // The dalvik bug is actually only for overlap with the second operand, For now we
     // make sure that there is no overlap with either register of either operand. Some vendor
     // optimization have bees seen to need this more conservative check.
-    return longOverlappingLong(register, leftReg) || longOverlappingLong(register, rightReg);
+    return longHalfOverlappingLong(register, leftReg)
+        || longHalfOverlappingLong(register, rightReg);
   }
 
   // Intervals overlap a move exception interval if one of the splits of the intervals does.
@@ -1482,14 +1484,7 @@
       // of finding another candidate to spill via allocateBlockedRegister.
       if (!unhandledInterval.getUses().first().hasConstraint()) {
         int nextConstrainedPosition = unhandledInterval.firstUseWithConstraint().getPosition();
-        int register;
-        // Arguments are always in the argument registers, so for arguments just use that register
-        // for the unconstrained prefix. For everything else, get a spill register.
-        if (unhandledInterval.isArgumentInterval()) {
-          register = unhandledInterval.getSplitParent().getRegister();
-        } else {
-          register = getSpillRegister(unhandledInterval);
-        }
+        int register = getSpillRegister(unhandledInterval);
         LiveIntervals split = unhandledInterval.splitBefore(nextConstrainedPosition);
         assignFreeRegisterToUnhandledInterval(unhandledInterval, register);
         unhandled.add(split);
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index 7e398e6..74600f4 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -88,6 +88,9 @@
   }
 
   public void setSpilled(boolean value) {
+    // Check that we always spill arguments to their original register.
+    assert getRegister() != NO_REGISTER;
+    assert !(value && isArgumentInterval()) || getRegister() == getSplitParent().getRegister();
     spilled = value;
   }
 
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..a5807c3 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
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.ir.synthetic;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.UseRegistry;
@@ -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,9 +38,9 @@
   }
 
   @Override
-  public final IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
-      throws ApiLevelException {
-    return new IRBuilder(encodedMethod, sourceCode, options).build();
+  public final IRCode buildIR(
+      DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
+    return new IRBuilder(encodedMethod, appInfo, sourceCode, options).build();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
index 03206b5..80a439a 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
@@ -6,7 +6,6 @@
 
 import static com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo.NO_THROW;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexProto;
@@ -18,9 +17,9 @@
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.SourceCode;
-import com.android.tools.r8.utils.ThrowingConsumer;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 public abstract class SyntheticSourceCode implements SourceCode {
@@ -43,7 +42,7 @@
   private Value[] paramValues;
 
   // Instruction constructors
-  private List<ThrowingConsumer<IRBuilder, ApiLevelException>> constructors = new ArrayList<>();
+  private List<Consumer<IRBuilder>> constructors = new ArrayList<>();
   private List<Predicate<IRBuilder>> traceEvents = new ArrayList<>();
 
   protected SyntheticSourceCode(DexType receiver, DexProto proto) {
@@ -63,12 +62,11 @@
     }
   }
 
-  protected final void add(ThrowingConsumer<IRBuilder, ApiLevelException> constructor) {
+  protected final void add(Consumer<IRBuilder> constructor) {
     add(constructor, doesNotEndBlock);
   }
 
-  protected final void add(
-      ThrowingConsumer<IRBuilder, ApiLevelException> constructor, Predicate<IRBuilder> traceEvent) {
+  protected final void add(Consumer<IRBuilder> constructor, Predicate<IRBuilder> traceEvent) {
     constructors.add(constructor);
     traceEvents.add(traceEvent);
   }
@@ -189,8 +187,7 @@
 
   @Override
   public final void buildInstruction(
-      IRBuilder builder, int instructionIndex, boolean firstBlockInstruction)
-      throws ApiLevelException {
+      IRBuilder builder, int instructionIndex, boolean firstBlockInstruction) {
     constructors.get(instructionIndex).accept(builder);
   }
 
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index e49ad85..2970fb4 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedAnnotation;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -34,6 +33,7 @@
 import com.android.tools.r8.graph.DexValue.UnknownDexValue;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.JarClassFileReader;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.ProguardMapSupplier;
 import com.android.tools.r8.utils.ExceptionUtils;
@@ -252,7 +252,7 @@
       }
     }
     writeAnnotations(visitor::visitAnnotation, method.annotations.annotations);
-    writeParameterAnnotations(visitor, method.parameterAnnotations);
+    writeParameterAnnotations(visitor, method.parameterAnnotationsList);
     if (!method.accessFlags.isAbstract() && !method.accessFlags.isNative()) {
       writeCode(method.getCode(), visitor);
     }
@@ -260,21 +260,21 @@
   }
 
   private void writeParameterAnnotations(
-      MethodVisitor visitor, DexAnnotationSetRefList parameterAnnotations) {
-    int missingParameterAnnotations = parameterAnnotations.getMissingParameterAnnotations();
-    for (int i = 0; i < missingParameterAnnotations; i++) {
-      AnnotationVisitor av =
-          visitor.visitParameterAnnotation(i, JarClassFileReader.SYNTHETIC_ANNOTATION, false);
-      if (av != null) {
-        av.visitEnd();
+      MethodVisitor visitor, ParameterAnnotationsList parameterAnnotations) {
+    for (int i = 0; i < parameterAnnotations.size(); i++) {
+      if (parameterAnnotations.isMissing(i)) {
+        AnnotationVisitor av =
+            visitor.visitParameterAnnotation(i, JarClassFileReader.SYNTHETIC_ANNOTATION, false);
+        if (av != null) {
+          av.visitEnd();
+        }
+      } else {
+        int iFinal = i;
+        writeAnnotations(
+            (d, vis) -> visitor.visitParameterAnnotation(iFinal, d, vis),
+            parameterAnnotations.get(i).annotations);
       }
     }
-    for (int i = 0; i < parameterAnnotations.values.length; i++) {
-      int parameterIndex = i + missingParameterAnnotations;
-      writeAnnotations(
-          (d, vis) -> visitor.visitParameterAnnotation(parameterIndex, d, vis),
-          parameterAnnotations.values[i].annotations);
-    }
   }
 
   private interface AnnotationConsumer {
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 30a5418..5b908af 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -110,9 +110,9 @@
     return (MethodSignature) canonicalizeSignature(signature);
   }
 
-  public Signature getRenamedFieldSignature(DexField field) {
+  public FieldSignature getRenamedFieldSignature(DexField field) {
     String type = deobfuscateType(field.type.toDescriptorString());
-    return canonicalizeSignature(new FieldSignature(field.name.toString(), type));
+    return (FieldSignature) canonicalizeSignature(new FieldSignature(field.name.toString(), type));
   }
 
   /**
@@ -231,6 +231,20 @@
     return memberNaming.signature;
   }
 
+  public FieldSignature originalSignatureOf(DexField field) {
+    String decoded = descriptorToJavaType(field.clazz.descriptor.toString());
+    FieldSignature memberSignature = getRenamedFieldSignature(field);
+    ClassNaming classNaming = getClassNaming(decoded);
+    if (classNaming == null) {
+      return memberSignature;
+    }
+    MemberNaming memberNaming = classNaming.lookup(memberSignature);
+    if (memberNaming == null) {
+      return memberSignature;
+    }
+    return (FieldSignature) memberNaming.signature;
+  }
+
   public String originalNameOf(DexType clazz) {
     return deobfuscateType(clazz.descriptor.toString());
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 871812a..28ca9fc 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -5,8 +5,6 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -15,9 +13,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.function.Predicate;
 
 public class AnnotationRemover {
 
@@ -127,20 +122,20 @@
     keep.ensureValid(options.forceProguardCompatibility, compatibility);
     for (DexProgramClass clazz : appInfo.classes()) {
       stripAttributes(clazz);
-      clazz.annotations = stripAnnotations(clazz.annotations, this::filterAnnotations);
+      clazz.annotations = clazz.annotations.keepIf(this::filterAnnotations);
       clazz.forEachMethod(this::processMethod);
       clazz.forEachField(this::processField);
     }
   }
 
   private void processMethod(DexEncodedMethod method) {
-    method.annotations = stripAnnotations(method.annotations, this::filterAnnotations);
-    method.parameterAnnotations = stripAnnotations(method.parameterAnnotations,
-        this::filterParameterAnnotations);
+    method.annotations = method.annotations.keepIf(this::filterAnnotations);
+    method.parameterAnnotationsList =
+        method.parameterAnnotationsList.keepIf(this::filterParameterAnnotations);
   }
 
   private void processField(DexEncodedField field) {
-      field.annotations = stripAnnotations(field.annotations, this::filterAnnotations);
+    field.annotations = field.annotations.keepIf(this::filterAnnotations);
   }
 
   private void stripAttributes(DexProgramClass clazz) {
@@ -152,52 +147,4 @@
     }
   }
 
-  private DexAnnotationSetRefList stripAnnotations(DexAnnotationSetRefList annotations,
-      Predicate<DexAnnotation> filter) {
-    DexAnnotationSet[] filtered = null;
-    for (int i = 0; i < annotations.values.length; i++) {
-      DexAnnotationSet updated = stripAnnotations(annotations.values[i], filter);
-      if (updated != annotations.values[i]) {
-        if (filtered == null) {
-          filtered = annotations.values.clone();
-          filtered[i] = updated;
-        }
-      }
-    }
-    if (filtered == null) {
-      return annotations;
-    } else {
-      if (Arrays.stream(filtered).allMatch(DexAnnotationSet::isEmpty)) {
-        return DexAnnotationSetRefList.empty();
-      }
-      return new DexAnnotationSetRefList(filtered);
-    }
-  }
-
-  private DexAnnotationSet stripAnnotations(DexAnnotationSet annotations,
-      Predicate<DexAnnotation> filter) {
-    ArrayList<DexAnnotation> filtered = null;
-    for (int i = 0; i < annotations.annotations.length; i++) {
-      DexAnnotation annotation = annotations.annotations[i];
-      if (filter.test(annotation)) {
-        if (filtered != null) {
-          filtered.add(annotation);
-        }
-      } else {
-        if (filtered == null) {
-          filtered = new ArrayList<>(annotations.annotations.length);
-          for (int j = 0; j < i; j++) {
-            filtered.add(annotations.annotations[j]);
-          }
-        }
-      }
-    }
-    if (filtered == null) {
-      return annotations;
-    } else if (filtered.isEmpty()) {
-      return DexAnnotationSet.empty();
-    } else {
-      return new DexAnnotationSet(filtered.toArray(new DexAnnotation[filtered.size()]));
-    }
-  }
 }
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..27b2da7 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -7,7 +7,6 @@
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
 import static com.android.tools.r8.shaking.ProguardConfigurationUtils.buildIdentifierNameStringRule;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unreachable;
@@ -15,7 +14,6 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.Descriptor;
 import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
@@ -552,14 +550,18 @@
 
   private void processAnnotations(DexAnnotation[] annotations) {
     for (DexAnnotation annotation : annotations) {
-      DexType type = annotation.annotation.type;
-      if (liveTypes.contains(type)) {
-        // The type of this annotation is already live, so pick up its dependencies.
-        handleAnnotationOfLiveType(annotation);
-      } else {
-        // Remember this annotation for later.
-        deferredAnnotations.computeIfAbsent(type, ignore -> new HashSet<>()).add(annotation);
-      }
+      processAnnotation(annotation);
+    }
+  }
+
+  private void processAnnotation(DexAnnotation annotation) {
+    DexType type = annotation.annotation.type;
+    if (liveTypes.contains(type)) {
+      // The type of this annotation is already live, so pick up its dependencies.
+      handleAnnotationOfLiveType(annotation);
+    } else {
+      // Remember this annotation for later.
+      deferredAnnotations.computeIfAbsent(type, ignore -> new HashSet<>()).add(annotation);
     }
   }
 
@@ -831,7 +833,7 @@
       return;
     }
     if (Log.ENABLED) {
-      Log.verbose(getClass(), "Register new instatiation of `%s`.", clazz);
+      Log.verbose(getClass(), "Register new instantiation of `%s`.", clazz);
     }
     workList.add(Action.markInstantiated(clazz, KeepReason.instantiatedIn(method)));
   }
@@ -1239,9 +1241,7 @@
         }
       }
       processAnnotations(method.annotations.annotations);
-      for (DexAnnotationSet parameterAnnotation : method.parameterAnnotations.values) {
-        processAnnotations(parameterAnnotation.annotations);
-      }
+      method.parameterAnnotationsList.forEachAnnotation(this::processAnnotation);
       if (protoLiteExtension != null && protoLiteExtension.appliesTo(method)) {
         protoLiteExtension.processMethod(method, new UseRegistry(method), protoLiteFields);
       } else {
@@ -1342,13 +1342,8 @@
   }
 
   private void handleProguardReflectiveBehavior(DexEncodedMethod method) {
-    try {
-      IRCode code = method.buildIR(options);
-      code.instructionIterator().forEachRemaining(this::handleProguardReflectiveBehavior);
-    } catch (ApiLevelException e) {
-      // Ignore this exception here. It will be hit again further in the pipeline when
-      // generating code.
-    }
+    IRCode code = method.buildIR(appInfo, options, appInfo.originFor(method.method.holder));
+    code.instructionIterator().forEachRemaining(this::handleProguardReflectiveBehavior);
   }
 
   private void handleProguardReflectiveBehavior(Instruction instruction) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
index fd1d305..568ad3d 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
@@ -75,8 +75,16 @@
 
   public abstract boolean matches(DexType type);
 
-  protected Iterable<String> getWildcards() {
-    return ImmutableList.of();
+  protected Iterable<ProguardWildcard> getWildcards() {
+    return Collections::emptyIterator;
+  }
+
+  static Iterable<ProguardWildcard> getWildcardsOrEmpty(ProguardClassNameList nameList) {
+    return nameList == null ? Collections::emptyIterator : nameList.getWildcards();
+  }
+
+  protected ProguardClassNameList materialize() {
+    return this;
   }
 
   public abstract void forEachTypeMatcher(Consumer<ProguardTypeMatcher> consumer);
@@ -140,11 +148,16 @@
     }
 
     @Override
-    protected Iterable<String> getWildcards() {
+    protected Iterable<ProguardWildcard> getWildcards() {
       return className.getWildcards();
     }
 
     @Override
+    protected SingleClassNameList materialize() {
+      return new SingleClassNameList(className.materialize());
+    }
+
+    @Override
     public void forEachTypeMatcher(Consumer<ProguardTypeMatcher> consumer) {
       consumer.accept(className);
     }
@@ -190,11 +203,17 @@
     }
 
     @Override
-    protected Iterable<String> getWildcards() {
+    protected Iterable<ProguardWildcard> getWildcards() {
       return classNames.stream()
           .map(ProguardTypeMatcher::getWildcards)
           .flatMap(it -> StreamSupport.stream(it.spliterator(), false))
-          .collect(Collectors.toList());
+          ::iterator;
+    }
+
+    @Override
+    protected PositiveClassNameList materialize() {
+      return new PositiveClassNameList(
+          classNames.stream().map(ProguardTypeMatcher::materialize).collect(Collectors.toList()));
     }
 
     @Override
@@ -248,11 +267,18 @@
     }
 
     @Override
-    protected Iterable<String> getWildcards() {
+    protected Iterable<ProguardWildcard> getWildcards() {
       return classNames.keySet().stream()
           .map(ProguardTypeMatcher::getWildcards)
           .flatMap(it -> StreamSupport.stream(it.spliterator(), false))
-          .collect(Collectors.toList());
+          ::iterator;
+    }
+
+    @Override
+    protected ProguardClassNameList materialize() {
+      Builder builder = builder();
+      classNames.forEach((m, negated) -> builder.addClassName(negated, m.materialize()));
+      return builder.build();
     }
 
     @Override
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 568ba54..4a25e42 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -18,6 +18,8 @@
 import com.android.tools.r8.shaking.ProguardConfiguration.Builder;
 import com.android.tools.r8.shaking.ProguardTypeMatcher.ClassOrType;
 import com.android.tools.r8.shaking.ProguardTypeMatcher.MatchSpecificType;
+import com.android.tools.r8.shaking.ProguardWildcard.BackReference;
+import com.android.tools.r8.shaking.ProguardWildcard.Pattern;
 import com.android.tools.r8.utils.IdentifierUtils;
 import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
 import com.android.tools.r8.utils.LongInterval;
@@ -34,12 +36,9 @@
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 public class ProguardConfigurationParser {
 
@@ -66,6 +65,8 @@
       "dontskipnonpubliclibraryclasses",
       "dontskipnonpubliclibraryclassmembers",
       "invokebasemethod",
+      // TODO(b/62524562): we may support this later.
+      "mergeinterfacesaggressively",
       "android");
 
   private static final List<String> IGNORED_CLASS_DESCRIPTOR_OPTIONS = ImmutableList.of(
@@ -73,8 +74,7 @@
       "whyarenotsimple");
 
   private static final List<String> WARNED_SINGLE_ARG_OPTIONS = ImmutableList.of(
-      // TODO -outjars (http://b/37137994) and -adaptresourcefilecontents (http://b/37139570)
-      // should be reported as errors, not just as warnings!
+      // TODO(b/37137994): -outjars should be reported as errors, not just as warnings!
       "outjars");
 
   private static final List<String> WARNED_FLAG_OPTIONS = ImmutableList.of(
@@ -138,8 +138,7 @@
     parse(ImmutableList.of(new ProguardConfigurationSourceFile(path)));
   }
 
-  // package visible for testing
-  void parse(ProguardConfigurationSource source) {
+  public void parse(ProguardConfigurationSource source) {
     parse(ImmutableList.of(source));
   }
 
@@ -340,9 +339,11 @@
       } else if (acceptString("adaptclassstrings")) {
         parseClassFilter(configurationBuilder::addAdaptClassStringsPattern);
       } else if (acceptString("adaptresourcefilenames")) {
+        // TODO(b/76377381): should be report an error until it's fully supported.
         parsePathFilter(configurationBuilder::addAdaptResourceFilenames);
       } else if (acceptString("adaptresourcefilecontents")) {
         parsePathFilter(configurationBuilder::addAdaptResourceFilecontents);
+        // TODO(b/37139570): should be report an error until it's fully supported.
       } else if (acceptString("identifiernamestring")) {
         configurationBuilder.addRule(parseIdentifierNameStringRule());
       } else if (acceptString("if")) {
@@ -571,30 +572,28 @@
         ProguardKeepRule subsequentRule = parseKeepRule();
         ifRuleBuilder.setSubsequentRule(subsequentRule);
         ProguardIfRule ifRule = ifRuleBuilder.build();
-        verifyWildcardRange(ifRule.getWildcards());
+        verifyAndLinkBackReferences(ifRule.getWildcards());
         return ifRule;
       }
       throw reporter.fatalError(new StringDiagnostic(
           "Expecting '-keep' option after '-if' option.", origin, getPosition(optionStart)));
     }
 
-    private void verifyWildcardRange(Iterable<String> wildcards) {
-      Pattern backReference = Pattern.compile("<(\\d+)>");
-      int i = 1;
-      Iterator<String> iterator = wildcards.iterator();
-      while (iterator.hasNext()) {
-        String wildcard = iterator.next();
-        Matcher m = backReference.matcher(wildcard);
-        if (m.matches()) {
-          int n = Integer.parseInt(m.group(1));
-          if (i <= n) {
+    void verifyAndLinkBackReferences(Iterable<ProguardWildcard> wildcards) {
+      List<Pattern> patterns = new ArrayList<>();
+      for (ProguardWildcard wildcard : wildcards) {
+        if (wildcard.isBackReference()) {
+          BackReference backReference = wildcard.asBackReference();
+          if (patterns.size() < backReference.referenceIndex) {
             throw reporter.fatalError(new StringDiagnostic(
-                "Wildcard <" + n + "> is invalid.", origin, getPosition()));
+                "Wildcard <" + backReference.referenceIndex + "> is invalid "
+                    + "(only seen " + patterns.size() + " at this point).",
+                origin, getPosition()));
           }
+          backReference.setReference(patterns.get(backReference.referenceIndex - 1));
         } else {
-          // Increase the index of wildcards for non-back-reference only
-          // to not allow one back-reference to point to another back-reference
-          i++;
+          assert wildcard.isPattern();
+          patterns.add(wildcard.asPattern());
         }
       }
     }
@@ -718,6 +717,11 @@
       }
     }
 
+    private StringDiagnostic parseClassTypeUnexpected(Origin origin, TextPosition start) {
+      return new StringDiagnostic(
+          "Expected [!]interface|@interface|class|enum", origin, getPosition(start));
+    }
+
     private void parseClassType(
         ProguardClassSpecification.Builder builder) throws ProguardRuleParserException {
       skipWhitespace();
@@ -725,17 +729,21 @@
       if (acceptChar('!')) {
         builder.setClassTypeNegated(true);
       }
-      if (acceptString("interface")) {
+      if (acceptChar('@')) {
+        skipWhitespace();
+        if (acceptString("interface")) {
+          builder.setClassType(ProguardClassType.ANNOTATION_INTERFACE);
+        } else {
+          throw reporter.fatalError(parseClassTypeUnexpected(origin, start));
+        }
+      } else if (acceptString("interface")) {
         builder.setClassType(ProguardClassType.INTERFACE);
-      } else if (acceptString("@interface")) {
-        builder.setClassType(ProguardClassType.ANNOTATION_INTERFACE);
       } else if (acceptString("class")) {
         builder.setClassType(ProguardClassType.CLASS);
       } else if (acceptString("enum")) {
         builder.setClassType(ProguardClassType.ENUM);
       } else {
-        throw reporter.fatalError(new StringDiagnostic(
-            "Expected [!]interface|@interface|class|enum", origin, getPosition(start)));
+        throw reporter.fatalError(parseClassTypeUnexpected(origin, start));
       }
     }
 
@@ -971,6 +979,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)
@@ -1162,7 +1175,7 @@
     }
 
     private IdentifierPatternWithWildcards acceptIdentifierWithBackreference(IdentifierType kind) {
-      ImmutableList.Builder<String> wildcardsCollector = ImmutableList.builder();
+      ImmutableList.Builder<ProguardWildcard> wildcardsCollector = ImmutableList.builder();
       StringBuilder currentAsterisks = null;
       StringBuilder currentBackreference = null;
       skipWhitespace();
@@ -1180,15 +1193,15 @@
                 throw reporter.fatalError(new StringDiagnostic(
                     "Wildcard <" + backreference + "> is invalid.", origin, getPosition()));
               }
+              wildcardsCollector.add(new BackReference(backreference));
+              currentBackreference = null;
+              end += Character.charCount(current);
+              continue;
             } catch (NumberFormatException e) {
               throw reporter.fatalError(new StringDiagnostic(
                   "Wildcard <" + currentBackreference.toString() + "> is invalid.",
                   origin, getPosition()));
             }
-            wildcardsCollector.add("<" + currentBackreference.toString() + ">");
-            currentBackreference = null;
-            end += Character.charCount(current);
-            continue;
           } else if (('0' <= current && current <= '9')
               // Only collect integer literal for the back reference.
               || (current == '-' && currentBackreference.length() == 0)) {
@@ -1209,7 +1222,7 @@
             end += Character.charCount(current);
             continue;
           } else {
-            wildcardsCollector.add(currentAsterisks.toString());
+            wildcardsCollector.add(new ProguardWildcard.Pattern(currentAsterisks.toString()));
             currentAsterisks = null;
           }
         }
@@ -1220,7 +1233,7 @@
           currentAsterisks.append((char) current);
           end += Character.charCount(current);
         } else if (current == '?' || current == '%') {
-          wildcardsCollector.add(String.valueOf((char) current));
+          wildcardsCollector.add(new ProguardWildcard.Pattern(String.valueOf((char) current)));
           end += Character.charCount(current);
         } else if (CLASS_NAME_PREDICATE.test(current) || current == '>') {
           end += Character.charCount(current);
@@ -1232,7 +1245,7 @@
         }
       }
       if (currentAsterisks != null) {
-        wildcardsCollector.add(currentAsterisks.toString());
+        wildcardsCollector.add(new ProguardWildcard.Pattern(currentAsterisks.toString()));
       }
       if (kind == IdentifierType.CLASS_NAME && currentBackreference != null) {
         // Proguard 6 reports this error message, so try to be compatible.
@@ -1479,9 +1492,9 @@
 
   static class IdentifierPatternWithWildcards {
     final String pattern;
-    final List<String> wildcards;
+    final List<ProguardWildcard> wildcards;
 
-    IdentifierPatternWithWildcards(String pattern, List<String> wildcards) {
+    IdentifierPatternWithWildcards(String pattern, List<ProguardWildcard> wildcards) {
       this.pattern = pattern;
       this.wildcards = wildcards;
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index 9791def..ddc55ab 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -4,10 +4,9 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
+import java.util.Collections;
 import java.util.List;
-import java.util.stream.Collectors;
 import java.util.stream.StreamSupport;
 
 public abstract class ProguardConfigurationRule extends ProguardClassSpecification {
@@ -36,22 +35,19 @@
     return false;
   }
 
-  protected Iterable<String> getWildcards() {
-    ProguardTypeMatcher classAnnotation = getClassAnnotation();
-    ProguardTypeMatcher inheritanceAnnotation = getInheritanceAnnotation();
-    ProguardTypeMatcher inheritanceClassName = getInheritanceClassName();
+  protected Iterable<ProguardWildcard> getWildcards() {
     List<ProguardMemberRule> memberRules = getMemberRules();
     return Iterables.concat(
-        classAnnotation != null ? classAnnotation.getWildcards() : ImmutableList.of(),
-        getClassNames().getWildcards(),
-        inheritanceAnnotation != null ? inheritanceAnnotation.getWildcards() : ImmutableList.of(),
-        inheritanceClassName != null ? inheritanceClassName.getWildcards() : ImmutableList.of(),
+        ProguardTypeMatcher.getWildcardsOrEmpty(getClassAnnotation()),
+        ProguardClassNameList.getWildcardsOrEmpty(getClassNames()),
+        ProguardTypeMatcher.getWildcardsOrEmpty(getInheritanceAnnotation()),
+        ProguardTypeMatcher.getWildcardsOrEmpty(getInheritanceClassName()),
         memberRules != null
             ? memberRules.stream()
                 .map(ProguardMemberRule::getWildcards)
                 .flatMap(it -> StreamSupport.stream(it.spliterator(), false))
-                .collect(Collectors.toList())
-            : ImmutableList.of()
+                ::iterator
+            : Collections::emptyIterator
     );
   }
 
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/shaking/ProguardIfRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
index 67a0572..47f26ed 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
@@ -5,6 +5,7 @@
 
 import com.google.common.collect.Iterables;
 import java.util.List;
+import java.util.stream.Collectors;
 
 public class ProguardIfRule extends ProguardKeepRule {
 
@@ -46,11 +47,29 @@
   }
 
   @Override
-  protected Iterable<String> getWildcards() {
+  protected Iterable<ProguardWildcard> getWildcards() {
     return Iterables.concat(super.getWildcards(), subsequentRule.getWildcards());
   }
 
   @Override
+  protected ProguardIfRule materialize() {
+    return new ProguardIfRule(
+        getClassAnnotation(),
+        getClassAccessFlags(),
+        getNegatedClassAccessFlags(),
+        getClassTypeNegated(),
+        getClassType(),
+        getClassNames().materialize(),
+        getInheritanceAnnotation() == null ? null : getInheritanceAnnotation().materialize(),
+        getInheritanceClassName() == null ? null : getInheritanceClassName().materialize(),
+        getInheritanceIsExtends(),
+        getMemberRules() == null ? null :
+            getMemberRules().stream()
+                .map(ProguardMemberRule::materialize).collect(Collectors.toList()),
+        subsequentRule.materialize());
+  }
+
+  @Override
   public boolean equals(Object o) {
     if (!(o instanceof ProguardIfRule)) {
       return false;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
index 8756f1a..6651dae 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
@@ -5,6 +5,7 @@
 
 import java.util.List;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 public class ProguardKeepRule extends ProguardConfigurationRule {
 
@@ -68,6 +69,24 @@
     return modifiers;
   }
 
+  protected ProguardKeepRule materialize() {
+    return new ProguardKeepRule(
+        getClassAnnotation(),
+        getClassAccessFlags(),
+        getNegatedClassAccessFlags(),
+        getClassTypeNegated(),
+        getClassType(),
+        getClassNames() == null ? null : getClassNames().materialize(),
+        getInheritanceAnnotation() == null ? null : getInheritanceAnnotation().materialize(),
+        getInheritanceClassName() == null ? null : getInheritanceClassName().materialize(),
+        getInheritanceIsExtends(),
+        getMemberRules() == null ? null :
+            getMemberRules().stream()
+                .map(ProguardMemberRule::materialize).collect(Collectors.toList()),
+        getType(),
+        getModifiers());
+  }
+
   @Override
   public boolean equals(Object o) {
     if (!(o instanceof ProguardKeepRule)) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
index 1e089a9..39737ea 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
 import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import java.util.Collections;
 import java.util.List;
@@ -266,20 +265,34 @@
     return false;
   }
 
-  Iterable<String> getWildcards() {
+  Iterable<ProguardWildcard> getWildcards() {
     return Iterables.concat(
-        annotation != null ? annotation.getWildcards() : ImmutableList.of(),
-        type != null ? type.getWildcards() : ImmutableList.of(),
-        name != null ? name.getWildcards() : ImmutableList.of(),
+        ProguardTypeMatcher.getWildcardsOrEmpty(annotation),
+        ProguardTypeMatcher.getWildcardsOrEmpty(type),
+        ProguardNameMatcher.getWildcardsOrEmpty(name),
         arguments != null
             ? arguments.stream()
                 .map(ProguardTypeMatcher::getWildcards)
                 .flatMap(it -> StreamSupport.stream(it.spliterator(), false))
-                .collect(Collectors.toList())
-            : ImmutableList.of()
+                ::iterator
+            : Collections::emptyIterator
     );
   }
 
+  ProguardMemberRule materialize() {
+    return new ProguardMemberRule(
+        getAnnotation() == null ? null : getAnnotation().materialize(),
+        getAccessFlags(),
+        getNegatedAccessFlags(),
+        getRuleType(),
+        getType() == null ? null : getType().materialize(),
+        getName() == null ? null : getName().materialize(),
+        getArguments() == null ? null :
+            getArguments().stream()
+                .map(ProguardTypeMatcher::materialize).collect(Collectors.toList()),
+        getReturnValue());
+  }
+
   @Override
   public boolean equals(Object o) {
     if (!(o instanceof ProguardMemberRule)) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardNameMatcher.java b/src/main/java/com/android/tools/r8/shaking/ProguardNameMatcher.java
index b8f583d..c217d45 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardNameMatcher.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardNameMatcher.java
@@ -4,8 +4,12 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
+import com.android.tools.r8.shaking.ProguardWildcard.BackReference;
+import com.android.tools.r8.shaking.ProguardWildcard.Pattern;
 import com.google.common.collect.ImmutableList;
+import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 public abstract class ProguardNameMatcher {
 
@@ -25,35 +29,53 @@
     }
   }
 
-  /**
-   * Determines if the proguard name pattern matches the given field or method name.
-   *
-   * @param pattern Proguard name pattern potentially with wildcards: ?, *.
-   * @param name The name to match against.
-   * @return true iff the pattern matches the name.
-   */
-  public static boolean matchFieldOrMethodName(String pattern, String name) {
-    return matchFieldOrMethodNameImpl(pattern, 0, name, 0);
-  }
-
   private static boolean matchFieldOrMethodNameImpl(
-      String pattern, int patternIndex, String name, int nameIndex) {
+      String pattern, int patternIndex,
+      String name, int nameIndex,
+      List<ProguardWildcard> wildcards, int wildcardIndex) {
+    ProguardWildcard wildcard;
+    Pattern wildcardPattern;
+    BackReference backReference;
     for (int i = patternIndex; i < pattern.length(); i++) {
       char patternChar = pattern.charAt(i);
       switch (patternChar) {
         case '*':
+          wildcard = wildcards.get(wildcardIndex);
+          assert wildcard.isPattern();
+          wildcardPattern = wildcard.asPattern();
           // Match the rest of the pattern against the rest of the name.
           for (int nextNameIndex = nameIndex; nextNameIndex <= name.length(); nextNameIndex++) {
-            if (matchFieldOrMethodNameImpl(pattern, i + 1, name, nextNameIndex)) {
+            wildcardPattern.setCaptured(name.substring(nameIndex, nextNameIndex));
+            if (matchFieldOrMethodNameImpl(
+                pattern, i + 1, name, nextNameIndex, wildcards, wildcardIndex + 1)) {
               return true;
             }
           }
           return false;
         case '?':
+          wildcard = wildcards.get(wildcardIndex);
+          assert wildcard.isPattern();
           if (nameIndex == name.length()) {
             return false;
           }
+          wildcardPattern = wildcard.asPattern();
+          wildcardPattern.setCaptured(name.substring(nameIndex, nameIndex + 1));
           nameIndex++;
+          wildcardIndex++;
+          break;
+        case '<':
+          wildcard = wildcards.get(wildcardIndex);
+          assert wildcard.isBackReference();
+          backReference = wildcard.asBackReference();
+          String captured = backReference.getCaptured();
+          if (captured == null
+              || name.length() < nameIndex + captured.length()
+              || !captured.equals(name.substring(nameIndex, nameIndex + captured.length()))) {
+            return false;
+          }
+          nameIndex = nameIndex + captured.length();
+          wildcardIndex++;
+          i = pattern.indexOf(">", i);
           break;
         default:
           if (nameIndex == name.length() || patternChar != name.charAt(nameIndex++)) {
@@ -67,20 +89,43 @@
 
   public abstract boolean matches(String name);
 
-  protected Iterable<String> getWildcards() {
-    return ImmutableList.of();
+  protected Iterable<ProguardWildcard> getWildcards() {
+    return Collections::emptyIterator;
+  }
+
+  static Iterable<ProguardWildcard> getWildcardsOrEmpty(ProguardNameMatcher nameMatcher) {
+    return nameMatcher == null ? Collections::emptyIterator : nameMatcher.getWildcards();
+  }
+
+  protected ProguardNameMatcher materialize() {
+    return this;
   }
 
   private static class MatchAllNames extends ProguardNameMatcher {
+    private final ProguardWildcard wildcard;
+
+    MatchAllNames() {
+      this(new Pattern("*"));
+    }
+
+    private MatchAllNames(ProguardWildcard wildcard) {
+      this.wildcard = wildcard;
+    }
 
     @Override
     public boolean matches(String name) {
+      wildcard.setCaptured(name);
       return true;
     }
 
     @Override
-    protected Iterable<String> getWildcards() {
-      return ImmutableList.of("*");
+    protected Iterable<ProguardWildcard> getWildcards() {
+      return ImmutableList.of(wildcard);
+    }
+
+    @Override
+    protected MatchAllNames materialize() {
+      return new MatchAllNames(wildcard.materialize());
     }
 
     @Override
@@ -92,7 +137,7 @@
   private static class MatchNamePattern extends ProguardNameMatcher {
 
     private final String pattern;
-    private final List<String> wildcards;
+    private final List<ProguardWildcard> wildcards;
 
     MatchNamePattern(IdentifierPatternWithWildcards identifierPatternWithWildcards) {
       this.pattern = identifierPatternWithWildcards.pattern;
@@ -101,15 +146,28 @@
 
     @Override
     public boolean matches(String name) {
-      return matchFieldOrMethodName(pattern, name);
+      boolean matched = matchFieldOrMethodNameImpl(pattern, 0, name, 0, wildcards, 0);
+      if (!matched) {
+        wildcards.forEach(ProguardWildcard::clearCaptured);
+      }
+      return matched;
     }
 
     @Override
-    protected Iterable<String> getWildcards() {
+    protected Iterable<ProguardWildcard> getWildcards() {
       return wildcards;
     }
 
     @Override
+    protected MatchNamePattern materialize() {
+      List<ProguardWildcard> materializedWildcards =
+          wildcards.stream().map(ProguardWildcard::materialize).collect(Collectors.toList());
+      IdentifierPatternWithWildcards identifierPatternWithMaterializedWildcards =
+          new IdentifierPatternWithWildcards(pattern, materializedWildcards);
+      return new MatchNamePattern(identifierPatternWithMaterializedWildcards);
+    }
+
+    @Override
     public String toString() {
       return pattern;
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
index 51d11bb..34ee554 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
@@ -8,8 +8,12 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
+import com.android.tools.r8.shaking.ProguardWildcard.BackReference;
+import com.android.tools.r8.shaking.ProguardWildcard.Pattern;
 import com.google.common.collect.ImmutableList;
+import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 public abstract class ProguardTypeMatcher {
 
@@ -29,8 +33,16 @@
 
   public abstract boolean matches(DexType type);
 
-  protected Iterable<String> getWildcards() {
-    return ImmutableList.of();
+  protected Iterable<ProguardWildcard> getWildcards() {
+    return Collections::emptyIterator;
+  }
+
+  static Iterable<ProguardWildcard> getWildcardsOrEmpty(ProguardTypeMatcher typeMatcher) {
+    return typeMatcher == null ? Collections::emptyIterator : typeMatcher.getWildcards();
+  }
+
+  protected ProguardTypeMatcher materialize() {
+    return this;
   }
 
   @Override
@@ -89,14 +101,30 @@
 
     private static final ProguardTypeMatcher MATCH_ALL_TYPES = new MatchAllTypes();
 
+    private final ProguardWildcard wildcard;
+
+    MatchAllTypes() {
+      this(new Pattern(MATCH_ALL_PATTERN));
+    }
+
+    private MatchAllTypes(ProguardWildcard wildcard) {
+      this.wildcard = wildcard;
+    }
+
     @Override
     public boolean matches(DexType type) {
+      wildcard.setCaptured(type.toSourceString());
       return true;
     }
 
     @Override
-    protected Iterable<String> getWildcards() {
-      return ImmutableList.of(MATCH_ALL_PATTERN);
+    protected Iterable<ProguardWildcard> getWildcards() {
+      return ImmutableList.of(wildcard);
+    }
+
+    @Override
+    protected MatchAllTypes materialize() {
+      return new MatchAllTypes(wildcard.materialize());
     }
 
     @Override
@@ -147,26 +175,41 @@
 
   private static class MatchClassTypes extends ProguardTypeMatcher {
 
-    private static final ProguardTypeMatcher MATCH_CLASS_TYPES = new MatchClassTypes(
-        MATCH_CLASS_PATTERN);
-    private static final ProguardTypeMatcher LEGACY_MATCH_CLASS_TYPES = new MatchClassTypes(
-        LEGACY_MATCH_CLASS_PATTERN);
+    private static final ProguardTypeMatcher MATCH_CLASS_TYPES =
+        new MatchClassTypes(MATCH_CLASS_PATTERN);
+    private static final ProguardTypeMatcher LEGACY_MATCH_CLASS_TYPES =
+        new MatchClassTypes(LEGACY_MATCH_CLASS_PATTERN);
 
     private final String pattern;
+    private final ProguardWildcard wildcard;
 
     private MatchClassTypes(String pattern) {
+      this(pattern, new Pattern(pattern));
+    }
+
+    private MatchClassTypes(String pattern, ProguardWildcard wildcard) {
       assert pattern.equals(LEGACY_MATCH_CLASS_PATTERN) || pattern.equals(MATCH_CLASS_PATTERN);
       this.pattern = pattern;
+      this.wildcard = wildcard;
     }
 
     @Override
     public boolean matches(DexType type) {
-      return type.isClassType();
+      if (type.isClassType()) {
+        wildcard.setCaptured(type.toSourceString());
+        return true;
+      }
+      return false;
     }
 
     @Override
-    protected Iterable<String> getWildcards() {
-      return ImmutableList.of(pattern);
+    protected Iterable<ProguardWildcard> getWildcards() {
+      return ImmutableList.of(wildcard);
+    }
+
+    @Override
+    protected MatchClassTypes materialize() {
+      return new MatchClassTypes(pattern, wildcard.materialize());
     }
 
     @Override
@@ -189,14 +232,33 @@
 
     private static final ProguardTypeMatcher MATCH_BASIC_TYPES = new MatchBasicTypes();
 
-    @Override
-    public boolean matches(DexType type) {
-      return type.isPrimitiveType();
+    private final ProguardWildcard wildcard;
+
+    MatchBasicTypes() {
+      this(new Pattern(MATCH_BASIC_PATTERN));
+    }
+
+    private MatchBasicTypes(ProguardWildcard wildcard) {
+      this.wildcard = wildcard;
     }
 
     @Override
-    protected Iterable<String> getWildcards() {
-      return ImmutableList.of(MATCH_BASIC_PATTERN);
+    public boolean matches(DexType type) {
+      if (type.isPrimitiveType()) {
+        wildcard.setCaptured(type.toSourceString());
+        return true;
+      }
+      return false;
+    }
+
+    @Override
+    protected Iterable<ProguardWildcard> getWildcards() {
+      return ImmutableList.of(wildcard);
+    }
+
+    @Override
+    protected MatchBasicTypes materialize() {
+      return new MatchBasicTypes(wildcard.materialize());
     }
 
     @Override
@@ -255,7 +317,7 @@
   private static class MatchTypePattern extends ProguardTypeMatcher {
 
     private final String pattern;
-    private final List<String> wildcards;
+    private final List<ProguardWildcard> wildcards;
     private final ClassOrType kind;
 
     private MatchTypePattern(
@@ -269,63 +331,110 @@
     public boolean matches(DexType type) {
       // TODO(herhut): Translate pattern to work on descriptors instead.
       String typeName = type.toSourceString();
-      return matchClassOrTypeNameImpl(pattern, 0, typeName, 0, kind);
+      boolean matched = matchClassOrTypeNameImpl(pattern, 0, typeName, 0, wildcards, 0, kind);
+      if (!matched) {
+        wildcards.forEach(ProguardWildcard::clearCaptured);
+      }
+      return matched;
     }
 
     @Override
-    protected Iterable<String> getWildcards() {
+    protected Iterable<ProguardWildcard> getWildcards() {
       return wildcards;
     }
 
+    @Override
+    protected MatchTypePattern materialize() {
+      List<ProguardWildcard> materializedWildcards =
+          wildcards.stream().map(ProguardWildcard::materialize).collect(Collectors.toList());
+      IdentifierPatternWithWildcards identifierPatternWithMaterializedWildcards =
+          new IdentifierPatternWithWildcards(pattern, materializedWildcards);
+      return new MatchTypePattern(identifierPatternWithMaterializedWildcards, kind);
+    }
+
     private static boolean matchClassOrTypeNameImpl(
-        String pattern, int patternIndex, String className, int nameIndex, ClassOrType kind) {
+        String pattern, int patternIndex,
+        String name, int nameIndex,
+        List<ProguardWildcard> wildcards, int wildcardIndex,
+        ClassOrType kind) {
+      ProguardWildcard wildcard;
+      Pattern wildcardPattern;
+      BackReference backReference;
       for (int i = patternIndex; i < pattern.length(); i++) {
         char patternChar = pattern.charAt(i);
         switch (patternChar) {
           case '*':
+            wildcard = wildcards.get(wildcardIndex);
+            assert wildcard.isPattern();
+            wildcardPattern = wildcard.asPattern();
             boolean includeSeparators = pattern.length() > (i + 1) && pattern.charAt(i + 1) == '*';
             int nextPatternIndex = i + (includeSeparators ? 2 : 1);
             // Fast cases for the common case where a pattern ends with '**' or '*'.
             if (nextPatternIndex == pattern.length()) {
+              wildcardPattern.setCaptured(name.substring(nameIndex, name.length()));
               if (includeSeparators) {
-                return kind == ClassOrType.CLASS || !isArrayType(className);
+                return kind == ClassOrType.CLASS || !isArrayType(name);
               }
-              boolean hasSeparators = containsSeparatorsStartingAt(className, nameIndex);
-              return !hasSeparators && (kind == ClassOrType.CLASS || !isArrayType(className));
+              boolean hasSeparators = containsSeparatorsStartingAt(name, nameIndex);
+              return !hasSeparators && (kind == ClassOrType.CLASS || !isArrayType(name));
             }
             // Match the rest of the pattern against the (non-empty) rest of the class name.
-            for (int nextNameIndex = nameIndex; nextNameIndex < className.length();
-                nextNameIndex++) {
-              if (!includeSeparators && className.charAt(nextNameIndex) == '.') {
-                return matchClassOrTypeNameImpl(pattern, nextPatternIndex, className, nextNameIndex,
+            for (int nextNameIndex = nameIndex; nextNameIndex < name.length(); nextNameIndex++) {
+              wildcardPattern.setCaptured(name.substring(nameIndex, nextNameIndex));
+              if (!includeSeparators && name.charAt(nextNameIndex) == '.') {
+                return matchClassOrTypeNameImpl(
+                    pattern, nextPatternIndex, name, nextNameIndex, wildcards, wildcardIndex + 1,
                     kind);
               }
-              if (kind == ClassOrType.TYPE && className.charAt(nextNameIndex) == '[') {
-                return matchClassOrTypeNameImpl(pattern, nextPatternIndex, className, nextNameIndex,
+              if (kind == ClassOrType.TYPE && name.charAt(nextNameIndex) == '[') {
+                return matchClassOrTypeNameImpl(
+                    pattern, nextPatternIndex, name, nextNameIndex, wildcards, wildcardIndex + 1,
                     kind);
               }
-              if (matchClassOrTypeNameImpl(pattern, nextPatternIndex, className, nextNameIndex,
+              if (matchClassOrTypeNameImpl(
+                  pattern, nextPatternIndex, name, nextNameIndex, wildcards, wildcardIndex + 1,
                   kind)) {
                 return true;
               }
             }
             // Finally, check the case where the '*' or '**' eats all of the class name.
-            return matchClassOrTypeNameImpl(pattern, nextPatternIndex, className,
-                className.length(),
+            wildcardPattern.setCaptured(name.substring(nameIndex, name.length()));
+            return matchClassOrTypeNameImpl(
+                pattern, nextPatternIndex, name, name.length(), wildcards, wildcardIndex + 1,
                 kind);
           case '?':
-            if (nameIndex == className.length() || className.charAt(nameIndex++) == '.') {
+            wildcard = wildcards.get(wildcardIndex);
+            assert wildcard.isPattern();
+            if (nameIndex == name.length() || name.charAt(nameIndex) == '.') {
               return false;
             }
+            wildcardPattern = wildcard.asPattern();
+            wildcardPattern.setCaptured(name.substring(nameIndex, nameIndex + 1));
+            nameIndex++;
+            wildcardIndex++;
+            break;
+          case '<':
+            wildcard = wildcards.get(wildcardIndex);
+            assert wildcard.isBackReference();
+            backReference = wildcard.asBackReference();
+            String captured = backReference.getCaptured();
+            if (captured == null
+                || name.length() < nameIndex + captured.length()
+                || !captured.equals(name.substring(nameIndex, nameIndex + captured.length()))) {
+              return false;
+            }
+            nameIndex = nameIndex + captured.length();
+            wildcardIndex++;
+            i = pattern.indexOf(">", i);
             break;
           default:
-            if (nameIndex == className.length() || patternChar != className.charAt(nameIndex++)) {
+            if (nameIndex == name.length() || patternChar != name.charAt(nameIndex++)) {
               return false;
             }
             break;
         }
       }
-      return nameIndex == className.length();
+      return nameIndex == name.length();
     }
 
     private static boolean containsSeparatorsStartingAt(String className, int nameIndex) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardWildcard.java b/src/main/java/com/android/tools/r8/shaking/ProguardWildcard.java
new file mode 100644
index 0000000..b6cb8e5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardWildcard.java
@@ -0,0 +1,134 @@
+// 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.errors.Unreachable;
+
+public abstract class ProguardWildcard {
+
+  abstract void setCaptured(String captured);
+  abstract void clearCaptured();
+  abstract String getCaptured();
+  abstract ProguardWildcard materialize();
+
+  boolean isPattern() {
+    return false;
+  }
+
+  Pattern asPattern() {
+    return null;
+  }
+
+  boolean isBackReference() {
+    return false;
+  }
+
+  BackReference asBackReference() {
+    return null;
+  }
+
+  static class Pattern extends ProguardWildcard {
+    final String pattern;
+    private String captured = null;
+
+    Pattern(String pattern) {
+      this.pattern = pattern;
+    }
+
+    @Override
+    synchronized void setCaptured(String captured) {
+      this.captured = captured;
+    }
+
+    @Override
+    synchronized void clearCaptured() {
+      captured = null;
+    }
+
+    @Override
+    synchronized String getCaptured() {
+      return captured;
+    }
+
+    @Override
+    Pattern materialize() {
+      if (captured == null) {
+        return this;
+      }
+      Pattern copy = new Pattern(pattern);
+      copy.setCaptured(captured);
+      return copy;
+    }
+
+    @Override
+    boolean isPattern() {
+      return true;
+    }
+
+    @Override
+    Pattern asPattern() {
+      return this;
+    }
+
+    @Override
+    public String toString() {
+      return pattern;
+    }
+  }
+
+  static class BackReference extends ProguardWildcard {
+    // Back-reference is not referable, hence the other type, Pattern, here.
+    Pattern reference;
+    final int referenceIndex;
+
+    BackReference(int referenceIndex) {
+      this.referenceIndex = referenceIndex;
+    }
+
+    void setReference(Pattern reference) {
+      this.reference = reference;
+    }
+
+    @Override
+    void setCaptured(String captured) {
+      throw new Unreachable("A back reference refers back to a previously matched wildcard.");
+    }
+
+    @Override
+    void clearCaptured() {
+      // no-op
+    }
+
+    @Override
+    String getCaptured() {
+      return reference != null ? reference.getCaptured() : null;
+    }
+
+    @Override
+    BackReference materialize() {
+      if (reference == null || reference.getCaptured() == null) {
+        return this;
+      }
+      BackReference copy = new BackReference(referenceIndex);
+      copy.setReference(reference.materialize());
+      return copy;
+    }
+
+    @Override
+    boolean isBackReference() {
+      return true;
+    }
+
+    @Override
+    BackReference asBackReference() {
+      return this;
+    }
+
+    @Override
+    public String toString() {
+      return "<" + referenceIndex + ">";
+    }
+  }
+
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 3912b1c..700e146 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -322,15 +322,80 @@
       Set<DexEncodedMethod> liveMethods,
       Set<DexEncodedField> liveFields) throws ExecutionException {
     application.timing.begin("Find consequent items for -if rules...");
+    Function<DexType, DexClass> definitionForWithLiveTypes = type -> {
+      DexClass clazz = appInfo.definitionFor(type);
+      if (clazz != null && liveTypes.contains(clazz.type)) {
+        return clazz;
+      }
+      return null;
+    };
     try {
       List<Future<?>> futures = new ArrayList<>();
       if (rules != null) {
         for (ProguardConfigurationRule rule : rules) {
           assert rule instanceof ProguardIfRule;
           ProguardIfRule ifRule = (ProguardIfRule) rule;
-          if (ruleSatisfiedWithLiveItems(
-              ifRule, appInfo::definitionFor, liveTypes, liveMethods, liveFields)) {
-            runPerRule(executorService, futures, ifRule.subsequentRule, ifRule);
+          // Depending on which types trigger the -if rule, the application of the subsequent
+          // -keep rule may vary (due to back references). So, we need to try all pairs of -if rule
+          // and live types.
+          for (DexType currentLiveType : liveTypes) {
+            if (ifRule.hasInheritanceClassName()) {
+              if (!satisfyInheritanceRule(currentLiveType, definitionForWithLiveTypes, ifRule)) {
+                // Try another live type since the current one doesn't satisfy the inheritance rule.
+                continue;
+              }
+            }
+            if (ifRule.getClassNames().matches(currentLiveType)) {
+              Collection<ProguardMemberRule> memberKeepRules = ifRule.getMemberRules();
+              if (memberKeepRules.isEmpty()) {
+                ProguardIfRule materializedRule = ifRule.materialize();
+                runPerRule(
+                    executorService, futures, materializedRule.subsequentRule, materializedRule);
+                // No member rule to satisfy. Move on to the next live type.
+                continue;
+              }
+              Set<DexItem> filteredFields = liveFields.stream()
+                  .filter(f -> f.field.getHolder() == currentLiveType)
+                  .collect(Collectors.toSet());
+              Set<DexItem> filteredMethods = liveMethods.stream()
+                  .filter(m -> m.method.getHolder() == currentLiveType)
+                  .collect(Collectors.toSet());
+              // If the number of member rules to hold is more than live members, we can't make it.
+              if (filteredFields.size() + filteredMethods.size() < memberKeepRules.size()) {
+                continue;
+              }
+              // Depending on which members trigger the -if rule, the application of the subsequent
+              // -keep rule may vary (due to back references). So, we need to try literally all
+              // combinations of live members.
+              // TODO(b/79486261): Some of those are equivalent from the point of view of -if rule.
+              Set<Set<DexItem>> combinationsOfMembers = Sets.combinations(
+                  Sets.union(filteredFields, filteredMethods), memberKeepRules.size());
+              for (Set<DexItem> combination : combinationsOfMembers) {
+                Set<DexEncodedField> fieldsInCombination = combination.stream()
+                    .filter(item -> item instanceof DexEncodedField)
+                    .map(item -> (DexEncodedField) item)
+                    .collect(Collectors.toSet());
+                Set<DexEncodedMethod> methodsInCombination = combination.stream()
+                    .filter(item -> item instanceof DexEncodedMethod)
+                    .map(item -> (DexEncodedMethod) item)
+                    .collect(Collectors.toSet());
+                // Member rules are combined as AND logic: if found unsatisfied member rule, this
+                // combination of live members is not a good fit.
+                boolean satisfied = true;
+                for (ProguardMemberRule memberRule : memberKeepRules) {
+                  if (!ruleSatisfiedByFields(memberRule, fieldsInCombination)
+                      && !ruleSatisfiedByMethods(memberRule, methodsInCombination)) {
+                    satisfied = false;
+                    break;
+                  }
+                }
+                if (satisfied) {
+                  ProguardIfRule materializedRule = ifRule.materialize();
+                  runPerRule(
+                      executorService, futures, materializedRule.subsequentRule, materializedRule);
+                }
+              }
+            }
           }
         }
         ThreadUtils.awaitFutures(futures);
@@ -439,58 +504,6 @@
             rule.getInheritanceAnnotation());
   }
 
-  private boolean ruleSatisfiedWithLiveItems(
-      ProguardIfRule ifRule,
-      Function<DexType, DexClass> definitionFor,
-      Set<DexType> liveTypes,
-      Set<DexEncodedMethod> liveMethods,
-      Set<DexEncodedField> liveFields) {
-    DexType matchedType = null;
-    Function<DexType, DexClass> definitionForWithLiveTypes = type -> {
-      DexClass clazz = definitionFor.apply(type);
-      if (clazz != null && liveTypes.contains(clazz.type)) {
-        return clazz;
-      }
-      return null;
-    };
-    for (DexType liveType : liveTypes) {
-      if (ifRule.hasInheritanceClassName()) {
-        if (!satisfyInheritanceRule(liveType, definitionForWithLiveTypes, ifRule)) {
-          // Try another live type since the current one doesn't satisfy the inheritance rule.
-          continue;
-        }
-      }
-      if (ifRule.getClassNames().matches(liveType)) {
-        matchedType = liveType;
-        final DexType filterType = matchedType;
-        Collection<ProguardMemberRule> memberKeepRules = ifRule.getMemberRules();
-        Set<DexEncodedMethod> filteredMethods =
-            liveMethods.stream().filter(method -> method.method.getHolder() == filterType)
-                .collect(Collectors.toSet());
-        Set<DexEncodedField> filteredFields =
-            liveFields.stream().filter(field -> field.field.getHolder() == filterType)
-                .collect(Collectors.toSet());
-        boolean notSatisfied = false;
-        for (ProguardMemberRule rule : memberKeepRules) {
-          if (!ruleSatisfiedByMethods(rule, filteredMethods)
-            && !ruleSatisfiedByFields(rule, filteredFields)) {
-            notSatisfied = true;
-            break;
-          }
-        }
-        // Try another live type if the current live type does not satisfy any member rule.
-        if (notSatisfied) {
-          continue;
-        }
-        // TODO(b/73800755): we may need to return the matched live type for back reference.
-        // Maybe, collect all matched live types.
-        return true;
-      }
-    }
-    // No live types satisfy the given -if rule.
-    return false;
-  }
-
   private boolean allRulesSatisfied(Collection<ProguardMemberRule> memberKeepRules,
       DexClass clazz) {
     for (ProguardMemberRule rule : memberKeepRules) {
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 1bf193b..1b026c0 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.ArchiveClassFileProvider;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.ClassFileResourceProvider;
-import com.android.tools.r8.DataResourceProvider;
 import com.android.tools.r8.DexFilePerClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DirectoryClassFileProvider;
@@ -49,7 +48,6 @@
 public class AndroidApp {
 
   private final ImmutableList<ProgramResourceProvider> programResourceProviders;
-  private final ImmutableList<DataResourceProvider> dataResourceProviders;
   private final ImmutableMap<Resource, String> programResourcesMainDescriptor;
   private final ImmutableList<ClassFileResourceProvider> classpathResourceProviders;
   private final ImmutableList<ClassFileResourceProvider> libraryResourceProviders;
@@ -61,7 +59,6 @@
   // See factory methods and AndroidApp.Builder below.
   private AndroidApp(
       ImmutableList<ProgramResourceProvider> programResourceProviders,
-      ImmutableList<DataResourceProvider> dataResourceProviders,
       ImmutableMap<Resource, String> programResourcesMainDescriptor,
       ImmutableList<ClassFileResourceProvider> classpathResourceProviders,
       ImmutableList<ClassFileResourceProvider> libraryResourceProviders,
@@ -69,7 +66,6 @@
       List<StringResource> mainDexListResources,
       List<String> mainDexClasses) {
     this.programResourceProviders = programResourceProviders;
-    this.dataResourceProviders = dataResourceProviders;
     this.programResourcesMainDescriptor = programResourcesMainDescriptor;
     this.classpathResourceProviders = classpathResourceProviders;
     this.libraryResourceProviders = libraryResourceProviders;
@@ -146,11 +142,6 @@
     return programResourceProviders;
   }
 
-  /** Get non program resource providers. */
-  public List<DataResourceProvider> getDataResourceProviders() {
-    return dataResourceProviders;
-  }
-
   /** Get classpath resource providers. */
   public List<ClassFileResourceProvider> getClasspathResourceProviders() {
     return classpathResourceProviders;
@@ -276,7 +267,6 @@
 
     private final List<ProgramResourceProvider> programResourceProviders = new ArrayList<>();
     private final List<ProgramResource> programResources = new ArrayList<>();
-    private final List<DataResourceProvider> dataResourceProviders = new ArrayList<>();
     private final Map<ProgramResource, String> programResourcesMainDescriptor = new HashMap<>();
     private final List<ClassFileResourceProvider> classpathResourceProviders = new ArrayList<>();
     private final List<ClassFileResourceProvider> libraryResourceProviders = new ArrayList<>();
@@ -300,7 +290,6 @@
       programResourceProviders.addAll(app.programResourceProviders);
       classpathResourceProviders.addAll(app.classpathResourceProviders);
       libraryResourceProviders.addAll(app.libraryResourceProviders);
-      dataResourceProviders.addAll(app.dataResourceProviders);
       mainDexListResources = app.mainDexListResources;
       mainDexListClasses = app.mainDexClasses;
     }
@@ -329,8 +318,6 @@
         ArchiveResourceProvider archiveResourceProvider =
             new ArchiveResourceProvider(archive, ignoreDexInArchive);
         addProgramResourceProvider(archiveResourceProvider);
-        addDataResourceProvider(archiveResourceProvider);
-
       }
       return this;
     }
@@ -341,12 +328,6 @@
       return this;
     }
 
-    public Builder addDataResourceProvider(DataResourceProvider provider) {
-      assert provider != null;
-      dataResourceProviders.add(provider);
-      return this;
-    }
-
     /**
      * Add classpath file resources.
      */
@@ -562,7 +543,6 @@
       }
       return new AndroidApp(
           ImmutableList.copyOf(programResourceProviders),
-          ImmutableList.copyOf(dataResourceProviders),
           ImmutableMap.copyOf(programResourcesMainDescriptor),
           ImmutableList.copyOf(classpathResourceProviders),
           ImmutableList.copyOf(libraryResourceProviders),
@@ -583,7 +563,6 @@
         ArchiveResourceProvider archiveResourceProvider = new ArchiveResourceProvider(
             FilteredClassPath.unfiltered(file), ignoreDexInArchive);
         addProgramResourceProvider(archiveResourceProvider);
-        addDataResourceProvider(archiveResourceProvider);
       } else {
         throw new CompilationError("Unsupported source file type", new PathOrigin(file));
       }
@@ -610,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/ArchiveResourceProvider.java b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
index 9c33fc5..37aa208 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
@@ -99,6 +99,11 @@
   }
 
   @Override
+  public DataResourceProvider getDataResourceProvider() {
+    return this;
+  }
+
+  @Override
   public void accept(Visitor resourceBrowser) throws ResourceException {
     try (ZipFile zipFile = new ZipFile(archive.getPath().toFile())) {
       final Enumeration<? extends ZipEntry> entries = zipFile.entries();
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/main/java/com/android/tools/r8/utils/FeatureClassMapping.java b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
index e8c6df9..873013e 100644
--- a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
+++ b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
@@ -77,6 +77,10 @@
     }
   }
 
+  public static FeatureClassMapping fromSpecification(Path file) throws FeatureMappingException {
+    return fromSpecification(file, new DexSplitter.Reporter());
+  }
+
   public static FeatureClassMapping fromSpecification(Path file, DexSplitter.Reporter reporter)
       throws FeatureMappingException {
     FeatureClassMapping mapping = new FeatureClassMapping();
@@ -93,6 +97,11 @@
     return mapping;
   }
 
+  public static FeatureClassMapping fromJarFiles(List<FeatureJar> featureJars, String baseName)
+      throws FeatureMappingException {
+    return fromJarFiles(featureJars, baseName, new DexSplitter.Reporter());
+  }
+
   public static FeatureClassMapping fromJarFiles(
       List<FeatureJar> featureJars, String baseName, DexSplitter.Reporter reporter)
       throws FeatureMappingException {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 3086662..92c68aa 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -4,10 +4,10 @@
 package com.android.tools.r8.utils;
 
 import com.android.tools.r8.ClassFileConsumer;
-import com.android.tools.r8.DexFilePerClassFileConsumer;
-import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DataResourceConsumer;
 import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.DexFilePerClassFileConsumer;
+import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.ProgramConsumer;
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.dex.Marker;
@@ -81,6 +81,7 @@
       enableDevirtualization = false;
       enableNonNullTracking = false;
       enableInlining = false;
+      enableClassInlining = false;
       enableSwitchMapRemoval = false;
       outline.enabled = false;
       enableValuePropagation = false;
@@ -97,6 +98,7 @@
   public boolean enableDevirtualization = true;
   public boolean enableNonNullTracking = true;
   public boolean enableInlining = true;
+  public boolean enableClassInlining = true;
   public int inliningInstructionLimit = 5;
   public boolean enableSwitchMapRemoval = true;
   public final OutlineOptions outline = new OutlineOptions();
@@ -183,6 +185,9 @@
   public OffOrAuto interfaceMethodDesugaring = OffOrAuto.Auto;
   // Defines try-with-resources rewriter behavior.
   public OffOrAuto tryWithResourcesDesugaring = OffOrAuto.Auto;
+  // Flag to turn on/off processing of @dalvik.annotation.codegen.CovariantReturnType and
+  // @dalvik.annotation.codegen.CovariantReturnType$CovariantReturnTypes.
+  public boolean processCovariantReturnTypeAnnotations = true;
 
   // Whether or not to check for valid multi-dex builds.
   //
diff --git a/src/main/java/com/android/tools/r8/utils/Reporter.java b/src/main/java/com/android/tools/r8/utils/Reporter.java
index 63b95b3..ebb7ce6 100644
--- a/src/main/java/com/android/tools/r8/utils/Reporter.java
+++ b/src/main/java/com/android/tools/r8/utils/Reporter.java
@@ -8,6 +8,8 @@
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import java.util.ArrayList;
 import java.util.Collection;
 
@@ -80,7 +82,15 @@
       if (errorCount != 0) {
         AbortException abort;
         if (lastError != null && lastError.getDiagnosticMessage() != null) {
-          abort = new AbortException("Error: " + lastError.getDiagnosticMessage());
+          StringBuilder builder = new StringBuilder("Error: ");
+          if (lastError.getOrigin() != Origin.unknown()) {
+            builder.append(lastError.getOrigin()).append(", ");
+          }
+          if (lastError.getPosition() != Position.UNKNOWN) {
+            builder.append(lastError.getPosition()).append(", ");
+          }
+          builder.append(lastError.getDiagnosticMessage());
+          abort = new AbortException(builder.toString());
         } else {
           abort = new AbortException();
         }
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/examples/multidex/main-dex-rules-whyareyoukeeping.txt b/src/test/examples/multidex/main-dex-rules-whyareyoukeeping.txt
new file mode 100644
index 0000000..4a94d41
--- /dev/null
+++ b/src/test/examples/multidex/main-dex-rules-whyareyoukeeping.txt
@@ -0,0 +1,30 @@
+# 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.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class * extends **.Instrumentation {
+  <init>();
+}
+-keep public class * extends **.Application {
+  <init>();
+  void attachBaseContext(*.Context);
+}
+-keep public class * extends **.Activity {
+  <init>();
+}
+-keep public class * extends **.Service {
+  <init>();
+}
+-keep public class * extends **.ContentProvider {
+ <init>();
+}
+-keep public class * extends **.BroadcastReceiver {
+ <init>();
+}
+-keep public class * extends **.BackupAgent {
+ <init>();
+}
+
+-whyareyoukeeping class **
diff --git a/src/test/examplesAndroidO/lambdadesugaring/ValueAdjustments.java b/src/test/examplesAndroidO/lambdadesugaring/ValueAdjustments.java
index f2344b5..1d6772a 100644
--- a/src/test/examplesAndroidO/lambdadesugaring/ValueAdjustments.java
+++ b/src/test/examplesAndroidO/lambdadesugaring/ValueAdjustments.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package lambdadesugaring;
 
+import java.io.Serializable;
+
 public class ValueAdjustments {
   interface B2i {
     int foo(Byte i);
@@ -37,6 +39,22 @@
     Number f();
   }
 
+  interface iSerializableOut {
+    Serializable f();
+  }
+
+  interface iSerializableInt {
+    Object f(Serializable s);
+  }
+
+  interface iComparableOut<T> {
+    Comparable<T> f();
+  }
+
+  interface iComparableInt<T> {
+    Object f(Comparable<T> c);
+  }
+
   interface iByte {
     Byte f();
   }
@@ -164,6 +182,30 @@
         .append(((iNumber) ValueAdjustments::D).f()).append('\n');
   }
 
+  private static void checkSerializableOut(StringBuffer builder) {
+    builder
+        .append(((iSerializableOut) ValueAdjustments::z).f()).append(' ')
+        .append(((iSerializableOut) ValueAdjustments::c).f()).append(' ')
+        .append(((iSerializableOut) ValueAdjustments::b).f()).append(' ')
+        .append(((iSerializableOut) ValueAdjustments::s).f()).append(' ')
+        .append(((iSerializableOut) ValueAdjustments::i).f()).append(' ')
+        .append(((iSerializableOut) ValueAdjustments::j).f()).append(' ')
+        .append(((iSerializableOut) ValueAdjustments::f).f()).append(' ')
+        .append(((iSerializableOut) ValueAdjustments::d).f()).append(' ');
+  }
+
+  private static void checkComparableOut(StringBuffer builder) {
+    builder
+        .append(((iComparableOut) ValueAdjustments::z).f()).append(' ')
+        .append(((iComparableOut) ValueAdjustments::c).f()).append(' ')
+        .append(((iComparableOut) ValueAdjustments::b).f()).append(' ')
+        .append(((iComparableOut) ValueAdjustments::s).f()).append(' ')
+        .append(((iComparableOut) ValueAdjustments::i).f()).append(' ')
+        .append(((iComparableOut) ValueAdjustments::j).f()).append(' ')
+        .append(((iComparableOut) ValueAdjustments::f).f()).append(' ')
+        .append(((iComparableOut) ValueAdjustments::d).f()).append(' ');
+  }
+
   private static void checkBoxes(StringBuffer builder) {
     builder
         .append(((iBoolean) ValueAdjustments::z).f()).append(' ')
@@ -310,20 +352,44 @@
         .append(((BnUnB) ValueAdjustments::boxingAndUnboxingW).foo(true, false, (byte) 1, (byte) 2,
             (char) 33, (char) 44, (short) 5, (short) 6, 7, 8, 9, 10L, 11, 12f, 13, 14d))
         .append('\n')
+        .append(((BnUnB) ValueAdjustments::allSerializable).foo(true, false, (byte) 1, (byte) 2,
+            (char) 33, (char) 44, (short) 5, (short) 6, 7, 8, 9, 10L, 11, 12f, 13, 14d))
+        .append('\n')
+        .append(((BnUnB) ValueAdjustments::allComparable).foo(true, false, (byte) 1, (byte) 2,
+            (char) 33, (char) 44, (short) 5, (short) 6, 7, 8, 9, 10L, 11, 12f, 13, 14d))
+        .append('\n')
         .append(((B2i) (Integer::new)).foo(Byte.valueOf((byte) 44))).append('\n');
   }
 
   static String boxingAndUnboxing(Boolean Z, boolean z, Byte B, byte b, Character C, char c,
       Short S, short s, Integer I, int i, Long L, long l, Float F, float f, Double D, double d) {
-    return "" + Z + ":" + z + ":" + B + ":" + b + ":" + C + ":" + c + ":" + S + ":" + s
-        + ":" + I + ":" + i + ":" + L + ":" + l + ":" + F + ":" + f + ":" + D + ":" + d;
+    return "boxingAndUnboxing: " + Z + ":" + z + ":" + B + ":" + b + ":" + C + ":" + c + ":" + S +
+        ":" + s + ":" + I + ":" + i + ":" + L + ":" + l + ":" + F + ":" + f + ":" + D + ":" + d;
   }
 
   static String boxingAndUnboxingW(boolean Z, boolean z, double B, double b,
       double C, double c, double S, double s, double I, double i, double L, double l,
       double F, double f, double D, double d) {
-    return "" + Z + ":" + z + ":" + B + ":" + b + ":" + C + ":" + c + ":" + S + ":" + s
-        + ":" + I + ":" + i + ":" + L + ":" + l + ":" + F + ":" + f + ":" + D + ":" + d;
+    return "boxingAndUnboxingW: " + Z + ":" + z + ":" + B + ":" + b + ":" + C + ":" + c + ":" + S +
+        ":" + s + ":" + I + ":" + i + ":" + L + ":" + l + ":" + F + ":" + f + ":" + D + ":" + d;
+  }
+
+  static String allSerializable(
+      Serializable Z, Serializable z, Serializable B, Serializable b,
+      Serializable C, Serializable c, Serializable S, Serializable s,
+      Serializable I, Serializable i, Serializable L, Serializable l,
+      Serializable F, Serializable f, Serializable D, Serializable d) {
+    return "allSerializable: " + Z + ":" + z + ":" + B + ":" + b + ":" + C + ":" + c + ":" + S +
+        ":" + s + ":" + I + ":" + i + ":" + L + ":" + l + ":" + F + ":" + f + ":" + D + ":" + d;
+  }
+
+  static String allComparable(
+      Comparable Z, Comparable z, Comparable B, Comparable b,
+      Comparable C, Comparable c, Comparable S, Comparable s,
+      Comparable I, Comparable i, Comparable L, Comparable l,
+      Comparable F, Comparable f, Comparable D, Comparable d) {
+    return "allComparable: " + Z + ":" + z + ":" + B + ":" + b + ":" + C + ":" + c + ":" + S +
+        ":" + s + ":" + I + ":" + i + ":" + L + ":" + l + ":" + F + ":" + f + ":" + D + ":" + d;
   }
 
   static boolean z() {
@@ -393,7 +459,7 @@
   private static void bB70348575(StringBuffer builder) {
     B70348575_C1 c1 = new B70348575_C1();
     B70348575_A1 a = c1.getB().get();
-    builder.append(a.greet()).append('\n');;
+    builder.append(a.greet()).append('\n');
   }
 
   public static void main(String[] args) {
@@ -410,6 +476,8 @@
 
     checkBoxes(builder);
     checkNumber(builder);
+    checkSerializableOut(builder);
+    checkComparableOut(builder);
     checkObject(builder);
 
     checkMisc(builder);
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..b27569b 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";
@@ -995,6 +995,14 @@
       "625-checker-licm-regressions"
   );
 
+  private static List<String> requireClassInliningToBeDisabled = ImmutableList.of(
+      // Test depends on exception produced for missing method or similar cases, but
+      // after class inlining removes class instantiations and references the exception
+      // is not produced.
+      "042-new-instance",
+      "075-verification-error"
+  );
+
   private static List<String> hasMissingClasses = ImmutableList.of(
       "091-override-package-private-method",
       "003-omnibus-opcodes",
@@ -1085,6 +1093,8 @@
     private final boolean outputMayDiffer;
     // Whether to disable inlining
     private final boolean disableInlining;
+    // Whether to disable class inlining
+    private final boolean disableClassInlining;
     // Has missing classes.
     private final boolean hasMissingClasses;
 
@@ -1102,6 +1112,7 @@
         boolean expectedToFailWithX8,
         boolean outputMayDiffer,
         boolean disableInlining,
+        boolean disableClassInlining,
         boolean hasMissingClasses,
         DexVm dexVm) {
       this.name = name;
@@ -1117,6 +1128,7 @@
       this.expectedToFailWithX8 = expectedToFailWithX8;
       this.outputMayDiffer = outputMayDiffer;
       this.disableInlining = disableInlining;
+      this.disableClassInlining = disableClassInlining;
       this.hasMissingClasses = hasMissingClasses;
     }
 
@@ -1142,6 +1154,7 @@
           false,
           false,
           disableInlining,
+          true, // Disable class inlining for JCTF tests.
           false,
           dexVm);
     }
@@ -1306,6 +1319,7 @@
                 expectedToFailWithCompilerSet.contains(name),
                 outputMayDiffer.contains(name),
                 requireInliningToBeDisabled.contains(name),
+                requireClassInliningToBeDisabled.contains(name),
                 hasMissingClasses.contains(name),
                 dexVm));
       }
@@ -1372,11 +1386,12 @@
       String resultPath,
       CompilationMode compilationMode,
       boolean disableInlining,
+      boolean disableClassInlining,
       boolean hasMissingClasses)
       throws IOException, ProguardRuleParserException, ExecutionException, CompilationException,
       CompilationFailedException {
     executeCompilerUnderTest(compilerUnderTest, fileNames, resultPath, compilationMode, null,
-        disableInlining, hasMissingClasses);
+        disableInlining, disableClassInlining, hasMissingClasses);
   }
 
   private void executeCompilerUnderTest(
@@ -1385,7 +1400,9 @@
       String resultPath,
       CompilationMode mode,
       String keepRulesFile,
-      boolean disableInlining, boolean hasMissingClasses)
+      boolean disableInlining,
+      boolean disableClassInlining,
+      boolean hasMissingClasses)
       throws IOException, ProguardRuleParserException, ExecutionException, CompilationException,
         CompilationFailedException {
     assert mode != null;
@@ -1523,6 +1540,9 @@
                 if (disableInlining) {
                   options.enableInlining = false;
                 }
+                if (disableClassInlining) {
+                  options.enableClassInlining = false;
+                }
                 options.lineNumberOptimization = LineNumberOptimization.OFF;
                 // Some tests actually rely on missing classes for what they test.
                 options.ignoreMissingClasses = hasMissingClasses;
@@ -1734,7 +1754,8 @@
       throws IOException, ProguardRuleParserException, ExecutionException, CompilationException,
       CompilationFailedException {
     executeCompilerUnderTest(compilerUnderTest, fileNames, resultDir.getAbsolutePath(), mode,
-        specification.disableInlining, specification.hasMissingClasses);
+        specification.disableInlining, specification.disableClassInlining,
+        specification.hasMissingClasses);
 
     if (!ToolHelper.artSupported()) {
       return;
@@ -1907,7 +1928,8 @@
       try {
         executeCompilerUnderTest(
             compilerUnderTest, fileNames, resultDir.getCanonicalPath(), compilationMode,
-            specification.disableInlining, specification.hasMissingClasses);
+            specification.disableInlining, specification.disableClassInlining,
+            specification.hasMissingClasses);
       } catch (CompilationException | CompilationFailedException e) {
         throw new CompilationError(e.getMessage(), e);
       } catch (ExecutionException e) {
@@ -1919,13 +1941,15 @@
       expectException(Throwable.class);
       executeCompilerUnderTest(
           compilerUnderTest, fileNames, resultDir.getCanonicalPath(), compilationMode,
-          specification.disableInlining, specification.hasMissingClasses);
+          specification.disableInlining, specification.disableClassInlining,
+          specification.hasMissingClasses);
       System.err.println("Should have failed R8/D8 compilation with an exception.");
       return;
     } else {
       executeCompilerUnderTest(
           compilerUnderTest, fileNames, resultDir.getCanonicalPath(), compilationMode,
-          specification.disableInlining, specification.hasMissingClasses);
+          specification.disableInlining, specification.disableClassInlining,
+          specification.hasMissingClasses);
     }
 
     if (!specification.skipArt && ToolHelper.artSupported()) {
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index 9d45f6a..62dd4af 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);
   }
 
@@ -136,14 +156,24 @@
         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));
+        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;
+                      if (output == Output.CF) {
+                        // Class inliner is not supported with CF backend yet.
+                        options.enableClassInlining = false;
+                      }
+                    }));
         break;
       }
       default:
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/bridgeremoval/B77836766.java b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
index 5ec1bce..cc8633e4 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
@@ -432,7 +432,7 @@
     assertEquals(0, javaResult.exitCode);
 
     AndroidApp processedApp = compileWithR8(jasminBuilder.build(), proguardConfig,
-        // Disable inlining to avoid the (short) tested method from being inlined then removed.
+        // Disable inlining to avoid the (short) tested method from being inlined and then removed.
         internalOptions -> internalOptions.enableInlining = false);
 
     // Run processed (output) program on ART
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
index 35a7613..60df7d2 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
@@ -109,7 +109,7 @@
     assertEquals(0, javaResult.exitCode);
 
     AndroidApp optimizedApp = compileWithR8(jasminBuilder.build(), proguardConfig,
-        // Disable inlining to avoid the (short) tested method from being inlined then removed.
+        // Disable inlining to avoid the (short) tested method from being inlined and then removed.
         internalOptions -> internalOptions.enableInlining = false);
 
     // Run optimized (output) program on ART
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..d6e0542 100644
--- a/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.cf;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.CompilationMode;
@@ -14,8 +13,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.errors.CompilationError;
-import com.android.tools.r8.errors.InvalidDebugInfoException;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import java.nio.file.Path;
@@ -36,18 +33,9 @@
     ProcessResult run1 = ToolHelper.runJava(out1, CLASS.getCanonicalName());
     assertEquals(runInput.toString(), run1.toString());
     Path out2 = temp.getRoot().toPath().resolve("out2.zip");
-    boolean invalidDebugInfo = false;
-    try {
-      build(builder -> builder.addProgramFiles(out1), new ClassFileConsumer.ArchiveConsumer(out2));
-    } catch (CompilationError e) {
-      invalidDebugInfo = e.getCause() instanceof InvalidDebugInfoException;
-    }
-    // TODO(b/77522100): Change to assertFalse when fixed.
-    assertTrue(invalidDebugInfo);
-    if (!invalidDebugInfo) {
-      ProcessResult run2 = ToolHelper.runJava(out2, CLASS.getCanonicalName());
-      assertEquals(runInput.toString(), run2.toString());
-    }
+    build(builder -> builder.addProgramFiles(out1), new ClassFileConsumer.ArchiveConsumer(out2));
+    ProcessResult run2 = ToolHelper.runJava(out2, CLASS.getCanonicalName());
+    assertEquals(runInput.toString(), run2.toString());
   }
 
   private void build(ThrowingConsumer<Builder, Exception> input, ProgramConsumer consumer)
@@ -58,11 +46,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/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index e256ae4..c6a2bd3 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -39,6 +39,7 @@
 
   private void configure(InternalOptions options) {
     options.enableClassMerging = true;
+    options.enableClassInlining = false;
   }
 
   private void runR8(Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
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/d8/D8FrameworkDexPassthroughMarkerTest.java b/src/test/java/com/android/tools/r8/d8/D8FrameworkDexPassthroughMarkerTest.java
index 9ce3d9c..7daaddd 100644
--- a/src/test/java/com/android/tools/r8/d8/D8FrameworkDexPassthroughMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/d8/D8FrameworkDexPassthroughMarkerTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
@@ -61,12 +62,14 @@
     Marker marker = new Marker(Tool.D8)
         .setVersion("1.0.0")
         .setMinApi(minApi);
-    Marker selfie = Marker.parse(marker.toString());
+    InternalOptions options = new InternalOptions();
+    DexString markerDexString = options.itemFactory.createString(marker.toString());
+    Marker selfie = Marker.parse(markerDexString);
     assert marker.equals(selfie);
-    AndroidApp app = ToolHelper.runD8(command, options -> options.setMarker(marker));
+    AndroidApp app = ToolHelper.runD8(command, opts -> opts.setMarker(marker));
     DexApplication dexApp =
         new ApplicationReader(
-                app, new InternalOptions(), new Timing("D8FrameworkDexPassthroughMarkerTest"))
+                app, options, new Timing("D8FrameworkDexPassthroughMarkerTest"))
             .read();
     Marker readMarker = dexApp.dexItemFactory.extractMarker();
     assertEquals(marker, readMarker);
diff --git a/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java b/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java
index 45e3c96..eca0441 100644
--- a/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java
@@ -51,8 +51,9 @@
       .add(KotlinD8Config.DEBUGGEE_KOTLIN_JAR, ContinuousSteppingTest::allVersions)
       .addAll(findAllJarsIn(Paths.get(ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR)),
           ContinuousSteppingTest::fromAndroidN)
-      .addAll(findAllJarsIn(Paths.get(ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR)),
-          ContinuousSteppingTest::fromAndroidO)
+      // TODO(b/79911828) Investigate timeout issues for Android O examples.
+      //  .addAll(findAllJarsIn(Paths.get(ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR)),
+      //      ContinuousSteppingTest::fromAndroidO)
       .build();
 
   private static final Map<Path, DebugTestConfig> compiledJarConfig = new HashMap<>();
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/LocalsLiveAtBlockEntryDebugTest.java b/src/test/java/com/android/tools/r8/debug/LocalsLiveAtBlockEntryDebugTest.java
new file mode 100644
index 0000000..36cdad0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LocalsLiveAtBlockEntryDebugTest.java
@@ -0,0 +1,117 @@
+// 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 static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.jasmin.JasminBuilder;
+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 java.util.Collections;
+import java.util.List;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Test to check that locals that are introduced in a block that is not hit, still start in the
+ * block where they first become visible.
+ *
+ * <p>See b/75251251 or b/78617758
+ */
+public class LocalsLiveAtBlockEntryDebugTest extends DebugTestBase {
+
+  final String className = "LocalsLiveAtEntry";
+  final String sourcefile = className + ".j";
+  final String methodName = "test";
+
+  @Rule
+  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  @Test
+  public void testCF() throws Throwable {
+    JasminBuilder builder = getBuilderForTest(className, methodName);
+    Path outdir = temp.newFolder().toPath();
+    builder.writeClassFiles(outdir);
+    CfDebugTestConfig config = new CfDebugTestConfig();
+    config.addPaths(outdir);
+    runTest(config);
+  }
+
+  @Test
+  @Ignore("b/78617758")
+  public void testD8() throws Throwable {
+    JasminBuilder builder = getBuilderForTest(className, methodName);
+    List<Path> outputs = builder.writeClassFiles(temp.newFolder().toPath());
+    runTest(new D8DebugTestConfig().compileAndAdd(temp, outputs));
+  }
+
+  private void runTest(DebugTestConfig config) throws Throwable {
+    DexInspector inspector =
+        new DexInspector(
+            (config instanceof CfDebugTestConfig)
+                ? Collections.singletonList(config.getPaths().get(1).resolve(className + ".class"))
+                : config.getPaths());
+    ClassSubject clazz = inspector.clazz(className);
+    MethodSubject method = clazz.method("void", methodName, ImmutableList.of("java.lang.Object"));
+    assertTrue(method.isPresent());
+    runDebugTest(
+        config,
+        className,
+        breakpoint(className, methodName),
+        run(),
+        checkLine(sourcefile, 1),
+        checkNoLocal("obj"),
+        stepOver(),
+        checkLine(sourcefile, 3),
+        checkLocal("obj"),
+        stepOver(),
+        checkLine(sourcefile, 100),
+        run());
+  }
+
+  private JasminBuilder getBuilderForTest(String testClassName, String testMethodName) {
+    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder.ClassBuilder clazz = builder.addClass(testClassName);
+
+    clazz.addStaticMethod(
+        testMethodName,
+        ImmutableList.of("Ljava/lang/Object;"),
+        "V",
+        ".limit stack 2",
+        ".limit locals 3",
+        ".var 0 is obj L" + testClassName + "; from L1 to L3",
+        "L0:", // Preamble code that does not have any live locals (eg, no formals too!).
+        ".line 1",
+        " ldc 42",
+        " lookupswitch",
+        "   0: L1",
+        "   default: L2",
+        "L1:", // late introduction of formals.
+        ".line 2",
+        " aconst_null",
+        " pop",
+        "L2:", // target block with first visible location of locals.
+        ".line 3",
+        " aconst_null",
+        " pop",
+        " return",
+        "L3:");
+
+    clazz.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        ".line 100",
+        "aconst_null",
+        "invokestatic " + testClassName + "/" + testMethodName + "(Ljava/lang/Object;)V",
+        "return");
+
+    return builder;
+  }
+}
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/debug/UnusedCheckCastTargetOptimizationTest.java b/src/test/java/com/android/tools/r8/debug/UnusedCheckCastTargetOptimizationTest.java
new file mode 100644
index 0000000..8fd6cd8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/UnusedCheckCastTargetOptimizationTest.java
@@ -0,0 +1,16 @@
+// 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 UnusedCheckCastTargetOptimizationTest {
+
+  class Super {}
+
+  class Subclass extends Super {}
+
+  public static void main(String[] args) {
+    Super[] b = new Subclass[10];
+    Subclass[] c = (Subclass[]) b;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/UnusedCheckCastTargetOptimizationTestRunner.java b/src/test/java/com/android/tools/r8/debug/UnusedCheckCastTargetOptimizationTestRunner.java
new file mode 100644
index 0000000..2b93218
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/UnusedCheckCastTargetOptimizationTestRunner.java
@@ -0,0 +1,32 @@
+// 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 org.junit.Test;
+
+public class UnusedCheckCastTargetOptimizationTestRunner extends DebugTestBase {
+
+  private static final Class MAIN_CLASS = UnusedCheckCastTargetOptimizationTest.class;
+  private static final Class SUPER_CLASS = UnusedCheckCastTargetOptimizationTest.Super.class;
+  private static final Class SUBCLASS_CLASS = UnusedCheckCastTargetOptimizationTest.Subclass.class;
+  private static final String FILE = MAIN_CLASS.getSimpleName() + ".java";
+  private static final String NAME = MAIN_CLASS.getCanonicalName();
+
+  @Test
+  public void test() throws Throwable {
+    runDebugTest(
+        new D8DebugTestConfig().compileAndAddClasses(temp, MAIN_CLASS, SUPER_CLASS, SUBCLASS_CLASS),
+        NAME,
+        breakpoint(NAME, "main", 14),
+        run(),
+        checkLine(FILE, 14),
+        checkLocal("b"),
+        checkNoLocal("c"),
+        stepOver(),
+        checkLine(FILE, 15),
+        checkLocal("b"),
+        checkLocal("c"),
+        run());
+  }
+}
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/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
index 9edc735..89935a7 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexCode.Try;
@@ -25,6 +24,7 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
@@ -80,7 +80,7 @@
             holder, dexItemFactory.createProto(dexItemFactory.voidType), "theMethod"),
         MethodAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC, false),
         DexAnnotationSet.empty(),
-        DexAnnotationSetRefList.empty(),
+        ParameterAnnotationsList.empty(),
         code);
   }
 
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..16785f6 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(appInfo, 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..aefe665 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
@@ -7,9 +7,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfo;
@@ -124,15 +122,11 @@
             .method(
                 new MethodSignature("subtractConstants8bitRegisters", "int", ImmutableList.of()))
             .getMethod();
-    try {
-      IRCode irCode = subtract.buildIR(TEST_OPTIONS);
-      TypeAnalysis analysis = new TypeAnalysis(appInfo, subtract, irCode);
-      analysis.forEach((v, l) -> {
-        assertEither(l, PRIMITIVE, NULL, TOP);
-      });
-    } catch (ApiLevelException e) {
-      fail(e.getMessage());
-    }
+    IRCode irCode = subtract.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    TypeAnalysis analysis = new TypeAnalysis(appInfo, subtract, irCode);
+    analysis.forEach((v, l) -> {
+      assertEither(l, PRIMITIVE, NULL, TOP);
+    });
   }
 
   // A couple branches, along with some recursive calls.
@@ -142,15 +136,11 @@
         inspector.clazz("Test")
             .method(new MethodSignature("fibonacci", "int", ImmutableList.of("int")))
             .getMethod();
-    try {
-      IRCode irCode = fib.buildIR(TEST_OPTIONS);
-      TypeAnalysis analysis = new TypeAnalysis(appInfo, fib, irCode);
-      analysis.forEach((v, l) -> {
-        assertEither(l, PRIMITIVE, NULL);
-      });
-    } catch (ApiLevelException e) {
-      fail(e.getMessage());
-    }
+    IRCode irCode = fib.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    TypeAnalysis analysis = new TypeAnalysis(appInfo, fib, irCode);
+    analysis.forEach((v, l) -> {
+      assertEither(l, PRIMITIVE, NULL);
+    });
   }
 
   // fill-array-data
@@ -160,32 +150,28 @@
         inspector.clazz("Test")
             .method(new MethodSignature("test1", "int[]", ImmutableList.of()))
             .getMethod();
-    try {
-      IRCode irCode = test1.buildIR(TEST_OPTIONS);
-      TypeAnalysis analysis = new TypeAnalysis(appInfo, test1, irCode);
-      Value array = null;
-      InstructionIterator iterator = irCode.instructionIterator();
-      while (iterator.hasNext()) {
-        Instruction instruction = iterator.next();
-        if (instruction instanceof NewArrayEmpty) {
-          array = instruction.outValue();
-          break;
-        }
+    IRCode irCode = test1.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    TypeAnalysis analysis = new TypeAnalysis(appInfo, test1, irCode);
+    Value array = null;
+    InstructionIterator iterator = irCode.instructionIterator();
+    while (iterator.hasNext()) {
+      Instruction instruction = iterator.next();
+      if (instruction instanceof NewArrayEmpty) {
+        array = instruction.outValue();
+        break;
       }
-      assertNotNull(array);
-      final Value finalArray = array;
-      analysis.forEach((v, l) -> {
-        if (v == finalArray) {
-          assertTrue(l.isArrayTypeLatticeElement());
-          ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
-          assertTrue(lattice.getArrayType().isPrimitiveArrayType());
-          assertEquals(1, lattice.getNesting());
-          assertFalse(lattice.isNullable());
-        }
-      });
-    } catch (ApiLevelException e) {
-      fail(e.getMessage());
     }
+    assertNotNull(array);
+    final Value finalArray = array;
+    analysis.forEach((v, l) -> {
+      if (v == finalArray) {
+        assertTrue(l.isArrayTypeLatticeElement());
+        ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
+        assertTrue(lattice.getArrayType().isPrimitiveArrayType());
+        assertEquals(1, lattice.getNesting());
+        assertFalse(lattice.isNullable());
+      }
+    });
   }
 
   // filled-new-array
@@ -195,32 +181,28 @@
         inspector.clazz("Test")
             .method(new MethodSignature("test4", "int[]", ImmutableList.of()))
             .getMethod();
-    try {
-      IRCode irCode = test4.buildIR(TEST_OPTIONS);
-      TypeAnalysis analysis = new TypeAnalysis(appInfo, test4, irCode);
-      Value array = null;
-      InstructionIterator iterator = irCode.instructionIterator();
-      while (iterator.hasNext()) {
-        Instruction instruction = iterator.next();
-        if (instruction instanceof InvokeNewArray) {
-          array = instruction.outValue();
-          break;
-        }
+    IRCode irCode = test4.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    TypeAnalysis analysis = new TypeAnalysis(appInfo, test4, irCode);
+    Value array = null;
+    InstructionIterator iterator = irCode.instructionIterator();
+    while (iterator.hasNext()) {
+      Instruction instruction = iterator.next();
+      if (instruction instanceof InvokeNewArray) {
+        array = instruction.outValue();
+        break;
       }
-      assertNotNull(array);
-      final Value finalArray = array;
-      analysis.forEach((v, l) -> {
-        if (v == finalArray) {
-          assertTrue(l.isArrayTypeLatticeElement());
-          ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
-          assertTrue(lattice.getArrayType().isPrimitiveArrayType());
-          assertEquals(1, lattice.getNesting());
-          assertFalse(lattice.isNullable());
-        }
-      });
-    } catch (ApiLevelException e) {
-      fail(e.getMessage());
     }
+    assertNotNull(array);
+    final Value finalArray = array;
+    analysis.forEach((v, l) -> {
+      if (v == finalArray) {
+        assertTrue(l.isArrayTypeLatticeElement());
+        ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
+        assertTrue(lattice.getArrayType().isPrimitiveArrayType());
+        assertEquals(1, lattice.getNesting());
+        assertFalse(lattice.isNullable());
+      }
+    });
   }
 
   // Make sure the analysis does not hang.
@@ -230,20 +212,16 @@
         inspector.clazz("Test")
             .method(new MethodSignature("loop2", "void", ImmutableList.of()))
             .getMethod();
-    try {
-      IRCode irCode = loop2.buildIR(TEST_OPTIONS);
-      TypeAnalysis analysis = new TypeAnalysis(appInfo, loop2, irCode);
-      analysis.forEach((v, l) -> {
-        if (l.isClassTypeLatticeElement()) {
-          ClassTypeLatticeElement lattice = l.asClassTypeLatticeElement();
-          assertEquals("Ljava/io/PrintStream;", lattice.getClassType().toDescriptorString());
-          // TODO(b/70795205): Can be refined by using control-flow info.
-          assertTrue(l.isNullable());
-        }
-      });
-    } catch (ApiLevelException e) {
-      fail(e.getMessage());
-    }
+    IRCode irCode = loop2.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    TypeAnalysis analysis = new TypeAnalysis(appInfo, loop2, irCode);
+    analysis.forEach((v, l) -> {
+      if (l.isClassTypeLatticeElement()) {
+        ClassTypeLatticeElement lattice = l.asClassTypeLatticeElement();
+        assertEquals("Ljava/io/PrintStream;", lattice.getClassType().toDescriptorString());
+        // TODO(b/70795205): Can be refined by using control-flow info.
+        assertTrue(l.isNullable());
+      }
+    });
   }
 
   // move-exception
@@ -253,19 +231,15 @@
         inspector.clazz("Test")
             .method(new MethodSignature("test2_throw", "int", ImmutableList.of()))
             .getMethod();
-    try {
-      IRCode irCode = test2.buildIR(TEST_OPTIONS);
-      TypeAnalysis analysis = new TypeAnalysis(appInfo, test2, irCode);
-      analysis.forEach((v, l) -> {
-        if (l.isClassTypeLatticeElement()) {
-          ClassTypeLatticeElement lattice = l.asClassTypeLatticeElement();
-          assertEquals("Ljava/lang/Throwable;", lattice.getClassType().toDescriptorString());
-          assertFalse(l.isNullable());
-        }
-      });
-    } catch (ApiLevelException e) {
-      fail(e.getMessage());
-    }
+    IRCode irCode = test2.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    TypeAnalysis analysis = new TypeAnalysis(appInfo, test2, irCode);
+    analysis.forEach((v, l) -> {
+      if (l.isClassTypeLatticeElement()) {
+        ClassTypeLatticeElement lattice = l.asClassTypeLatticeElement();
+        assertEquals("Ljava/lang/Throwable;", lattice.getClassType().toDescriptorString());
+        assertFalse(l.isNullable());
+      }
+    });
   }
 
   // One very complicated example.
@@ -283,13 +257,9 @@
         ConstString.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, false),
         CheckCast.class, new ClassTypeLatticeElement(test, true),
         NewInstance.class, new ClassTypeLatticeElement(test, false));
-    try {
-      IRCode irCode = method.buildIR(TEST_OPTIONS);
-      TypeAnalysis analysis = new TypeAnalysis(appInfo, method, irCode);
-      analysis.forEach((v, l) -> verifyTypeEnvironment(expectedLattices, v, l));
-    } catch (ApiLevelException e) {
-      fail(e.getMessage());
-    }
+    IRCode irCode = method.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    TypeAnalysis analysis = new TypeAnalysis(appInfo, method, irCode);
+    analysis.forEach((v, l) -> verifyTypeEnvironment(expectedLattices, v, l));
   }
 
   // One more complicated example.
@@ -305,13 +275,9 @@
       ConstString.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, false),
       InstanceOf.class, PRIMITIVE,
       StaticGet.class, new ClassTypeLatticeElement(test, true));
-    try {
-      IRCode irCode = method.buildIR(TEST_OPTIONS);
-      TypeAnalysis analysis = new TypeAnalysis(appInfo, method, irCode);
-      analysis.forEach((v, l) -> verifyTypeEnvironment(expectedLattices, v, l));
-    } catch (ApiLevelException e) {
-      fail(e.getMessage());
-    }
+    IRCode irCode = method.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    TypeAnalysis analysis = new TypeAnalysis(appInfo, method, irCode);
+    analysis.forEach((v, l) -> verifyTypeEnvironment(expectedLattices, v, l));
   }
 
   private static void assertEither(TypeLatticeElement actual, TypeLatticeElement... expected) {
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/A.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/A.java
new file mode 100644
index 0000000..c700163
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/A.java
@@ -0,0 +1,11 @@
+// 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.desugar.annotations;
+
+public class A {
+  public A method() {
+    return new A();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/B.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/B.java
new file mode 100644
index 0000000..7d8134f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/B.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.ir.desugar.annotations;
+
+public class B extends A {
+  @Override
+  public A method() {
+    return new B();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/C.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/C.java
new file mode 100644
index 0000000..90030d5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/C.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.ir.desugar.annotations;
+
+public class C extends B {
+  @Override
+  public A method() {
+    return new C();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/Client.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/Client.java
new file mode 100644
index 0000000..0c6bd44
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/Client.java
@@ -0,0 +1,17 @@
+// 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.desugar.annotations;
+
+public class Client {
+  public static void main(String[] args) {
+    A a = new A().method();
+    A b = new B().method();
+    A c = new C().method();
+
+    System.out.println("a=" + a.getClass().getSimpleName());
+    System.out.println("b=" + b.getClass().getSimpleName());
+    System.out.println("c=" + c.getClass().getSimpleName());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnType.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnType.java
new file mode 100644
index 0000000..3d7a7fa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnType.java
@@ -0,0 +1,27 @@
+// 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.desugar.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+// This is a copy of dalvik.annotation.codegen.CovariantReturnType.
+@Repeatable(CovariantReturnType.CovariantReturnTypes.class)
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.METHOD})
+public @interface CovariantReturnType {
+  Class<?> returnType();
+
+  int presentAfter();
+
+  @Retention(RetentionPolicy.CLASS)
+  @Target({ElementType.METHOD})
+  @interface CovariantReturnTypes {
+    CovariantReturnType[] value();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java
new file mode 100644
index 0000000..9c58d25
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java
@@ -0,0 +1,191 @@
+// 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.desugar.annotations;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import java.util.Collections;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class CovariantReturnTypeAnnotationTransformerTest extends AsmTestBase {
+  public static final String PACKAGE_NAME = "com/android/tools/r8/ir/desugar/annotations";
+  public static final String CRT_NAME = "dalvik/annotation/codegen/CovariantReturnType";
+  public static final String CRTS_SIMPLE_NAME = "CovariantReturnTypes";
+
+  @Test
+  public void testVersion1WithClient1And2() throws Exception {
+    AndroidApp input =
+        buildAndroidApp(
+            ToolHelper.getClassAsBytes(Client.class),
+            ToolHelper.getClassAsBytes(A.class),
+            ToolHelper.getClassAsBytes(B.class),
+            ToolHelper.getClassAsBytes(C.class));
+
+    // Version 1 of the library should always work.
+    succeedsIndependentOfFlag(input, false);
+  }
+
+  @Test
+  public void testVersion1WithClient3() throws Exception {
+    AndroidApp input =
+        buildAndroidApp(
+            com.android.tools.r8.ir.desugar.annotations.version3.ClientDump.dump(),
+            ToolHelper.getClassAsBytes(A.class),
+            ToolHelper.getClassAsBytes(B.class),
+            ToolHelper.getClassAsBytes(C.class));
+
+    // There will be no methods with the signature "L.../B;->method()L.../B;" and
+    // "L.../C;->method()L.../C;".
+    failsIndependentOfFlag(input);
+  }
+
+  @Ignore("b/78618808")
+  @Test
+  public void testVersion2WithClient1And2() throws Exception {
+    AndroidApp input =
+        buildAndroidApp(
+            ToolHelper.getClassAsBytes(Client.class),
+            ToolHelper.getClassAsBytes(A.class),
+            com.android.tools.r8.ir.desugar.annotations.version2.BDump.dump(),
+            com.android.tools.r8.ir.desugar.annotations.version2.CDump.dump());
+
+    // Version 2 of the library should always work.
+    succeedsIndependentOfFlag(input, true);
+  }
+
+  @Ignore("b/78618808")
+  @Test
+  public void testVersion2WithClient3() throws Exception {
+    AndroidApp input =
+        buildAndroidApp(
+            com.android.tools.r8.ir.desugar.annotations.version3.ClientDump.dump(),
+            ToolHelper.getClassAsBytes(A.class),
+            com.android.tools.r8.ir.desugar.annotations.version2.BDump.dump(),
+            com.android.tools.r8.ir.desugar.annotations.version2.CDump.dump());
+
+    // If CovariantReturnType annotations are processed, then synthetic methods with the signatures
+    // "L.../B;->method()L.../B;" and "L.../C;->method()L.../C;" will be added by D8.
+    succeedsWithOption(input, true, true);
+
+    // If CovariantReturnType annotations are ignored, then there will be no methods with the
+    // signatures "L.../B;->method()L.../B;" and "L.../C;->method()L.../C;".
+    failsWithOption(input, false);
+  }
+
+  @Test
+  public void testVersion3WithClient3() throws Exception {
+    AndroidApp input =
+        buildAndroidApp(
+            com.android.tools.r8.ir.desugar.annotations.version3.ClientDump.dump(),
+            ToolHelper.getClassAsBytes(A.class),
+            com.android.tools.r8.ir.desugar.annotations.version3.BDump.dump(),
+            com.android.tools.r8.ir.desugar.annotations.version3.CDump.dump());
+
+    // Version 3 of the library should always work.
+    succeedsIndependentOfFlag(input, false);
+  }
+
+  @Test
+  public void testVersion3WithClient1And2() throws Exception {
+    AndroidApp input =
+        buildAndroidApp(
+            ToolHelper.getClassAsBytes(Client.class),
+            ToolHelper.getClassAsBytes(A.class),
+            com.android.tools.r8.ir.desugar.annotations.version3.BDump.dump(),
+            com.android.tools.r8.ir.desugar.annotations.version3.CDump.dump());
+
+    // Version 3 of the library should always work with client 1.
+    succeedsIndependentOfFlag(input, false);
+  }
+
+  private void succeedsWithOption(
+      AndroidApp input, boolean option, boolean checkPresenceOfSyntheticMethods) throws Exception {
+    AndroidApp output =
+        compileWithD8(input, options -> options.processCovariantReturnTypeAnnotations = option);
+    String stdout = runOnArt(output, Client.class.getCanonicalName());
+    Assert.assertEquals(getExpectedOutput(), stdout);
+
+    if (option && checkPresenceOfSyntheticMethods) {
+      checkPresenceOfSyntheticMethods(output);
+    }
+  }
+
+  private void failsWithOption(AndroidApp input, boolean option) throws Exception {
+    AndroidApp output =
+        compileWithD8(input, options -> options.processCovariantReturnTypeAnnotations = option);
+    ToolHelper.ProcessResult result = runOnArtRaw(output, Client.class.getCanonicalName());
+    assertThat(result.stderr, containsString("java.lang.NoSuchMethodError"));
+  }
+
+  private void succeedsIndependentOfFlag(AndroidApp input, boolean checkPresenceOfSyntheticMethods)
+      throws Exception {
+    succeedsWithOption(input, true, checkPresenceOfSyntheticMethods);
+    succeedsWithOption(input, false, checkPresenceOfSyntheticMethods);
+  }
+
+  private void failsIndependentOfFlag(AndroidApp input) throws Exception {
+    failsWithOption(input, true);
+    failsWithOption(input, false);
+  }
+
+  private void checkPresenceOfSyntheticMethods(AndroidApp output) throws Exception {
+    DexInspector inspector = new DexInspector(output);
+
+    // Get classes A, B, and C.
+    DexInspector.ClassSubject clazzA = inspector.clazz(A.class.getCanonicalName());
+    assertThat(clazzA, isPresent());
+
+    DexInspector.ClassSubject clazzB = inspector.clazz(B.class.getCanonicalName());
+    assertThat(clazzB, isPresent());
+
+    DexInspector.ClassSubject clazzC = inspector.clazz(C.class.getCanonicalName());
+    assertThat(clazzC, isPresent());
+
+    // Check that the original methods are there, and that they are not synthetic.
+    DexInspector.MethodSubject methodA =
+        clazzB.method(A.class.getCanonicalName(), "method", Collections.emptyList());
+    assertThat(methodA, isPresent());
+    Assert.assertTrue(!methodA.getMethod().isSyntheticMethod());
+
+    DexInspector.MethodSubject methodB =
+        clazzB.method(A.class.getCanonicalName(), "method", Collections.emptyList());
+    assertThat(methodB, isPresent());
+    Assert.assertTrue(!methodB.getMethod().isSyntheticMethod());
+
+    DexInspector.MethodSubject methodC =
+        clazzC.method(A.class.getCanonicalName(), "method", Collections.emptyList());
+    assertThat(methodC, isPresent());
+    Assert.assertTrue(!methodC.getMethod().isSyntheticMethod());
+
+    // Check that a synthetic method has been added to class B.
+    DexInspector.MethodSubject methodB2 =
+        clazzB.method(B.class.getCanonicalName(), "method", Collections.emptyList());
+    assertThat(methodB2, isPresent());
+    Assert.assertTrue(methodB2.getMethod().isSyntheticMethod());
+
+    // Check that two synthetic methods have been added to class C.
+    DexInspector.MethodSubject methodC2 =
+        clazzC.method(B.class.getCanonicalName(), "method", Collections.emptyList());
+    assertThat(methodC2, isPresent());
+    Assert.assertTrue(methodC2.getMethod().isSyntheticMethod());
+
+    DexInspector.MethodSubject methodC3 =
+        clazzC.method(C.class.getCanonicalName(), "method", Collections.emptyList());
+    assertThat(methodC3, isPresent());
+    Assert.assertTrue(methodC3.getMethod().isSyntheticMethod());
+  }
+
+  private String getExpectedOutput() {
+    return "a=A\nb=B\nc=C\n";
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/B.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/B.java
new file mode 100644
index 0000000..342e3d4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/B.java
@@ -0,0 +1,16 @@
+// 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.desugar.annotations.version2;
+
+import com.android.tools.r8.ir.desugar.annotations.A;
+import com.android.tools.r8.ir.desugar.annotations.CovariantReturnType;
+
+public class B extends A {
+  @CovariantReturnType(returnType = B.class, presentAfter = 25)
+  @Override
+  public A method() {
+    return new B();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/BDump.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/BDump.java
new file mode 100644
index 0000000..3dadc2b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/BDump.java
@@ -0,0 +1,60 @@
+// 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.desugar.annotations.version2;
+
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.CRT_NAME;
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.PACKAGE_NAME;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+// Generated by running tools/asmifier.py on build/classes/test/com/android/tools/r8/ir/desugar/
+// annotations/version2/B.class, removing the subpackage "version2" from all class names, and
+// manually changing the name of the CovariantReturnType annotation to
+// "Ldalvik/annotation/codegen/CovariantReturnType;".
+public class BDump implements Opcodes {
+  public static byte[] dump() throws Exception {
+
+    ClassWriter cw = new ClassWriter(0);
+    FieldVisitor fv;
+    MethodVisitor mv;
+    AnnotationVisitor av0;
+
+    cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, PACKAGE_NAME + "/B", null, PACKAGE_NAME + "/A", null);
+
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/A", "<init>", "()V", false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "method", "()L" + PACKAGE_NAME + "/A;", null, null);
+      {
+        av0 = mv.visitAnnotation("L" + CRT_NAME + ";", false);
+        av0.visit("returnType", Type.getType("L" + PACKAGE_NAME + "/B;"));
+        av0.visit("presentAfter", new Integer(25));
+        av0.visitEnd();
+      }
+      mv.visitCode();
+      mv.visitTypeInsn(NEW, PACKAGE_NAME + "/B");
+      mv.visitInsn(DUP);
+      mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/B", "<init>", "()V", false);
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(2, 1);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/C.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/C.java
new file mode 100644
index 0000000..52fe17f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/C.java
@@ -0,0 +1,17 @@
+// 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.desugar.annotations.version2;
+
+import com.android.tools.r8.ir.desugar.annotations.A;
+import com.android.tools.r8.ir.desugar.annotations.CovariantReturnType;
+
+public class C extends B {
+  @CovariantReturnType(returnType = B.class, presentAfter = 25)
+  @CovariantReturnType(returnType = C.class, presentAfter = 28)
+  @Override
+  public A method() {
+    return new C();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/CDump.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/CDump.java
new file mode 100644
index 0000000..bc06250
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/CDump.java
@@ -0,0 +1,80 @@
+// 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.desugar.annotations.version2;
+
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.*;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+// Generated by running tools/asmifier.py on build/classes/test/com/android/tools/r8/ir/desugar/
+// annotations/version2/C.class, removing the subpackage "version2" from all class names, and
+// manually changing the name of the CovariantReturnType and CovariantReturnTypes annotations to
+// "Ldalvik/annotation/codegen/CovariantReturnType;" and
+// "Ldalvik/annotation/codegen/CovariantReturnType$CovariantReturnTypes;", respectively.
+public class CDump implements Opcodes {
+  public static byte[] dump() throws Exception {
+
+    ClassWriter cw = new ClassWriter(0);
+    FieldVisitor fv;
+    MethodVisitor mv;
+    AnnotationVisitor av0;
+
+    cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, PACKAGE_NAME + "/C", null, PACKAGE_NAME + "/B", null);
+
+    cw.visitInnerClass(
+        CRT_NAME + "$" + CRTS_SIMPLE_NAME,
+        CRT_NAME,
+        CRTS_SIMPLE_NAME,
+        ACC_PUBLIC + ACC_STATIC + ACC_ANNOTATION + ACC_ABSTRACT + ACC_INTERFACE);
+
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/B", "<init>", "()V", false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "method", "()L" + PACKAGE_NAME + "/A;", null, null);
+      {
+        av0 = mv.visitAnnotation("L" + CRT_NAME + "$" + CRTS_SIMPLE_NAME + ";", false);
+        {
+          AnnotationVisitor av1 = av0.visitArray("value");
+          {
+            AnnotationVisitor av2 = av1.visitAnnotation(null, "L" + CRT_NAME + ";");
+            av2.visit("returnType", Type.getType("L" + PACKAGE_NAME + "/B;"));
+            av2.visit("presentAfter", new Integer(25));
+            av2.visitEnd();
+          }
+          {
+            AnnotationVisitor av2 = av1.visitAnnotation(null, "L" + CRT_NAME + ";");
+            av2.visit("returnType", Type.getType("L" + PACKAGE_NAME + "/C;"));
+            av2.visit("presentAfter", new Integer(28));
+            av2.visitEnd();
+          }
+          av1.visitEnd();
+        }
+        av0.visitEnd();
+      }
+      mv.visitCode();
+      mv.visitTypeInsn(NEW, PACKAGE_NAME + "/C");
+      mv.visitInsn(DUP);
+      mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/C", "<init>", "()V", false);
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(2, 1);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/B.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/B.java
new file mode 100644
index 0000000..1b1fa1c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/B.java
@@ -0,0 +1,14 @@
+// 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.desugar.annotations.version3;
+
+import com.android.tools.r8.ir.desugar.annotations.A;
+
+public class B extends A {
+  @Override
+  public B method() {
+    return new B();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/BDump.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/BDump.java
new file mode 100644
index 0000000..91c16c3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/BDump.java
@@ -0,0 +1,72 @@
+// 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.desugar.annotations.version3;
+
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.PACKAGE_NAME;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generated by running tools/asmifier.py on build/classes/test/com/android/tools/r8/ir/desugar/
+// annotations/version3/B.class, and removing the subpackage "version3" from all class names.
+public class BDump implements Opcodes {
+  public static byte[] dump() throws Exception {
+
+    ClassWriter cw = new ClassWriter(0);
+    FieldVisitor fv;
+    MethodVisitor mv;
+    AnnotationVisitor av0;
+
+    cw.visit(
+        V1_8,
+        ACC_PUBLIC + ACC_SUPER,
+        "" + PACKAGE_NAME + "/B",
+        null,
+        "" + PACKAGE_NAME + "/A",
+        null);
+
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(INVOKESPECIAL, "" + PACKAGE_NAME + "/A", "<init>", "()V", false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "method", "()L" + PACKAGE_NAME + "/B;", null, null);
+      mv.visitCode();
+      mv.visitTypeInsn(NEW, "" + PACKAGE_NAME + "/B");
+      mv.visitInsn(DUP);
+      mv.visitMethodInsn(INVOKESPECIAL, "" + PACKAGE_NAME + "/B", "<init>", "()V", false);
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(2, 1);
+      mv.visitEnd();
+    }
+    {
+      mv =
+          cw.visitMethod(
+              ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC,
+              "method",
+              "()L" + PACKAGE_NAME + "/A;",
+              null,
+              null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "" + PACKAGE_NAME + "/B", "method", "()L" + PACKAGE_NAME + "/B;", false);
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/C.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/C.java
new file mode 100644
index 0000000..fd3bedc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/C.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.ir.desugar.annotations.version3;
+
+public class C extends B {
+  @Override
+  public C method() {
+    return new C();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/CDump.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/CDump.java
new file mode 100644
index 0000000..219c3f1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/CDump.java
@@ -0,0 +1,82 @@
+// 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.desugar.annotations.version3;
+
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.PACKAGE_NAME;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generated by running tools/asmifier.py on build/classes/test/com/android/tools/r8/ir/desugar/
+// annotations/version3/C.class, and removing the subpackage "version3" from all class names.
+public class CDump implements Opcodes {
+  public static byte[] dump() throws Exception {
+
+    ClassWriter cw = new ClassWriter(0);
+    FieldVisitor fv;
+    MethodVisitor mv;
+    AnnotationVisitor av0;
+
+    cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, PACKAGE_NAME + "/C", null, PACKAGE_NAME + "/B", null);
+
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/B", "<init>", "()V", false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "method", "()L" + PACKAGE_NAME + "/C;", null, null);
+      mv.visitCode();
+      mv.visitTypeInsn(NEW, PACKAGE_NAME + "/C");
+      mv.visitInsn(DUP);
+      mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/C", "<init>", "()V", false);
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(2, 1);
+      mv.visitEnd();
+    }
+    {
+      mv =
+          cw.visitMethod(
+              ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC,
+              "method",
+              "()L" + PACKAGE_NAME + "/B;",
+              null,
+              null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, PACKAGE_NAME + "/C", "method", "()L" + PACKAGE_NAME + "/C;", false);
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    {
+      mv =
+          cw.visitMethod(
+              ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC,
+              "method",
+              "()L" + PACKAGE_NAME + "/A;",
+              null,
+              null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, PACKAGE_NAME + "/C", "method", "()L" + PACKAGE_NAME + "/C;", false);
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/Client.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/Client.java
new file mode 100644
index 0000000..f2feffa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/Client.java
@@ -0,0 +1,19 @@
+// 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.desugar.annotations.version3;
+
+import com.android.tools.r8.ir.desugar.annotations.A;
+
+public class Client {
+  public static void main(String[] args) {
+    A a = new A().method();
+    B b = new B().method();
+    C c = new C().method();
+
+    System.out.println("a=" + a.getClass().getSimpleName());
+    System.out.println("b=" + b.getClass().getSimpleName());
+    System.out.println("c=" + c.getClass().getSimpleName());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/ClientDump.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/ClientDump.java
new file mode 100644
index 0000000..84971e4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/ClientDump.java
@@ -0,0 +1,144 @@
+// 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.desugar.annotations.version3;
+
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.PACKAGE_NAME;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generated by running tools/asmifier.py on build/classes/test/com/android/tools/r8/ir/desugar/
+// annotations/version3/Client.class, and removing the subpackage "version3" from all class names.
+public class ClientDump implements Opcodes {
+  public static byte[] dump() throws Exception {
+
+    ClassWriter cw = new ClassWriter(0);
+    FieldVisitor fv;
+    MethodVisitor mv;
+    AnnotationVisitor av0;
+
+    cw.visit(
+        V1_8, ACC_PUBLIC + ACC_SUPER, PACKAGE_NAME + "/Client", null, "java/lang/Object", null);
+
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+      mv.visitCode();
+      mv.visitTypeInsn(NEW, PACKAGE_NAME + "/A");
+      mv.visitInsn(DUP);
+      mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/A", "<init>", "()V", false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, PACKAGE_NAME + "/A", "method", "()L" + PACKAGE_NAME + "/A;", false);
+      mv.visitVarInsn(ASTORE, 1);
+      mv.visitTypeInsn(NEW, PACKAGE_NAME + "/B");
+      mv.visitInsn(DUP);
+      mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/B", "<init>", "()V", false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, PACKAGE_NAME + "/B", "method", "()L" + PACKAGE_NAME + "/B;", false);
+      mv.visitVarInsn(ASTORE, 2);
+      mv.visitTypeInsn(NEW, PACKAGE_NAME + "/C");
+      mv.visitInsn(DUP);
+      mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/C", "<init>", "()V", false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, PACKAGE_NAME + "/C", "method", "()L" + PACKAGE_NAME + "/C;", false);
+      mv.visitVarInsn(ASTORE, 3);
+      mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
+      mv.visitInsn(DUP);
+      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
+      mv.visitLdcInsn("a=");
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "java/lang/StringBuilder",
+          "append",
+          "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+          false);
+      mv.visitVarInsn(ALOAD, 1);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/Class", "getSimpleName", "()Ljava/lang/String;", false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "java/lang/StringBuilder",
+          "append",
+          "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+          false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
+      mv.visitInsn(DUP);
+      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
+      mv.visitLdcInsn("b=");
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "java/lang/StringBuilder",
+          "append",
+          "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+          false);
+      mv.visitVarInsn(ALOAD, 2);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/Class", "getSimpleName", "()Ljava/lang/String;", false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "java/lang/StringBuilder",
+          "append",
+          "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+          false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
+      mv.visitInsn(DUP);
+      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
+      mv.visitLdcInsn("c=");
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "java/lang/StringBuilder",
+          "append",
+          "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+          false);
+      mv.visitVarInsn(ALOAD, 3);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/Class", "getSimpleName", "()Ljava/lang/String;", false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "java/lang/StringBuilder",
+          "append",
+          "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+          false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(3, 4);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/CheckCastRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/CheckCastRemovalTest.java
index 1f7c990..be7ee92 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/CheckCastRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/CheckCastRemovalTest.java
@@ -50,7 +50,7 @@
     List<String> pgConfigs = ImmutableList.of(
         "-keep class " + CLASS_NAME + " { *; }",
         "-dontshrink");
-    AndroidApp app = compileWithR8(builder, pgConfigs, null);
+    AndroidApp app = compileWithR8(builder, pgConfigs, o -> o.enableClassInlining = false);
 
     DexEncodedMethod method = getMethod(app, CLASS_NAME, main);
     assertNotNull(method);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
index e8f2a2a..47f0db3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
@@ -110,7 +110,7 @@
             .addProguardConfigurationFiles(proguardConfig)
             .setDisableMinification(true)
             .build(),
-        null);
+        o -> o.enableClassInlining = false);
     return dexOutputDir.resolve("classes.dex");
   }
 }
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..7c90976 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(appInfo, TEST_OPTIONS, Origin.unknown());
     checkCountOfNonNull(irCode, 0);
 
     NonNullTracker nonNullTracker = new NonNullTracker();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
new file mode 100644
index 0000000..e9319d2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -0,0 +1,161 @@
+// 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.optimize.classinliner;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.OutputMode;
+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.code.NewInstance;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.ClassWithFinal;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceBA;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.EmptyClass;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.EmptyClassWithInitializer;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.Iface1;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.Iface1Impl;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.Iface2;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.Iface2Impl;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.ReferencedFields;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.TrivialTestClass;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.Sets;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+public class ClassInlinerTest extends TestBase {
+  @Test
+  public void testTrivial() throws Exception {
+    byte[][] classes = {
+        ToolHelper.getClassAsBytes(TrivialTestClass.class),
+        ToolHelper.getClassAsBytes(TrivialTestClass.Inner.class),
+        ToolHelper.getClassAsBytes(ReferencedFields.class),
+        ToolHelper.getClassAsBytes(EmptyClass.class),
+        ToolHelper.getClassAsBytes(EmptyClassWithInitializer.class),
+        ToolHelper.getClassAsBytes(Iface1.class),
+        ToolHelper.getClassAsBytes(Iface1Impl.class),
+        ToolHelper.getClassAsBytes(Iface2.class),
+        ToolHelper.getClassAsBytes(Iface2Impl.class),
+        ToolHelper.getClassAsBytes(CycleReferenceAB.class),
+        ToolHelper.getClassAsBytes(CycleReferenceBA.class),
+        ToolHelper.getClassAsBytes(ClassWithFinal.class)
+    };
+    String main = TrivialTestClass.class.getCanonicalName();
+    ProcessResult javaOutput = runOnJava(main, classes);
+    assertEquals(0, javaOutput.exitCode);
+
+    AndroidApp app = runR8(buildAndroidApp(classes), TrivialTestClass.class);
+
+    DexInspector inspector = new DexInspector(app);
+    ClassSubject clazz = inspector.clazz(TrivialTestClass.class);
+
+    assertEquals(
+        Collections.singleton("java.lang.StringBuilder"),
+        collectNewInstanceTypes(clazz, "testInner"));
+
+    assertEquals(
+        Collections.emptySet(),
+        collectNewInstanceTypes(clazz, "testConstructorMapping1"));
+
+    assertEquals(
+        Collections.singleton(
+            "com.android.tools.r8.ir.optimize.classinliner.trivial.ReferencedFields"),
+        collectNewInstanceTypes(clazz, "testConstructorMapping2"));
+
+    assertEquals(
+        Collections.singleton("java.lang.StringBuilder"),
+        collectNewInstanceTypes(clazz, "testConstructorMapping3"));
+
+    assertEquals(
+        Collections.emptySet(),
+        collectNewInstanceTypes(clazz, "testEmptyClass"));
+
+    assertEquals(
+        Collections.singleton(
+            "com.android.tools.r8.ir.optimize.classinliner.trivial.EmptyClassWithInitializer"),
+        collectNewInstanceTypes(clazz, "testEmptyClassWithInitializer"));
+
+    assertEquals(
+        Collections.singleton(
+            "com.android.tools.r8.ir.optimize.classinliner.trivial.ClassWithFinal"),
+        collectNewInstanceTypes(clazz, "testClassWithFinalizer"));
+
+    assertEquals(
+        Collections.emptySet(),
+        collectNewInstanceTypes(clazz, "testCallOnIface1"));
+
+    assertEquals(
+        Collections.singleton(
+            "com.android.tools.r8.ir.optimize.classinliner.trivial.Iface2Impl"),
+        collectNewInstanceTypes(clazz, "testCallOnIface2"));
+
+    assertEquals(
+        Sets.newHashSet(
+            "com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB",
+            "java.lang.StringBuilder"),
+        collectNewInstanceTypes(clazz, "testCycles"));
+
+    assertEquals(
+        Sets.newHashSet("java.lang.StringBuilder",
+            "com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB"),
+        collectNewInstanceTypes(inspector.clazz(CycleReferenceAB.class), "foo", "int"));
+
+    assertFalse(inspector.clazz(CycleReferenceBA.class).isPresent());
+  }
+
+  private Set<String> collectNewInstanceTypes(
+      ClassSubject clazz, String methodName, String... params) {
+    assertNotNull(clazz);
+    MethodSignature signature = new MethodSignature(methodName, "void", params);
+    DexCode code = clazz.method(signature).getMethod().getCode().asDexCode();
+    return filterInstructionKind(code, NewInstance.class)
+        .map(insn -> ((NewInstance) insn).getType().toSourceString())
+        .collect(Collectors.toSet());
+  }
+
+  private AndroidApp runR8(AndroidApp app, Class mainClass) throws Exception {
+    String config = keepMainProguardConfiguration(mainClass) + "\n"
+        + "-dontobfuscate\n"
+        + "-allowaccessmodification";
+
+    AndroidApp compiled = compileWithR8(app, config, o -> o.enableClassInlining = true);
+
+    // Materialize file for execution.
+    Path generatedDexFile = temp.getRoot().toPath().resolve("classes.jar");
+    compiled.writeToZip(generatedDexFile, OutputMode.DexIndexed);
+
+    // Run with ART.
+    String artOutput = ToolHelper.runArtNoVerificationErrors(
+        generatedDexFile.toString(), mainClass.getCanonicalName());
+
+    // Compare with Java.
+    ToolHelper.ProcessResult javaResult = ToolHelper.runJava(
+        ToolHelper.getClassPathForTests(), mainClass.getCanonicalName());
+
+    if (javaResult.exitCode != 0) {
+      System.out.println(javaResult.stdout);
+      System.err.println(javaResult.stderr);
+      fail("JVM failed for: " + mainClass);
+    }
+    assertEquals("JVM and ART output differ", javaResult.stdout, artOutput);
+
+    return compiled;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/ClassWithFinal.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/ClassWithFinal.java
new file mode 100644
index 0000000..7e0239c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/ClassWithFinal.java
@@ -0,0 +1,16 @@
+// 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.optimize.classinliner.trivial;
+
+public class ClassWithFinal {
+  public String doNothing() {
+    return "nothing at all";
+  }
+
+  @Override
+  protected void finalize() throws Throwable {
+    doNothing();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceAB.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceAB.java
new file mode 100644
index 0000000..d26641a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceAB.java
@@ -0,0 +1,26 @@
+// 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.optimize.classinliner.trivial;
+
+public class CycleReferenceAB {
+  private String a;
+
+  public CycleReferenceAB(String a) {
+    this.a = a;
+  }
+
+  public void foo(int depth) {
+    CycleReferenceBA ba = new CycleReferenceBA("depth=" + depth);
+    System.out.println("CycleReferenceAB::foo(" + depth + ")");
+    if (depth > 0) {
+      ba.foo(depth - 1);
+    }
+  }
+
+  @Override
+  public String toString() {
+    return "CycleReferenceAB(" + a + ")";
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceBA.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceBA.java
new file mode 100644
index 0000000..1c5d147
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceBA.java
@@ -0,0 +1,26 @@
+// 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.optimize.classinliner.trivial;
+
+public class CycleReferenceBA {
+  private String a;
+
+  public CycleReferenceBA(String a) {
+    this.a = a;
+  }
+
+  public void foo(int depth) {
+    CycleReferenceAB ab = new CycleReferenceAB("depth=" + depth);
+    System.out.println("CycleReferenceBA::foo(" + depth + ")");
+    if (depth > 0) {
+      ab.foo(depth - 1);
+    }
+  }
+
+  @Override
+  public String toString() {
+    return "CycleReferenceBA(" + a + ")";
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/EmptyClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/EmptyClass.java
new file mode 100644
index 0000000..cedfd77
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/EmptyClass.java
@@ -0,0 +1,11 @@
+// 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.optimize.classinliner.trivial;
+
+public class EmptyClass {
+  public String doNothing() {
+    return "nothing at all";
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/EmptyClassWithInitializer.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/EmptyClassWithInitializer.java
new file mode 100644
index 0000000..9671ea5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/EmptyClassWithInitializer.java
@@ -0,0 +1,15 @@
+// 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.optimize.classinliner.trivial;
+
+public class EmptyClassWithInitializer {
+  public String doNothing() {
+    return "nothing at all";
+  }
+
+  static {
+    System.out.println("EmptyClassWithInitializer.<clinit>()");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface1.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface1.java
new file mode 100644
index 0000000..8320ec9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface1.java
@@ -0,0 +1,9 @@
+// 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.optimize.classinliner.trivial;
+
+public interface Iface1 {
+  void foo();
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface1Impl.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface1Impl.java
new file mode 100644
index 0000000..e42a04b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface1Impl.java
@@ -0,0 +1,18 @@
+// 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.optimize.classinliner.trivial;
+
+public class Iface1Impl implements Iface1 {
+  final String value;
+
+  public Iface1Impl(String value) {
+    this.value = value;
+  }
+
+  @Override
+  public void foo() {
+    System.out.println(value);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface2.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface2.java
new file mode 100644
index 0000000..93a3d5d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface2.java
@@ -0,0 +1,9 @@
+// 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.optimize.classinliner.trivial;
+
+public interface Iface2 extends Iface1 {
+  Integer CONSTANT = 123;
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface2Impl.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface2Impl.java
new file mode 100644
index 0000000..17dd4c1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface2Impl.java
@@ -0,0 +1,18 @@
+// 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.optimize.classinliner.trivial;
+
+public class Iface2Impl implements Iface2 {
+  final String value;
+
+  public Iface2Impl(String value) {
+    this.value = value;
+  }
+
+  @Override
+  public void foo() {
+    System.out.println(value);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/ReferencedFields.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/ReferencedFields.java
new file mode 100644
index 0000000..90263d6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/ReferencedFields.java
@@ -0,0 +1,31 @@
+// 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.optimize.classinliner.trivial;
+
+public class ReferencedFields {
+  private String a;
+  private String b;
+
+  public ReferencedFields(String a, String b) {
+    this.a = a;
+    this.b = b;
+  }
+
+  public ReferencedFields(String a) {
+    this.a = a;
+  }
+
+  public String getConcat() {
+    return a + " " + b;
+  }
+
+  public String getA() {
+    return a;
+  }
+
+  public String getB() {
+    return b;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java
new file mode 100644
index 0000000..7e3cba2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java
@@ -0,0 +1,122 @@
+// 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.optimize.classinliner.trivial;
+
+public class TrivialTestClass {
+  private static int ID = 0;
+
+  private static String next() {
+    return Integer.toString(ID++);
+  }
+
+  public static void main(String[] args) {
+    TrivialTestClass test = new TrivialTestClass();
+    test.testInner();
+    test.testConstructorMapping1();
+    test.testConstructorMapping2();
+    test.testConstructorMapping3();
+    test.testEmptyClass();
+    test.testEmptyClassWithInitializer();
+    test.testClassWithFinalizer();
+    test.testCallOnIface1();
+    test.testCallOnIface2();
+    test.testCycles();
+  }
+
+  private synchronized void testInner() {
+    Inner inner = new Inner("inner{", 123, next() + "}");
+    System.out.println(inner.toString() + " " + inner.getPrefix() + " = " + inner.prefix);
+  }
+
+  private synchronized void testConstructorMapping1() {
+    ReferencedFields o = new ReferencedFields(next());
+    System.out.println(o.getA());
+  }
+
+  private synchronized void testConstructorMapping2() {
+    ReferencedFields o = new ReferencedFields(next());
+    System.out.println(o.getB());
+  }
+
+  private synchronized void testConstructorMapping3() {
+    ReferencedFields o = new ReferencedFields(next(), next());
+    System.out.println(o.getA() + o.getB() + o.getConcat());
+  }
+
+  private synchronized void testEmptyClass() {
+    new EmptyClass();
+  }
+
+  private synchronized void testEmptyClassWithInitializer() {
+    new EmptyClassWithInitializer();
+  }
+
+  private synchronized void testClassWithFinalizer() {
+    new ClassWithFinal();
+  }
+
+  private void callOnIface1(Iface1 iface) {
+    iface.foo();
+  }
+
+  private synchronized void testCallOnIface1() {
+    callOnIface1(new Iface1Impl(next()));
+  }
+
+  private void callOnIface2(Iface2 iface) {
+    iface.foo();
+  }
+
+  private synchronized void testCallOnIface2() {
+    callOnIface2(new Iface2Impl(next()));
+    System.out.println(Iface2Impl.CONSTANT); // Keep constant referenced
+  }
+
+  private synchronized void testCycles() {
+    new CycleReferenceAB("first").foo(3);
+    new CycleReferenceBA("second").foo(4);
+  }
+
+  public class Inner {
+    private String prefix;
+    private String suffix;
+    private double id;
+
+    public Inner(String prefix, double id, String suffix) {
+      this.prefix = prefix;
+      this.suffix = suffix;
+      this.id = id;
+    }
+
+    @Override
+    public String toString() {
+      return prefix + id + suffix;
+    }
+
+    public String getPrefix() {
+      return prefix;
+    }
+
+    public String getSuffix() {
+      return suffix;
+    }
+
+    public double getId() {
+      return id;
+    }
+
+    public void setPrefix(String prefix) {
+      this.prefix = prefix;
+    }
+
+    public void setSuffix(String suffix) {
+      this.suffix = suffix;
+    }
+
+    public void setId(double id) {
+      this.id = id;
+    }
+  }
+}
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
new file mode 100644
index 0000000..08058e5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/B79405526.java
@@ -0,0 +1,72 @@
+// 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.regalloc;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import org.junit.Test;
+
+public class B79405526 extends TestBase {
+  @Test
+  public void test() throws Exception {
+    AndroidApp app = compileWithD8(readClasses(TestClass.class));
+    DexInspector inspector = new DexInspector(app);
+    DexInspector.ClassSubject clazz = inspector.clazz(TestClass.class);
+    assertThat(clazz, isPresent());
+    // 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;
+      nop(null, getObject("", this));
+      TestClass z = getObject(null);
+      nop(null, getObject("", this, X.Y.getLong(getLong() - 0L)));
+      if (getBoolean()) {
+        z = getObject(this);
+        nop1(null, null);
+      }
+      nop2(null, null, null, null);
+    }
+
+    public static void nop(Object a, Object b, Object... c) {}
+
+    public static void nop1(Object... a) {}
+
+    private void nop2(Object a, Object b, Object c, Object d, Object... e) {}
+
+    public boolean getBoolean() {
+      return true;
+    }
+
+    public long getLong() {
+      return 0L;
+    }
+
+    private TestClass getObject(Object a) {
+      return null;
+    }
+
+    private static TestClass getObject(Object a, Object... c) {
+      return null;
+    }
+
+    public enum X {
+      Y {};
+
+      public long getLong(long var1) {
+        return 0L;
+      }
+    }
+  }
+}
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 e5838c3..eb6e943 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;
@@ -26,6 +28,7 @@
 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.List;
 
@@ -84,6 +87,7 @@
     private final List<String> fields = new ArrayList<>();
     private boolean makeInit = false;
     private boolean hasInit = false;
+    private final List<String> clinit = new ArrayList<>();
     private boolean isInterface = false;
     private String access = "public";
 
@@ -136,6 +140,19 @@
       return addMethod("public", name, argumentTypes, returnType, lines);
     }
 
+    /**
+     * 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,
+        String... lines) {
+      makeInit = true;
+      return addMethod("public static native", name, argumentTypes, returnType, lines);
+    }
+
     public MethodSignature addBridgeMethod(
         String name,
         List<String> argumentTypes,
@@ -202,6 +219,10 @@
       return new MethodSignature(name, returnJavaType, argumentJavaTypes);
     }
 
+    public void addClassInitializer(String... lines) {
+      clinit.addAll(Arrays.asList(lines));
+    }
+
     public FieldSignature addField(String flags, String name, String type, String value) {
       fields.add(
           ".field " + flags + " " + name + " " + type + (value != null ? (" = " + value) : ""));
@@ -247,6 +268,11 @@
       for (String method : methods) {
         builder.append(method).append("\n");
       }
+      if (!clinit.isEmpty()) {
+        builder.append(".method public static <clinit>()V\n");
+        clinit.forEach(line -> builder.append(line).append('\n'));
+        builder.append(".end method\n");
+      }
       return builder.toString();
     }
 
@@ -358,6 +384,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/jasmin/JumpSubroutineTests.java b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
index 5e41a25..ec18fac 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
@@ -8,11 +8,15 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
 import com.android.tools.r8.utils.AndroidApp;
 import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import org.junit.Test;
 
 public class JumpSubroutineTests extends JasminTestBase {
@@ -22,10 +26,28 @@
     assertEquals(expected, javaResult);
     String artResult = runOnArtD8(builder, main);
     assertEquals(expected, artResult);
+    String cfFrontendResult = runOnJavaR8CfFrontend(builder, main);
+    assertEquals(expected, cfFrontendResult);
     String dxArtResult = runOnArtDx(builder, main);
     assertEquals(expected, dxArtResult);
   }
 
+  private String runOnJavaR8CfFrontend(JasminBuilder builder, String main) throws Exception {
+    Path inputJar = temp.getRoot().toPath().resolve("input.jar");
+    Path outputJar = temp.getRoot().toPath().resolve("output.jar");
+    builder.writeJar(inputJar, null);
+    ToolHelper.runR8(
+        R8Command.builder()
+            .addProgramFiles(inputJar)
+            .setOutput(outputJar, OutputMode.ClassFile)
+            .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+            .build(),
+        options -> options.enableCfFrontend = true);
+    ProcessResult processResult = ToolHelper.runJava(outputJar, main);
+    assertEquals(0, processResult.exitCode);
+    return processResult.stdout;
+  }
+
   private void expectDxFailure(JasminBuilder builder) throws Exception {
     // This expects this dx failure:
     // Uncaught translation error: com.android.dex.util.ExceptionWithContext: returning from
@@ -387,6 +409,8 @@
     assertEquals(expected, javaResult);
     String artResult = runOnArtD8(builder, clazz.name);
     assertEquals(expected, artResult);
+    String cfFrontendResult = runOnJavaR8CfFrontend(builder, clazz.name);
+    assertEquals(expected, cfFrontendResult);
     // This fails with dx.
     expectDxFailure(builder);
   }
@@ -1344,6 +1368,8 @@
     String artResult = runOnArtD8(builder, clazz.name);
     // The ASM jsr inliner does not get the control-flow dependent ret right in his case.
     assertNotEquals(expected, artResult);
+    String cfFrontendResult = runOnJavaR8CfFrontend(builder, clazz.name);
+    assertNotEquals(expected, cfFrontendResult);
     // This fails with dx.
     expectDxFailure(builder);
   }
@@ -1392,6 +1418,8 @@
     String artResult = runOnArtD8(builder, clazz.name);
     // The ASM jsr inliner does not get the control-flow dependent ret right in his case.
     assertNotEquals(expected, artResult);
+    String cfFrontendResult = runOnJavaR8CfFrontend(builder, clazz.name);
+    assertNotEquals(expected, cfFrontendResult);
     // This fails with dx.
     expectDxFailure(builder);
   }
@@ -1441,6 +1469,8 @@
     String artResult = runOnArtD8(builder, clazz.name);
     // The ASM jsr inliner does not get the control-flow dependent ret right in his case.
     assertNotEquals(expected, artResult);
+    String cfFrontendResult = runOnJavaR8CfFrontend(builder, clazz.name);
+    assertNotEquals(expected, cfFrontendResult);
     // This fails with dx.
     expectDxFailure(builder);
   }
diff --git a/src/test/java/com/android/tools/r8/jasmin/Regress80124071.java b/src/test/java/com/android/tools/r8/jasmin/Regress80124071.java
new file mode 100644
index 0000000..0eda515
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/jasmin/Regress80124071.java
@@ -0,0 +1,71 @@
+// 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.jasmin;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class Regress80124071 extends JasminTestBase {
+
+  private JasminBuilder buildClass() {
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
+    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+    clazz.addPrivateVirtualMethod("privateMethod", ImmutableList.of(), "V",
+        ".limit stack 2",
+        ".limit locals 1",
+        "getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "ldc \"privateMethod\"",
+        "invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V",
+        "return");
+
+    clazz.addDefaultConstructor();
+
+    clazz.addMainMethod(
+        ".limit stack 3",
+        ".limit locals 1",
+        "new Test",
+        "dup",
+        "invokespecial Test/<init>()V",
+        // Should have been invokespecial but JVM is OK with it so we need to transform
+        // to invoke-direct to be able to run on Art.
+        "invokevirtual Test/privateMethod()V",
+        "new TestSub",
+        "dup",
+        "dup",
+        "invokespecial TestSub/<init>()V",
+        // Should have been invokespecial but JVM is OK with it and invokes the private method
+        // on the Test class. Therefore, there is no virtual dispatch and we need to transform
+        // to invoke-direct to be able to run on Art.
+        "invokevirtual Test/privateMethod()V",
+        "invokevirtual TestSub/privateMethod()V",
+        "return");
+
+    JasminBuilder.ClassBuilder subclazz = builder.addClass("TestSub", "Test");
+
+    subclazz.addVirtualMethod("privateMethod", ImmutableList.of(), "V",
+        ".limit stack 2",
+        ".limit locals 1",
+        "getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "ldc \"privateMethod2\"",
+        "invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V",
+        "return");
+
+    return builder;
+  }
+
+  @Test
+  public void test() throws Exception {
+    JasminBuilder builder = buildClass();
+    String jvm = runOnArtDx(builder, "Test");
+    String dx = runOnJava(builder, "Test");
+    assertEquals(jvm, dx);
+    String d8 = runOnArtD8(builder, "Test");
+    assertEquals(jvm, d8);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index 93f4249..bb83093 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.utils.DexInspector.FieldSubject;
 import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -34,6 +35,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Consumer;
 import org.junit.Assume;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -183,13 +185,23 @@
         + "}";
   }
 
-  protected void runTest(String folder, String mainClass, AndroidAppInspector inspector)
-      throws Exception {
-    runTest(folder, mainClass, null, inspector);
+  protected void runTest(String folder, String mainClass,
+      AndroidAppInspector inspector) throws Exception {
+    runTest(folder, mainClass, null, null, inspector);
+  }
+
+  protected void runTest(String folder, String mainClass,
+      Consumer<InternalOptions> optionsConsumer, AndroidAppInspector inspector) throws Exception {
+    runTest(folder, mainClass, null, optionsConsumer, inspector);
+  }
+
+  protected void runTest(String folder, String mainClass,
+      String extraProguardRules, AndroidAppInspector inspector) throws Exception {
+    runTest(folder, mainClass, extraProguardRules, null, inspector);
   }
 
   protected void runTest(String folder, String mainClass, String extraProguardRules,
-      AndroidAppInspector inspector) throws Exception {
+      Consumer<InternalOptions> optionsConsumer, AndroidAppInspector inspector) throws Exception {
     Assume.assumeTrue(ToolHelper.artSupported());
 
     String proguardRules = buildProguardRules(mainClass);
@@ -206,7 +218,7 @@
     // Build with R8
     AndroidApp.Builder builder = AndroidApp.builder();
     builder.addProgramFiles(classpath);
-    AndroidApp app = compileWithR8(builder.build(), proguardRules);
+    AndroidApp app = compileWithR8(builder.build(), proguardRules, optionsConsumer);
 
     // Materialize file for execution.
     Path generatedDexFile = temp.getRoot().toPath().resolve("classes.jar");
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
index 315e980..ee9065f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
@@ -252,7 +252,7 @@
   @Test
   public void testTrivialKs() throws Exception {
     final String mainClassName = "lambdas_kstyle_trivial.MainKt";
-    runTest("lambdas_kstyle_trivial", mainClassName, null, (app) -> {
+    runTest("lambdas_kstyle_trivial", mainClassName, (app) -> {
       Verifier verifier = new Verifier(app);
       String pkg = "lambdas_kstyle_trivial";
 
@@ -293,7 +293,7 @@
   @Test
   public void testCapturesKs() throws Exception {
     final String mainClassName = "lambdas_kstyle_captures.MainKt";
-    runTest("lambdas_kstyle_captures", mainClassName, null, (app) -> {
+    runTest("lambdas_kstyle_captures", mainClassName, (app) -> {
       Verifier verifier = new Verifier(app);
       String pkg = "lambdas_kstyle_captures";
       String grpPkg = allowAccessModification ? "" : pkg;
@@ -318,7 +318,7 @@
   @Test
   public void testGenericsNoSignatureKs() throws Exception {
     final String mainClassName = "lambdas_kstyle_generics.MainKt";
-    runTest("lambdas_kstyle_generics", mainClassName, null, (app) -> {
+    runTest("lambdas_kstyle_generics", mainClassName, (app) -> {
       Verifier verifier = new Verifier(app);
       String pkg = "lambdas_kstyle_generics";
       String grpPkg = allowAccessModification ? "" : pkg;
@@ -387,7 +387,7 @@
   @Test
   public void testTrivialJs() throws Exception {
     final String mainClassName = "lambdas_jstyle_trivial.MainKt";
-    runTest("lambdas_jstyle_trivial", mainClassName, null, (app) -> {
+    runTest("lambdas_jstyle_trivial", mainClassName, (app) -> {
       Verifier verifier = new Verifier(app);
       String pkg = "lambdas_jstyle_trivial";
       String grp = allowAccessModification ? "" : pkg;
@@ -435,7 +435,7 @@
   @Test
   public void testSingleton() throws Exception {
     final String mainClassName = "lambdas_singleton.MainKt";
-    runTest("lambdas_singleton", mainClassName, null, (app) -> {
+    runTest("lambdas_singleton", mainClassName, (app) -> {
       Verifier verifier = new Verifier(app);
       String pkg = "lambdas_singleton";
       String grp = allowAccessModification ? "" : pkg;
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index cb3820e..f884376 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -12,6 +12,8 @@
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.DexInspector.FieldSubject;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.function.Consumer;
 import org.junit.Test;
 
 public class R8KotlinPropertiesTest extends AbstractR8KotlinTestBase {
@@ -57,11 +59,35 @@
           .addProperty("internalLateInitProp", JAVA_LANG_STRING, Visibility.INTERNAL)
           .addProperty("publicLateInitProp", JAVA_LANG_STRING, Visibility.PUBLIC);
 
+  private static final TestKotlinClass OBJECT_PROPERTY_CLASS =
+      new TestKotlinClass("properties.ObjectProperties")
+          .addProperty("privateProp", JAVA_LANG_STRING, Visibility.PRIVATE)
+          .addProperty("protectedProp", JAVA_LANG_STRING, Visibility.PROTECTED)
+          .addProperty("internalProp", JAVA_LANG_STRING, Visibility.INTERNAL)
+          .addProperty("publicProp", JAVA_LANG_STRING, Visibility.PUBLIC)
+          .addProperty("primitiveProp", "int", Visibility.PUBLIC)
+          .addProperty("privateLateInitProp", JAVA_LANG_STRING, Visibility.PRIVATE)
+          .addProperty("internalLateInitProp", JAVA_LANG_STRING, Visibility.INTERNAL)
+          .addProperty("publicLateInitProp", JAVA_LANG_STRING, Visibility.PUBLIC);
+
+  private static final TestFileLevelKotlinClass FILE_PROPERTY_CLASS =
+      new TestFileLevelKotlinClass("properties.FilePropertiesKt")
+          .addProperty("privateProp", JAVA_LANG_STRING, Visibility.PRIVATE)
+          .addProperty("protectedProp", JAVA_LANG_STRING, Visibility.PROTECTED)
+          .addProperty("internalProp", JAVA_LANG_STRING, Visibility.INTERNAL)
+          .addProperty("publicProp", JAVA_LANG_STRING, Visibility.PUBLIC)
+          .addProperty("primitiveProp", "int", Visibility.PUBLIC)
+          .addProperty("privateLateInitProp", JAVA_LANG_STRING, Visibility.PRIVATE)
+          .addProperty("internalLateInitProp", JAVA_LANG_STRING, Visibility.INTERNAL)
+          .addProperty("publicLateInitProp", JAVA_LANG_STRING, Visibility.PUBLIC);
+
+  private Consumer<InternalOptions> disableClassInliner = o -> o.enableClassInlining = false;
+
   @Test
   public void testMutableProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
     String mainClass = addMainToClasspath("properties/MutablePropertyKt",
         "mutableProperty_noUseOfProperties");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
@@ -78,7 +104,7 @@
   public void testMutableProperty_privateIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath("properties/MutablePropertyKt",
         "mutableProperty_usePrivateProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
@@ -100,7 +126,7 @@
   public void testMutableProperty_protectedIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath("properties/MutablePropertyKt",
         "mutableProperty_useProtectedProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
@@ -123,7 +149,7 @@
   public void testMutableProperty_internalIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath("properties/MutablePropertyKt",
         "mutableProperty_useInternalProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
@@ -146,7 +172,7 @@
   public void testMutableProperty_publicIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath("properties/MutablePropertyKt",
         "mutableProperty_usePublicProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
@@ -169,7 +195,7 @@
   public void testMutableProperty_primitivePropertyIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath("properties/MutablePropertyKt",
         "mutableProperty_usePrimitiveProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
@@ -194,7 +220,7 @@
   public void testLateInitProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
     String mainClass = addMainToClasspath("properties/LateInitPropertyKt",
         "lateInitProperty_noUseOfProperties");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -211,7 +237,7 @@
   public void testLateInitProperty_privateIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties/LateInitPropertyKt", "lateInitProperty_usePrivateLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -234,7 +260,7 @@
   public void testLateInitProperty_protectedIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath("properties/LateInitPropertyKt",
         "lateInitProperty_useProtectedLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -255,7 +281,7 @@
   public void testLateInitProperty_internalIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties/LateInitPropertyKt", "lateInitProperty_useInternalLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -274,7 +300,7 @@
   public void testLateInitProperty_publicIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties/LateInitPropertyKt", "lateInitProperty_usePublicLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -293,7 +319,7 @@
   public void testUserDefinedProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
     String mainClass = addMainToClasspath(
         "properties/UserDefinedPropertyKt", "userDefinedProperty_noUseOfProperties");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           USER_DEFINED_PROPERTY_CLASS.getClassName());
@@ -310,7 +336,7 @@
   public void testUserDefinedProperty_publicIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties/UserDefinedPropertyKt", "userDefinedProperty_useProperties");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           USER_DEFINED_PROPERTY_CLASS.getClassName());
@@ -336,7 +362,7 @@
   public void testCompanionProperty_primitivePropertyCannotBeInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties.CompanionPropertiesKt", "companionProperties_usePrimitiveProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject outerClass = checkClassExists(dexInspector,
           "properties.CompanionProperties");
@@ -367,7 +393,7 @@
   public void testCompanionProperty_privatePropertyIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties.CompanionPropertiesKt", "companionProperties_usePrivateProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject outerClass = checkClassExists(dexInspector,
           "properties.CompanionProperties");
@@ -401,7 +427,7 @@
   public void testCompanionProperty_internalPropertyCannotBeInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties.CompanionPropertiesKt", "companionProperties_useInternalProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject outerClass = checkClassExists(dexInspector,
           "properties.CompanionProperties");
@@ -432,7 +458,7 @@
   public void testCompanionProperty_publicPropertyCannotBeInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties.CompanionPropertiesKt", "companionProperties_usePublicProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject outerClass = checkClassExists(dexInspector,
           "properties.CompanionProperties");
@@ -464,7 +490,7 @@
     final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
         "companionLateInitProperties_usePrivateLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject outerClass = checkClassExists(dexInspector, testedClass.getOuterClassName());
       ClassSubject companionClass = checkClassExists(dexInspector, testedClass.getClassName());
@@ -495,7 +521,7 @@
     final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
         "companionLateInitProperties_useInternalLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject outerClass = checkClassExists(dexInspector, testedClass.getOuterClassName());
       ClassSubject companionClass = checkClassExists(dexInspector, testedClass.getClassName());
@@ -519,7 +545,7 @@
     final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
         "companionLateInitProperties_usePublicLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject outerClass = checkClassExists(dexInspector, testedClass.getOuterClassName());
       ClassSubject companionClass = checkClassExists(dexInspector, testedClass.getClassName());
@@ -537,4 +563,363 @@
       assertTrue(fieldSubject.getField().accessFlags.isPublic());
     });
   }
+
+  @Test
+  public void testObjectClass_primitivePropertyCannotBeInlined() throws Exception {
+    final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
+    String mainClass = addMainToClasspath(
+        "properties.ObjectPropertiesKt", "objectProperties_usePrimitiveProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject objectClass = checkClassExists(dexInspector, testedClass.getClassName());
+      String propertyName = "primitiveProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(objectClass, "int", propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
+
+      MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
+      MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
+
+      // Getter and setter cannot be inlined when we don't know if null check semantic is
+      // preserved.
+      checkMethodIsPresent(objectClass, getter);
+      checkMethodIsPresent(objectClass, setter);
+      if (allowAccessModification) {
+        assertTrue(fieldSubject.getField().accessFlags.isPublic());
+      } else {
+        assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+      }
+    });
+  }
+
+  @Test
+  public void testObjectClass_privatePropertyIsAlwaysInlined() throws Exception {
+    final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
+    String mainClass = addMainToClasspath(
+        "properties.ObjectPropertiesKt", "objectProperties_usePrivateProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject objectClass = checkClassExists(dexInspector, testedClass.getClassName());
+      String propertyName = "privateProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(objectClass, JAVA_LANG_STRING, propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
+
+      MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
+      MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
+
+      // A private property has no getter/setter.
+      checkMethodIsAbsent(objectClass, getter);
+      checkMethodIsAbsent(objectClass, setter);
+
+      if (allowAccessModification) {
+        assertTrue(fieldSubject.getField().accessFlags.isPublic());
+      } else {
+        assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+      }
+    });
+  }
+
+  @Test
+  public void testObjectClass_internalPropertyCannotBeInlined() throws Exception {
+    final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
+    String mainClass = addMainToClasspath(
+        "properties.ObjectPropertiesKt", "objectProperties_useInternalProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject objectClass = checkClassExists(dexInspector, testedClass.getClassName());
+      String propertyName = "internalProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(objectClass, JAVA_LANG_STRING, propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
+
+      MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
+      MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
+
+      // Getter and setter cannot be inlined when we don't know if null check semantic is
+      // preserved.
+      checkMethodIsPresent(objectClass, getter);
+      checkMethodIsPresent(objectClass, setter);
+      if (allowAccessModification) {
+        assertTrue(fieldSubject.getField().accessFlags.isPublic());
+      } else {
+        assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+      }
+    });
+  }
+
+  @Test
+  public void testObjectClass_publicPropertyCannotBeInlined() throws Exception {
+    final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
+    String mainClass = addMainToClasspath(
+        "properties.ObjectPropertiesKt", "objectProperties_usePublicProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject objectClass = checkClassExists(dexInspector, testedClass.getClassName());
+      String propertyName = "publicProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(objectClass, JAVA_LANG_STRING, propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
+
+      MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
+      MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
+
+      // Getter and setter cannot be inlined when we don't know if null check semantic is
+      // preserved.
+      checkMethodIsPresent(objectClass, getter);
+      checkMethodIsPresent(objectClass, setter);
+      if (allowAccessModification) {
+        assertTrue(fieldSubject.getField().accessFlags.isPublic());
+      } else {
+        assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+      }
+    });
+  }
+
+  @Test
+  public void testObjectClass_privateLateInitPropertyIsAlwaysInlined() throws Exception {
+    final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
+    String mainClass = addMainToClasspath(
+        "properties.ObjectPropertiesKt", "objectProperties_useLateInitPrivateProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject objectClass = checkClassExists(dexInspector, testedClass.getClassName());
+      String propertyName = "privateLateInitProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(objectClass, JAVA_LANG_STRING, propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
+
+      MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
+      MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
+
+      // A private property has no getter/setter.
+      checkMethodIsAbsent(objectClass, getter);
+      checkMethodIsAbsent(objectClass, setter);
+
+      if (allowAccessModification) {
+        assertTrue(fieldSubject.getField().accessFlags.isPublic());
+      } else {
+        assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+      }
+    });
+  }
+
+  @Test
+  public void testObjectClass_internalLateInitPropertyCannotBeInlined() throws Exception {
+    final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
+    String mainClass = addMainToClasspath(
+        "properties.ObjectPropertiesKt", "objectProperties_useLateInitInternalProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject objectClass = checkClassExists(dexInspector, testedClass.getClassName());
+      String propertyName = "internalLateInitProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(objectClass, JAVA_LANG_STRING, propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
+
+      MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
+      MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
+
+      // Getter and setter cannot be inlined when we don't know if null check semantic is
+      // preserved.
+      checkMethodIsPresent(objectClass, getter);
+      checkMethodIsPresent(objectClass, setter);
+      assertTrue(fieldSubject.getField().accessFlags.isPublic());
+    });
+  }
+
+  @Test
+  public void testObjectClass_publicLateInitPropertyCannotBeInlined() throws Exception {
+    final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
+    String mainClass = addMainToClasspath(
+        "properties.ObjectPropertiesKt", "objectProperties_useLateInitPublicProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject objectClass = checkClassExists(dexInspector, testedClass.getClassName());
+      String propertyName = "publicLateInitProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(objectClass, JAVA_LANG_STRING, propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
+
+      MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
+      MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
+
+      // Getter and setter cannot be inlined when we don't know if null check semantic is
+      // preserved.
+      checkMethodIsPresent(objectClass, getter);
+      checkMethodIsPresent(objectClass, setter);
+      assertTrue(fieldSubject.getField().accessFlags.isPublic());
+    });
+  }
+
+  @Test
+  public void testFileLevel_primitivePropertyIsInlinedIfAccessIsRelaxed() throws Exception {
+    final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
+    String mainClass = addMainToClasspath(
+        "properties.FilePropertiesKt", "fileProperties_usePrimitiveProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject objectClass = checkClassExists(dexInspector, testedClass.getClassName());
+      String propertyName = "primitiveProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(objectClass, "int", propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
+
+      MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
+      MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
+
+      if (allowAccessModification) {
+        assertTrue(fieldSubject.getField().accessFlags.isPublic());
+        checkMethodIsAbsent(objectClass, getter);
+        checkMethodIsAbsent(objectClass, setter);
+      } else {
+        assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+        checkMethodIsPresent(objectClass, getter);
+        checkMethodIsPresent(objectClass, setter);
+      }
+    });
+  }
+
+  @Test
+  public void testFileLevel_privatePropertyIsAlwaysInlined() throws Exception {
+    final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
+    String mainClass = addMainToClasspath(
+        "properties.FilePropertiesKt", "fileProperties_usePrivateProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject objectClass = checkClassExists(dexInspector, testedClass.getClassName());
+      String propertyName = "privateProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(objectClass, JAVA_LANG_STRING, propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
+
+      MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
+      MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
+
+      // A private property has no getter/setter.
+      checkMethodIsAbsent(objectClass, getter);
+      checkMethodIsAbsent(objectClass, setter);
+      if (allowAccessModification) {
+        assertTrue(fieldSubject.getField().accessFlags.isPublic());
+      } else {
+        assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+      }
+    });
+  }
+
+  @Test
+  public void testFileLevel_internalPropertyGetterIsInlinedIfAccessIsRelaxed() throws Exception {
+    final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
+    String mainClass = addMainToClasspath(
+        "properties.FilePropertiesKt", "fileProperties_useInternalProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject objectClass = checkClassExists(dexInspector, testedClass.getClassName());
+      String propertyName = "internalProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(objectClass, JAVA_LANG_STRING, propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
+
+      // We expect getter to be inlined when access (of the backing field) is relaxed to public.
+      // Note: the setter is considered as a regular method (because of KotlinC adding extra null
+      // checks), thus we cannot say if the setter would be inlined or not by R8.
+      MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
+      if (allowAccessModification) {
+        assertTrue(fieldSubject.getField().accessFlags.isPublic());
+        checkMethodIsAbsent(objectClass, getter);
+      } else {
+        assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+        checkMethodIsPresent(objectClass, getter);
+      }
+    });
+  }
+
+  @Test
+  public void testFileLevel_publicPropertyGetterIsInlinedIfAccessIsRelaxed() throws Exception {
+    final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
+    String mainClass = addMainToClasspath(
+        "properties.FilePropertiesKt", "fileProperties_usePublicProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject objectClass = checkClassExists(dexInspector, testedClass.getClassName());
+      String propertyName = "publicProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(objectClass, JAVA_LANG_STRING, propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
+
+      // We expect getter to be inlined when access (of the backing field) is relaxed to public.
+      // On the other hand, the setter is considered as a regular method (because of null checks),
+      // thus we cannot say if it can be inlined or not.
+      MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
+
+      if (allowAccessModification) {
+        assertTrue(fieldSubject.getField().accessFlags.isPublic());
+        checkMethodIsAbsent(objectClass, getter);
+      } else {
+        assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+        checkMethodIsPresent(objectClass, getter);
+      }
+    });
+  }
+
+  @Test
+  public void testFileLevel_privateLateInitPropertyIsAlwaysInlined() throws Exception {
+    final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
+    String mainClass = addMainToClasspath(
+        "properties.FilePropertiesKt", "fileProperties_useLateInitPrivateProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject fileClass = checkClassExists(dexInspector, testedClass.getClassName());
+      String propertyName = "privateLateInitProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(fileClass, JAVA_LANG_STRING, propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
+
+      MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
+      MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
+
+      // A private property has no getter/setter.
+      checkMethodIsAbsent(fileClass, getter);
+      checkMethodIsAbsent(fileClass, setter);
+      if (allowAccessModification) {
+        assertTrue(fieldSubject.getField().accessFlags.isPublic());
+      } else {
+        assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+      }
+    });
+  }
+
+  @Test
+  public void testFileLevel_internalLateInitPropertyCannotBeInlined() throws Exception {
+    final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
+    String mainClass = addMainToClasspath(
+        "properties.FilePropertiesKt", "fileProperties_useLateInitInternalProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject objectClass = checkClassExists(dexInspector, testedClass.getClassName());
+      String propertyName = "internalLateInitProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(objectClass, JAVA_LANG_STRING, propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
+      assertTrue(fieldSubject.getField().accessFlags.isPublic());
+
+      MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
+      MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
+
+      // Field is public and getter is small so we expect to always inline it.
+      checkMethodIsAbsent(objectClass, getter);
+
+      // Setter has null check of new value, thus may not be inlined.
+      checkMethodIsPresent(objectClass, setter);
+    });
+  }
+
+  @Test
+  public void testFileLevel_publicLateInitPropertyCannotBeInlined() throws Exception {
+    final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
+    String mainClass = addMainToClasspath(
+        "properties.FilePropertiesKt", "fileProperties_useLateInitPublicProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject objectClass = checkClassExists(dexInspector, testedClass.getClassName());
+      String propertyName = "publicLateInitProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(objectClass, JAVA_LANG_STRING, propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
+
+      MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
+      MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
+
+      checkMethodIsAbsent(objectClass, getter);
+      checkMethodIsPresent(objectClass, setter);
+      assertTrue(fieldSubject.getField().accessFlags.isPublic());
+    });
+  }
+
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/TestFileLevelKotlinClass.java b/src/test/java/com/android/tools/r8/kotlin/TestFileLevelKotlinClass.java
new file mode 100644
index 0000000..db35897
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/TestFileLevelKotlinClass.java
@@ -0,0 +1,33 @@
+// 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.kotlin;
+
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+
+public class TestFileLevelKotlinClass extends TestKotlinClass {
+
+  public TestFileLevelKotlinClass(String className) {
+    super(className);
+  }
+
+  @Override
+  public TestFileLevelKotlinClass addProperty(String name, String type, Visibility visibility) {
+    return (TestFileLevelKotlinClass) super.addProperty(name, type, visibility);
+  }
+
+  @Override
+  public MethodSignature getGetterForProperty(String propertyName) {
+    KotlinProperty property = getProperty(propertyName);
+    // File-level properties accessor do not apply mangling.
+    return getGetterForProperty(property, false);
+  }
+
+  @Override
+  public MethodSignature getSetterForProperty(String propertyName) {
+    KotlinProperty property = getProperty(propertyName);
+    // File-level properties accessor do not apply mangling.
+    return getSetterForProperty(property, false);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/TestKotlinClass.java b/src/test/java/com/android/tools/r8/kotlin/TestKotlinClass.java
index 0ec0f64..685df6a 100644
--- a/src/test/java/com/android/tools/r8/kotlin/TestKotlinClass.java
+++ b/src/test/java/com/android/tools/r8/kotlin/TestKotlinClass.java
@@ -104,22 +104,12 @@
 
   public MemberNaming.MethodSignature getGetterForProperty(String propertyName) {
     KotlinProperty property = getProperty(propertyName);
-    String type = property.type;
-    String getterName = computeGetterName(propertyName);
-    if (property.getVisibility() == Visibility.INTERNAL) {
-      getterName = appendInternalSuffix(getterName);
-    }
-    return new MemberNaming.MethodSignature(getterName, type, Collections.emptyList());
+    return getGetterForProperty(property, property.getVisibility() == Visibility.INTERNAL);
   }
 
   public MemberNaming.MethodSignature getSetterForProperty(String propertyName) {
     KotlinProperty property = getProperty(propertyName);
-    String setterName = computeSetterName(propertyName);
-    if (property.getVisibility() == Visibility.INTERNAL) {
-      setterName = appendInternalSuffix(setterName);
-    }
-    return new MemberNaming.MethodSignature(setterName, "void",
-        Collections.singleton(property.getType()));
+    return getSetterForProperty(property, property.getVisibility() == Visibility.INTERNAL);
   }
 
   public MemberNaming.MethodSignature getGetterAccessorForProperty(String propertyName,
@@ -152,6 +142,26 @@
     return new MemberNaming.MethodSignature(setterName, "void", argumentTypes);
   }
 
+  protected final MemberNaming.MethodSignature getGetterForProperty(KotlinProperty property,
+      boolean applyMangling) {
+    String type = property.type;
+    String getterName = computeGetterName(property.name);
+    if (applyMangling) {
+      getterName = appendInternalSuffix(getterName);
+    }
+    return new MemberNaming.MethodSignature(getterName, type, Collections.emptyList());
+  }
+
+  protected final MemberNaming.MethodSignature getSetterForProperty(KotlinProperty property,
+      boolean applyMangling) {
+    String setterName = computeSetterName(property.name);
+    if (applyMangling) {
+      setterName = appendInternalSuffix(setterName);
+    }
+    return new MemberNaming.MethodSignature(setterName, "void",
+        Collections.singleton(property.getType()));
+  }
+
   private static String computeGetterName(String propertyName) {
     if (propertyName.length() > 2 && propertyName.startsWith("is")
         && (propertyName.charAt(2) == '_' || Character.isUpperCase(propertyName.charAt(2)))) {
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..590baa6 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -25,11 +25,11 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -40,6 +40,7 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Position;
@@ -617,9 +618,9 @@
                     DexString.EMPTY_ARRAY),
                 access,
                 DexAnnotationSet.empty(),
-                DexAnnotationSetRefList.empty(),
+                ParameterAnnotationsList.empty(),
                 code);
-        IRCode ir = code.buildIR(method, options);
+        IRCode ir = code.buildIR(method, null, 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/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index bb2628a..43b4c0f 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -15,8 +15,12 @@
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -40,6 +44,23 @@
   @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
   @Test
+  public void traceMainDexList001_whyareyoukeeping() throws Throwable {
+    PrintStream stdout = System.out;
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    System.setOut(new PrintStream(baos));
+    doTest(
+        "traceMainDexList001_1",
+        "multidex001",
+        EXAMPLE_BUILD_DIR,
+        Paths.get(EXAMPLE_SRC_DIR, "multidex", "main-dex-rules-whyareyoukeeping.txt"),
+        Paths.get(EXAMPLE_SRC_DIR, "multidex001", "ref-list-1.txt"),
+        AndroidApiLevel.I);
+    String output = new String(baos.toByteArray(), Charset.defaultCharset());
+    Assert.assertTrue(output.contains("is live because referenced in keep rule:"));
+    System.setOut(stdout);
+  }
+
+  @Test
   public void traceMainDexList001_1() throws Throwable {
     doTest(
         "traceMainDexList001_1",
@@ -271,10 +292,9 @@
   }
 
   private String mainDexStringToDescriptor(String mainDexString) {
-    final String CLASS_EXTENSION = ".class";
-    Assert.assertTrue(mainDexString.endsWith(CLASS_EXTENSION));
+    Assert.assertTrue(mainDexString.endsWith(FileUtils.CLASS_EXTENSION));
     return DescriptorUtils.getDescriptorFromClassBinaryName(
-        mainDexString.substring(0, mainDexString.length() - CLASS_EXTENSION.length()));
+        mainDexString.substring(0, mainDexString.length() - FileUtils.CLASS_EXTENSION.length()));
   }
 
   private void checkSameMainDexEntry(String reference, String computed) {
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/naming/b80083341/AnotherClass.java b/src/test/java/com/android/tools/r8/naming/b80083341/AnotherClass.java
new file mode 100644
index 0000000..e612fec
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/b80083341/AnotherClass.java
@@ -0,0 +1,7 @@
+// 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.naming.b80083341;
+
+public class AnotherClass {
+}
diff --git a/src/test/java/com/android/tools/r8/naming/b80083341/B80083341.java b/src/test/java/com/android/tools/r8/naming/b80083341/B80083341.java
new file mode 100644
index 0000000..1a1ae50
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/b80083341/B80083341.java
@@ -0,0 +1,54 @@
+// 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.naming.b80083341;
+
+import static com.android.tools.r8.utils.DescriptorUtils.getClassNameFromDescriptor;
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+public class B80083341 extends TestBase {
+
+  @Ignore("b/80083341")
+  @Test
+  public void test() throws Exception {
+    Class mainClass = TestMain.class;
+    List<String> config = ImmutableList.of(
+        "-printmapping",
+        "-keepattributes EnclosingMethod,InnerClasses,Signature",
+        "-repackageclasses",
+        "-keepclassmembers class " + mainClass.getCanonicalName() + " {",
+        "  public void main(...);",
+        "}",
+        "-keep,allowobfuscation class " + mainClass.getCanonicalName() + " {",
+        "}"
+    );
+    AndroidApp app = readClassesAndAndriodJar(ImmutableList.of(
+        mainClass, TestClass.class, AnotherClass.class,
+        PackagePrivateClass.class, PackagePrivateClass.Itf.class, PackagePrivateClass.Impl.class
+    ));
+    AndroidApp processedApp = compileWithR8(app, String.join(System.lineSeparator(), config));
+    DexInspector inspector = new DexInspector(processedApp);
+    ClassSubject mainSubject = inspector.clazz(mainClass);
+    assertThat(mainSubject, isPresent());
+
+    ProcessResult artResult =
+        runOnArtRaw(processedApp, getClassNameFromDescriptor(mainSubject.getFinalDescriptor()));
+    assertEquals(0, artResult.exitCode);
+    assertEquals(-1, artResult.stderr.indexOf("IllegalAccessError"));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/b80083341/PackagePrivateClass.java b/src/test/java/com/android/tools/r8/naming/b80083341/PackagePrivateClass.java
new file mode 100644
index 0000000..62a4181
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/b80083341/PackagePrivateClass.java
@@ -0,0 +1,27 @@
+// 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.naming.b80083341;
+
+final class PackagePrivateClass {
+  private static final boolean flag = false;
+
+  private PackagePrivateClass() {}
+
+  interface Itf<T> {
+    boolean foo();
+  }
+
+  static class Impl<T> implements Itf<T> {
+    private final Object[] objs;
+
+    Impl(int size) {
+      objs = new Object[size];
+    }
+
+    @Override
+    public boolean foo() {
+      return flag;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/b80083341/TestClass.java b/src/test/java/com/android/tools/r8/naming/b80083341/TestClass.java
new file mode 100644
index 0000000..a5dc174
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/b80083341/TestClass.java
@@ -0,0 +1,8 @@
+// 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.naming.b80083341;
+
+public class TestClass {
+  PackagePrivateClass.Impl<AnotherClass> anotherClasses = new PackagePrivateClass.Impl<>(8);
+}
diff --git a/src/test/java/com/android/tools/r8/naming/b80083341/TestMain.java b/src/test/java/com/android/tools/r8/naming/b80083341/TestMain.java
new file mode 100644
index 0000000..6be514c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/b80083341/TestMain.java
@@ -0,0 +1,10 @@
+// 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.naming.b80083341;
+
+public class TestMain {
+  public static void main(String[] args) {
+    new TestClass().anotherClasses.foo();
+  }
+}
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/b69825683/Regress69825683Test.java b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
index 9026740..a185f4c 100644
--- a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
@@ -33,7 +33,7 @@
         "-dontobfuscate"),
         Origin.unknown());
     builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    AndroidApp app = ToolHelper.runR8(builder.build());
+    AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
     DexInspector inspector = new DexInspector(app);
     List<FoundClassSubject> classes = inspector.allClasses();
 
@@ -66,7 +66,7 @@
         "-dontobfuscate"),
         Origin.unknown());
     builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    AndroidApp app = ToolHelper.runR8(builder.build());
+    AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
     DexInspector inspector = new DexInspector(app);
     List<FoundClassSubject> classes = inspector.allClasses();
 
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/resource/DataResourceTest.java b/src/test/java/com/android/tools/r8/resource/DataResourceTest.java
index 167262f..dbd4e23 100644
--- a/src/test/java/com/android/tools/r8/resource/DataResourceTest.java
+++ b/src/test/java/com/android/tools/r8/resource/DataResourceTest.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -35,9 +35,10 @@
     ProcessResult referenceResult = ToolHelper.runJava(inputJar, mainClassName);
 
     Path r8Out = temp.getRoot().toPath().resolve("r8out.jar");
-    R8Command.Builder builder = R8Command.builder()
-        .addProgramFiles(inputJar)
-        .setOutput(r8Out, OutputMode.DexIndexed);
+    R8Command.Builder builder =
+        R8Command.builder()
+            .addProgramFiles(inputJar)
+            .setProgramConsumer(new DexIndexedConsumer.ArchiveConsumer(r8Out, true));
     ToolHelper.runR8(builder.build());
 
     ProcessResult r8Result = ToolHelper.runArtRaw(r8Out.toString(), mainClassName);
diff --git a/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java b/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
new file mode 100644
index 0000000..9e0429b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
@@ -0,0 +1,108 @@
+// 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.containsString;
+import static org.junit.Assert.assertEquals;
+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.naming.MemberNaming.FieldSignature;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+public class FieldTypeTest extends TestBase {
+
+  @Ignore("b/78788577")
+  @Test
+  public void test_brokenTypeHierarchy() throws Exception {
+    JasminBuilder jasminBuilder = new JasminBuilder();
+    // interface Itf
+    ClassBuilder itf = jasminBuilder.addInterface("Itf");
+    MethodSignature foo = itf.addAbstractMethod("foo", ImmutableList.of(), "V");
+    // class Impl /* implements Itf */
+    ClassBuilder impl = jasminBuilder.addClass("Impl");
+    impl.addDefaultConstructor();
+    impl.addVirtualMethod("foo", ImmutableList.of(), "V",
+        ".limit locals 2",
+        ".limit stack 2",
+        "getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "ldc \"" + "foo" + "\"",
+        "invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "return");
+    impl.addVirtualMethod("toString", ImmutableList.of(), "Ljava/lang/String;",
+        ".limit locals 1",
+        ".limit stack 2",
+        "ldc \"" + impl.name + "\"",
+        "areturn");
+    ClassBuilder client = jasminBuilder.addClass("Client");
+    FieldSignature obj = client.addStaticFinalField("obj", itf.getDescriptor(), null);
+    client.addClassInitializer(
+        ".limit locals 1",
+        ".limit stack 2",
+        "new " + impl.name,
+        "dup",
+        "invokespecial " + impl.name + "/<init>()V",
+        "putstatic " + client.name + "/" + obj.name + " " + itf.getDescriptor(),
+        "return"
+    );
+
+    ClassBuilder mainClass = jasminBuilder.addClass("Main");
+    mainClass.addMainMethod(
+        ".limit locals 2",
+        ".limit stack 2",
+        "getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "getstatic " + client.name + "/" + obj.name + " " + itf.getDescriptor(),
+        /*
+        "astore_0",
+        "aload_0",
+        // java.lang.IncompatibleClassChangeError:
+        //     Class Impl does not implement the requested interface Itf
+        "invokeinterface " + itf.name + "/" + foo.name + "()V 1",
+        "aload_0",
+        */
+        "invokevirtual java/io/PrintStream/print(Ljava/lang/Object;)V",
+        "return"
+    );
+
+    final String mainClassName = mainClass.name;
+    String proguardConfig = keepMainProguardConfiguration(mainClass.name, false, false);
+
+    // Run input program on java.
+    Path outputDirectory = temp.newFolder().toPath();
+    jasminBuilder.writeClassFiles(outputDirectory);
+    ProcessResult javaResult = ToolHelper.runJava(outputDirectory, mainClassName);
+    assertEquals(0, javaResult.exitCode);
+    assertThat(javaResult.stdout, containsString(impl.name));
+
+    AndroidApp processedApp = compileWithR8(jasminBuilder.build(), proguardConfig,
+        // Disable inlining to avoid the (short) tested method from being inlined and then removed.
+        internalOptions -> internalOptions.enableInlining = false);
+
+    // Run processed (output) program on ART
+    ProcessResult artResult = runOnArtRaw(processedApp, mainClassName);
+    assertEquals(0, artResult.exitCode);
+    assertThat(artResult.stdout, containsString(impl.name));
+    assertEquals(-1, artResult.stderr.indexOf("DoFieldPut"));
+
+    DexInspector inspector = new DexInspector(processedApp);
+    ClassSubject itfSubject = inspector.clazz(itf.name);
+    assertThat(itfSubject, isPresent());
+  }
+
+}
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 286544b..f558ee7 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -43,6 +43,7 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.function.Function;
 import org.junit.Before;
@@ -138,6 +139,8 @@
   private Reporter reporter;
   private KeepingDiagnosticHandler handler;
   private ProguardConfigurationParser parser;
+  private List<String> whiteSpace = ImmutableList.of("", " ", "   ", "\t", " \t", " \t", " \t ", " \t\t \t ");
+
 
   @Before
   public void reset() {
@@ -788,12 +791,10 @@
 
   @Test
   public void parseKeepModifiers() {
-    List<String> ws = ImmutableList.of("", " ", "   ", "\t", " \t", " \t", " \t ", " \t\t \t ");
-
-    for (String before : ws) {
-      for (String after : ws) {
+    for (String before : whiteSpace) {
+      for (String after : whiteSpace) {
         reset();
-        ProguardConfiguration config = parseAndVerifyParserEndsCleanly(ImmutableList.of(
+        parseAndVerifyParserEndsCleanly(ImmutableList.of(
             "-keep"
                 + before + "," + after + "includedescriptorclasses"
                 + before + "," + after + "allowshrinking"
@@ -806,6 +807,16 @@
   }
 
   @Test
+  public void parseKeepAnnotation() {
+    for (String space : whiteSpace) {
+      reset();
+      parseAndVerifyParserEndsCleanly(ImmutableList.of(
+          "-keep @" + space + "interface A"
+      ));
+    }
+  }
+
+  @Test
   public void regress78442725() {
     parseAndVerifyParserEndsCleanly(ImmutableList.of(
         "-keep, includedescriptorclasses class in.uncod.android.bypass.Document { *; }",
@@ -1388,7 +1399,17 @@
     assertEquals("**$R**", if0.getClassNames().toString());
     assertEquals(ProguardKeepRuleType.KEEP, if0.subsequentRule.getType());
     assertEquals("**$D<2>", if0.subsequentRule.getClassNames().toString());
-    // TODO(b/73800755): Test <2> matches with expected wildcard: ** after '$R'.
+
+    // Test <2> literally refers to the second wildcard in the rule.
+    Iterator<ProguardWildcard> it1 = if0.getClassNames().getWildcards().iterator();
+    it1.next();
+    ProguardWildcard secondWildcardInIf = it1.next();
+    assertTrue(secondWildcardInIf.isPattern());
+    Iterator<ProguardWildcard> it2 = if0.subsequentRule.getWildcards().iterator();
+    it2.next();
+    ProguardWildcard backReference = it2.next();
+    assertTrue(backReference.isBackReference());
+    assertSame(secondWildcardInIf.asPattern(), backReference.asBackReference().reference);
 
     verifyWithProguard6(proguardConfig);
   }
@@ -1670,6 +1691,17 @@
   }
 
   @Test
+  public void parse_mergeinterfaceaggressively() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-mergeinterfacesaggressively"
+    );
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(proguardConfig);
+    verifyParserEndsCleanly();
+  }
+
+  @Test
   public void parse_addconfigurationdebugging() throws Exception {
     Path proguardConfig = writeTextToTempFile(
         "-addconfigurationdebugging"
@@ -1695,6 +1727,24 @@
     verifyWithProguard(proguardConfig);
   }
 
+  @Test
+  public void parse_regress79925760() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-keep public @ interface test.MyAnnotation"
+    );
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(proguardConfig);
+    verifyParserEndsCleanly();
+
+    ProguardConfiguration config = parser.getConfig();
+    assertEquals(1, config.getRules().size());
+    ProguardKeepRule rule = (ProguardKeepRule) config.getRules().get(0);
+    assertEquals(ProguardClassType.ANNOTATION_INTERFACE, rule.getClassType());
+
+    verifyWithProguard(proguardConfig);
+  }
+
   private ProguardConfiguration parseAndVerifyParserEndsCleanly(List<String> config) {
     parser.parse(createConfigurationForTesting(config));
     verifyParserEndsCleanly();
@@ -1707,6 +1757,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/ProguardNameMatchingTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardNameMatchingTest.java
index 4a734a4..983928b 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardNameMatchingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardNameMatchingTest.java
@@ -9,8 +9,11 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
 import com.android.tools.r8.shaking.ProguardTypeMatcher.ClassOrType;
+import com.android.tools.r8.shaking.ProguardWildcard.BackReference;
+import com.android.tools.r8.shaking.ProguardWildcard.Pattern;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import org.junit.Test;
@@ -24,7 +27,7 @@
 
   private static boolean matchTypeName(String typeName, String pattern) {
     return ProguardTypeMatcher.create(
-        toIdentifierPatternWithWildCards(pattern), ClassOrType.TYPE, dexItemFactory)
+        toIdentifierPatternWithWildCards(pattern, false), ClassOrType.TYPE, dexItemFactory)
         .matches(dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(typeName)));
   }
 
@@ -39,8 +42,10 @@
       for (String pattern : patterns) {
         boolean isNegated = pattern.startsWith("!");
         String actualPattern = isNegated ? pattern.substring(1) : pattern;
-        listBuilder.addClassName(isNegated, ProguardTypeMatcher.create(
-            toIdentifierPatternWithWildCards(actualPattern), ClassOrType.CLASS, dexItemFactory));
+        listBuilder.addClassName(isNegated,
+            ProguardTypeMatcher.create(
+                toIdentifierPatternWithWildCards(actualPattern, false),
+                ClassOrType.CLASS, dexItemFactory));
       }
       builder.addPattern(listBuilder.build());
     }
@@ -48,6 +53,12 @@
         .matches(dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(className)));
   }
 
+  private static boolean matchMemberName(String pattern, String memberName) {
+    ProguardNameMatcher nameMatcher =
+        ProguardNameMatcher.create(toIdentifierPatternWithWildCards(pattern, true));
+    return nameMatcher.matches(memberName);
+  }
+
   @Test
   public void matchClassNames() {
     assertTrue(matchClassName("", "**"));
@@ -67,22 +78,29 @@
 
     assertTrue(matchClassName("java.lang.Object", "java.*g.O*"));
     assertTrue(matchClassName("java.lang.Object", "java.*g.O?je?t"));
+    assertTrue(matchClassName("java.lang.Object", "?ava.*g.Ob<1>e*"));
+    assertTrue(matchClassName("java.lang.Object", "j?v<1>.*<1>*g.Obj*"));
+    assertTrue(matchClassName("java.lang.Object", "j*v?.*<2>*g.Obj*"));
     assertFalse(matchClassName("java.lang.Object", "java.*g.O?je?t?"));
     assertFalse(matchClassName("java.lang.Object", "java?lang.Object"));
     assertTrue(matchClassName("java.lang.Object", "*a*.*a**"));
     assertTrue(matchClassName("java.lang.Object", "*a**a**"));
+    assertTrue(matchClassName("java.lang.Object", "*?**<2>**"));
 
     assertTrue(matchClassName("java.lang.Object", "!java.util.**", "java**"));
     assertFalse(matchClassName("java.lang.Object", "!java.**", "java.lang.*"));
     assertTrue(matchClassName("java.lang.Object", "java.lang.*", "!java.**"));
 
     assertTrue(matchClassName("boobar", "!foobar", "*bar"));
+    assertTrue(matchClassName("boobar", "!foobar", "?*<1>ar"));
     assertFalse(matchClassName("foobar", "!foobar", "*bar"));
 
     assertFalse(matchClassName("foo", "!boo"));
     assertFalse(matchClassName("foo", "baz,!boo"));
 
     assertFalse(matchClassName("boo", "!boo", "**"));
+    assertFalse(matchClassName("boo", "!b*<1>", "**"));
+    assertFalse(matchClassName("boo", "!b?<1>", "**"));
     assertTrue(matchClassName("boo", "**", "!boo"));
     assertTrue(matchClassName("boo",
         ImmutableList.of(ImmutableList.of("!boo"), ImmutableList.of("**"))));
@@ -132,48 +150,91 @@
 
   @Test
   public void matchFieldOrMethodNames() {
-    assertTrue(ProguardNameMatcher.matchFieldOrMethodName("*", ""));
-    assertTrue(ProguardNameMatcher.matchFieldOrMethodName("*", "get"));
-    assertTrue(ProguardNameMatcher.matchFieldOrMethodName("get*", "get"));
-    assertTrue(ProguardNameMatcher.matchFieldOrMethodName("get*", "getObject"));
-    assertTrue(ProguardNameMatcher.matchFieldOrMethodName("*t", "get"));
-    assertTrue(ProguardNameMatcher.matchFieldOrMethodName("g*t*", "getObject"));
-    assertTrue(
-        ProguardNameMatcher.matchFieldOrMethodName("g*t***************", "getObject"));
-    assertFalse(ProguardNameMatcher.matchFieldOrMethodName("get*y", "getObject"));
-    assertFalse(ProguardNameMatcher.matchFieldOrMethodName("getObject?", "getObject"));
-    assertTrue(ProguardNameMatcher.matchFieldOrMethodName("getObject?", "getObject1"));
-    assertTrue(ProguardNameMatcher.matchFieldOrMethodName("getObject?", "getObject5"));
+    assertTrue(matchMemberName("*", ""));
+    assertTrue(matchMemberName("*", "get"));
+    assertTrue(matchMemberName("get*", "get"));
+    assertTrue(matchMemberName("get*", "getObject"));
+    assertTrue(matchMemberName("*t", "get"));
+    assertTrue(matchMemberName("g*t*", "getObject"));
+    assertTrue(matchMemberName("g*t***************", "getObject"));
+    assertFalse(matchMemberName("get*y", "getObject"));
+    assertFalse(matchMemberName("getObject?", "getObject"));
+    assertTrue(matchMemberName("getObject?", "getObject1"));
+    assertTrue(matchMemberName("getObject?", "getObject5"));
+
+    assertTrue(matchMemberName("ge?Objec<1>", "getObject"));
+    assertTrue(matchMemberName("ge*Objec<1>", "getObject"));
+    assertTrue(matchMemberName("ge*Objec<1>", "geObjec"));
+    assertTrue(matchMemberName("g?*Obj<1>c<2>", "getObject"));
+    assertTrue(matchMemberName("g??Obj<1>c<2>", "getObject"));
+    assertTrue(matchMemberName("g*?Obj<1>c<2>", "getObject"));
+    assertFalse(matchMemberName("g?*Obj<1>c<2>", "getObject1"));
+    assertTrue(matchMemberName("g**Obj<1>c<2>", "getObject"));
+    assertFalse(matchMemberName("g**Obj<1>c<2>", "getObject1"));
+    assertTrue(matchMemberName("g?*Obj<1>c<2>?", "getObject1"));
+    assertTrue(matchMemberName("g**Obj<1>c<2>?", "getObject1"));
+    assertTrue(matchMemberName("*foo<1>", "foofoofoo"));
+    assertTrue(matchMemberName("*foo<1>", "barfoobar"));
+    assertFalse(matchMemberName("*foo<1>", "barfoobaz"));
   }
 
-  private static IdentifierPatternWithWildcards toIdentifierPatternWithWildCards(String pattern) {
-    ImmutableList.Builder<String> builder = ImmutableList.builder();
+  private static IdentifierPatternWithWildcards toIdentifierPatternWithWildCards(
+      String pattern, boolean isForNameMatcher) {
+    ImmutableList.Builder<ProguardWildcard> builder = ImmutableList.builder();
     String allPattern = "";
     String backReference = "";
     for (int i = 0; i < pattern.length(); i++) {
       char patternChar = pattern.charAt(i);
-      if (patternChar == '?' || patternChar == '%') {
-        builder.add(String.valueOf(patternChar));
-      } else if (patternChar == '*') {
-        allPattern += patternChar;
-      } else if (patternChar == '<') {
-        backReference += patternChar;
-      } else if (patternChar == '>') {
-        backReference += patternChar;
-        builder.add(backReference);
-        backReference = "";
-      } else {
-        if (allPattern.length() > 0) {
-          builder.add(allPattern);
-          allPattern = "";
-        } else if (backReference.length() > 0) {
+      if (backReference.length() > 0) {
+        if (patternChar == '>') {
+          backReference += patternChar;
+          builder.add(new BackReference(
+              Integer.parseInt(backReference.substring(1, backReference.length() - 1))));
+          backReference = "";
+        } else {
           backReference += patternChar;
         }
+        continue;
+      } else if (allPattern.length() > 0) {
+        if (patternChar == '*') {
+          allPattern += patternChar;
+          continue;
+        } else {
+          builder.add(new Pattern(allPattern));
+          allPattern = "";
+        }
+      }
+
+      if (patternChar == '*') {
+        if (isForNameMatcher) {
+          builder.add(new Pattern(String.valueOf(patternChar)));
+        } else {
+          allPattern += patternChar;
+        }
+      } else if (patternChar == '?' || patternChar == '%') {
+        builder.add(new Pattern(String.valueOf(patternChar)));
+      } else if (patternChar == '<') {
+        backReference += patternChar;
       }
     }
     if (allPattern.length() > 0) {
-      builder.add(allPattern);
+      builder.add(new Pattern(allPattern));
     }
-    return new IdentifierPatternWithWildcards(pattern, builder.build());
+    List<ProguardWildcard> wildcards = builder.build();
+    linkBackReferences(wildcards);
+    return new IdentifierPatternWithWildcards(pattern, wildcards);
+  }
+
+  private static void linkBackReferences(Iterable<ProguardWildcard> wildcards) {
+    List<Pattern> patterns = new ArrayList<>();
+    for (ProguardWildcard wildcard : wildcards) {
+      if (wildcard.isBackReference()) {
+        BackReference backReference = wildcard.asBackReference();
+        backReference.setReference(patterns.get(backReference.referenceIndex - 1));
+      } else {
+        assert wildcard.isPattern();
+        patterns.add(wildcard.asPattern());
+      }
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
index 3de225d..5845dd3 100644
--- a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
@@ -39,7 +39,7 @@
         "-dontobfuscate"),
         Origin.unknown());
     builder.addProguardConfiguration(additionalKeepRules, Origin.unknown());
-    AndroidApp app = ToolHelper.runR8(builder.build());
+    AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
     inspection.accept(new DexInspector(app));
   }
 
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/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 1151648..91fc9d4 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -599,7 +599,7 @@
     AndroidApp app;
     builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
     try {
-      app = ToolHelper.runR8(builder.build());
+      app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
     } catch (CompilationError e) {
       assertTrue(!forceProguardCompatibility && (!innerClasses || !enclosingMethod));
       return;
@@ -656,7 +656,7 @@
     builder.setMinApiLevel(AndroidApiLevel.O.getLevel());
     Path proguardCompatibilityRules = temp.newFile().toPath();
     builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules);
-    AndroidApp app = ToolHelper.runR8(builder.build());
+    AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
     inspection.accept(new DexInspector(app));
     // Check the Proguard compatibility configuration generated.
     ProguardConfigurationParser parser =
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
index 080df2a..4d0281f 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
@@ -59,11 +59,6 @@
 
   @Test
   public void ifOnAnnotation_withNthWildcard() throws Exception {
-    // TODO(b/73800755): not implemented yet.
-    if (shrinker == Shrinker.R8) {
-      return;
-    }
-
     List<String> config = ImmutableList.of(
         "-keepattributes *Annotation*",
         "-keep class **.Main* {",
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
index c26f43f..76c1023 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
@@ -133,11 +133,6 @@
       return;
     }
 
-    // TODO(b/73800755): not implemented yet.
-    if (shrinker == Shrinker.R8) {
-      return;
-    }
-
     ClassSubject clazz = dexInspector.clazz(DependentUser.class);
     assertThat(clazz, isRenamed());
     // Members of DependentUser are not used anywhere.
@@ -251,11 +246,6 @@
       return;
     }
 
-    // TODO(b/73800755): not implemented yet.
-    if (shrinker == Shrinker.R8) {
-      return;
-    }
-
     ClassSubject clazz = dexInspector.clazz(DependentUser.class);
     assertThat(clazz, isRenamed());
     MethodSubject m = clazz.method("void", "callFoo", ImmutableList.of());
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
index 2bf8a27..73c3011 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
@@ -3,9 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.ifrule;
 
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
 import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
 import com.android.tools.r8.utils.DexInspector;
 import com.google.common.collect.ImmutableList;
@@ -89,11 +86,6 @@
 
   @Test
   public void ifOnField_withNthWildcard() throws Exception {
-    // TODO(b/73800755): not implemented yet.
-    if (shrinker == Shrinker.R8) {
-      return;
-    }
-
     List<String> config = ImmutableList.of(
         "-keep class **.MainUsesR {",
         "  public static void main(java.lang.String[]);",
@@ -136,11 +128,6 @@
 
   @Test
   public void ifOnFieldWithCapture_withNthWildcard() throws Exception {
-    // TODO(b/73800755): not implemented yet.
-    if (shrinker == Shrinker.R8) {
-      return;
-    }
-
     List<String> config = ImmutableList.of(
         "-keep class **.MainWithIf {",
         "  public static void main(java.lang.String[]);",
@@ -180,11 +167,6 @@
 
   @Test
   public void ifOnFieldWithInner_withNthWildcard() throws Exception {
-    // TODO(b/73800755): not implemented yet.
-    if (shrinker == Shrinker.R8) {
-      return;
-    }
-
     List<String> config = ImmutableList.of(
         "-keep class **.MainWithInner {",
         "  public static void main(java.lang.String[]);",
@@ -204,33 +186,6 @@
   }
 
   @Test
-  public void ifOnFieldWithInner_outOfRange() throws Exception {
-    // TODO(b/73800755): not implemented yet.
-    if (shrinker == Shrinker.R8) {
-      return;
-    }
-
-    List<String> config = ImmutableList.of(
-        "-keep class **.MainWithInner {",
-        "  public static void main(java.lang.String[]);",
-        "}",
-        "-if class **$*R",
-        "-keep class <1>$<3>D"
-    );
-
-    try {
-      runShrinker(shrinker, CLASSES, config);
-      fail("Expect to see an error about wrong range of <n>.");
-    } catch (Error e) {
-      // "Invalid reference to wildcard (3, must lie between 1 and 2)"
-      String message = e.getMessage();
-      assertTrue(message.contains("Invalid"));
-      assertTrue(message.contains("wildcard"));
-      assertTrue(message.contains("3"));
-    }
-  }
-
-  @Test
   public void ifOnFieldInImplementer_withoutNthWildcard() throws Exception {
     List<String> config = ImmutableList.of(
         "-keep class **.MainUsesImpl {",
@@ -254,11 +209,6 @@
 
   @Test
   public void ifOnFieldInImplementer_withNthWildcard() throws Exception {
-    // TODO(b/73800755): not implemented yet.
-    if (shrinker == Shrinker.R8) {
-      return;
-    }
-
     List<String> config = ImmutableList.of(
         "-keep class **.MainUsesImpl {",
         "  public static void main(java.lang.String[]);",
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..6b90d88 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(null, 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"))
diff --git a/src/test/kotlinR8TestResources/properties/FileProperties.kt b/src/test/kotlinR8TestResources/properties/FileProperties.kt
new file mode 100644
index 0000000..4671189
--- /dev/null
+++ b/src/test/kotlinR8TestResources/properties/FileProperties.kt
@@ -0,0 +1,82 @@
+// 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 properties
+
+private var privateProp: String = "privateProp"
+internal var internalProp: String = "internalProp"
+public var publicProp: String = "publicProp"
+
+private lateinit var privateLateInitProp: String
+internal lateinit var internalLateInitProp: String
+public lateinit var publicLateInitProp: String
+
+public var primitiveProp: Int = Int.MAX_VALUE
+
+// Serves as intermediate to make sure the access to a property is done from a separate class.
+private object Intermediate {
+    fun readPrivateProp() = privateProp
+    fun writePrivateProp(s: String) { privateProp = s }
+
+    fun readInternalProp() = internalProp
+    fun writeInternalProp(s: String) { internalProp = s }
+
+    fun readPublicProp() = publicProp
+    fun writePublicProp(s: String) { publicProp = s }
+
+    fun readPrimitiveProp() = primitiveProp
+    fun writePrimitiveProp(i: Int) { primitiveProp = i }
+
+    fun readLateInitPrivateProp() = privateLateInitProp
+    fun writeLateInitPrivateProp(s: String) { privateLateInitProp = s }
+
+    fun readLateInitInternalProp() = internalLateInitProp
+    fun writeLateInitInternalProp(s: String) { internalLateInitProp = s }
+
+    fun readLateInitPublicProp() = publicLateInitProp
+    fun writeLateInitPublicProp(s: String) { publicLateInitProp = s }
+}
+
+fun doNotUseProperties(): String {
+    return "doNotUseProperties"
+}
+
+fun fileProperties_noUseOfProperties() {
+    println(ObjectProperties.doNotUseProperties())
+}
+
+fun fileProperties_usePrivateProp() {
+    Intermediate.writePrivateProp("foo")
+    println(Intermediate.readPrivateProp())
+}
+
+fun fileProperties_useInternalProp() {
+    Intermediate.writeInternalProp("foo")
+    println(Intermediate.readInternalProp())
+}
+
+fun fileProperties_usePublicProp() {
+    Intermediate.writePublicProp("foo")
+    println(Intermediate.readPublicProp())
+}
+
+fun fileProperties_usePrimitiveProp() {
+    Intermediate.writePrimitiveProp(Int.MIN_VALUE)
+    println(Intermediate.readPrimitiveProp())
+}
+
+fun fileProperties_useLateInitPrivateProp() {
+    Intermediate.writeLateInitPrivateProp("foo")
+    println(Intermediate.readLateInitPrivateProp())
+}
+
+fun fileProperties_useLateInitInternalProp() {
+    Intermediate.writeLateInitInternalProp( "foo")
+    println(Intermediate.readLateInitInternalProp())
+}
+
+fun fileProperties_useLateInitPublicProp() {
+    Intermediate.writeLateInitPublicProp("foo")
+    println(Intermediate.readLateInitPublicProp())
+}
diff --git a/src/test/kotlinR8TestResources/properties/ObjectProperties.kt b/src/test/kotlinR8TestResources/properties/ObjectProperties.kt
new file mode 100644
index 0000000..d05e2a6
--- /dev/null
+++ b/src/test/kotlinR8TestResources/properties/ObjectProperties.kt
@@ -0,0 +1,76 @@
+// 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 properties
+
+object ObjectProperties {
+    private var privateProp: String = "privateProp"
+    internal var internalProp: String = "internalProp"
+    public var publicProp: String = "publicProp"
+
+    private lateinit var privateLateInitProp: String
+    internal lateinit var internalLateInitProp: String
+    public lateinit var publicLateInitProp: String
+
+    public var primitiveProp: Int = Int.MAX_VALUE
+
+    fun callSetterPrivateProp(v: String) {
+        privateProp = v
+    }
+
+    fun callGetterPrivateProp(): String {
+        return privateProp
+    }
+
+    fun callSetterLateInitPrivateProp(v: String) {
+        privateLateInitProp = v
+    }
+
+    fun callGetterLateInitPrivateProp(): String {
+        return privateLateInitProp
+    }
+
+    fun doNotUseProperties(): String {
+        return "doNotUseProperties"
+    }
+}
+
+fun objectProperties_noUseOfProperties() {
+    println(ObjectProperties.doNotUseProperties())
+}
+
+fun objectProperties_usePrivateProp() {
+    ObjectProperties.callSetterPrivateProp("foo")
+    println(ObjectProperties.callGetterPrivateProp())
+}
+
+fun objectProperties_useInternalProp() {
+    ObjectProperties.internalProp = "foo"
+    println(ObjectProperties.internalProp)
+}
+
+fun objectProperties_usePublicProp() {
+    ObjectProperties.publicProp = "foo"
+    println(ObjectProperties.publicProp)
+}
+
+fun objectProperties_usePrimitiveProp() {
+    ObjectProperties.primitiveProp = Int.MIN_VALUE
+    println(ObjectProperties.primitiveProp)
+}
+
+fun objectProperties_useLateInitPrivateProp() {
+    ObjectProperties.callSetterLateInitPrivateProp("foo")
+    println(ObjectProperties.callGetterLateInitPrivateProp())
+}
+
+fun objectProperties_useLateInitInternalProp() {
+    ObjectProperties.internalLateInitProp = "foo"
+    println(ObjectProperties.internalLateInitProp)
+}
+
+fun objectProperties_useLateInitPublicProp() {
+    ObjectProperties.publicLateInitProp = "foo"
+    println(ObjectProperties.publicLateInitProp)
+}
diff --git a/tools/archive.py b/tools/archive.py
index 815e416..07f138a 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -3,22 +3,25 @@
 # 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.
 
-import gradle
 import create_maven_release
-import d8
+import gradle
 import os
-import r8
+import shutil
 import subprocess
 import sys
+import toolhelper
 import utils
-import shutil
 import zipfile
 
 ARCHIVE_BUCKET = 'r8-releases'
 
+def GetToolVersion(jar_path):
+  output = subprocess.check_output(['java', '-jar', jar_path, '--version'])
+  return output.splitlines()[0].strip()
+
 def GetVersion():
-  r8_version = r8.run(['--version'], build = False).splitlines()[0].strip()
-  d8_version = d8.run(['--version'], build = False).splitlines()[0].strip()
+  r8_version = GetToolVersion(utils.R8_JAR)
+  d8_version = GetToolVersion(utils.D8_JAR)
   # The version printed is "D8 vVERSION_NUMBER" and "R8 vVERSION_NUMBER"
   # Sanity check that versions match.
   if d8_version.split()[1] != r8_version.split()[1]:
@@ -49,21 +52,30 @@
                       'in a commit that have -dev in version')
   return True
 
-def GetStorageDestination(storage_prefix, version, file_name, is_master):
+def GetStorageDestination(storage_prefix,
+                          version_or_path,
+                          file_name,
+                          is_master):
   # We archive master commits under raw/master instead of directly under raw
-  version_dir = GetVersionDestination(storage_prefix, version, is_master)
+  version_dir = GetVersionDestination(storage_prefix,
+                                      version_or_path,
+                                      is_master)
   return '%s/%s' % (version_dir, file_name)
 
-def GetVersionDestination(storage_prefix, version, is_master):
+def GetVersionDestination(storage_prefix, version_or_path, is_master):
   archive_dir = 'raw/master' if is_master else 'raw'
-  return '%s%s/%s/%s' % (storage_prefix, ARCHIVE_BUCKET, archive_dir, version)
+  return '%s%s/%s/%s' % (storage_prefix, ARCHIVE_BUCKET,
+                         archive_dir, version_or_path)
 
-def GetUploadDestination(version, file_name, is_master):
-  return GetStorageDestination('gs://', version, file_name, is_master)
+def GetUploadDestination(version_or_path, file_name, is_master):
+  return GetStorageDestination('gs://', version_or_path, file_name, is_master)
 
-def GetUrl(version, file_name, is_master):
+def GetUrl(version_or_path, file_name, is_master):
   return GetStorageDestination('http://storage.googleapis.com/',
-                               version, file_name, is_master)
+                               version_or_path, file_name, is_master)
+
+def GetMavenUrl(is_master):
+  return GetVersionDestination('http://storage.googleapis.com/', '', is_master)
 
 def Main():
   if not 'BUILDBOT_BUILDERNAME' in os.environ:
@@ -113,14 +125,13 @@
       print('Uploading %s to %s' % (tagged_jar, destination))
       utils.upload_file_to_cloud_storage(tagged_jar, destination)
       print('File available at: %s' % GetUrl(version, file_name, is_master))
-    # Upload extracted maven directory for easy testing in studio.
-    zip_ref = zipfile.ZipFile(utils.MAVEN_ZIP, 'r')
-    zip_ref.extractall(temp)
-    zip_ref.close()
-    utils.upload_dir_to_cloud_storage(
-        os.path.join(temp, 'com'),
-        GetUploadDestination(version, 'com', is_master))
-    print('Maven repo root available at: %s' % GetUrl(version, '', is_master))
+      if file == utils.R8_JAR:
+        # Upload R8 to a maven compatible location.
+        maven_dst = GetUploadDestination(utils.get_maven_path(version),
+                                         'r8-%s.jar' % version, is_master)
+        utils.upload_file_to_cloud_storage(tagged_jar, maven_dst)
+        print('Maven repo root available at: %s' % GetMavenUrl(is_master))
+
 
 if __name__ == '__main__':
   sys.exit(Main())
diff --git a/tools/bisect.py b/tools/bisect.py
index c8d1501..74c880e 100755
--- a/tools/bisect.py
+++ b/tools/bisect.py
@@ -3,33 +3,8 @@
 # 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.
 
-import gradle
-import os
-import subprocess
 import sys
-import utils
-
-JAR = os.path.join(utils.REPO_ROOT, 'build', 'libs', 'bisect.jar')
-
-def run(args, build, debug):
-  if build:
-    gradle.RunGradle(['bisect'])
-  cmd = ['java']
-  if debug:
-    cmd.append('-ea')
-  cmd.extend(['-jar', JAR])
-  cmd.extend(args)
-  subprocess.check_call(cmd)
-
-def main():
-  build = True
-  args = []
-  for arg in sys.argv[1:]:
-    if arg in ("--build", "--no-build"):
-      build = arg == "--build"
-    else:
-      args.append(arg)
-  run(args, build, True)
+import toolhelper
 
 if __name__ == '__main__':
-  sys.exit(main())
+  sys.exit(toolhelper.run('bisect', sys.argv[1:]))
diff --git a/tools/compatdx.py b/tools/compatdx.py
index 25a59db..c4cb320 100755
--- a/tools/compatdx.py
+++ b/tools/compatdx.py
@@ -3,42 +3,8 @@
 # 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.
 
-import gradle
-import os
-import subprocess
 import sys
-import utils
-
-def run(args, build = True, debug = True, profile = False, track_memory_file=None):
-  if build:
-    gradle.RunGradle(['CompatDX'])
-  cmd = []
-  if track_memory_file:
-    cmd.extend(['tools/track_memory.sh', track_memory_file])
-  cmd.append('java')
-  if debug:
-    cmd.append('-ea')
-  if profile:
-    cmd.append('-agentlib:hprof=cpu=samples,interval=1,depth=8')
-  cmd.extend(['-jar', utils.COMPATDX_JAR])
-  cmd.extend(args)
-  subprocess.check_call(cmd)
-
-def main():
-  build = True
-  args = []
-  for arg in sys.argv[1:]:
-    if arg in ("--build", "--no-build"):
-      build = arg == "--build"
-    else:
-      args.append(arg)
-  try:
-    run(args, build)
-  except subprocess.CalledProcessError as e:
-    # In case anything relevant was printed to stdout, normally this is already
-    # on stderr.
-    print(e.output)
-    return e.returncode
+import toolhelper
 
 if __name__ == '__main__':
-  sys.exit(main())
+  sys.exit(toolhelper.run('compatdx', sys.argv[1:]))
diff --git a/tools/compatproguard.py b/tools/compatproguard.py
new file mode 100755
index 0000000..10542e3
--- /dev/null
+++ b/tools/compatproguard.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# 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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+  sys.exit(toolhelper.run('compatproguard', sys.argv[1:]))
diff --git a/tools/create_maven_release.py b/tools/create_maven_release.py
index 0a7f122..662d53e 100755
--- a/tools/create_maven_release.py
+++ b/tools/create_maven_release.py
@@ -210,7 +210,7 @@
   # Create directory structure for this version.
   version = determine_version()
   with utils.TempDir() as tmp_dir:
-    version_dir = join(tmp_dir, 'com', 'android', 'tools', 'r8', version)
+    version_dir = join(tmp_dir, utils.get_maven_path(version))
     makedirs(version_dir)
     # Write the pom file.
     pom_file = join(version_dir, 'r8-' + version + '.pom')
diff --git a/tools/d8.py b/tools/d8.py
index bf97845..18a4a67 100755
--- a/tools/d8.py
+++ b/tools/d8.py
@@ -3,46 +3,8 @@
 # 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.
 
-import gradle
-import os
-import subprocess
 import sys
-import utils
-
-def run(args, build=True, debug=True, profile=False,
-        track_memory_file=None):
-  if build:
-    gradle.RunGradle(['D8'])
-  cmd = []
-  if track_memory_file:
-    cmd.extend(['tools/track_memory.sh', track_memory_file])
-  cmd.append('java')
-  if debug:
-    cmd.append('-ea')
-  if profile:
-    cmd.append('-agentlib:hprof=cpu=samples,interval=1,depth=8')
-  cmd.extend(['-jar', utils.D8_JAR])
-  cmd.extend(args)
-  utils.PrintCmd(cmd)
-  result = subprocess.check_output(cmd)
-  print(result)
-  return result
-
-def main():
-  build = True
-  args = []
-  for arg in sys.argv[1:]:
-    if arg in ("--build", "--no-build"):
-      build = arg == "--build"
-    else:
-      args.append(arg)
-  try:
-    run(args, build)
-  except subprocess.CalledProcessError as e:
-    # In case anything relevant was printed to stdout, normally this is already
-    # on stderr.
-    print(e.output)
-    return e.returncode
+import toolhelper
 
 if __name__ == '__main__':
-  sys.exit(main())
+  sys.exit(toolhelper.run('d8', sys.argv[1:]))
diff --git a/tools/d8logger.py b/tools/d8logger.py
new file mode 100755
index 0000000..9fb3508
--- /dev/null
+++ b/tools/d8logger.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# 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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+  sys.exit(toolhelper.run('d8logger', sys.argv[1:]))
diff --git a/tools/dexfilemerger.py b/tools/dexfilemerger.py
new file mode 100755
index 0000000..7bdfc22
--- /dev/null
+++ b/tools/dexfilemerger.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# 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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+  sys.exit(toolhelper.run('dexfilemerger', sys.argv[1:]))
diff --git a/tools/dexsegments.py b/tools/dexsegments.py
new file mode 100755
index 0000000..f984063
--- /dev/null
+++ b/tools/dexsegments.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# 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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+  sys.exit(toolhelper.run('dexsegments', sys.argv[1:]))
diff --git a/tools/dexsplitter.py b/tools/dexsplitter.py
new file mode 100755
index 0000000..415b149
--- /dev/null
+++ b/tools/dexsplitter.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# 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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+  sys.exit(toolhelper.run('dexsplitter', sys.argv[1:]))
diff --git a/tools/disasm.py b/tools/disasm.py
new file mode 100755
index 0000000..0d2599a
--- /dev/null
+++ b/tools/disasm.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# 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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+  sys.exit(toolhelper.run('disasm', sys.argv[1:]))
diff --git a/tools/extractmarker.py b/tools/extractmarker.py
new file mode 100755
index 0000000..36a9c88
--- /dev/null
+++ b/tools/extractmarker.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# 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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+  sys.exit(toolhelper.run('extractmarker', sys.argv[1:]))
diff --git a/tools/golem_build.py b/tools/golem_build.py
new file mode 100755
index 0000000..6d3a9b7
--- /dev/null
+++ b/tools/golem_build.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+# 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.
+
+# Utility script to make it easier to update what golem builds.
+
+import gradle
+import sys
+
+GRADLE_ARGS = ['--no-daemon']
+BUILD_TARGETS = ['R8', 'D8', 'buildExampleJars', 'CompatDx',
+                 'downloadAndroidCts', 'downloadDx']
+
+def Main():
+  gradle.RunGradle(GRADLE_ARGS + BUILD_TARGETS)
+
+if __name__ == '__main__':
+  sys.exit(Main())
diff --git a/tools/jardiff.py b/tools/jardiff.py
new file mode 100755
index 0000000..894131c
--- /dev/null
+++ b/tools/jardiff.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# 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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+  sys.exit(toolhelper.run('jardiff', sys.argv[1:]))
diff --git a/tools/maindex.py b/tools/maindex.py
new file mode 100755
index 0000000..ff5329a
--- /dev/null
+++ b/tools/maindex.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# 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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+  sys.exit(toolhelper.run('maindex', sys.argv[1:]))
diff --git a/tools/minify_tool.py b/tools/minify_tool.py
new file mode 100755
index 0000000..08b2ada
--- /dev/null
+++ b/tools/minify_tool.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python
+# 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.
+
+'''
+Run R8 (with the class-file backend) to optimize a command-line program.
+
+Given an input JAR (default: r8.jar) and a main-class, generates a new input JAR
+with the given main-class in the manifest along with a -keep rule for keeping
+just the main entrypoint, and runs R8 in release+classfile mode on the JAR.
+'''
+
+import argparse
+import os
+import re
+import sys
+import time
+import toolhelper
+import utils
+import zipfile
+
+KEEP = '-keep public class %s { public static void main(...); }\n'
+MANIFEST_PATH = 'META-INF/MANIFEST.MF'
+MANIFEST = 'Manifest-Version: 1.0\nMain-Class: %s\n\n'
+MANIFEST_PATTERN = r'Main-Class:\s*(\S+)'
+RT = os.path.join(utils.REPO_ROOT, 'third_party/openjdk/openjdk-rt-1.8/rt.jar')
+
+parser = argparse.ArgumentParser(description=__doc__.strip(),
+                                 formatter_class=argparse.RawTextHelpFormatter)
+parser.add_argument(
+    '-i', '--input-jar', default=utils.R8_JAR,
+    help='Input JAR to use (default: build/libs/r8.jar)')
+parser.add_argument(
+    '-o', '--output-jar',
+    help='Path to output JAR (default: build/libs/<MainClass>-min.jar)')
+parser.add_argument(
+    '-l', '--lib', default=RT,
+    help='Path to rt.jar to use instead of OpenJDK 1.8')
+parser.add_argument(
+    '-m', '--mainclass',
+    help='Create/overwrite MANIFEST.MF with the given Main-Class')
+parser.add_argument(
+    '-O', '--no-debug', dest='debug', action='store_false',
+    help='Disable assertions when running R8')
+parser.add_argument(
+    '--print-runtimeraw', metavar='BENCHMARKNAME',
+    help='Print "<BENCHMARKNAME>(RunTimeRaw): <elapsed> ms" at the end')
+
+def generate_output_name(input_jar, mainclass):
+  if not mainclass:
+    input_base, input_ext = os.path.splitext(input_jar)
+    return '%s-min%s' % (input_base, input_ext)
+  base = mainclass[mainclass.rindex('.')+1:] if '.' in mainclass else mainclass
+  return os.path.join(utils.LIBS, '%s-min.jar' % base)
+
+def repackage(input_jar, output_jar, mainclass):
+  print("Repackaging %s to %s with Main-Class: %s..." %
+        (input_jar, output_jar, mainclass))
+  manifest = MANIFEST % mainclass
+  with zipfile.ZipFile(input_jar, 'r') as input_zf:
+    with zipfile.ZipFile(output_jar, 'w') as output_zf:
+      for zipinfo in input_zf.infolist():
+        if zipinfo.filename.upper() == MANIFEST_PATH:
+          assert manifest is not None
+          output_zf.writestr(MANIFEST_PATH, manifest)
+          manifest = None
+        else:
+          output_zf.writestr(zipinfo, input_zf.read(zipinfo))
+      if manifest is not None:
+        output_zf.writestr(MANIFEST_PATH, manifest)
+
+def extract_mainclass(input_jar):
+  with zipfile.ZipFile(input_jar, 'r') as input_zf:
+    try:
+      manifest = input_zf.getinfo(MANIFEST_PATH)
+    except KeyError:
+      raise SystemExit('No --mainclass specified and no manifest in input JAR.')
+    mo = re.search(MANIFEST_PATTERN, input_zf.read(manifest))
+    if not mo:
+      raise SystemExit(
+          'No --mainclass specified and no Main-Class in input JAR manifest.')
+    return mo.group(1)
+
+def minify_tool(mainclass=None, input_jar=utils.R8_JAR, output_jar=None, lib=RT,
+                debug=True, build=True, print_runtimeraw=None):
+  if output_jar is None:
+    output_jar = generate_output_name(input_jar, mainclass)
+  with utils.TempDir() as path:
+    if mainclass:
+      tmp_input_path = os.path.join(path, 'input.jar')
+      repackage(input_jar, tmp_input_path, mainclass)
+    else:
+      tmp_input_path = input_jar
+      mainclass = extract_mainclass(input_jar)
+    keep_path = os.path.join(path, 'keep.txt')
+    with open(keep_path, 'w') as fp:
+      fp.write(KEEP % mainclass)
+    args = ('--lib', lib,
+            '--classfile',
+            '--output', output_jar,
+            '--pg-conf', keep_path,
+            '--release',
+            tmp_input_path)
+    start_time = time.time()
+    return_code = toolhelper.run('r8', args, debug=debug, build=build)
+    if print_runtimeraw:
+      elapsed_ms = 1000 * (time.time() - start_time)
+      print('%s-Total(RunTimeRaw): %s ms' % (print_runtimeraw, elapsed_ms))
+    return return_code
+
+if __name__ == '__main__':
+  sys.exit(minify_tool(**vars(parser.parse_args())))
diff --git a/tools/r8.py b/tools/r8.py
index 3ae4da7..60c60a0 100755
--- a/tools/r8.py
+++ b/tools/r8.py
@@ -3,46 +3,8 @@
 # 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.
 
-import gradle
-import os
-import subprocess
 import sys
-import utils
-
-def run(args, build=True, debug=True, profile=False,
-        track_memory_file=None):
-  if build:
-    gradle.RunGradle(['r8'])
-  cmd = []
-  if track_memory_file:
-    cmd.extend(['tools/track_memory.sh', track_memory_file])
-  cmd.append('java')
-  if debug:
-    cmd.append('-ea')
-  if profile:
-    cmd.append('-agentlib:hprof=cpu=samples,interval=1,depth=8')
-  cmd.extend(['-jar', utils.R8_JAR])
-  cmd.extend(args)
-  utils.PrintCmd(cmd)
-  result = subprocess.check_output(cmd)
-  print(result)
-  return result
-
-def main():
-  build = True
-  args = []
-  for arg in sys.argv[1:]:
-    if arg in ("--build", "--no-build"):
-      build = arg == "--build"
-    else:
-      args.append(arg)
-  try:
-    run(args, build)
-  except subprocess.CalledProcessError as e:
-    # In case anything relevant was printed to stdout, normally this is already
-    # on stderr.
-    print(e.output)
-    return e.returncode
+import toolhelper
 
 if __name__ == '__main__':
-  sys.exit(main())
+  sys.exit(toolhelper.run('r8', sys.argv[1:]))
diff --git a/tools/run-d8-on-gmscore.py b/tools/run-d8-on-gmscore.py
index 63865d0..4a64693 100755
--- a/tools/run-d8-on-gmscore.py
+++ b/tools/run-d8-on-gmscore.py
@@ -3,11 +3,11 @@
 # 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.
 
-import d8
 import gmscore_data
 import optparse
 import os
 import sys
+import toolhelper
 
 def ParseOptions():
   result = optparse.OptionParser()
@@ -70,8 +70,13 @@
     with open(options.dump_args_file, 'w') as args_file:
       args_file.writelines([arg + os.linesep for arg in args])
   else:
-    d8.run(args, not options.no_build, not options.no_debug, options.profile,
-           options.track_memory_to_file)
+    toolhelper.run(
+        'd8',
+        args,
+        build=not options.no_build,
+        debug=not options.no_debug,
+        profile=options.profile,
+        track_memory_file=options.track_memory_to_file)
 
 if __name__ == '__main__':
   sys.exit(main())
diff --git a/tools/run_bootstrap_benchmark.py b/tools/run_bootstrap_benchmark.py
new file mode 100755
index 0000000..0c608f9
--- /dev/null
+++ b/tools/run_bootstrap_benchmark.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+# 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.
+
+import argparse
+import minify_tool
+import os
+import sys
+import utils
+
+
+PINNED_R8_JAR = os.path.join(utils.REPO_ROOT, 'third_party/r8/r8.jar')
+
+parser = argparse.ArgumentParser()
+parser.add_argument(
+    '--print-runtimeraw', metavar='BENCHMARKNAME',
+    help='Print "<BENCHMARKNAME>(RunTimeRaw): <elapsed> ms" at the end')
+
+
+if __name__ == '__main__':
+  sys.exit(minify_tool.minify_tool(input_jar=PINNED_R8_JAR, debug=False,
+                                   build=False, **vars(parser.parse_args())))
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index b9bcc60..41340c7 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -10,10 +10,9 @@
 import sys
 import time
 
-import d8
 import gmail_data
 import gmscore_data
-import r8
+import toolhelper
 import utils
 import youtube_data
 
@@ -191,22 +190,20 @@
       if options.print_memoryuse and not options.track_memory_to_file:
         options.track_memory_to_file = os.path.join(temp,
             utils.MEMORY_USE_TMP_FILE)
-      if options.compiler == 'd8':
-        d8.run(args, not options.no_build, not options.no_debug,
-            options.profile, options.track_memory_to_file)
-      else:
-        if app_provided_pg_conf:
-          # Ensure that output of -printmapping and -printseeds go to the output
-          # location and not where the app Proguard configuration places them.
-          if outdir.endswith('.zip') or outdir.endswith('.jar'):
-            pg_outdir = os.path.dirname(outdir)
-          else:
-            pg_outdir = outdir
-          additional_pg_conf = GenerateAdditionalProguardConfiguration(
-              temp, os.path.abspath(pg_outdir))
-          args.extend(['--pg-conf', additional_pg_conf])
-        r8.run(args, not options.no_build, not options.no_debug,
-            options.profile, options.track_memory_to_file)
+      if options.compiler == 'r8' and app_provided_pg_conf:
+        # Ensure that output of -printmapping and -printseeds go to the output
+        # location and not where the app Proguard configuration places them.
+        if outdir.endswith('.zip') or outdir.endswith('.jar'):
+          pg_outdir = os.path.dirname(outdir)
+        else:
+          pg_outdir = outdir
+        additional_pg_conf = GenerateAdditionalProguardConfiguration(
+            temp, os.path.abspath(pg_outdir))
+        args.extend(['--pg-conf', additional_pg_conf])
+      toolhelper.run(options.compiler, args, build=not options.no_build,
+                     debug=not options.no_debug,
+                     profile=options.profile,
+                     track_memory_file=options.track_memory_to_file)
       if options.print_memoryuse:
         print('{}(MemoryUse): {}'
             .format(options.print_memoryuse,
diff --git a/tools/toolhelper.py b/tools/toolhelper.py
new file mode 100644
index 0000000..a7f509c
--- /dev/null
+++ b/tools/toolhelper.py
@@ -0,0 +1,38 @@
+# 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.
+
+import gradle
+import os
+import subprocess
+import sys
+import utils
+
+def run(tool, args, build=None, debug=True,
+        profile=False, track_memory_file=None):
+  if build is None:
+    build, args = extract_build_from_args(args)
+  if build:
+    gradle.RunGradle(['r8'])
+  cmd = []
+  if track_memory_file:
+    cmd.extend(['tools/track_memory.sh', track_memory_file])
+  cmd.append('java')
+  if debug:
+    cmd.append('-ea')
+  if profile:
+    cmd.append('-agentlib:hprof=cpu=samples,interval=1,depth=8')
+  cmd.extend(['-jar', utils.R8_JAR, tool])
+  cmd.extend(args)
+  utils.PrintCmd(cmd)
+  return subprocess.call(cmd)
+
+def extract_build_from_args(input_args):
+  build = True
+  args = []
+  for arg in input_args:
+    if arg in ("--build", "--no-build"):
+      build = arg == "--build"
+    else:
+      args.append(arg)
+  return build, args
diff --git a/tools/utils.py b/tools/utils.py
index a138461..9f6959f 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -15,8 +15,6 @@
 TOOLS_DIR = os.path.abspath(os.path.normpath(os.path.join(__file__, '..')))
 REPO_ROOT = os.path.realpath(os.path.join(TOOLS_DIR, '..'))
 MEMORY_USE_TMP_FILE = 'memory_use.tmp'
-DEX_SEGMENTS_JAR = os.path.join(REPO_ROOT, 'build', 'libs',
-    'dexsegments.jar')
 DEX_SEGMENTS_RESULT_PATTERN = re.compile('- ([^:]+): ([0-9]+)')
 BUILD = os.path.join(REPO_ROOT, 'build')
 LIBS = os.path.join(BUILD, 'libs')
@@ -189,7 +187,7 @@
 # Return a dictionary: {segment_name -> segments_size}
 def getDexSegmentSizes(dex_files):
   assert len(dex_files) > 0
-  cmd = ['java', '-jar', DEX_SEGMENTS_JAR]
+  cmd = ['java', '-jar', R8_JAR, 'dexsegments']
   cmd.extend(dex_files)
   PrintCmd(cmd)
   output = subprocess.check_output(cmd)
@@ -207,6 +205,9 @@
 
   return result
 
+def get_maven_path(version):
+  return os.path.join('com', 'android', 'tools', 'r8', version)
+
 def print_dexsegments(prefix, dex_files):
   for segment_name, size in getDexSegmentSizes(dex_files).items():
     print('{}-{}(CodeSize): {}'