Merge "Allow whitespace between @ and interface in Proguard rules"
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 86ba02f..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 {
@@ -703,20 +566,6 @@
     }
 }
 
-task jardiff(type: Jar) {
-    from sourceSets.main.output
-    baseName 'jardiff'
-    manifest {
-      attributes 'Main-Class': 'com.android.tools.r8.JarDiff'
-    }
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps jardiff
-    if (!project.hasProperty('exclude_deps')) {
-        // Also include dependencies
-        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
-    }
-}
-
 task sourceJar(type: Jar, dependsOn: classes) {
     classifier = 'src'
     from sourceSets.main.allSource
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index aa99407..2d52c64 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);
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/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 8a46d57..dafa35a 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -237,7 +237,7 @@
         Path path,
         OutputMode mode,
         boolean consumeDataResources) {
-      return super.createProgramOutputConsumer(path, mode, true);
+      return super.createProgramOutputConsumer(path, mode, false);
     }
 
     @Override
@@ -701,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 124bccc..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.16-dev";
+  public static final String LABEL = "1.2.17-dev";
 
   private Version() {
   }
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 733f2d9..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
@@ -216,9 +216,7 @@
           // Current stack: ..., value1, value2, value1copy
           state.pop();
           // Output stack: ..., value1, value2
-          throw new Unimplemented(
-              "Building IR for CfStackInstruction " + opcode + " not supported");
-          // break;
+          break;
         }
     }
   }
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 df16622..382bc66 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -17,7 +17,6 @@
 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;
@@ -30,6 +29,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;
@@ -106,7 +106,7 @@
     }
 
     @Override
-    public boolean add(DexAnnotationSetRefList annotationSetRefList) {
+    public boolean add(ParameterAnnotationsList parameterAnnotationsList) {
       return true;
     }
 
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..4767c64 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;
@@ -510,12 +510,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 +546,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) {
@@ -990,7 +995,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 +1077,7 @@
     }
 
     @Override
-    public boolean add(DexAnnotationSetRefList annotationSetRefList) {
+    public boolean add(ParameterAnnotationsList annotationSetRefList) {
       if (annotationSetRefList.isEmpty()) {
         return false;
       }
@@ -1120,7 +1125,7 @@
       return annotationSets.keySet();
     }
 
-    public Collection<DexAnnotationSetRefList> getAnnotationSetRefLists() {
+    public Collection<ParameterAnnotationsList> getAnnotationSetRefLists() {
       return annotationSetRefLists.keySet();
     }
 
@@ -1200,7 +1205,7 @@
       return lookup(annotationSet, annotationSets);
     }
 
-    public int getOffsetFor(DexAnnotationSetRefList annotationSetRefList) {
+    public int getOffsetFor(ParameterAnnotationsList annotationSetRefList) {
       if (annotationSetRefList.isEmpty()) {
         return 0;
       }
@@ -1261,7 +1266,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);
     }
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 31628ad..b36f30c 100644
--- a/src/main/java/com/android/tools/r8/dex/Marker.java
+++ b/src/main/java/com/android/tools/r8/dex/Marker.java
@@ -4,11 +4,12 @@
 package com.android.tools.r8.dex;
 
 import com.android.tools.r8.graph.DexString;
-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.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.
@@ -26,22 +27,17 @@
   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() {
@@ -57,70 +53,58 @@
   }
 
   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(PREFIX + 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.
@@ -142,11 +126,11 @@
 
   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 55bc2db..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,15 +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;
 
@@ -475,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/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 1ac2246..6e93a90 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -5,23 +5,15 @@
 
 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;
@@ -29,7 +21,6 @@
 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;
@@ -209,29 +200,19 @@
   }
 
   @Override
-  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
+  public IRCode buildIR(
+      DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
+      InternalOptions options,
+      Origin origin)
       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);
-    }
-    return internalBuild(encodedMethod, options, null, null, origin);
+    return internalBuild(encodedMethod, appInfo, options, null, null, origin);
   }
 
   @Override
   public IRCode buildInliningIR(
       DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
       InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
@@ -239,11 +220,13 @@
       throws ApiLevelException {
     assert valueNumberGenerator != null;
     assert callerPosition != null;
-    return internalBuild(encodedMethod, options, valueNumberGenerator, callerPosition, origin);
+    return internalBuild(
+        encodedMethod, appInfo, options, valueNumberGenerator, callerPosition, origin);
   }
 
   private IRCode internalBuild(
       DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
       InternalOptions options,
       ValueNumberGenerator generator,
       Position callerPosition,
@@ -254,8 +237,8 @@
     CfSourceCode source = new CfSourceCode(this, encodedMethod, callerPosition, origin);
     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();
   }
 
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 5a4faff..6f86412 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -18,11 +18,15 @@
 public abstract class Code extends CachedHashValueDexItem {
 
   public abstract IRCode buildIR(
-      DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
+      DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
+      InternalOptions options,
+      Origin origin)
       throws ApiLevelException;
 
   public IRCode buildInliningIR(
       DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
       InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
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 aa5d145..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);
       }
     }
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 9ee8fba..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 {
 
@@ -115,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 4bb1314..0000000
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSetRefList.java
+++ /dev/null
@@ -1,72 +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,
-      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;
-  }
-
-  public int getMissingParameterAnnotations() {
-    return missingParameterAnnotations;
-  }
-}
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/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index d9560c8..685f32e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -164,19 +164,20 @@
   }
 
   @Override
-  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
+  public IRCode buildIR(DexEncodedMethod encodedMethod, AppInfo appInfo,
+      InternalOptions options, Origin origin)
       throws ApiLevelException {
     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,
-      InternalOptions options,
+      AppInfo appInfo, InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin)
@@ -187,7 +188,8 @@
             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();
   }
 
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 d514a9a..ee29ff3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -44,9 +44,12 @@
 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 {
@@ -93,7 +96,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;
@@ -102,12 +105,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();
   }
@@ -228,23 +231,25 @@
     compilationState = CompilationState.NOT_PROCESSED;
   }
 
-  public IRCode buildIR(InternalOptions options, Origin origin) throws ApiLevelException {
-    return code == null ? null : code.buildIR(this, options, origin);
+  public IRCode buildIR(
+      AppInfo appInfo, InternalOptions options, Origin origin) throws ApiLevelException {
+    return code == null ? null : code.buildIR(this, appInfo, options, origin);
   }
 
   public IRCode buildInliningIRForTesting(
       InternalOptions options, ValueNumberGenerator valueNumberGenerator)
       throws ApiLevelException {
-    return buildInliningIR(options, valueNumberGenerator, null, Origin.unknown());
+    return buildInliningIR(null, options, valueNumberGenerator, null, Origin.unknown());
   }
 
   public IRCode buildInliningIR(
-      InternalOptions options,
+      AppInfo appInfo, InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin)
       throws ApiLevelException {
-    return code.buildInliningIR(this, options, valueNumberGenerator, callerPosition, origin);
+    return code.buildInliningIR(
+        this, appInfo, options, valueNumberGenerator, callerPosition, origin);
   }
 
   public void setCode(Code code) {
@@ -272,7 +277,7 @@
       code.collectIndexedItems(indexedItems, this.method);
     }
     annotations.collectIndexedItems(indexedItems);
-    parameterAnnotations.collectIndexedItems(indexedItems);
+    parameterAnnotationsList.collectIndexedItems(indexedItems);
   }
 
   @Override
@@ -281,7 +286,7 @@
       code.collectMixedSectionItems(mixedItems);
     }
     annotations.collectMixedSectionItems(mixedItems);
-    parameterAnnotations.collectMixedSectionItems(mixedItems);
+    parameterAnnotationsList.collectMixedSectionItems(mixedItems);
   }
 
   public Code getCode() {
@@ -537,7 +542,7 @@
   }
 
   public boolean hasAnnotation() {
-    return !annotations.isEmpty() || !parameterAnnotations.isEmpty();
+    return !annotations.isEmpty() || !parameterAnnotationsList.isEmpty();
   }
 
   public void registerCodeReferences(UseRegistry registry) {
@@ -564,6 +569,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.
@@ -600,6 +607,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;
@@ -641,6 +657,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;
     }
@@ -698,6 +727,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();
   }
@@ -727,7 +765,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;
@@ -737,7 +775,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/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index b971ab4..86ca12a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -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");
@@ -237,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(
@@ -325,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);
     }
   }
 
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 f005588..6cc2fb4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java
@@ -60,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/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index e403c14..02f0801 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -446,7 +446,7 @@
     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;
@@ -508,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
@@ -573,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()) {
@@ -596,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 {
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 6c414ac..3bd4cde 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -104,18 +104,19 @@
   }
 
   @Override
-  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
+  public IRCode buildIR(DexEncodedMethod encodedMethod, AppInfo appInfo,
+      InternalOptions options, Origin origin)
       throws ApiLevelException {
     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,
-      InternalOptions options,
+      AppInfo appInfo, InternalOptions options,
       ValueNumberGenerator generator,
       Position callerPosition,
       Origin origin)
@@ -123,28 +124,28 @@
     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,
-      InternalOptions options,
+      AppInfo appInfo, InternalOptions options,
       ValueNumberGenerator generator,
       Position callerPosition)
       throws ApiLevelException {
     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,
-      InternalOptions options,
+      AppInfo appInfo, InternalOptions options,
       ValueNumberGenerator generator,
       Position callerPosition)
       throws ApiLevelException {
@@ -155,8 +156,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();
   }
 
@@ -186,15 +187,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.
    */
@@ -202,18 +232,24 @@
 
     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()) {
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index e816bd5..bd6d3f7 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -64,6 +64,7 @@
 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;
@@ -78,6 +79,12 @@
 
 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) {
 
@@ -107,17 +114,34 @@
   @Override
   public CfCode asCfCode() {
     if (code == null) {
+      ReparseContext context = this.context;
       assert context != null;
-      // The SecondVistor is in charge of setting the context to null.
-      DexProgramClass owner = context.owner;
-      ClassReader classReader = new ClassReader(context.classCache);
-      classReader.accept(new ClassCodeVisitor(context, application), ClassReader.EXPAND_FRAMES);
-      assert verifyNoReparseContext(owner);
+      // 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;
@@ -151,21 +175,23 @@
   }
 
   @Override
-  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
+  public IRCode buildIR(DexEncodedMethod encodedMethod, AppInfo appInfo,
+      InternalOptions options, Origin origin)
       throws ApiLevelException {
-    return asCfCode().buildIR(encodedMethod, options, origin);
+    return asCfCode().buildIR(encodedMethod, appInfo, options, origin);
   }
 
   @Override
   public IRCode buildInliningIR(
       DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
       InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin)
       throws ApiLevelException {
     return asCfCode().buildInliningIR(
-        encodedMethod, options, valueNumberGenerator, callerPosition, origin);
+        encodedMethod, appInfo, options, valueNumberGenerator, callerPosition, origin);
   }
 
   @Override
@@ -188,11 +214,14 @@
     private final ReparseContext context;
     private final JarApplicationReader application;
     private int methodIndex = 0;
+    private boolean usrJsrInliner;
 
-    ClassCodeVisitor(ReparseContext context, JarApplicationReader application) {
+    ClassCodeVisitor(
+        ReparseContext context, JarApplicationReader application, boolean useJsrInliner) {
       super(Opcodes.ASM6);
       this.context = context;
       this.application = application;
+      this.usrJsrInliner = useJsrInliner;
     }
 
     @Override
@@ -204,6 +233,9 @@
         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;
@@ -218,6 +250,7 @@
     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;
@@ -579,7 +612,7 @@
           type = ValueType.OBJECT;
           break;
         case Opcodes.RET:
-          throw new Unreachable("RET should be handled by the ASM jsr inliner");
+          throw new JsrEncountered("RET should be handled by the ASM jsr inliner");
         default:
           throw new Unreachable("Unexpected VarInsn opcode: " + opcode);
       }
@@ -668,7 +701,7 @@
             instructions.add(new CfIf(type, ValueType.OBJECT, target));
             break;
           case Opcodes.JSR:
-            throw new Unreachable("JSR should be handled by the ASM jsr inliner");
+            throw new JsrEncountered("JSR should be handled by the ASM jsr inliner");
           default:
             throw new Unreachable("Unexpected JumpInsn opcode: " + opcode);
         }
@@ -780,14 +813,19 @@
     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));
+          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)));
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/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/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/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 1a210b2..c697105 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,6 +8,7 @@
 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;
@@ -295,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.
@@ -315,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;
@@ -1043,7 +1042,7 @@
   public void addInvoke(
       Type type, DexItem item, DexProto callSiteProto, List<Value> arguments, boolean itf)
       throws ApiLevelException {
-    if (type == Invoke.Type.POLYMORPHIC) {
+    if (type == Type.POLYMORPHIC) {
       assert item instanceof DexMethod;
       if (!options.canUseInvokePolymorphic()) {
         throw new ApiLevelException(
@@ -1058,6 +1057,19 @@
             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));
   }
 
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 20cbd10..8477d9b 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
@@ -46,6 +46,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 +88,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 +162,9 @@
       this.identifierNameStringMarker = null;
       this.devirtualizer = null;
     }
+    this.classInliner =
+        (options.enableClassInlining && options.enableInlining && inliner != null)
+            ? new ClassInliner(appInfo.dexItemFactory) : null;
   }
 
   /**
@@ -578,7 +583,7 @@
       feedback.markProcessed(method, Constraint.NEVER);
       return;
     }
-    IRCode code = method.buildIR(options, appInfo.originFor(method.method.holder));
+    IRCode code = method.buildIR(appInfo, options, appInfo.originFor(method.method.holder));
     if (code == null) {
       feedback.markProcessed(method, Constraint.NEVER);
       return;
@@ -686,6 +691,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 +719,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/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/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/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..d9bd4bd 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
@@ -10,7 +10,6 @@
 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 +26,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 +171,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 +187,7 @@
                       | Constants.ACC_BRIDGE,
                   false),
               DexAnnotationSet.empty(),
-              DexAnnotationSetRefList.empty(),
+              ParameterAnnotationsList.empty(),
               new SynthesizedCode(
                   new LambdaBridgeMethodSourceCode(this, mainMethod, bridgeMethod)));
     }
@@ -208,7 +208,7 @@
                     | Constants.ACC_SYNTHETIC,
                 true),
             DexAnnotationSet.empty(),
-            DexAnnotationSetRefList.empty(),
+            ParameterAnnotationsList.empty(),
             new SynthesizedCode(new LambdaConstructorSourceCode(this)));
 
     // Class constructor for stateless lambda classes.
@@ -219,7 +219,7 @@
               MethodAccessFlags.fromSharedAccessFlags(
                   Constants.ACC_SYNTHETIC | Constants.ACC_STATIC, true),
               DexAnnotationSet.empty(),
-              DexAnnotationSetRefList.empty(),
+              ParameterAnnotationsList.empty(),
               new SynthesizedCode(new LambdaClassConstructorSourceCode(this)));
     }
     return methods;
@@ -492,7 +492,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();
@@ -532,7 +532,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/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..90081d1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -0,0 +1,351 @@
+// 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.ApiLevelException;
+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) throws ApiLevelException {
+    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 c2af6ea..59ddc36 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
@@ -56,7 +56,7 @@
       return;
     }
     DexEncodedMethod initializer = clazz.getClassInitializer();
-    IRCode code = initializer.getCode().buildIR(initializer, options, clazz.origin);
+    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 d6efdbb..860cc21 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
@@ -21,6 +21,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 +29,6 @@
 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;
@@ -43,10 +43,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;
@@ -270,7 +271,7 @@
         throws ApiLevelException {
       // Build the IR for a yet not processed method, and perform minimal IR processing.
       Origin origin = appInfo.originFor(target.method.holder);
-      IRCode code = target.buildInliningIR(options, generator, callerPosition, origin);
+      IRCode code = target.buildInliningIR(appInfo, options, generator, callerPosition, origin);
       if (!target.isProcessed()) {
         new LensCodeRewriter(graphLense, appInfo).rewrite(code, target);
       }
@@ -278,7 +279,7 @@
     }
   }
 
-  private int numberOfInstructions(IRCode code) {
+  final int numberOfInstructions(IRCode code) {
     int numOfInstructions = 0;
     for (BasicBlock block : code.blocks) {
       numOfInstructions += block.getInstructions().size();
@@ -364,6 +365,23 @@
     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) throws ApiLevelException {
+
+    ForcedInliningOracle oracle = new ForcedInliningOracle(method, invokesToInline);
+    performInliningImpl(oracle, oracle, method, code);
+  }
+
   public void performInlining(
       DexEncodedMethod method,
       IRCode code,
@@ -371,29 +389,40 @@
       Predicate<DexEncodedMethod> isProcessedConcurrently,
       CallSiteInformation callSiteInformation)
       throws ApiLevelException {
-    int instruction_allowance = 1500;
-    instruction_allowance -= numberOfInstructions(code);
-    if (instruction_allowance < 0) {
-      return;
-    }
-    InliningOracle oracle =
-        new InliningOracle(
+
+    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)
+      throws ApiLevelException {
+    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();
@@ -416,49 +445,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()) {
@@ -481,4 +488,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 beb4caa..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,296 +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().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;
-  }
-
-  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..2b0c7e5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.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.optimize;
+
+import com.android.tools.r8.ApiLevelException;
+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) throws ApiLevelException;
+
+  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 5a257df..9462354 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
@@ -7,11 +7,11 @@
 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 +22,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;
@@ -1003,10 +1004,11 @@
     }
 
     @Override
-    public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
+    public IRCode buildIR(DexEncodedMethod encodedMethod,
+        AppInfo appInfo, InternalOptions options, Origin origin)
         throws ApiLevelException {
       OutlineSourceCode source = new OutlineSourceCode(outline);
-      IRBuilder builder = new IRBuilder(encodedMethod, source, options);
+      IRBuilder builder = new IRBuilder(encodedMethod, appInfo, source, options);
       return builder.build();
     }
 
@@ -1057,7 +1059,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++;
     }
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 bd85615..1284671 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
@@ -93,7 +93,7 @@
     List<DexEncodedField> switchMapFields = Arrays.stream(clazz.staticFields())
         .filter(this::maybeIsSwitchMap).collect(Collectors.toList());
     if (!switchMapFields.isEmpty()) {
-      IRCode initializer = clazz.getClassInitializer().buildIR(options, clazz.origin);
+      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..7d13b1a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -0,0 +1,385 @@
+// 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.ApiLevelException;
+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) throws ApiLevelException;
+  }
+
+  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) throws ApiLevelException {
+
+    // 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) throws ApiLevelException {
+    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..466117f 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
@@ -327,12 +327,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/synthetic/SynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
index b2100e2..f8134f4 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
@@ -6,6 +6,7 @@
 
 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;
@@ -39,9 +40,10 @@
 
   @Override
   public final IRCode buildIR(
-      DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
+      DexEncodedMethod encodedMethod, AppInfo appInfo,
+      InternalOptions options, Origin origin)
       throws ApiLevelException {
-    return new IRBuilder(encodedMethod, sourceCode, options).build();
+    return new IRBuilder(encodedMethod, appInfo, sourceCode, options).build();
   }
 
   @Override
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/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 25df235..a87de4b 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -15,7 +15,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 +551,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);
     }
   }
 
@@ -1239,9 +1242,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 {
@@ -1343,7 +1344,7 @@
 
   private void handleProguardReflectiveBehavior(DexEncodedMethod method) {
     try {
-      IRCode code = method.buildIR(options, appInfo.originFor(method.method.holder));
+      IRCode code = method.buildIR(appInfo, options, appInfo.originFor(method.method.holder));
       code.instructionIterator().forEachRemaining(this::handleProguardReflectiveBehavior);
     } catch (ApiLevelException e) {
       // Ignore this exception here. It will be hit again further in the pipeline when
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 36b5ec7..4a25e42 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -65,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(
@@ -72,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(
@@ -338,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")) {
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/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/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index ac0955e..b27569b 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -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 b973818..62dd4af 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -155,24 +155,27 @@
                 .build());
         break;
       }
-      case R8:
-        {
-          R8Command command =
-              addInputFile(R8Command.builder())
-                  .setOutput(getOutputFile(), outputMode)
-                  .setMode(mode)
-                  .build();
-          ExceptionUtils.withR8CompilationHandler(
-              command.getReporter(),
-              () ->
-                  ToolHelper.runR8(
-                      command,
-                      options -> {
-                        options.lineNumberOptimization = LineNumberOptimization.OFF;
-                        options.enableCfFrontend = frontend == Frontend.CF;
-                      }));
-          break;
-        }
+      case R8: {
+        R8Command command =
+            addInputFile(R8Command.builder())
+                .setOutput(getOutputFile(), outputMode)
+                .setMode(mode)
+                .build();
+        ExceptionUtils.withR8CompilationHandler(
+            command.getReporter(),
+            () ->
+                ToolHelper.runR8(
+                    command,
+                    options -> {
+                      options.lineNumberOptimization = LineNumberOptimization.OFF;
+                      options.enableCfFrontend = frontend == Frontend.CF;
+                      if (output == Output.CF) {
+                        // Class inliner is not supported with CF backend yet.
+                        options.enableClassInlining = false;
+                      }
+                    }));
+        break;
+      }
       default:
         throw new Unreachable();
     }
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/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/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/analysis/type/NullabilityTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
index 443a20e..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
@@ -58,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, Origin.unknown());
+    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 509b922..967b33e 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
@@ -125,7 +125,7 @@
                 new MethodSignature("subtractConstants8bitRegisters", "int", ImmutableList.of()))
             .getMethod();
     try {
-      IRCode irCode = subtract.buildIR(TEST_OPTIONS, Origin.unknown());
+      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);
@@ -143,7 +143,7 @@
             .method(new MethodSignature("fibonacci", "int", ImmutableList.of("int")))
             .getMethod();
     try {
-      IRCode irCode = fib.buildIR(TEST_OPTIONS, Origin.unknown());
+      IRCode irCode = fib.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
       TypeAnalysis analysis = new TypeAnalysis(appInfo, fib, irCode);
       analysis.forEach((v, l) -> {
         assertEither(l, PRIMITIVE, NULL);
@@ -161,7 +161,7 @@
             .method(new MethodSignature("test1", "int[]", ImmutableList.of()))
             .getMethod();
     try {
-      IRCode irCode = test1.buildIR(TEST_OPTIONS, Origin.unknown());
+      IRCode irCode = test1.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
       TypeAnalysis analysis = new TypeAnalysis(appInfo, test1, irCode);
       Value array = null;
       InstructionIterator iterator = irCode.instructionIterator();
@@ -196,7 +196,7 @@
             .method(new MethodSignature("test4", "int[]", ImmutableList.of()))
             .getMethod();
     try {
-      IRCode irCode = test4.buildIR(TEST_OPTIONS, Origin.unknown());
+      IRCode irCode = test4.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
       TypeAnalysis analysis = new TypeAnalysis(appInfo, test4, irCode);
       Value array = null;
       InstructionIterator iterator = irCode.instructionIterator();
@@ -231,7 +231,7 @@
             .method(new MethodSignature("loop2", "void", ImmutableList.of()))
             .getMethod();
     try {
-      IRCode irCode = loop2.buildIR(TEST_OPTIONS, Origin.unknown());
+      IRCode irCode = loop2.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
       TypeAnalysis analysis = new TypeAnalysis(appInfo, loop2, irCode);
       analysis.forEach((v, l) -> {
         if (l.isClassTypeLatticeElement()) {
@@ -254,7 +254,7 @@
             .method(new MethodSignature("test2_throw", "int", ImmutableList.of()))
             .getMethod();
     try {
-      IRCode irCode = test2.buildIR(TEST_OPTIONS, Origin.unknown());
+      IRCode irCode = test2.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
       TypeAnalysis analysis = new TypeAnalysis(appInfo, test2, irCode);
       analysis.forEach((v, l) -> {
         if (l.isClassTypeLatticeElement()) {
@@ -284,7 +284,7 @@
         CheckCast.class, new ClassTypeLatticeElement(test, true),
         NewInstance.class, new ClassTypeLatticeElement(test, false));
     try {
-      IRCode irCode = method.buildIR(TEST_OPTIONS, Origin.unknown());
+      IRCode irCode = method.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
       TypeAnalysis analysis = new TypeAnalysis(appInfo, method, irCode);
       analysis.forEach((v, l) -> verifyTypeEnvironment(expectedLattices, v, l));
     } catch (ApiLevelException e) {
@@ -306,7 +306,7 @@
       InstanceOf.class, PRIMITIVE,
       StaticGet.class, new ClassTypeLatticeElement(test, true));
     try {
-      IRCode irCode = method.buildIR(TEST_OPTIONS, Origin.unknown());
+      IRCode irCode = method.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
       TypeAnalysis analysis = new TypeAnalysis(appInfo, method, irCode);
       analysis.forEach((v, l) -> verifyTypeEnvironment(expectedLattices, v, l));
     } catch (ApiLevelException e) {
diff --git a/src/test/java/com/android/tools/r8/ir/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 fac2ca0..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
@@ -48,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, Origin.unknown());
+    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/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 2ff2f71..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, Origin.unknown());
+        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/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/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/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 28ddd4a..f558ee7 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -1691,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"
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/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/smali/CatchSuccessorFallthroughTest.java b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
index 31abcf1..6b90d88 100644
--- a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
+++ b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
@@ -68,7 +68,7 @@
 
     DexEncodedMethod method = getMethod(originalApplication, methodSig);
     // Get the IR pre-optimization.
-    IRCode code = method.buildIR(new InternalOptions(), Origin.unknown());
+    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/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 76df65e..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]:
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/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 3b80a88..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)