Merge commit 'e96740d1ac9f93e21fd1416fda48c9e69185a0e8' into dev-release
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 36adabe..ebebbf1 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -110,7 +110,7 @@
       return;
     }
     if (command.isPrintVersion()) {
-      Version.printToolVersion("D8");
+      System.out.println("D8 " + Version.getVersionString());
       return;
     }
     InternalOptions options = command.getInternalOptions();
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index ab6796b..19a0118 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -656,8 +656,7 @@
             // TODO(b/130721661): Enable this assert.
             // assert Inliner.verifyNoMethodsInlinedDueToSingleCallSite(appView);
 
-            assert appView.verticallyMergedClasses() == null
-                || appView.verticallyMergedClasses().verifyAllSourcesPruned(appViewWithLiveness);
+            assert appView.allMergedClasses().verifyAllSourcesPruned(appViewWithLiveness);
 
             processWhyAreYouKeepingAndCheckDiscarded(
                 appView.rootSet(),
@@ -886,7 +885,7 @@
       return;
     }
     if (command.isPrintVersion()) {
-      Version.printToolVersion("R8");
+      System.out.println("R8 " + Version.getVersionString());
       return;
     }
     InternalOptions options = command.getInternalOptions();
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 9a9c4cb..918c373 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -16,21 +16,79 @@
   private Version() {
   }
 
-  public static void printToolVersion(String toolName) {
-    System.out.println(toolName + " " + Version.LABEL);
-    System.out.println(VersionProperties.INSTANCE.getDescription());
+  /** Returns current R8 version (with additional info) as a string. */
+  public static String getVersionString() {
+    return LABEL + " (" + VersionProperties.INSTANCE.getDescription() + ")";
   }
 
-  /** Is this a development version of the D8/R8 library. */
-  public static boolean isDev() {
+  /**
+   * Returns the major version number of the compiler.
+   *
+   * @return Major version or -1 for an unreleased build.
+   */
+  public static int getMajorVersion() {
+    if (LABEL.equals("master")) {
+      return -1;
+    }
+    int start = 0;
+    int end = LABEL.indexOf('.');
+    return Integer.parseInt(LABEL.substring(start, end));
+  }
+
+  /**
+   * Returns the minor version number of the compiler.
+   *
+   * @return Minor version or -1 for an unreleased build.
+   */
+  public static int getMinorVersion() {
+    if (LABEL.equals("master")) {
+      return -1;
+    }
+    int start = LABEL.indexOf('.') + 1;
+    int end = LABEL.indexOf('.', start);
+    return Integer.parseInt(LABEL.substring(start, end));
+  }
+
+  /**
+   * Returns the patch version number of the compiler.
+   *
+   * @return Patch version or -1 for an unreleased build.
+   */
+  public static int getPatchVersion() {
+    if (LABEL.equals("master")) {
+      return -1;
+    }
+    int skip = LABEL.indexOf('.') + 1;
+    int start = LABEL.indexOf('.', skip) + 1;
+    int end = LABEL.indexOf('.', start);
+    return Integer.parseInt(LABEL.substring(start, end));
+  }
+
+  /**
+   * Returns the pre-release version information of the compiler.
+   *
+   * @return Pre-release information if present, the empty string if absent, and null for an
+   *     unreleased build.
+   */
+  public static String getPreReleaseString() {
+    if (LABEL.equals("master")) {
+      return null;
+    }
+    int start = LABEL.indexOf('-') + 1;
+    if (start > 0) {
+      return LABEL.substring(start);
+    }
+    return "";
+  }
+
+  /**
+   * Is this a development version of the D8/R8 library.
+   *
+   * @return True if the build is not a release or if it is a development release.
+   */
+  public static boolean isDevelopmentVersion() {
     return LABEL.equals("master")
         || LABEL.endsWith("-dev")
         || VersionProperties.INSTANCE.isEngineering();
   }
-
-  /** Returns current R8 version (with additional info) as a string. */
-  @SuppressWarnings("unused") // used by external tools to obtain R8 version
-  public static String getVersionString() {
-    return LABEL + " (" + VersionProperties.INSTANCE.getDescription() + ")";
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
index 68e9e69..a52c2ea 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -326,7 +326,7 @@
       return;
     }
     if (dexArgs.version) {
-      Version.printToolVersion("CompatDx");
+      System.out.println("CompatDx " + Version.getVersionString());
       return;
     }
     CompilationMode mode = CompilationMode.RELEASE;
diff --git a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
index 514f785..177f2ae 100644
--- a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
+++ b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
@@ -173,7 +173,7 @@
   }
 
   private static void printVersion() {
-    Version.printToolVersion("CompatProguard");
+    System.out.println("CompatProguard " + Version.getVersionString());
   }
 
   private static void printHelp() {
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 6eb6a94..3b51579 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -535,9 +535,12 @@
   public DexEncodedField resolveFieldOn(DexType type, DexField desc) {
     assert checkIfObsolete();
     DexClass holder = definitionFor(type);
-    if (holder == null) {
-      return null;
-    }
+    return holder != null ? resolveFieldOn(holder, desc) : null;
+  }
+
+  public DexEncodedField resolveFieldOn(DexClass holder, DexField desc) {
+    assert checkIfObsolete();
+    assert holder != null;
     // Step 1: Class declares the field.
     DexEncodedField result = holder.lookupField(desc);
     if (result != null) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index a2d6967..99a2a2b 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -5,6 +5,9 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis.InitializedClassesInInstanceMethods;
+import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses;
+import com.android.tools.r8.graph.classmerging.MergedClassesCollection;
+import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
 import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
 import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteShrinker;
@@ -14,7 +17,6 @@
 import com.android.tools.r8.ir.optimize.CallSiteOptimizationInfoPropagator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
-import com.android.tools.r8.shaking.VerticalClassMerger.VerticallyMergedClasses;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OptionalBool;
 import com.google.common.base.Predicates;
@@ -52,6 +54,7 @@
   private Predicate<DexType> classesEscapingIntoLibrary = Predicates.alwaysTrue();
   private InitializedClassesInInstanceMethods initializedClassesInInstanceMethods;
   private Set<DexMethod> unneededVisibilityBridgeMethods = ImmutableSet.of();
+  private HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses;
   private VerticallyMergedClasses verticallyMergedClasses;
 
   private AppView(
@@ -296,6 +299,28 @@
     this.unneededVisibilityBridgeMethods = unneededVisibilityBridgeMethods;
   }
 
+  public MergedClassesCollection allMergedClasses() {
+    MergedClassesCollection collection = new MergedClassesCollection();
+    if (horizontallyMergedLambdaClasses != null) {
+      collection.add(horizontallyMergedLambdaClasses);
+    }
+    if (verticallyMergedClasses != null) {
+      collection.add(verticallyMergedClasses);
+    }
+    return collection;
+  }
+
+  // Get the result of horizontal lambda class merging. Returns null if horizontal lambda class
+  // merging has not been run.
+  public HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses() {
+    return horizontallyMergedLambdaClasses;
+  }
+
+  public void setHorizontallyMergedLambdaClasses(
+      HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses) {
+    this.horizontallyMergedLambdaClasses = horizontallyMergedLambdaClasses;
+  }
+
   // Get the result of vertical class merging. Returns null if vertical class merging has not been
   // run.
   public VerticallyMergedClasses verticallyMergedClasses() {
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index eafa809..e2adfbb 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -107,6 +107,7 @@
     ps.println("#");
     ps.println("# Method: '" + methodName + "':");
     writeAnnotations(method.annotations, ps);
+    ps.println("# " + method.accessFlags);
     ps.println("#");
     ps.println();
     Code code = method.getCode();
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 47930c2..5a7e785 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1092,15 +1092,6 @@
     return m1.method.slowCompareTo(m2.method);
   }
 
-  public static class ClassInlinerEligibility {
-
-    public final boolean returnsReceiver;
-
-    public ClassInlinerEligibility(boolean returnsReceiver) {
-      this.returnsReceiver = returnsReceiver;
-    }
-  }
-
   public MethodOptimizationInfo getOptimizationInfo() {
     checkIfObsolete();
     return optimizationInfo;
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 523fd85..488b6c7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -34,7 +34,8 @@
   private String toStringCache = null;
 
   DexType(DexString descriptor) {
-    assert !descriptor.toString().contains(".");
+    assert !descriptor.toString().contains(".")
+        : "Malformed descriptor: " + descriptor.toString();
     this.descriptor = descriptor;
   }
 
@@ -180,12 +181,7 @@
   }
 
   public boolean isPrimitiveType() {
-    return isPrimitiveType((char) descriptor.content[0]);
-  }
-
-  private boolean isPrimitiveType(char c) {
-    return c == 'Z' || c == 'B' || c == 'S' || c == 'C' || c == 'I' || c == 'F' || c == 'J'
-        || c == 'D';
+    return DescriptorUtils.isPrimitiveType((char) descriptor.content[0]);
   }
 
   public boolean isVoidType() {
@@ -244,7 +240,7 @@
     if (!isArrayType()) {
       return false;
     }
-    return isPrimitiveType((char) descriptor.content[1]);
+    return DescriptorUtils.isPrimitiveType((char) descriptor.content[1]);
   }
 
   public boolean isWideType() {
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java b/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java
new file mode 100644
index 0000000..1facc45
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2019, 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.classmerging;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Set;
+
+public class HorizontallyMergedLambdaClasses implements MergedClasses {
+
+  private final Set<DexType> sources;
+
+  public HorizontallyMergedLambdaClasses(Set<DexType> sources) {
+    this.sources = sources;
+  }
+
+  @Override
+  public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) {
+    for (DexType source : sources) {
+      assert appView.appInfo().wasPruned(source)
+          : "Expected horizontally merged lambda class `"
+              + source.toSourceString()
+              + "` to be absent";
+    }
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/MergedClasses.java b/src/main/java/com/android/tools/r8/graph/classmerging/MergedClasses.java
new file mode 100644
index 0000000..7b9bba3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/classmerging/MergedClasses.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2019, 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.classmerging;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public interface MergedClasses {
+
+  boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView);
+}
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/MergedClassesCollection.java b/src/main/java/com/android/tools/r8/graph/classmerging/MergedClassesCollection.java
new file mode 100644
index 0000000..ad7c71d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/classmerging/MergedClassesCollection.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2019, 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.classmerging;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.ArrayList;
+import java.util.List;
+
+public class MergedClassesCollection implements MergedClasses {
+
+  private List<MergedClasses> collection = new ArrayList<>();
+
+  public void add(MergedClasses mergedClasses) {
+    collection.add(mergedClasses);
+  }
+
+  @Override
+  public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) {
+    for (MergedClasses mergedClasses : collection) {
+      assert mergedClasses.verifyAllSourcesPruned(appView);
+    }
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java b/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java
new file mode 100644
index 0000000..237f9ab
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2019, 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.classmerging;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class VerticallyMergedClasses implements MergedClasses {
+
+  private final Map<DexType, DexType> mergedClasses;
+  private final Map<DexType, List<DexType>> sources;
+
+  public VerticallyMergedClasses(Map<DexType, DexType> mergedClasses) {
+    Map<DexType, List<DexType>> sources = Maps.newIdentityHashMap();
+    mergedClasses.forEach(
+        (source, target) -> sources.computeIfAbsent(target, key -> new ArrayList<>()).add(source));
+    this.mergedClasses = mergedClasses;
+    this.sources = sources;
+  }
+
+  public List<DexType> getSourcesFor(DexType type) {
+    return sources.getOrDefault(type, ImmutableList.of());
+  }
+
+  public DexType getTargetFor(DexType type) {
+    assert mergedClasses.containsKey(type);
+    return mergedClasses.get(type);
+  }
+
+  public boolean hasBeenMergedIntoSubtype(DexType type) {
+    return mergedClasses.containsKey(type);
+  }
+
+  @Override
+  public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) {
+    for (List<DexType> sourcesForTarget : sources.values()) {
+      for (DexType source : sourcesForTarget) {
+        assert appView.appInfo().wasPruned(source)
+            : "Expected vertically merged class `" + source.toSourceString() + "` to be absent";
+      }
+    }
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
index d9f4e2d..0605623 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
@@ -109,7 +109,7 @@
       if (isConstantArrayThroughoutMethod(root, assumedNotToDependOnEnvironment)) {
         return false;
       }
-      if (root.getAbstractValue(appView).isSingleEnumValue()) {
+      if (root.getAbstractValue(appView, context).isSingleEnumValue()) {
         return false;
       }
       if (isNewInstanceWithoutEnvironmentDependentFields(root, assumedNotToDependOnEnvironment)) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
index 8e327aa..4adc57e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
@@ -83,6 +83,11 @@
     return new ClassTypeLatticeElement(type, nullability, lazyInterfaces, variants, appView);
   }
 
+  public boolean isRelatedTo(ClassTypeLatticeElement other, AppView<?> appView) {
+    return lessThanOrEqualUpToNullability(other, appView)
+        || other.lessThanOrEqualUpToNullability(this, appView);
+  }
+
   @Override
   public ReferenceTypeLatticeElement getOrCreateVariant(Nullability nullability) {
     ClassTypeLatticeElement variant = variants.get(nullability);
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index a4c8d4b..b2273e9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -559,13 +559,30 @@
   }
 
   public int numberInstructions(int nextInstructionNumber) {
+    return numberInstructions(nextInstructionNumber, INSTRUCTION_NUMBER_DELTA);
+  }
+
+  public int numberInstructions(int nextInstructionNumber, int increment) {
     for (Instruction instruction : instructions) {
       instruction.setNumber(nextInstructionNumber);
-      nextInstructionNumber += INSTRUCTION_NUMBER_DELTA;
+      nextInstructionNumber += increment;
     }
     return nextInstructionNumber;
   }
 
+  public void clearInstructionNumbers() {
+    for (Instruction instruction : instructions) {
+      instruction.clearNumber();
+    }
+  }
+
+  public boolean hasNoInstructionNumbers() {
+    for (Instruction instruction : instructions) {
+      assert instruction.getNumber() == -1;
+    }
+    return true;
+  }
+
   public LinkedList<Instruction> getInstructions() {
     return instructions;
   }
@@ -1881,7 +1898,7 @@
    * {@code target} is the same block than the current {@link BasicBlock}.
    */
   public boolean hasPathTo(BasicBlock target) {
-    List<BasicBlock> visitedBlocks = new ArrayList<>();
+    Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
     ArrayDeque<BasicBlock> blocks = new ArrayDeque<>();
     blocks.push(this);
 
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 8740a9e..805aad9 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
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.analysis.TypeChecker;
 import com.android.tools.r8.ir.analysis.ValueMayDependOnEnvironmentAnalysis;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
@@ -17,7 +18,6 @@
 import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.shaking.VerticalClassMerger.VerticallyMergedClasses;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.DequeUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -958,6 +958,25 @@
     return blocks;
   }
 
+  public void numberInstructionsPerBlock() {
+    for (BasicBlock block : blocks) {
+      block.numberInstructions(0, 1);
+    }
+  }
+
+  public void clearInstructionNumbers() {
+    for (BasicBlock block : blocks) {
+      block.clearInstructionNumbers();
+    }
+  }
+
+  public boolean hasNoInstructionNumbers() {
+    for (BasicBlock block : blocks) {
+      assert block.hasNoInstructionNumbers();
+    }
+    return true;
+  }
+
   public int numberRemainingInstructions() {
     for (Instruction instruction : instructions()) {
       if (instruction.getNumber() == -1) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
index 3fc4de0..f6054e1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
@@ -201,4 +201,9 @@
   public boolean mayHaveStringSwitch() {
     return get(Opcodes.STRING_SWITCH);
   }
+
+  public boolean mayHaveArithmeticOrLogicalBinop() {
+    // TODO(b/7145202413): Implement this.
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 228dc2b..6893464 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -362,6 +362,10 @@
     this.number = number;
   }
 
+  public void clearNumber() {
+    this.number = -1;
+  }
+
   /**
    * Compare equality of two class-equivalent instructions modulo their values and positions.
    */
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index c7aeff1..f9ca31d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
@@ -847,7 +846,7 @@
     return definition.isOutConstant() && !hasLocalInfo();
   }
 
-  public AbstractValue getAbstractValue(AppView<?> appView) {
+  public AbstractValue getAbstractValue(AppView<?> appView, DexType context) {
     if (!appView.enableWholeProgramOptimizations()) {
       return UnknownValue.getInstance();
     }
@@ -857,15 +856,7 @@
       return UnknownValue.getInstance();
     }
 
-    if (root.definition.isFieldGet()) {
-      FieldInstruction fieldGet = root.definition.asFieldInstruction();
-      DexEncodedField field = appView.appInfo().resolveField(fieldGet.getField());
-      if (field != null) {
-        return field.getOptimizationInfo().getAbstractValue();
-      }
-    }
-
-    return UnknownValue.getInstance();
+    return root.definition.getAbstractValue(appView, context);
   }
 
   public boolean isPhi() {
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 7a64973..fd1b111 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
@@ -1106,7 +1106,7 @@
     }
 
     if (lambdaMerger != null) {
-      lambdaMerger.rewriteCode(method, code);
+      lambdaMerger.rewriteCode(method, code, inliner);
       assert code.isConsistentSSA();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 3092293..21f800c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -30,6 +30,7 @@
 import com.android.tools.r8.graph.GraphLense.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.GraphLense.RewrittenPrototypeDescription.RemovedArgumentsInfo;
 import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
+import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -61,7 +62,6 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.logging.Log;
-import com.android.tools.r8.shaking.VerticalClassMerger.VerticallyMergedClasses;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.HashSet;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 6e96719..0f6bd4f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -6,12 +6,12 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -57,7 +57,7 @@
 
   void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
 
-  void setClassInlinerEligibility(DexEncodedMethod method, ClassInlinerEligibility eligibility);
+  void setClassInlinerEligibility(DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility);
 
   void setInitializerInfo(DexEncodedMethod method, InitializerInfo info);
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index 4da2dcd..49b11a5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -91,12 +91,18 @@
           if (!resolutionResult.isValidVirtualTarget(appView.options())) {
             continue;
           }
+          // If the resolution ended up with a single target, check if it is a library override.
+          // And if so, bail out early (to avoid expensive target lookup).
+          if (resolutionResult.hasSingleTarget()
+              && isLibraryMethodOrLibraryMethodOverride(resolutionResult.getSingleTarget())) {
+            continue;
+          }
         }
         Collection<DexEncodedMethod> targets = invoke.lookupTargets(appView, context.method.holder);
         assert invoke.isInvokeMethodWithDynamicDispatch()
             // For other invocation types, the size of targets should be at most one.
             || targets == null || targets.size() <= 1;
-        if (targets == null || targets.isEmpty()) {
+        if (targets == null || targets.isEmpty() || hasLibraryOverrides(targets)) {
           continue;
         }
         for (DexEncodedMethod target : targets) {
@@ -110,7 +116,7 @@
         Collection<DexEncodedMethod> targets =
             appView.appInfo().lookupLambdaImplementedMethods(
                 instruction.asInvokeCustom().getCallSite());
-        if (targets == null) {
+        if (targets == null || targets.isEmpty() || hasLibraryOverrides(targets)) {
           continue;
         }
         for (DexEncodedMethod target : targets) {
@@ -120,6 +126,30 @@
     }
   }
 
+  // TODO(b/140204899): Instead of reprocessing here, pass stopping criteria to lookup?
+  // If any of target method is a library method override, bail out entirely/early.
+  private boolean hasLibraryOverrides(Collection<DexEncodedMethod> targets) {
+    for (DexEncodedMethod target : targets) {
+      if (isLibraryMethodOrLibraryMethodOverride(target)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private boolean isLibraryMethodOrLibraryMethodOverride(DexEncodedMethod target) {
+    // Not a program method.
+    if (!target.isProgramMethod(appView)) {
+      return true;
+    }
+    // If the method overrides a library method, it is unsure how the method would be invoked by
+    // that library.
+    if (target.isLibraryMethodOverride().isTrue()) {
+      return true;
+    }
+    return false;
+  }
+
   // Record arguments for the given method if necessary.
   // At the same time, if it decides to bail out, make the corresponding info immutable so that we
   // can avoid recording arguments for the same method accidentally.
@@ -142,9 +172,19 @@
     if (appView.appInfo().isPinned(target.method)) {
       return CallSiteOptimizationInfo.TOP;
     }
+    // Not a program method.
+    if (!target.isProgramMethod(appView)) {
+      // But, should not be reachable, since we already bail out.
+      assert false
+          : "Trying to compute call site optimization info for " + target.toSourceString();
+      return CallSiteOptimizationInfo.TOP;
+    }
     // If the method overrides a library method, it is unsure how the method would be invoked by
     // that library.
     if (target.isLibraryMethodOverride().isTrue()) {
+      // But, should not be reachable, since we already bail out.
+      assert false
+          : "Trying to compute call site optimization info for " + target.toSourceString();
       return CallSiteOptimizationInfo.TOP;
     }
     // If the program already has illegal accesses, method resolution results will reflect that too.
@@ -165,11 +205,9 @@
       return;
     }
     // TODO(b/139246447): Assert no BOTTOM left.
-    if (!callSiteOptimizationInfo.isConcreteCallSiteOptimizationInfo()) {
+    if (!callSiteOptimizationInfo.hasUsefulOptimizationInfo(appView, code.method)) {
       return;
     }
-    assert callSiteOptimizationInfo.asConcreteCallSiteOptimizationInfo()
-        .hasUsefulOptimizationInfo(appView, code.method);
     Set<Value> affectedValues = Sets.newIdentityHashSet();
     List<Assume<?>> assumeInstructions = new LinkedList<>();
     List<Instruction> constants = new LinkedList<>();
@@ -264,17 +302,16 @@
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       for (DexEncodedMethod method : clazz.methods()) {
         assert !method.isObsolete();
-        if (method.shouldNotHaveCode()) {
-          assert !method.hasCode();
+        if (method.shouldNotHaveCode()
+            || !method.hasCode()
+            || method.getCode().isEmptyVoidMethod()) {
           continue;
         }
         // TODO(b/139246447): Assert no BOTTOM left.
         CallSiteOptimizationInfo callSiteOptimizationInfo = method.getCallSiteOptimizationInfo();
-        if (!callSiteOptimizationInfo.isConcreteCallSiteOptimizationInfo()) {
+        if (!callSiteOptimizationInfo.hasUsefulOptimizationInfo(appView, method)) {
           continue;
         }
-        assert callSiteOptimizationInfo.asConcreteCallSiteOptimizationInfo()
-            .hasUsefulOptimizationInfo(appView, method);
         targetsToRevisit.add(method);
         if (appView.options().testing.callSiteOptimizationInfoInspector != null) {
           appView.options().testing.callSiteOptimizationInfoInspector.accept(method);
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 e1c583d..d48d1f2 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
@@ -1635,28 +1635,61 @@
   }
 
   /**
-   * If an instruction is known to be a /lit8 or /lit16 instruction, update the instruction to use
-   * its own constant that will be defined just before the instruction. This transformation allows
-   * to decrease pressure on register allocation by defining the shortest range of constant used
-   * by this kind of instruction. D8 knowns at build time that constant will be encoded
-   * directly into the final Dex instruction.
+   * If an instruction is known to be a binop/lit8 or binop//lit16 instruction, update the
+   * instruction to use its own constant that will be defined just before the instruction. This
+   * transformation allows to decrease pressure on register allocation by defining the shortest
+   * range of constant used by this kind of instruction. D8 knowns at build time that constant will
+   * be encoded directly into the final Dex instruction.
    */
   public void useDedicatedConstantForLitInstruction(IRCode code) {
+    if (!code.metadata().mayHaveArithmeticOrLogicalBinop()) {
+      return;
+    }
+
+    code.numberInstructionsPerBlock();
+
     for (BasicBlock block : code.blocks) {
       InstructionListIterator instructionIterator = block.listIterator(code);
+      // Collect all the non constant in values for binop/lit8 or binop/lit16 instructions.
+      Set<Value> binopsWithLit8OrLit16NonConstantValues = Sets.newIdentityHashSet();
       while (instructionIterator.hasNext()) {
         Instruction currentInstruction = instructionIterator.next();
-        if (shouldBeLitInstruction(currentInstruction)) {
-          assert currentInstruction.isBinop();
-          Binop binop = currentInstruction.asBinop();
-          Value constValue;
-          if (binop.leftValue().isConstNumber()) {
-            constValue = binop.leftValue();
-          } else if (binop.rightValue().isConstNumber()) {
-            constValue = binop.rightValue();
-          } else {
-            throw new Unreachable();
+        if (!isBinopWithLit8OrLit16(currentInstruction)) {
+          continue;
+        }
+        Value value = binopWithLit8OrLit16NonConstant(currentInstruction.asBinop());
+        assert value != null;
+        binopsWithLit8OrLit16NonConstantValues.add(value);
+      }
+      if (binopsWithLit8OrLit16NonConstantValues.isEmpty()) {
+        continue;
+      }
+      // Find last use in block of all the non constant in values for binop/lit8 or binop/lit16
+      // instructions.
+      Reference2IntMap<Value> lastUseOfBinopsWithLit8OrLit16NonConstantValues =
+          new Reference2IntOpenHashMap<>();
+      lastUseOfBinopsWithLit8OrLit16NonConstantValues.defaultReturnValue(-1);
+      while (instructionIterator.hasPrevious()) {
+        Instruction currentInstruction = instructionIterator.previous();
+        for (Value value : Iterables.concat(currentInstruction.inValues(), currentInstruction.getDebugValues())) {
+          if (!binopsWithLit8OrLit16NonConstantValues.contains(value)) {
+            continue;
           }
+          if (!lastUseOfBinopsWithLit8OrLit16NonConstantValues.containsKey(value)) {
+            lastUseOfBinopsWithLit8OrLit16NonConstantValues.put(
+                value, currentInstruction.getNumber());
+          }
+        }
+      }
+      // Do the transformation except if the binop can use the binop/2addr format.
+      while (instructionIterator.hasNext()) {
+        Instruction currentInstruction = instructionIterator.next();
+        if (!isBinopWithLit8OrLit16(currentInstruction)) {
+          continue;
+        }
+        Binop binop = currentInstruction.asBinop();
+        if (!canBe2AddrInstruction(binop, lastUseOfBinopsWithLit8OrLit16NonConstantValues)) {
+          Value constValue = binopWithLit8OrLit16Constant(currentInstruction);
           if (constValue.numberOfAllUsers() > 1) {
             // No need to do the transformation if the const value is already used only one time.
             ConstNumber newConstant = ConstNumber
@@ -1673,24 +1706,48 @@
       }
     }
 
+    code.clearInstructionNumbers();
+    assert code.hasNoInstructionNumbers();
+
     assert code.isConsistentSSA();
   }
 
-  /**
-   * A /lit8 or /lit16 instruction only concerns arithmetic or logical instruction. /lit8 or /lit16
-   * instructions generate bigger code than 2addr instructions, thus we favor 2addr instructions
-   * rather than /lit8 or /lit16 instructions.
-   */
-  private static boolean shouldBeLitInstruction(Instruction instruction) {
-    if (instruction.isArithmeticBinop() || instruction.isLogicalBinop()) {
-      Binop binop = instruction.asBinop();
-      if (!binop.needsValueInRegister(binop.leftValue()) ||
-          !binop.needsValueInRegister(binop.rightValue())) {
-        return !canBe2AddrInstruction(binop);
-      }
+  // Check if a binop can be represented in the binop/lit8 or binop/lit16 form.
+  private static boolean isBinopWithLit8OrLit16(Instruction instruction) {
+    if (!instruction.isArithmeticBinop() && !instruction.isLogicalBinop()) {
+      return false;
     }
+    Binop binop = instruction.asBinop();
+    // If one of the values does not need a register it is implicitly a binop/lit8 or binop/lit16.
+    boolean result =
+        !binop.needsValueInRegister(binop.leftValue())
+            || !binop.needsValueInRegister(binop.rightValue());
+    assert !result || binop.leftValue().isConstNumber() || binop.rightValue().isConstNumber();
+    return result;
+  }
 
-    return false;
+  // Return the constant in-value of a binop/lit8 or binop/lit16 instruction.
+  private static Value binopWithLit8OrLit16Constant(Instruction instruction) {
+    assert isBinopWithLit8OrLit16(instruction);
+    Binop binop = instruction.asBinop();
+    if (binop.leftValue().isConstNumber()) {
+      return binop.leftValue();
+    } else if (binop.rightValue().isConstNumber()) {
+      return binop.rightValue();
+    } else {
+      throw new Unreachable();
+    }
+  }
+
+  // Return the non-constant in-value of a binop/lit8 or binop/lit16 instruction.
+  private static Value binopWithLit8OrLit16NonConstant(Binop binop) {
+    if (binop.leftValue().isConstNumber()) {
+      return binop.rightValue();
+    } else if (binop.rightValue().isConstNumber()) {
+      return binop.leftValue();
+    } else {
+      throw new Unreachable();
+    }
   }
 
   /**
@@ -1698,28 +1755,28 @@
    * argument is no longer needed after the binary operation and can be overwritten. That is
    * definitely the case if there is no path between the binary operation and all other usages.
    */
-  private static boolean canBe2AddrInstruction(Binop binop) {
-    Value value = null;
-    if (binop.needsValueInRegister(binop.leftValue())) {
-      value = binop.leftValue();
-    } else if (binop.isCommutative() && binop.needsValueInRegister(binop.rightValue())) {
-      value = binop.rightValue();
+  private static boolean canBe2AddrInstruction(
+      Binop binop, Reference2IntMap<Value> lastUseOfRelevantValue) {
+    Value value = binopWithLit8OrLit16NonConstant(binop);
+    assert value != null;
+    int number = lastUseOfRelevantValue.getInt(value);
+    if (number > 0 && number > binop.getNumber()) {
+      return false;
+    }
+    Iterable<Instruction> users =
+        value.debugUsers() != null
+            ? Iterables.concat(value.uniqueUsers(), value.debugUsers())
+            : value.uniqueUsers();
+
+    for (Instruction user : users) {
+      if (hasPath(binop, user)) {
+        return false;
+      }
     }
 
-    if (value != null) {
-      Iterable<Instruction> users = value.debugUsers() != null ?
-          Iterables.concat(value.uniqueUsers(), value.debugUsers()) : value.uniqueUsers();
-
-      for (Instruction user : users) {
-        if (hasPath(binop, user)) {
-          return false;
-        }
-      }
-
-      for (Phi user : value.uniquePhiUsers()) {
-        if (binop.getBlock().hasPathTo(user.getBlock())) {
-          return false;
-        }
+    for (Phi user : value.uniquePhiUsers()) {
+      if (binop.getBlock().hasPathTo(user.getBlock())) {
+        return false;
       }
     }
 
@@ -1734,8 +1791,10 @@
     BasicBlock sourceBlock = source.getBlock();
     BasicBlock targetBlock = target.getBlock();
     if (sourceBlock == targetBlock) {
-      return sourceBlock.getInstructions().indexOf(source) <
-          targetBlock.getInstructions().indexOf(target);
+      // Instructions must be numbered when getting here.
+      assert source.getNumber() != -1;
+      assert target.getNumber() != -1;
+      return source.getNumber() < target.getNumber();
     }
 
     return source.getBlock().hasPathTo(targetBlock);
@@ -2542,9 +2601,10 @@
               }
             }
           } else {
-            AbstractValue abstractValue = lhs.getAbstractValue(appView);
+            DexType context = code.method.method.holder;
+            AbstractValue abstractValue = lhs.getAbstractValue(appView, context);
             if (abstractValue.isSingleEnumValue()) {
-              AbstractValue otherAbstractValue = rhs.getAbstractValue(appView);
+              AbstractValue otherAbstractValue = rhs.getAbstractValue(appView, context);
               if (abstractValue == otherAbstractValue) {
                 simplifyIfWithKnownCondition(code, block, theIf, 0);
               } else if (otherAbstractValue.isSingleEnumValue()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index f53d6d8..1382317 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -131,7 +131,7 @@
       }
       // Only canonicalize enum values. This is only OK if the instruction cannot have side effects.
       if (current.isStaticGet()) {
-        if (!current.outValue().getAbstractValue(appView).isSingleEnumValue()) {
+        if (!current.outValue().getAbstractValue(appView, context).isSingleEnumValue()) {
           continue;
         }
         if (current.instructionMayHaveSideEffects(appView, context)) {
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 c3fe726..6a9c3af 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
@@ -812,6 +812,14 @@
   public void performForcedInlining(
       DexEncodedMethod method,
       IRCode code,
+      Map<? extends InvokeMethod, InliningInfo> invokesToInline) {
+    performForcedInlining(
+        method, code, invokesToInline, new InliningIRProvider(appView, method, code));
+  }
+
+  public void performForcedInlining(
+      DexEncodedMethod method,
+      IRCode code,
       Map<? extends InvokeMethod, InliningInfo> invokesToInline,
       InliningIRProvider inliningIRProvider) {
     ForcedInliningOracle oracle = new ForcedInliningOracle(appView, method, invokesToInline);
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
index 2945c6e..ddb7e99 100644
--- 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
@@ -220,16 +220,25 @@
           continue;
         }
 
+        assert processor.getReceivers().verifyReceiverSetsAreDisjoint();
+
         // Is inlining allowed.
-        if (processor.getEstimatedCombinedSizeForInlining()
-            >= appView.options().classInliningInstructionAllowance) {
+        InliningIRProvider inliningIRProvider = new InliningIRProvider(appView, method, code);
+        ClassInlinerCostAnalysis costAnalysis =
+            new ClassInlinerCostAnalysis(
+                appView, inliningIRProvider, processor.getReceivers().getDefiniteReceiverAliases());
+        if (costAnalysis.willExceedInstructionBudget(
+            code, processor.getDirectInlinees(), processor.getIndirectInlinees())) {
+          // This root is unlikely to be inlined in the future.
+          rootsIterator.remove();
           continue;
         }
 
         // Inline the class instance.
-        InliningIRProvider inliningIRProvider = new InliningIRProvider(appView, method, code);
         anyInlinedMethods |= processor.processInlining(code, defaultOracle, inliningIRProvider);
 
+        assert inliningIRProvider.verifyIRCacheIsEmpty();
+
         // Restore normality.
         Set<Value> affectedValues = Sets.newIdentityHashSet();
         code.removeAllTrivialPhis(affectedValues);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
new file mode 100644
index 0000000..9b9670e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
@@ -0,0 +1,149 @@
+// Copyright (c) 2019, 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 com.android.tools.r8.ir.code.Opcodes.ARGUMENT;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
+import static com.android.tools.r8.ir.code.Opcodes.RETURN;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
+import com.google.common.collect.Sets;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** Analysis that estimates the cost of class inlining an object allocation. */
+class ClassInlinerCostAnalysis {
+
+  private final AppView<?> appView;
+  private final InliningIRProvider inliningIRProvider;
+  private final Set<Value> definiteReceiverAliases;
+
+  private int estimatedCost = 0;
+
+  ClassInlinerCostAnalysis(
+      AppView<?> appView,
+      InliningIRProvider inliningIRProvider,
+      Set<Value> definiteReceiverAliases) {
+    this.appView = appView;
+    this.inliningIRProvider = inliningIRProvider;
+    this.definiteReceiverAliases = definiteReceiverAliases;
+  }
+
+  boolean willExceedInstructionBudget(
+      IRCode code,
+      Map<InvokeMethod, DexEncodedMethod> directInlinees,
+      List<DexEncodedMethod> indirectInlinees) {
+    for (DexEncodedMethod inlinee : indirectInlinees) {
+      // We do not have the corresponding invoke instruction for the inlinees that are not called
+      // directly from `code` (these are called indirectly from one of the methods in
+      // `directInlinees`). Therefore, we currently choose not to build IR for estimating the number
+      // of non-materializing instructions, since we cannot cache the IR (it would have the wrong
+      // position).
+      int increment = inlinee.getCode().estimatedSizeForInlining();
+      if (exceedsInstructionBudgetAfterIncrement(increment)) {
+        return true;
+      }
+    }
+
+    // Visit the direct inlinees in a deterministic order to ensure that the state of the value
+    // number generated is deterministic for each inlinee.
+    int numberOfSeenDirectInlinees = 0;
+    int numberOfDirectInlinees = directInlinees.size();
+    for (InvokeMethod invoke : code.<InvokeMethod>instructions(Instruction::isInvokeMethod)) {
+      DexEncodedMethod inlinee = directInlinees.get(invoke);
+      if (inlinee == null) {
+        // Not a direct inlinee.
+        continue;
+      }
+      IRCode inliningIR = inliningIRProvider.getAndCacheInliningIR(invoke, inlinee);
+      int increment =
+          inlinee.getCode().estimatedSizeForInlining()
+              - estimateNumberOfNonMaterializingInstructions(invoke, inliningIR);
+      assert increment >= 0;
+      if (exceedsInstructionBudgetAfterIncrement(increment)) {
+        return true;
+      }
+      if (++numberOfSeenDirectInlinees == numberOfDirectInlinees) {
+        break;
+      }
+    }
+
+    // Verify that all direct inlinees have been visited.
+    assert numberOfSeenDirectInlinees == numberOfDirectInlinees;
+
+    return false;
+  }
+
+  private boolean exceedsInstructionBudgetAfterIncrement(int increment) {
+    estimatedCost += increment;
+    return estimatedCost > appView.options().classInliningInstructionAllowance;
+  }
+
+  // TODO(b/143176500): Do not include instructions that will be canonicalized after inlining.
+  //  Take care of that fact that after inlining the first method, this could introduce a constant
+  //  in the caller, which could then lead to a constant in the second inlinee being canonicalized.
+  // TODO(b/143176500): Do not include instructions that will be dead code eliminated as a result of
+  //  constant arguments.
+  private int estimateNumberOfNonMaterializingInstructions(InvokeMethod invoke, IRCode inlinee) {
+    int result = 0;
+    Set<Value> receiverAliasesInInlinee = null;
+    for (Instruction instruction : inlinee.instructions()) {
+      switch (instruction.opcode()) {
+        case ARGUMENT:
+          // Intentionally not counted as a non-materializing instruction, since there are no
+          // argument instructions in the CF/DEX code.
+          break;
+
+        case INSTANCE_GET:
+        case INSTANCE_PUT:
+          // Will not materialize after class inlining if the object is the "root" instance.
+          Value object =
+              instruction.isInstanceGet()
+                  ? instruction.asInstanceGet().object()
+                  : instruction.asInstancePut().object();
+          Value root = object.getAliasedValue();
+          if (receiverAliasesInInlinee == null) {
+            receiverAliasesInInlinee = getReceiverAliasesInInlinee(invoke, inlinee);
+          }
+          if (receiverAliasesInInlinee.contains(root)) {
+            result++;
+          }
+          break;
+
+        case RETURN:
+          // Wil not materialize after class inlining.
+          result++;
+          break;
+
+        default:
+          // Will materialize.
+          break;
+      }
+    }
+    return result;
+  }
+
+  private Set<Value> getReceiverAliasesInInlinee(InvokeMethod invoke, IRCode inlinee) {
+    List<Value> arguments = inlinee.collectArguments();
+    Set<Value> receiverAliasesInInlinee = Sets.newIdentityHashSet();
+    for (int i = 0; i < invoke.inValues().size(); i++) {
+      Value inValue = invoke.inValues().get(i);
+      if (definiteReceiverAliases.contains(inValue)) {
+        receiverAliasesInInlinee.add(arguments.get(i));
+      } else {
+        assert !definiteReceiverAliases.contains(inValue.getAliasedValue());
+      }
+    }
+    return receiverAliasesInInlinee;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java
new file mode 100644
index 0000000..a80a9ec
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2019, 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.utils.OptionalBool;
+
+public class ClassInlinerEligibilityInfo {
+
+  /**
+   * Set to {@link OptionalBool#TRUE} if the method is guaranteed to return the receiver, {@link
+   * OptionalBool#FALSE} if the method is guaranteed not to return the receiver, and {@link
+   * OptionalBool#UNKNOWN} if the method may return the receiver.
+   */
+  final OptionalBool returnsReceiver;
+
+  public ClassInlinerEligibilityInfo(OptionalBool returnsReceiver) {
+    this.returnsReceiver = returnsReceiver;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java
new file mode 100644
index 0000000..f6e6e70
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java
@@ -0,0 +1,135 @@
+// Copyright (c) 2019, 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.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.BooleanLatticeElement;
+import com.android.tools.r8.utils.OptionalBool;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This analysis determines whether a method returns the receiver. The analysis is specific to the
+ * class inliner, and the result is therefore not sound in general.
+ *
+ * <p>The analysis makes the following assumptions.
+ *
+ * <ul>
+ *   <li>None of the given method's arguments is an alias of the receiver (except for the receiver
+ *       itself).
+ *   <li>The receiver is not stored in the heap. Thus, it is guaranteed that (i) all field-get
+ *       instructions do not return an alias of the receiver, and (ii) invoke instructions can only
+ *       return an alias of the receiver if the receiver is given as an argument.
+ * </ul>
+ */
+public class ClassInlinerReceiverAnalysis {
+
+  private final AppView<?> appView;
+  private final DexEncodedMethod method;
+  private final IRCode code;
+  private final Value receiver;
+
+  private final Map<Value, OptionalBool> isReceiverAliasCache = new IdentityHashMap<>();
+
+  public ClassInlinerReceiverAnalysis(AppView<?> appView, DexEncodedMethod method, IRCode code) {
+    this.appView = appView;
+    this.method = method;
+    this.code = code;
+    this.receiver = code.getThis();
+    assert !receiver.hasAliasedValue();
+    assert receiver.getTypeLattice().isClassType();
+  }
+
+  public OptionalBool computeReturnsReceiver() {
+    if (method.method.proto.returnType.isVoidType()) {
+      return OptionalBool.FALSE;
+    }
+
+    List<BasicBlock> normalExitBlocks = code.computeNormalExitBlocks();
+    if (normalExitBlocks.isEmpty()) {
+      return OptionalBool.FALSE;
+    }
+
+    BooleanLatticeElement result = OptionalBool.BOTTOM;
+    for (BasicBlock block : normalExitBlocks) {
+      Value returnValue = block.exit().asReturn().returnValue();
+      result = result.join(getOrComputeIsReceiverAlias(returnValue));
+
+      // Stop as soon as we reach unknown.
+      if (result.isUnknown()) {
+        return OptionalBool.UNKNOWN;
+      }
+    }
+    assert !result.isBottom();
+    return result.asOptionalBool();
+  }
+
+  private OptionalBool getOrComputeIsReceiverAlias(Value value) {
+    Value root = value.getAliasedValue();
+    return isReceiverAliasCache.computeIfAbsent(root, this::computeIsReceiverAlias);
+  }
+
+  private OptionalBool computeIsReceiverAlias(Value value) {
+    assert !value.hasAliasedValue();
+
+    if (value == receiver) {
+      // Guaranteed to return the receiver.
+      return OptionalBool.TRUE;
+    }
+
+    ClassTypeLatticeElement valueType = value.getTypeLattice().asClassTypeLatticeElement();
+    if (valueType == null) {
+      return OptionalBool.FALSE;
+    }
+
+    ClassTypeLatticeElement receiverType = receiver.getTypeLattice().asClassTypeLatticeElement();
+    if (!valueType.isRelatedTo(receiverType, appView)) {
+      // Guaranteed not to return the receiver.
+      return OptionalBool.FALSE;
+    }
+
+    if (value.isPhi()) {
+      // Not sure what is returned.
+      return OptionalBool.UNKNOWN;
+    }
+
+    Instruction definition = value.definition;
+    if (definition.isArrayGet() || definition.isFieldGet()) {
+      // Guaranteed not to return the receiver, since the class inliner does not allow the
+      // receiver to flow into any arrays or fields.
+      return OptionalBool.FALSE;
+    }
+
+    if (definition.isConstInstruction() || definition.isCreatingInstanceOrArray()) {
+      // Guaranteed not to return the receiver.
+      return OptionalBool.FALSE;
+    }
+
+    if (definition.isInvokeMethod()) {
+      // Since the class inliner does not allow the receiver to flow into the heap, the only way for
+      // the invoked method to return the receiver is if one of the given arguments is an alias of
+      // the receiver.
+      InvokeMethod invoke = definition.asInvokeMethod();
+      for (Value argument : invoke.arguments()) {
+        if (getOrComputeIsReceiverAlias(argument).isPossiblyTrue()) {
+          return OptionalBool.UNKNOWN;
+        }
+      }
+
+      return OptionalBool.FALSE;
+    }
+
+    // Not sure what is returned.
+    return OptionalBool.UNKNOWN;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverSet.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverSet.java
new file mode 100644
index 0000000..5a8c59d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverSet.java
@@ -0,0 +1,127 @@
+// Copyright (c) 2019, 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.ir.code.Value;
+import com.android.tools.r8.ir.optimize.classinliner.InlineCandidateProcessor.AliasKind;
+import com.android.tools.r8.utils.SetUtils;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BooleanSupplier;
+
+class ClassInlinerReceiverSet {
+
+  private final Value root;
+
+  private final Set<Value> definiteReceiverAliases;
+  private final Set<Value> maybeReceiverAliases = Sets.newIdentityHashSet();
+
+  // Set of values that are not allowed to become an alias of the receiver.
+  private final Set<Value> illegalReceiverAliases = Sets.newIdentityHashSet();
+
+  // Set of values that are allowed to become an alias of the receiver under certain circumstances.
+  private final Map<Value, List<BooleanSupplier>> deferredAliasValidityChecks =
+      new IdentityHashMap<>();
+
+  ClassInlinerReceiverSet(Value root) {
+    this.definiteReceiverAliases = SetUtils.newIdentityHashSet(root);
+    this.root = root;
+  }
+
+  Set<Value> getDefiniteReceiverAliases() {
+    return definiteReceiverAliases;
+  }
+
+  Set<Value> getMaybeReceiverAliases() {
+    return maybeReceiverAliases;
+  }
+
+  boolean addReceiverAlias(Value alias, AliasKind kind) {
+    if (isIllegalReceiverAlias(alias)) {
+      return false; // Not allowed.
+    }
+    // All checks passed.
+    deferredAliasValidityChecks.remove(alias);
+    boolean changed;
+    if (kind == AliasKind.DEFINITE) {
+      assert !maybeReceiverAliases.contains(alias);
+      changed = definiteReceiverAliases.add(alias);
+    } else {
+      assert !definiteReceiverAliases.contains(alias);
+      changed = maybeReceiverAliases.add(alias);
+    }
+    // Verify that the state changed. Otherwise, we are analyzing the same instruction more than
+    // once.
+    assert changed : alias.toString() + " already added as an alias";
+    return true;
+  }
+
+  boolean addIllegalReceiverAlias(Value value) {
+    if (isReceiverAlias(value)) {
+      return false;
+    }
+    illegalReceiverAliases.add(value);
+    // Since `value` is never allowed as a receiver, there is no need to keep the validity checks
+    // around.
+    deferredAliasValidityChecks.remove(value);
+    return true;
+  }
+
+  void addDeferredAliasValidityCheck(Value value, BooleanSupplier deferredValidityCheck) {
+    assert !isReceiverAlias(value);
+    // Only add the deferred validity check if `value` may be allowed as a receiver (i.e., it is not
+    // already illegal).
+    if (illegalReceiverAliases.contains(value)) {
+      assert !deferredAliasValidityChecks.containsKey(value);
+    } else {
+      deferredAliasValidityChecks
+          .computeIfAbsent(value, ignore -> new ArrayList<>())
+          .add(deferredValidityCheck);
+    }
+  }
+
+  boolean isReceiverAlias(Value value) {
+    return isDefiniteReceiverAlias(value) || isMaybeReceiverAlias(value);
+  }
+
+  boolean isDefiniteReceiverAlias(Value value) {
+    return definiteReceiverAliases.contains(value);
+  }
+
+  private boolean isMaybeReceiverAlias(Value value) {
+    return maybeReceiverAliases.contains(value);
+  }
+
+  private boolean isIllegalReceiverAlias(Value value) {
+    if (illegalReceiverAliases.contains(value)) {
+      return true;
+    }
+    List<BooleanSupplier> deferredValidityChecks = deferredAliasValidityChecks.get(value);
+    if (deferredValidityChecks != null) {
+      for (BooleanSupplier deferredValidityCheck : deferredValidityChecks) {
+        if (!deferredValidityCheck.getAsBoolean()) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  void reset() {
+    deferredAliasValidityChecks.clear();
+    definiteReceiverAliases.clear();
+    definiteReceiverAliases.add(root);
+    maybeReceiverAliases.clear();
+  }
+
+  boolean verifyReceiverSetsAreDisjoint() {
+    assert Sets.intersection(getMaybeReceiverAliases(), getDefiniteReceiverAliases()).isEmpty();
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 096699b..36112c9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -4,11 +4,12 @@
 
 package com.android.tools.r8.ir.optimize.classinliner;
 
+
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
@@ -21,6 +22,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.If;
 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.InstructionOrPhi;
 import com.android.tools.r8.ir.code.Invoke.Type;
@@ -43,9 +45,7 @@
 import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
 import com.android.tools.r8.kotlin.KotlinInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.Pair;
-import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
@@ -63,6 +63,12 @@
 import java.util.function.Supplier;
 
 final class InlineCandidateProcessor {
+
+  enum AliasKind {
+    DEFINITE,
+    MAYBE
+  }
+
   private static final ImmutableSet<If.Type> ALLOWED_ZERO_TEST_TYPES =
       ImmutableSet.of(If.Type.EQ, If.Type.NE);
 
@@ -86,12 +92,12 @@
   private final List<Pair<InvokeMethod, Integer>> unusedArguments
       = new ArrayList<>();
 
-  private int estimatedCombinedSizeForInlining = 0;
+  private final Map<InvokeMethod, DexEncodedMethod> directInlinees = new IdentityHashMap<>();
+  private final List<DexEncodedMethod> indirectInlinees = new ArrayList<>();
 
-  // Set of values that may be an alias of the "root" instance (including the root instance itself).
-  // TODO(b/144825216): Distinguish the "may-aliases" from the "must-aliases" such that the cost
-  //  analysis is not optimistic.
-  private final Set<Value> receivers;
+  // Sets of values that must/may be an alias of the "root" instance (including the root instance
+  // itself).
+  private final ClassInlinerReceiverSet receivers;
 
   InlineCandidateProcessor(
       AppView<AppInfoWithLiveness> appView,
@@ -108,14 +114,18 @@
     this.method = method;
     this.root = root;
     this.isProcessedConcurrently = isProcessedConcurrently;
-    this.receivers = SetUtils.newIdentityHashSet(root.outValue());
+    this.receivers = new ClassInlinerReceiverSet(root.outValue());
   }
 
-  int getEstimatedCombinedSizeForInlining() {
-    return estimatedCombinedSizeForInlining;
+  Map<InvokeMethod, DexEncodedMethod> getDirectInlinees() {
+    return directInlinees;
   }
 
-  Set<Value> getReceivers() {
+  List<DexEncodedMethod> getIndirectInlinees() {
+    return indirectInlinees;
+  }
+
+  ClassInlinerReceiverSet getReceivers() {
     return receivers;
   }
 
@@ -282,24 +292,28 @@
       for (Instruction user : currentUsers) {
         if (user.isAssume()) {
           Value alias = user.outValue();
+          if (receivers.isReceiverAlias(alias)) {
+            continue; // Already processed.
+          }
           if (alias.hasPhiUsers()) {
             return alias.firstPhiUser(); // Not eligible.
           }
-          receivers.add(alias);
+          if (!receivers.addReceiverAlias(alias, AliasKind.DEFINITE)) {
+            return user; // Not eligible.
+          }
           indirectUsers.addAll(alias.uniqueUsers());
           continue;
         }
         // Field read/write.
         if (user.isInstanceGet()
-            || (user.isInstancePut() && user.asInstancePut().value() != eligibleInstance)) {
-          DexField field = user.asFieldInstruction().getField();
-          if (field.holder == eligibleClass
-              && eligibleClassDefinition.lookupInstanceField(field) != null) {
-            // Since class inliner currently only supports classes directly extending
-            // java.lang.Object, we don't need to worry about fields defined in superclasses.
-            continue;
+            || (user.isInstancePut()
+                && receivers.addIllegalReceiverAlias(user.asInstancePut().value()))) {
+          DexEncodedField field =
+              appView.appInfo().resolveField(user.asFieldInstruction().getField());
+          if (field == null || field.isStatic()) {
+            return user; // Not eligible.
           }
-          return user; // Not eligible.
+          continue;
         }
 
         if (user.isInvokeMethod()) {
@@ -393,7 +407,7 @@
       methodCallsOnInstance.clear();
       extraMethodCalls.clear();
       unusedArguments.clear();
-      estimatedCombinedSizeForInlining = 0;
+      receivers.reset();
 
       // Repeat user analysis
       InstructionOrPhi ineligibleUser = areInstanceUsersEligible(defaultOracle);
@@ -405,6 +419,7 @@
         // methods (which is bad for memory), or we would need to analyze the called methods before
         // inlining them. The latter could be good solution, since we are going to build IR for the
         // methods that need to be inlined anyway.
+        assert appView.options().testing.allowClassInlinerGracefulExit;
         return true;
       }
       assert extraMethodCalls.isEmpty()
@@ -432,7 +447,7 @@
       block.listIterator(code, invoke).add(nullValue);
       assert nullValue.getBlock() == block;
 
-      int argIndex = unusedArgument.getSecond() + (invoke.isInvokeMethodWithReceiver() ? 1 : 0);
+      int argIndex = unusedArgument.getSecond();
       invoke.replaceValue(argIndex, nullValue.outValue());
     }
     unusedArguments.clear();
@@ -453,10 +468,44 @@
     if (methodCallsOnInstance.isEmpty()) {
       return false;
     }
+
     assert methodCallsOnInstance.keySet().stream()
         .map(InvokeMethodWithReceiver::getReceiver)
-        .allMatch(receivers::contains);
+        .allMatch(receivers::isReceiverAlias);
+
     inliner.performForcedInlining(method, code, methodCallsOnInstance, inliningIRProvider);
+
+    // In case we are class inlining an object allocation that does not inherit directly from
+    // java.lang.Object, we need keep force inlining the constructor until we reach
+    // java.lang.Object.<init>().
+    if (root.isNewInstance()) {
+      do {
+        methodCallsOnInstance.clear();
+        for (Instruction instruction : eligibleInstance.uniqueUsers()) {
+          if (instruction.isInvokeDirect()) {
+            InvokeDirect invoke = instruction.asInvokeDirect();
+            Value receiver = invoke.getReceiver();
+            if (receiver == eligibleInstance) {
+              DexMethod invokedMethod = invoke.getInvokedMethod();
+              if (appView.dexItemFactory().isConstructor(invokedMethod)
+                  && invokedMethod != appView.dexItemFactory().objectMethods.constructor) {
+                methodCallsOnInstance.put(
+                    invoke,
+                    new InliningInfo(
+                        appView.definitionFor(invokedMethod), root.asNewInstance().clazz));
+                break;
+              }
+            } else {
+              assert receiver.getAliasedValue() != eligibleInstance;
+            }
+          }
+        }
+        if (!methodCallsOnInstance.isEmpty()) {
+          inliner.performForcedInlining(method, code, methodCallsOnInstance, inliningIRProvider);
+        }
+      } while (!methodCallsOnInstance.isEmpty());
+    }
+
     return true;
   }
 
@@ -468,7 +517,7 @@
       Assume<?> assumeInstruction = user.asAssume();
       Value src = assumeInstruction.src();
       Value dest = assumeInstruction.outValue();
-      assert receivers.contains(dest);
+      assert receivers.isReceiverAlias(dest);
       assert !dest.hasPhiUsers();
       dest.replaceUsers(src);
       removeInstruction(user);
@@ -481,13 +530,14 @@
   private void removeMiscUsages(IRCode code) {
     boolean needToRemoveUnreachableBlocks = false;
     for (Instruction user : eligibleInstance.uniqueUsers()) {
-      // Remove the call to superclass constructor.
-      if (root.isNewInstance()
-          && user.isInvokeDirect()
-          && appView.dexItemFactory().isConstructor(user.asInvokeDirect().getInvokedMethod())
-          && user.asInvokeDirect().getInvokedMethod().holder == eligibleClassDefinition.superType) {
-        removeInstruction(user);
-        continue;
+      // Remove the call to java.lang.Object.<init>().
+      if (user.isInvokeDirect()) {
+        InvokeDirect invoke = user.asInvokeDirect();
+        if (root.isNewInstance()
+            && invoke.getInvokedMethod() == appView.dexItemFactory().objectMethods.constructor) {
+          removeInstruction(invoke);
+          continue;
+        }
       }
 
       if (user.isIf()) {
@@ -601,7 +651,10 @@
                 + "` after field reads removed: "
                 + user);
       }
-      if (user.asInstancePut().getField().holder != eligibleClass) {
+      InstancePut instancePut = user.asInstancePut();
+      DexEncodedField field =
+          appView.appInfo().resolveFieldOn(eligibleClassDefinition, instancePut.getField());
+      if (field == null) {
         throw new Unreachable(
             "Unexpected field write left in method `"
                 + method.method.toSourceString()
@@ -618,11 +671,18 @@
     assert isEligibleSingleTarget(singleTarget);
 
     // Must be a constructor called on the receiver.
-    if (ListUtils.lastIndexMatching(
-        invoke.inValues(), v -> v.getAliasedValue() == eligibleInstance) != 0) {
+    if (!receivers.isDefiniteReceiverAlias(invoke.getReceiver())) {
       return null;
     }
 
+    // None of the subsequent arguments may be an alias of the receiver.
+    List<Value> inValues = invoke.inValues();
+    for (int i = 1; i < inValues.size(); i++) {
+      if (!receivers.addIllegalReceiverAlias(inValues.get(i))) {
+        return null;
+      }
+    }
+
     // Must be a constructor of the exact same class.
     DexMethod init = invoke.getInvokedMethod();
     if (init.holder != eligibleClass) {
@@ -639,7 +699,7 @@
 
     if (isDesugaredLambda) {
       // Lambda desugaring synthesizes eligible constructors.
-      markSizeForInlining(singleTarget);
+      markSizeForInlining(invoke, singleTarget);
       return new InliningInfo(singleTarget, eligibleClass);
     }
 
@@ -681,10 +741,10 @@
   //   other invocations. In that case, we should add all indirect users of the out value to ensure
   //   they can also be inlined.
   private boolean isEligibleInvokeWithAllUsersAsReceivers(
-      ClassInlinerEligibility eligibility,
+      ClassInlinerEligibilityInfo eligibility,
       InvokeMethodWithReceiver invoke,
       Set<Instruction> indirectUsers) {
-    if (!eligibility.returnsReceiver) {
+    if (eligibility.returnsReceiver.isFalse()) {
       return true;
     }
 
@@ -700,9 +760,13 @@
       return false;
     }
 
-    // Since the invoke-instruction may return the receiver, the out-value may be an alias of the
-    // receiver.
-    receivers.add(outValue);
+    // Add the out-value as a definite-alias if the invoke instruction is guaranteed to return the
+    // receiver. Otherwise, the out-value may be an alias of the receiver, and it is added to the
+    // may-alias set.
+    AliasKind kind = eligibility.returnsReceiver.isTrue() ? AliasKind.DEFINITE : AliasKind.MAYBE;
+    if (!receivers.addReceiverAlias(outValue, kind)) {
+      return false;
+    }
 
     Set<Instruction> currentUsers = outValue.uniqueUsers();
     while (!currentUsers.isEmpty()) {
@@ -713,24 +777,29 @@
           if (outValueAlias.hasPhiUsers() || outValueAlias.hasDebugUsers()) {
             return false;
           }
-          receivers.add(outValueAlias);
+          if (!receivers.addReceiverAlias(outValueAlias, kind)) {
+            return false;
+          }
           indirectOutValueUsers.addAll(outValueAlias.uniqueUsers());
           continue;
         }
-        if (!instruction.isInvokeMethodWithReceiver()) {
-          return false;
-        }
-        InvokeMethodWithReceiver user = instruction.asInvokeMethodWithReceiver();
-        if (user.getReceiver().getAliasedValue() != outValue) {
-          return false;
-        }
-        for (int i = 1; i < user.inValues().size(); i++) {
-          if (user.inValues().get(i).getAliasedValue() == outValue) {
+
+        if (instruction.isInvokeMethodWithReceiver()) {
+          InvokeMethodWithReceiver user = instruction.asInvokeMethodWithReceiver();
+          if (user.getReceiver().getAliasedValue() != outValue) {
             return false;
           }
+          for (int i = 1; i < user.inValues().size(); i++) {
+            if (user.inValues().get(i).getAliasedValue() == outValue) {
+              return false;
+            }
+          }
+          indirectUsers.add(user);
+          continue;
         }
+
+        return false;
       }
-      indirectUsers.addAll(currentUsers);
       currentUsers = indirectOutValueUsers;
     }
 
@@ -743,10 +812,15 @@
       Set<Instruction> indirectUsers,
       Supplier<InliningOracle> defaultOracle) {
     assert isEligibleSingleTarget(singleTarget);
-    if (ListUtils.lastIndexMatching(
-        invoke.inValues(), v -> v.getAliasedValue() == eligibleInstance) > 0) {
-      return null; // Instance passed as an argument.
+
+    // None of the none-receiver arguments may be an alias of the receiver.
+    List<Value> inValues = invoke.inValues();
+    for (int i = 1; i < inValues.size(); i++) {
+      if (!receivers.addIllegalReceiverAlias(inValues.get(i))) {
+        return null;
+      }
     }
+
     // TODO(b/141719453): Should not constrain library overrides if all instantiations are inlined.
     if (singleTarget.isLibraryMethodOverride().isTrue()) {
       InliningOracle inliningOracle = defaultOracle.get();
@@ -756,7 +830,7 @@
       }
     }
     return isEligibleVirtualMethodCall(
-        !invoke.getBlock().hasCatchHandlers(),
+        invoke,
         invoke.getInvokedMethod(),
         singleTarget,
         eligibility -> isEligibleInvokeWithAllUsersAsReceivers(eligibility, invoke, indirectUsers));
@@ -766,16 +840,16 @@
     DexEncodedMethod singleTarget = eligibleClassDefinition.lookupVirtualMethod(callee);
     if (isEligibleSingleTarget(singleTarget)) {
       return isEligibleVirtualMethodCall(
-          false, callee, singleTarget, eligibility -> !eligibility.returnsReceiver);
+          null, callee, singleTarget, eligibility -> eligibility.returnsReceiver.isFalse());
     }
     return null;
   }
 
   private InliningInfo isEligibleVirtualMethodCall(
-      boolean allowMethodsWithoutNormalReturns,
+      InvokeMethodWithReceiver invoke,
       DexMethod callee,
       DexEncodedMethod singleTarget,
-      Predicate<ClassInlinerEligibility> eligibilityAcceptanceCheck) {
+      Predicate<ClassInlinerEligibilityInfo> eligibilityAcceptanceCheck) {
     assert isEligibleSingleTarget(singleTarget);
 
     // We should not inline a method if the invocation has type interface or virtual and the
@@ -794,13 +868,13 @@
     }
 
     if (isDesugaredLambda && !singleTarget.accessFlags.isBridge()) {
-      markSizeForInlining(singleTarget);
+      markSizeForInlining(invoke, singleTarget);
       return new InliningInfo(singleTarget, eligibleClass);
     }
 
     MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
 
-    ClassInlinerEligibility eligibility = optimizationInfo.getClassInlinerEligibility();
+    ClassInlinerEligibilityInfo eligibility = optimizationInfo.getClassInlinerEligibility();
     if (eligibility == null) {
       return null;
     }
@@ -811,7 +885,7 @@
       return null;
     }
 
-    markSizeForInlining(singleTarget);
+    markSizeForInlining(invoke, singleTarget);
     return new InliningInfo(singleTarget, eligibleClass);
   }
 
@@ -820,10 +894,11 @@
         && appView.dexItemFactory().isConstructor(invoke.getInvokedMethod())) {
       return false;
     }
-    if (invoke.isInvokeMethodWithReceiver()
-        && invoke.asInvokeMethodWithReceiver().getReceiver().getAliasedValue()
-            == eligibleInstance) {
-      return false;
+    if (invoke.isInvokeMethodWithReceiver()) {
+      Value receiver = invoke.asInvokeMethodWithReceiver().getReceiver();
+      if (!receivers.addIllegalReceiverAlias(receiver)) {
+        return false;
+      }
     }
     if (invoke.isInvokeSuper()) {
       return false;
@@ -863,13 +938,13 @@
 
     // If we got here with invocation on receiver the user is ineligible.
     if (invoke.isInvokeMethodWithReceiver()) {
-      if (arguments.get(0).getAliasedValue() == eligibleInstance) {
+      InvokeMethodWithReceiver invokeMethodWithReceiver = invoke.asInvokeMethodWithReceiver();
+      Value receiver = invokeMethodWithReceiver.getReceiver();
+      if (!receivers.addIllegalReceiverAlias(receiver)) {
         return false;
       }
 
       // TODO(b/124842076) Extend this check to use checksNullReceiverBeforeAnySideEffect.
-      InvokeMethodWithReceiver invokeMethodWithReceiver = invoke.asInvokeMethodWithReceiver();
-      Value receiver = invokeMethodWithReceiver.getReceiver();
       if (receiver.getTypeLattice().isNullable()) {
         return false;
       }
@@ -883,8 +958,11 @@
     }
 
     for (int argIndex = 0; argIndex < arguments.size(); argIndex++) {
-      Value argument = arguments.get(argIndex);
-      if (argument == eligibleInstance && optimizationInfo.getParameterUsages(argIndex).notUsed()) {
+      Value argument = arguments.get(argIndex).getAliasedValue();
+      ParameterUsage parameterUsage = optimizationInfo.getParameterUsages(argIndex);
+      if (receivers.isDefiniteReceiverAlias(argument)
+          && parameterUsage != null
+          && parameterUsage.notUsed()) {
         // Reference can be removed since it's not used.
         unusedArguments.add(new Pair<>(invoke, argIndex));
       }
@@ -893,7 +971,7 @@
     extraMethodCalls.put(invoke, new InliningInfo(singleTarget, null));
 
     // Looks good.
-    markSizeForInlining(singleTarget);
+    markSizeForInlining(invoke, singleTarget);
     return true;
   }
 
@@ -904,16 +982,19 @@
       Supplier<InliningOracle> defaultOracle) {
     // Go through all arguments, see if all usages of eligibleInstance are good.
     for (int argIndex = 0; argIndex < arguments.size(); argIndex++) {
-      Value argument = arguments.get(argIndex).getAliasedValue();
-      if (argument != eligibleInstance) {
-        continue; // Nothing to worry about.
-      }
-
-      // Have parameter usage info?
       MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
       ParameterUsage parameterUsage = optimizationInfo.getParameterUsages(argIndex);
-      if (!isEligibleParameterUsage(parameterUsage, invoke, defaultOracle)) {
-        return false;
+
+      Value argument = arguments.get(argIndex);
+      if (receivers.isReceiverAlias(argument)) {
+        // Have parameter usage info?
+        if (!isEligibleParameterUsage(parameterUsage, invoke, defaultOracle)) {
+          return false;
+        }
+      } else {
+        // Nothing to worry about, unless `argument` becomes an alias of the receiver later.
+        receivers.addDeferredAliasValidityCheck(
+            argument, () -> isEligibleParameterUsage(parameterUsage, invoke, defaultOracle));
       }
     }
     return true;
@@ -1015,9 +1096,14 @@
         kotlinInfo.asSyntheticClass().isLambda();
   }
 
-  private void markSizeForInlining(DexEncodedMethod inlinee) {
+  private void markSizeForInlining(InvokeMethod invoke, DexEncodedMethod inlinee) {
+    assert !isProcessedConcurrently.test(inlinee);
     if (!exemptFromInstructionLimit(inlinee)) {
-      estimatedCombinedSizeForInlining += inlinee.getCode().estimatedSizeForInlining();
+      if (invoke != null) {
+        directInlinees.put(invoke, inlinee);
+      } else {
+        indirectInlinees.add(inlinee);
+      }
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
index c38c0ed..702074a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import java.util.List;
@@ -99,6 +100,11 @@
   public boolean hasUsefulOptimizationInfo(AppView<?> appView, DexEncodedMethod encodedMethod) {
     TypeLatticeElement[] staticTypes = getStaticTypes(appView, encodedMethod);
     for (int i = 0; i < size; i++) {
+      ParameterUsage parameterUsage = encodedMethod.getOptimizationInfo().getParameterUsages(i);
+      // If the parameter is not used, passing accurate argument info doesn't matter.
+      if (parameterUsage != null && parameterUsage.notUsed()) {
+        continue;
+      }
       AbstractValue abstractValue = getAbstractArgumentValue(i);
       if (abstractValue.isNonTrivial()) {
         assert appView.options().enablePropagationOfConstantsAtCallSites;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index e6d0589..7ecbf18 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -4,12 +4,12 @@
 
 package com.android.tools.r8.ir.optimize.info;
 
-import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
+import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
 import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
@@ -32,7 +32,7 @@
   static boolean DOES_NOT_USE_IDENTIFIER_NAME_STRING = false;
   static boolean UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT = false;
   static boolean UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT = false;
-  static ClassInlinerEligibility UNKNOWN_CLASS_INLINER_ELIGIBILITY = null;
+  static ClassInlinerEligibilityInfo UNKNOWN_CLASS_INLINER_ELIGIBILITY = null;
   static boolean UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS = false;
   static ParameterUsagesInfo UNKNOWN_PARAMETER_USAGE_INFO = null;
   static boolean UNKNOWN_MAY_HAVE_SIDE_EFFECTS = true;
@@ -140,7 +140,7 @@
   }
 
   @Override
-  public ClassInlinerEligibility getClassInlinerEligibility() {
+  public ClassInlinerEligibilityInfo getClassInlinerEligibility() {
     return UNKNOWN_CLASS_INLINER_ELIGIBILITY;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index 26aab1b..d9c4083 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -4,11 +4,11 @@
 
 package com.android.tools.r8.ir.optimize.info;
 
-import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
 import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
@@ -55,7 +55,7 @@
 
   boolean neverReturnsNormally();
 
-  ClassInlinerEligibility getClassInlinerEligibility();
+  ClassInlinerEligibilityInfo getClassInlinerEligibility();
 
   Set<DexType> getInitializedClassesOnNormalExit();
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 13844a6..ee49c97 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -38,6 +37,8 @@
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.DynamicTypeOptimization;
+import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
+import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerReceiverAnalysis;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsageBuilder;
 import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
@@ -123,7 +124,6 @@
       return;
     }
 
-    boolean receiverUsedAsReturnValue = false;
     boolean seenSuperInitCall = false;
     for (Instruction insn : receiver.aliasedUsers()) {
       if (insn.isAssume()) {
@@ -134,11 +134,6 @@
         continue;
       }
 
-      if (insn.isReturn()) {
-        receiverUsedAsReturnValue = true;
-        continue;
-      }
-
       if (insn.isInstanceGet() || insn.isInstancePut()) {
         if (insn.isInstancePut()) {
           InstancePut instancePutInstruction = insn.asInstancePut();
@@ -176,16 +171,23 @@
         return;
       }
 
+      if (insn.isReturn()) {
+        continue;
+      }
+
       // Other receiver usages make the method not eligible.
       return;
     }
+
     if (instanceInitializer && !seenSuperInitCall) {
       // Call to super constructor not found?
       return;
     }
 
     feedback.setClassInlinerEligibility(
-        method, new ClassInlinerEligibility(receiverUsedAsReturnValue));
+        method,
+        new ClassInlinerEligibilityInfo(
+            new ClassInlinerReceiverAnalysis(appView, method, code).computeReturnsReceiver()));
   }
 
   private void identifyParameterUsages(
@@ -442,7 +444,7 @@
 
       if (insn.isInstancePut()) {
         InstancePut instancePut = insn.asInstancePut();
-        DexEncodedField field = clazz.lookupInstanceField(instancePut.getField());
+        DexEncodedField field = appView.appInfo().resolveFieldOn(clazz, instancePut.getField());
         if (field == null
             || instancePut.object() != receiver
             || (instancePut.value() != receiver && !instancePut.value().isArgument())) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index 2f9c55b..a0828e9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -7,12 +7,12 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.IteratorUtils;
@@ -228,7 +228,7 @@
 
   @Override
   public synchronized void setClassInlinerEligibility(
-      DexEncodedMethod method, ClassInlinerEligibility eligibility) {
+      DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility) {
     getMethodOptimizationInfoForUpdating(method).setClassInlinerEligibility(eligibility);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index 1be0c5c..e106dc8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -7,12 +7,12 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.BitSet;
@@ -109,8 +109,7 @@
 
   @Override
   public void setClassInlinerEligibility(
-      DexEncodedMethod method, ClassInlinerEligibility eligibility) {
-  }
+      DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility) {}
 
   @Override
   public void setInitializerInfo(DexEncodedMethod method, InitializerInfo info) {}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 1d1dea5..0178a2d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -7,12 +7,12 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.BitSet;
@@ -156,7 +156,7 @@
 
   @Override
   public void setClassInlinerEligibility(
-      DexEncodedMethod method, ClassInlinerEligibility eligibility) {
+      DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility) {
     // Ignored.
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
index e41989a..ef87dcf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
@@ -118,8 +118,8 @@
     }
 
     public boolean notUsed() {
-      return ifZeroTest == null
-          && callsReceiver == null
+      return (ifZeroTest == null || ifZeroTest.isEmpty())
+          && (callsReceiver == null || callsReceiver.isEmpty())
           && !hasFieldAssignment
           && !hasFieldRead
           && !isAssignedToField
@@ -147,8 +147,9 @@
     // Returns false if the instruction is not supported.
     public boolean note(Instruction instruction) {
       if (instruction.isAssume()) {
-        // Keep examining other users, but the param usage builder should consider aliased users.
-        return true;
+        // Keep examining other users if there are no phi users, but the param usage builder should
+        // consider aliased users.
+        return !instruction.outValue().hasPhiUsers();
       }
       if (instruction.isIf()) {
         return note(instruction.asIf());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index c5a12c2..74dd886 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -6,11 +6,11 @@
 
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
 import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
@@ -49,7 +49,7 @@
       DefaultMethodOptimizationInfo.UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT;
   // Stores information about instance methods and constructors for
   // class inliner, null value indicates that the method is not eligible.
-  private ClassInlinerEligibility classInlinerEligibility =
+  private ClassInlinerEligibilityInfo classInlinerEligibility =
       DefaultMethodOptimizationInfo.UNKNOWN_CLASS_INLINER_ELIGIBILITY;
   private InitializerInfo initializerInfo = null;
   private boolean initializerEnablingJavaAssertions =
@@ -235,7 +235,7 @@
   }
 
   @Override
-  public ClassInlinerEligibility getClassInlinerEligibility() {
+  public ClassInlinerEligibilityInfo getClassInlinerEligibility() {
     return classInlinerEligibility;
   }
 
@@ -300,7 +300,7 @@
     this.reachabilitySensitive = reachabilitySensitive;
   }
 
-  void setClassInlinerEligibility(ClassInlinerEligibility eligibility) {
+  void setClassInlinerEligibility(ClassInlinerEligibilityInfo eligibility) {
     this.classInlinerEligibility = eligibility;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
index 580e1b2..b1408dd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
@@ -38,6 +38,12 @@
     return method.buildInliningIR(context, appView, valueNumberGenerator, position, origin);
   }
 
+  public IRCode getAndCacheInliningIR(InvokeMethod invoke, DexEncodedMethod method) {
+    IRCode inliningIR = getInliningIR(invoke, method);
+    cacheInliningIR(invoke, inliningIR);
+    return inliningIR;
+  }
+
   public void cacheInliningIR(InvokeMethod invoke, IRCode code) {
     IRCode existing = cache.put(invoke, code);
     assert existing == null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
index 5c3e91c..ae5e066 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
@@ -80,10 +80,6 @@
     }
   }
 
-  public final boolean allLambdas(Predicate<LambdaInfo> predicate) {
-    return !anyLambda(lambda -> !predicate.test(lambda));
-  }
-
   public final boolean anyLambda(Predicate<LambdaInfo> predicate) {
     assert verifyLambdaIds(false);
     for (LambdaInfo info : lambdas.values()) {
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 a165998..3bb09ce 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
@@ -15,18 +15,23 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses;
 import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
 import com.android.tools.r8.ir.code.IRCode;
 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.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
 import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
@@ -40,6 +45,8 @@
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.ThrowingConsumer;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -84,7 +91,8 @@
 
   private abstract static class Mode {
 
-    void rewriteCode(DexEncodedMethod method, IRCode code, DexEncodedMethod context) {}
+    void rewriteCode(
+        DexEncodedMethod method, IRCode code, Inliner inliner, DexEncodedMethod context) {}
 
     void analyzeCode(DexEncodedMethod method, IRCode code) {}
   }
@@ -99,22 +107,50 @@
 
   private class ApplyMode extends Mode {
 
-    private final Set<DexType> lambdaGroupsClasses;
+    private final Map<DexProgramClass, LambdaGroup> lambdaGroups;
     private final LambdaMergerOptimizationInfoFixer optimizationInfoFixer;
 
     ApplyMode(
-        Set<DexType> lambdaGroupTypes, LambdaMergerOptimizationInfoFixer optimizationInfoFixer) {
-      this.lambdaGroupsClasses = lambdaGroupTypes;
+        Map<DexProgramClass, LambdaGroup> lambdaGroups,
+        LambdaMergerOptimizationInfoFixer optimizationInfoFixer) {
+      this.lambdaGroups = lambdaGroups;
       this.optimizationInfoFixer = optimizationInfoFixer;
     }
 
     @Override
-    void rewriteCode(DexEncodedMethod method, IRCode code, DexEncodedMethod context) {
-      if (lambdaGroupsClasses.contains(method.method.holder)) {
-        // Don't rewrite the methods that we have synthesized for the lambda group classes.
+    void rewriteCode(
+        DexEncodedMethod method, IRCode code, Inliner inliner, DexEncodedMethod context) {
+      DexProgramClass clazz = appView.definitionFor(method.method.holder).asProgramClass();
+      assert clazz != null;
+
+      LambdaGroup lambdaGroup = lambdaGroups.get(clazz);
+      if (lambdaGroup == null) {
+        // Only rewrite the methods that have not been synthesized for the lambda group classes.
+        new ApplyStrategy(method, code, context, optimizationInfoFixer).processCode();
         return;
       }
-      new ApplyStrategy(method, code, context, optimizationInfoFixer).processCode();
+
+      if (method.isInitializer()) {
+        // Should not require rewriting.
+        return;
+      }
+
+      assert method.isVirtualMethod();
+      assert context == null;
+
+      Map<InvokeVirtual, InliningInfo> invokesToInline = new IdentityHashMap<>();
+      for (InvokeVirtual invoke : code.<InvokeVirtual>instructions(Instruction::isInvokeVirtual)) {
+        DexType holder = invoke.getInvokedMethod().holder;
+        if (lambdaGroup.containsLambda(holder)) {
+          DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.method.holder);
+          assert singleTarget != null;
+          invokesToInline.put(invoke, new InliningInfo(singleTarget, singleTarget.method.holder));
+        }
+      }
+
+      assert invokesToInline.size() > 1;
+
+      inliner.performForcedInlining(method, code, invokesToInline);
     }
   }
 
@@ -143,7 +179,6 @@
   private final Set<DexEncodedMethod> methodsToReprocess = Sets.newIdentityHashSet();
 
   private final AppView<AppInfoWithLiveness> appView;
-  private final DexItemFactory factory;
   private final Kotlin kotlin;
   private final DiagnosticsHandler reporter;
 
@@ -155,17 +190,20 @@
   private final LambdaTypeVisitor lambdaChecker;
 
   public LambdaMerger(AppView<AppInfoWithLiveness> appView) {
+    DexItemFactory factory = appView.dexItemFactory();
     this.appView = appView;
-    this.factory = appView.dexItemFactory();
     this.kotlin = factory.kotlin;
     this.reporter = appView.options().reporter;
 
-    this.lambdaInvalidator = new LambdaTypeVisitor(factory, this::isMergeableLambda,
-        this::invalidateLambda);
-    this.lambdaChecker = new LambdaTypeVisitor(factory, this::isMergeableLambda,
-        type -> {
-          throw new Unreachable("Unexpected lambda " + type.toSourceString());
-        });
+    this.lambdaInvalidator =
+        new LambdaTypeVisitor(factory, this::isMergeableLambda, this::invalidateLambda);
+    this.lambdaChecker =
+        new LambdaTypeVisitor(
+            factory,
+            this::isMergeableLambda,
+            type -> {
+              throw new Unreachable("Unexpected lambda " + type.toSourceString());
+            });
   }
 
   private void invalidateLambda(DexType lambda) {
@@ -251,20 +289,20 @@
    *       no more invalid lambda class references.
    * </ol>
    */
-  public final void rewriteCode(DexEncodedMethod method, IRCode code) {
+  public final void rewriteCode(DexEncodedMethod method, IRCode code, Inliner inliner) {
     if (mode != null) {
-      mode.rewriteCode(method, code, null);
+      mode.rewriteCode(method, code, inliner, null);
     }
   }
 
   /**
-   * Similar to {@link #rewriteCode(DexEncodedMethod, IRCode)}, but for rewriting code for inlining.
-   * The {@param context} is the caller that {@param method} is being inlined into.
+   * Similar to {@link #rewriteCode(DexEncodedMethod, IRCode, Inliner)}, but for rewriting code for
+   * inlining. The {@param context} is the caller that {@param method} is being inlined into.
    */
   public final void rewriteCodeForInlining(
       DexEncodedMethod method, IRCode code, DexEncodedMethod context) {
     if (mode != null) {
-      mode.rewriteCode(method, code, context);
+      mode.rewriteCode(method, code, null, context);
     }
   }
 
@@ -288,12 +326,7 @@
 
     // Remove invalidated lambdas, compact groups to ensure
     // sequential lambda ids, create group lambda classes.
-    Map<LambdaGroup, DexProgramClass> lambdaGroupsClasses = finalizeLambdaGroups();
-
-    // Mark all the implementation methods for force inlining.
-    for (LambdaGroup group : lambdaGroupsClasses.keySet()) {
-      group.forEachLambda(info -> info.clazz.virtualMethods().forEach(feedback::markForceInline));
-    }
+    BiMap<LambdaGroup, DexProgramClass> lambdaGroupsClasses = finalizeLambdaGroups();
 
     // Fixup optimization info to ensure that the optimization info does not refer to any merged
     // lambdas.
@@ -302,9 +335,7 @@
     feedback.fixupOptimizationInfos(appView, executorService, optimizationInfoFixer);
 
     // Switch to APPLY strategy.
-    Set<DexType> lambdaGroupTypes =
-        lambdaGroupsClasses.values().stream().map(clazz -> clazz.type).collect(Collectors.toSet());
-    this.mode = new ApplyMode(lambdaGroupTypes, optimizationInfoFixer);
+    this.mode = new ApplyMode(lambdaGroupsClasses.inverse(), optimizationInfoFixer);
 
     // Add synthesized lambda group classes to the builder.
     for (Entry<LambdaGroup, DexProgramClass> entry : lambdaGroupsClasses.entrySet()) {
@@ -323,23 +354,15 @@
           encodedMethod -> encodedMethod.markProcessed(ConstraintWithTarget.NEVER));
     }
 
-    // Verify that all implementation methods are marked for force inlining (i.e., check that the
-    // delayed optimization feedback has been flushed).
-    assert lambdaGroupsClasses.keySet().stream()
-        .allMatch(
-            group ->
-                group.allLambdas(
-                    lambda ->
-                        lambda.clazz.virtualMethods().stream()
-                            .map(DexEncodedMethod::getOptimizationInfo)
-                            .allMatch(MethodOptimizationInfo::forceInline)));
-
     converter.optimizeSynthesizedClasses(lambdaGroupsClasses.values(), executorService);
 
     // Rewrite lambda class references into lambda group class
     // references inside methods from the processing queue.
     rewriteLambdaReferences(converter, executorService, feedback);
     this.mode = null;
+
+    appView.setHorizontallyMergedLambdaClasses(
+        new HorizontallyMergedLambdaClasses(lambdas.keySet()));
   }
 
   private void analyzeLambdaClassesStructure(ExecutorService service) throws ExecutionException {
@@ -365,7 +388,7 @@
     ThreadUtils.awaitFutures(futures);
   }
 
-  private Map<LambdaGroup, DexProgramClass> finalizeLambdaGroups() {
+  private BiMap<LambdaGroup, DexProgramClass> finalizeLambdaGroups() {
     for (DexType lambda : invalidatedLambdas) {
       LambdaGroup group = lambdas.get(lambda);
       assert group != null;
@@ -378,7 +401,7 @@
     removeTrivialLambdaGroups();
 
     // Compact lambda groups, synthesize lambda group classes.
-    Map<LambdaGroup, DexProgramClass> result = new LinkedHashMap<>();
+    BiMap<LambdaGroup, DexProgramClass> result = HashBiMap.create();
     for (LambdaGroup group : groups.values()) {
       assert !group.isTrivial() : "No trivial group is expected here.";
       group.compact();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index b9ef1c9..a467cb2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -35,6 +35,7 @@
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
 import java.util.ArrayList;
@@ -57,7 +58,8 @@
   private final ClassStaticizer classStaticizer;
   private final IRConverter converter;
 
-  private final Map<DexEncodedMethod, Collection<Consumer<IRCode>>> processingQueue =
+  // Optimization order matters, hence a collection that preserves orderings.
+  private final Map<DexEncodedMethod, ImmutableList.Builder<Consumer<IRCode>>> processingQueue =
       new IdentityHashMap<>();
 
   private final Set<DexEncodedMethod> referencingExtraMethods = Sets.newIdentityHashSet();
@@ -75,7 +77,7 @@
     this.converter = converter;
   }
 
-  final void run(OptimizationFeedback optimizationFeedback, ExecutorService executorService)
+  final void run(OptimizationFeedback feedback, ExecutorService executorService)
       throws ExecutionException {
     // Filter out candidates based on the information we collected while examining methods.
     finalEligibilityCheck();
@@ -84,15 +86,20 @@
     prepareCandidates();
 
     // Enqueue all host class initializers (only remove instantiations).
-    enqueueMethodsWithCodeOptimization(
-        hostClassInits.keySet(), this::removeCandidateInstantiation);
+    enqueueMethodsWithCodeOptimizations(
+        hostClassInits.keySet(),
+        optimizations ->
+            optimizations
+                .add(this::removeCandidateInstantiation)
+                .add(this::insertAssumeInstructions)
+                .add(collectOptimizationInfo(feedback)));
 
     // Enqueue instance methods to be staticized (only remove references to 'this').
-    enqueueMethodsWithCodeOptimization(
-        methodsToBeStaticized, this::removeReferencesToThis);
+    enqueueMethodsWithCodeOptimizations(
+        methodsToBeStaticized, optimizations -> optimizations.add(this::removeReferencesToThis));
 
     // Process queued methods with associated optimizations
-    processMethodsConcurrently(optimizationFeedback, executorService);
+    processMethodsConcurrently(feedback, executorService);
 
     // TODO(b/140767158): Merge the remaining part below.
     // Convert instance methods into static methods with an extra parameter.
@@ -103,10 +110,16 @@
     // a result of staticizing.)
     methods.addAll(referencingExtraMethods);
     methods.addAll(hostClassInits.keySet());
-    enqueueMethodsWithCodeOptimization(methods, this::rewriteReferences);
+    enqueueMethodsWithCodeOptimizations(
+        methods,
+        optimizations ->
+            optimizations
+                .add(this::rewriteReferences)
+                .add(this::insertAssumeInstructions)
+                .add(collectOptimizationInfo(feedback)));
 
     // Process queued methods with associated optimizations
-    processMethodsConcurrently(optimizationFeedback, executorService);
+    processMethodsConcurrently(feedback, executorService);
   }
 
   private void finalEligibilityCheck() {
@@ -249,15 +262,11 @@
     referencingExtraMethods.removeAll(removedInstanceMethods);
   }
 
-  private void enqueueMethodsWithCodeOptimization(
-      Iterable<DexEncodedMethod> methods, Consumer<IRCode> optimization) {
+  private void enqueueMethodsWithCodeOptimizations(
+      Iterable<DexEncodedMethod> methods,
+      Consumer<ImmutableList.Builder<Consumer<IRCode>>> extension) {
     for (DexEncodedMethod method : methods) {
-      processingQueue
-          .computeIfAbsent(
-              method,
-              // Optimization order might matter, hence a collection that preserves orderings.
-              k -> new ArrayList<>())
-          .add(optimization);
+      extension.accept(processingQueue.computeIfAbsent(method, ignore -> ImmutableList.builder()));
     }
   }
 
@@ -275,7 +284,7 @@
       OptimizationFeedback feedback, ExecutorService executorService) throws ExecutionException {
     ThreadUtils.processItems(
         processingQueue.keySet(),
-        method -> forEachMethod(method, processingQueue.get(method), feedback),
+        method -> forEachMethod(method, processingQueue.get(method).build(), feedback),
         executorService);
     // TODO(b/140767158): No need to clear if we can do every thing in one go.
     processingQueue.clear();
@@ -289,12 +298,18 @@
     Origin origin = appView.appInfo().originFor(method.method.holder);
     IRCode code = method.buildIR(appView, origin);
     codeOptimizations.forEach(codeOptimization -> codeOptimization.accept(code));
-    CodeRewriter.insertAssumeInstructions(code, converter.assumers);
-    converter.collectOptimizationInfo(code, feedback);
     CodeRewriter.removeAssumeInstructions(appView, code);
     converter.finalizeIR(method, code, feedback);
   }
 
+  private void insertAssumeInstructions(IRCode code) {
+    CodeRewriter.insertAssumeInstructions(code, converter.assumers);
+  }
+
+  private Consumer<IRCode> collectOptimizationInfo(OptimizationFeedback feedback) {
+    return code -> converter.collectOptimizationInfo(code, feedback);
+  }
+
   private void removeCandidateInstantiation(IRCode code) {
     CandidateInfo candidateInfo = hostClassInits.get(code.method);
     assert candidateInfo != null;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index a83c407..acb6c55 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -11,7 +11,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.ListIterator;
 import kotlinx.metadata.KmClass;
+import kotlinx.metadata.KmType;
 import kotlinx.metadata.KmTypeVisitor;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -40,23 +42,36 @@
 
   @Override
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-    kmClass.getSupertypes().removeIf(
-        kmType -> {
-          Box<Boolean> isLive = new Box<>(false);
-          kmType.accept(new KmTypeVisitor() {
-            @Override
-            public void visitClass(String name) {
-              String descriptor = DescriptorUtils.javaTypeToDescriptorIfValidJavaType(name);
-              if (descriptor != null) {
-                DexType type = appView.dexItemFactory().createType(name);
-                DexType renamedType = lens.lookupType(type, appView.dexItemFactory());
-                isLive.set(appView.appInfo().isLiveProgramType(renamedType));
-              }
-            }
-          });
-          return !isLive.get();
+    ListIterator<KmType> superTypeIterator = kmClass.getSupertypes().listIterator();
+    while (superTypeIterator.hasNext()) {
+      KmType kmType = superTypeIterator.next();
+      Box<Boolean> isLive = new Box<>(false);
+      Box<DexType> renamed = new Box<>(null);
+      kmType.accept(new KmTypeVisitor() {
+        @Override
+        public void visitClass(String name) {
+          String descriptor = DescriptorUtils.getDescriptorFromKotlinClassifier(name);
+          DexType type = appView.dexItemFactory().createType(descriptor);
+          isLive.set(appView.appInfo().isLiveProgramType(type));
+          DexType renamedType = lens.lookupType(type, appView.dexItemFactory());
+          if (renamedType != type) {
+            renamed.set(renamedType);
+          }
         }
-    );
+      });
+      if (!isLive.get()) {
+        superTypeIterator.remove();
+        continue;
+      }
+      if (renamed.get() != null) {
+        // TODO(b/70169921): need a general util to convert the current clazz's access flag.
+        KmType renamedKmType = new KmType(kmType.getFlags());
+        renamedKmType.visitClass(
+            DescriptorUtils.descriptorToInternalName(renamed.get().toDescriptorString()));
+        superTypeIterator.remove();
+        superTypeIterator.add(renamedKmType);
+      }
+    }
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index 5ccf3e8..226a5f9 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.naming.MemberNaming.Signature.SignatureKind;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
 import java.io.IOException;
 import java.io.StringWriter;
 import java.io.Writer;
@@ -30,9 +31,10 @@
   public static class Builder extends ClassNaming.Builder {
     private final String originalName;
     private final String renamedName;
-    private final Map<MethodSignature, MemberNaming> methodMembers = new HashMap<>();
-    private final Map<FieldSignature, MemberNaming> fieldMembers = new HashMap<>();
-    private final Map<String, List<MappedRange>> mappedRangesByName = new HashMap<>();
+    private final Map<MethodSignature, MemberNaming> methodMembers = Maps.newHashMap();
+    private final Map<FieldSignature, MemberNaming> fieldMembers = Maps.newHashMap();
+    private final Map<String, List<MappedRange>> mappedRangesByName = Maps.newHashMap();
+    private final Map<String, List<MemberNaming>> mappedNamingsByName = Maps.newHashMap();
 
     private Builder(String renamedName, String originalName) {
       this.originalName = originalName;
@@ -46,6 +48,9 @@
       } else {
         fieldMembers.put((FieldSignature) entry.getRenamedSignature(), entry);
       }
+      mappedNamingsByName
+          .computeIfAbsent(entry.getRenamedName(), m -> new ArrayList<>())
+          .add(entry);
       return this;
     }
 
@@ -63,7 +68,7 @@
       }
 
       return new ClassNamingForNameMapper(
-          renamedName, originalName, methodMembers, fieldMembers, map);
+          renamedName, originalName, methodMembers, fieldMembers, map, mappedNamingsByName);
     }
 
     /** The parameters are forwarded to MappedRange constructor, see explanation there. */
@@ -192,17 +197,21 @@
   /** Map of renamed name -> MappedRangesOfName */
   public final Map<String, MappedRangesOfName> mappedRangesByRenamedName;
 
+  public final Map<String, List<MemberNaming>> mappedNamingsByName;
+
   private ClassNamingForNameMapper(
       String renamedName,
       String originalName,
       Map<MethodSignature, MemberNaming> methodMembers,
       Map<FieldSignature, MemberNaming> fieldMembers,
-      Map<String, MappedRangesOfName> mappedRangesByRenamedName) {
+      Map<String, MappedRangesOfName> mappedRangesByRenamedName,
+      Map<String, List<MemberNaming>> mappedNamingsByName) {
     this.renamedName = renamedName;
     this.originalName = originalName;
     this.methodMembers = ImmutableMap.copyOf(methodMembers);
     this.fieldMembers = ImmutableMap.copyOf(fieldMembers);
     this.mappedRangesByRenamedName = mappedRangesByRenamedName;
+    this.mappedNamingsByName = mappedNamingsByName;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
index 18f8459..054917e 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -87,6 +87,10 @@
     return signature.kind() == SignatureKind.METHOD;
   }
 
+  public boolean isFieldNaming() {
+    return signature.kind() == SignatureKind.FIELD;
+  }
+
   public Position getPosition() {
     return position;
   }
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
index 011a7ba..0cee87f 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
@@ -91,7 +91,7 @@
     if (minApiLevel != null) {
       builder.append("# " + MARKER_KEY_MIN_API + ": " + minApiLevel + "\n");
     }
-    if (Version.isDev()) {
+    if (Version.isDevelopmentVersion()) {
       builder.append(
           "# " + MARKER_KEY_COMPILER_HASH + ": " + VersionProperties.INSTANCE.getSha() + "\n");
     }
diff --git a/src/main/java/com/android/tools/r8/references/ArrayReference.java b/src/main/java/com/android/tools/r8/references/ArrayReference.java
index 96cb926..f95a60c 100644
--- a/src/main/java/com/android/tools/r8/references/ArrayReference.java
+++ b/src/main/java/com/android/tools/r8/references/ArrayReference.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.DescriptorUtils;
 
 /** Reference to an array type. */
 @Keep
@@ -34,6 +35,13 @@
     throw new Unreachable("Invalid array type descriptor: " + descriptor);
   }
 
+  static ArrayReference fromBaseType(TypeReference baseType, int dimensions) {
+    return new ArrayReference(
+        dimensions,
+        baseType,
+        DescriptorUtils.toArrayDescriptor(dimensions, baseType.getDescriptor()));
+  }
+
   public int getDimensions() {
     return dimensions;
   }
diff --git a/src/main/java/com/android/tools/r8/references/ClassReference.java b/src/main/java/com/android/tools/r8/references/ClassReference.java
index 1159ec3..9c0f479 100644
--- a/src/main/java/com/android/tools/r8/references/ClassReference.java
+++ b/src/main/java/com/android/tools/r8/references/ClassReference.java
@@ -30,6 +30,11 @@
   }
 
   @Override
+  public ClassReference asClass() {
+    return this;
+  }
+
+  @Override
   public String getDescriptor() {
     return descriptor;
   }
diff --git a/src/main/java/com/android/tools/r8/references/PrimitiveReference.java b/src/main/java/com/android/tools/r8/references/PrimitiveReference.java
index 66c1ae7..eb4ae72 100644
--- a/src/main/java/com/android/tools/r8/references/PrimitiveReference.java
+++ b/src/main/java/com/android/tools/r8/references/PrimitiveReference.java
@@ -105,6 +105,11 @@
   }
 
   @Override
+  public PrimitiveReference asPrimitive() {
+    return this;
+  }
+
+  @Override
   public abstract String getDescriptor();
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/references/Reference.java b/src/main/java/com/android/tools/r8/references/Reference.java
index 8a3d278..94d4ea0 100644
--- a/src/main/java/com/android/tools/r8/references/Reference.java
+++ b/src/main/java/com/android/tools/r8/references/Reference.java
@@ -118,6 +118,16 @@
     return getInstance().arrays.computeIfAbsent(descriptor, ArrayReference::fromDescriptor);
   }
 
+  /** Get an array reference from a base type and dimensions. */
+  public static ArrayReference array(TypeReference baseType, int dimensions) {
+    String arrayDescriptor =
+        DescriptorUtils.toArrayDescriptor(dimensions, baseType.getDescriptor());
+    return getInstance()
+        .arrays
+        .computeIfAbsent(
+            arrayDescriptor, descriptor -> ArrayReference.fromBaseType(baseType, dimensions));
+  }
+
   /** Get a method reference from its full reference specification. */
   public static MethodReference method(
       ClassReference holderClass,
diff --git a/src/main/java/com/android/tools/r8/references/TypeReference.java b/src/main/java/com/android/tools/r8/references/TypeReference.java
index 44fb54a..bfef3f6 100644
--- a/src/main/java/com/android/tools/r8/references/TypeReference.java
+++ b/src/main/java/com/android/tools/r8/references/TypeReference.java
@@ -31,6 +31,33 @@
     return false;
   }
 
+  /**
+   * Return a non-null ClassTypeReference if this type is ClassTypeReference
+   *
+   * @return this with static type of ClassTypeReference
+   */
+  default ClassReference asClass() {
+    return null;
+  }
+
+  /**
+   * Return a non-null ArrayReference if this type is ArrayReference
+   *
+   * @return this with static type of ArrayReference
+   */
+  default ArrayReference asArray() {
+    return null;
+  }
+
+  /**
+   * Return a non-null PrimitiveReference if this type is PrimitiveReference
+   *
+   * @return this with static type of PrimitiveReference
+   */
+  default PrimitiveReference asPrimitive() {
+    return null;
+  }
+
   default String getTypeName() {
     return DescriptorUtils.descriptorToJavaType(getDescriptor());
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index f5b0de8..d004c90 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -567,7 +567,7 @@
 
   public boolean isLiveProgramType(DexType type) {
     DexClass clazz = definitionFor(type);
-    return clazz.isProgramClass() && isLiveProgramClass(clazz.asProgramClass());
+    return clazz != null && clazz.isProgramClass() && isLiveProgramClass(clazz.asProgramClass());
   }
 
   public boolean isNonProgramTypeOrLiveProgramType(DexType type) {
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 0652a33..cee2d66 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -395,7 +395,7 @@
       } else {
         String unknownOption = acceptString();
         String devMessage = "";
-        if (Version.isDev()
+        if (Version.isDevelopmentVersion()
             && unknownOption != null
             && (unknownOption.equals("forceinline") || unknownOption.equals("neverinline"))) {
           devMessage = ", this option needs to be turned on explicitly if used for tests.";
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index af0efe5..e7a7fe5 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -289,7 +289,8 @@
         // Final classes cannot be abstract, so we have to keep the method in that case.
         // Also some other kinds of methods cannot be abstract, so keep them around.
         boolean allowAbstract =
-            clazz.accessFlags.isAbstract()
+            (!options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()
+                    || clazz.accessFlags.isAbstract())
                 && !method.accessFlags.isFinal()
                 && !method.accessFlags.isNative()
                 && !method.accessFlags.isStrict()
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index f07499b..9f4b3e3 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -33,6 +33,7 @@
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool;
@@ -47,10 +48,8 @@
 import com.android.tools.r8.utils.Timing;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
@@ -88,44 +87,6 @@
  */
 public class VerticalClassMerger {
 
-  public static class VerticallyMergedClasses {
-
-    private final Map<DexType, DexType> mergedClasses;
-    private final Map<DexType, List<DexType>> sources;
-
-    private VerticallyMergedClasses(Map<DexType, DexType> mergedClasses) {
-      Map<DexType, List<DexType>> sources = Maps.newIdentityHashMap();
-      mergedClasses.forEach(
-          (source, target) ->
-              sources.computeIfAbsent(target, key -> new ArrayList<>()).add(source));
-      this.mergedClasses = mergedClasses;
-      this.sources = sources;
-    }
-
-    public List<DexType> getSourcesFor(DexType type) {
-      return sources.getOrDefault(type, ImmutableList.of());
-    }
-
-    public DexType getTargetFor(DexType type) {
-      assert mergedClasses.containsKey(type);
-      return mergedClasses.get(type);
-    }
-
-    public boolean hasBeenMergedIntoSubtype(DexType type) {
-      return mergedClasses.containsKey(type);
-    }
-
-    public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) {
-      for (List<DexType> sourcesForTarget : sources.values()) {
-        for (DexType source : sourcesForTarget) {
-          assert appView.appInfo().wasPruned(source)
-              : "Expected vertically merged class `" + source.toSourceString() + "` to be absent";
-        }
-      }
-      return true;
-    }
-  }
-
   private enum AbortReason {
     ALREADY_MERGED,
     ALWAYS_INLINE,
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index 09fa0fe..c3916cb 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -96,6 +96,23 @@
   }
 
   /**
+   * Produces an array descriptor having the number of dimensions specified and the
+   * baseTypeDescriptor as base.
+   *
+   * @param dimensions number of dimensions
+   * @param baseTypeDescriptor the base type
+   * @return the descriptor string
+   */
+  public static String toArrayDescriptor(int dimensions, String baseTypeDescriptor) {
+    StringBuilder sb = new StringBuilder();
+    for (int i = 0; i < dimensions; i++) {
+      sb.append('[');
+    }
+    sb.append(baseTypeDescriptor);
+    return sb.toString();
+  }
+
+  /**
    * Determine the given {@param typeName} is a valid jvms binary name or not (jvms 4.2.1).
    *
    * @param typeName the jvms binary name
@@ -177,13 +194,40 @@
             classNameMapper == null ? clazz : classNameMapper.deobfuscateClassName(clazz);
         return originalName;
       case '[':
-        return descriptorToJavaType(descriptor.substring(1, descriptor.length()), classNameMapper)
-            + "[]";
+        return descriptorToJavaType(descriptor.substring(1), classNameMapper) + "[]";
       default:
         return primitiveDescriptorToJavaType(c);
     }
   }
 
+  public static boolean isPrimitiveDescriptor(String descriptor) {
+    if (descriptor.length() != 1) {
+      return false;
+    }
+    return isPrimitiveType(descriptor.charAt(0));
+  }
+
+  public static boolean isPrimitiveType(char c) {
+    return c == 'Z' || c == 'B' || c == 'S' || c == 'C' || c == 'I' || c == 'F' || c == 'J'
+        || c == 'D';
+  }
+
+  public static boolean isArrayDescriptor(String descriptor) {
+    if (descriptor.length() < 2) {
+      return false;
+    }
+    if (descriptor.charAt(0) == '[') {
+      return isDescriptor(descriptor.substring(1));
+    }
+    return false;
+  }
+
+  public static boolean isDescriptor(String descriptor) {
+    return isClassDescriptor(descriptor)
+        || isPrimitiveDescriptor(descriptor)
+        || isArrayDescriptor(descriptor);
+  }
+
   public static String primitiveDescriptorToJavaType(char primitive) {
     switch (primitive) {
       case 'V':
@@ -300,6 +344,17 @@
   }
 
   /**
+   * Convert a fully qualified name of a classifier in Kotlin metadata to a descriptor.
+   * @param className "org/foo/bar/Baz.Nested"
+   * @return a class descriptor like "Lorg/foo/bar/Baz$Nested;"
+   */
+  public static String getDescriptorFromKotlinClassifier(String className) {
+    assert className != null;
+    assert !className.contains("[") : className;
+    return 'L' + className.replace(JAVA_PACKAGE_SEPARATOR, INNER_CLASS_SEPARATOR) + ';';
+  }
+
+  /**
    * Get unqualified class name from its binary name.
    *
    * @param classBinaryName a class binary name i.e. "java/lang/Object" or "a/b/C$Inner"
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 3a0b969..dc998e2 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -202,7 +202,8 @@
   public boolean enableDevirtualization = true;
   public boolean enableNonNullTracking = true;
   public boolean enableInlining =
-      !Version.isDev() || System.getProperty("com.android.tools.r8.disableinlining") == null;
+      !Version.isDevelopmentVersion()
+          || System.getProperty("com.android.tools.r8.disableinlining") == null;
   // TODO(b/141451716): Evaluate the effect of allowing inlining in the inlinee.
   public boolean applyInliningToInlinee = false;
   public int applyInliningToInlineeMaxDepth = 0;
@@ -293,7 +294,7 @@
     if (!isGeneratingClassFiles()) {
       marker.setMinApi(minApiLevel);
     }
-    if (Version.isDev()) {
+    if (Version.isDevelopmentVersion()) {
       marker.setSha1(VersionProperties.INSTANCE.getSha());
     }
     return marker;
@@ -985,9 +986,11 @@
     public boolean addCallEdgesForLibraryInvokes = false;
 
     public boolean allowTypeErrors =
-        !Version.isDev() || System.getProperty("com.android.tools.r8.allowTypeErrors") != null;
+        !Version.isDevelopmentVersion()
+            || System.getProperty("com.android.tools.r8.allowTypeErrors") != null;
     public boolean allowInvokeErrors = false;
     public boolean disableL8AnnotationRemoval = false;
+    public boolean allowClassInlinerGracefulExit = true;
     public boolean allowUnusedProguardConfigurationRules = true;
     public boolean reportUnusedProguardConfigurationRules = false;
     public boolean alwaysUsePessimisticRegisterAllocation = false;
diff --git a/src/main/java/com/android/tools/r8/utils/OptionalBool.java b/src/main/java/com/android/tools/r8/utils/OptionalBool.java
index 54029d5..abb048d 100644
--- a/src/main/java/com/android/tools/r8/utils/OptionalBool.java
+++ b/src/main/java/com/android/tools/r8/utils/OptionalBool.java
@@ -62,4 +62,18 @@
   public OptionalBool asOptionalBool() {
     return this;
   }
+
+  @Override
+  public boolean equals(Object other) {
+    return this == other;
+  }
+
+  @Override
+  public int hashCode() {
+    return System.identityHashCode(this);
+  }
+
+  // Force all subtypes to implement toString().
+  @Override
+  public abstract String toString();
 }
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index 44a26be..265d333 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -309,4 +309,13 @@
     }
     return true;
   }
+
+  public static char lastChar(String s) {
+    return charFromEnd(s, 0);
+  }
+
+  public static char charFromEnd(String s, int charsFromEnd) {
+    assert s.length() > charsFromEnd;
+    return s.charAt(s.length() - (charsFromEnd + 1));
+  }
 }
diff --git a/src/main/keep.txt b/src/main/keep.txt
index 2748f63..d33b879 100644
--- a/src/main/keep.txt
+++ b/src/main/keep.txt
@@ -12,7 +12,13 @@
 -keep public class com.android.tools.r8.compatdx.CompatDx { public static void main(java.lang.String[]); }
 -keep public class com.android.tools.r8.dexfilemerger.DexFileMerger { public static void main(java.lang.String[]); }
 -keep public class com.android.tools.r8.dexsplitter.DexSplitter { public static void main(java.lang.String[]); }
+
 -keep public class com.android.tools.r8.Version { public static java.lang.String getVersionString(); }
+-keep public class com.android.tools.r8.Version { public static int getMajorVersion(); }
+-keep public class com.android.tools.r8.Version { public static int getMinorVersion(); }
+-keep public class com.android.tools.r8.Version { public static int getPatchVersion(); }
+-keep public class com.android.tools.r8.Version { public static java.lang.String getPreReleaseString(); }
+-keep public class com.android.tools.r8.Version { public static boolean isDevelopmentVersion(); }
 
 -keepattributes LineNumberTable, InnerClasses, EnclosingMethod, Exceptions, Signature
 
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
index 4c14226..7203f39 100644
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.Version;
 import com.android.tools.r8.origin.ArchiveEntryOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
@@ -59,6 +60,8 @@
    * </pre>
    */
   public static void main(String[] args) {
+    // Check version API
+    checkVersionApi();
     // Parse arguments with the commandline parser to make use of its API.
     R8Command.Builder cmd = R8Command.parse(args, origin);
     CompilationMode mode = cmd.getMode();
@@ -507,4 +510,25 @@
       }
     }
   }
+
+  private static void checkVersionApi() {
+    if (Version.getVersionString() == null) {
+      throw new RuntimeException("Expected getVersionString API");
+    }
+    if (Version.getMajorVersion() < -1) {
+      throw new RuntimeException("Expected getMajorVersion API");
+    }
+    if (Version.getMinorVersion() < -1) {
+      throw new RuntimeException("Expected getMinorVersion API");
+    }
+    if (Version.getPatchVersion() < -1) {
+      throw new RuntimeException("Expected getPatchVersion API");
+    }
+    if (Version.getPreReleaseString() == null && false) {
+      throw new RuntimeException("Expected getPreReleaseString API");
+    }
+    if (Version.isDevelopmentVersion() && false) {
+      throw new RuntimeException("Expected isDevelopmentVersion API");
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index db0c7b2..a483cfa 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -191,6 +191,10 @@
     return self();
   }
 
+  public T allowClassInlinerGracefulExit() {
+    return addOptionsModification(options -> options.testing.allowClassInlinerGracefulExit = true);
+  }
+
   public T allowUnusedProguardConfigurationRules() {
     return addOptionsModification(
         options -> options.testing.allowUnusedProguardConfigurationRules = true);
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index b90b40c..023e0e7 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -19,13 +19,33 @@
 import com.android.tools.r8.dex.ApplicationReader;
 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.AppServices;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.SmaliWriter;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerFactory;
+import com.android.tools.r8.shaking.ProguardClassFilter;
+import com.android.tools.r8.shaking.ProguardClassNameList;
+import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.shaking.ProguardKeepRule;
+import com.android.tools.r8.shaking.ProguardKeepRule.Builder;
+import com.android.tools.r8.shaking.ProguardKeepRuleType;
+import com.android.tools.r8.shaking.ProguardTypeMatcher;
+import com.android.tools.r8.shaking.RootSetBuilder;
+import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.shaking.serviceloader.ServiceLoaderMultipleTest.Greeter;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -34,6 +54,7 @@
 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.ListUtils;
 import com.android.tools.r8.utils.PreloadedClassFileProvider;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.TestDescriptionWatcher;
@@ -52,6 +73,7 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
@@ -66,6 +88,8 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -387,6 +411,11 @@
     return buildClasses(programClasses, libraryClasses).build();
   }
 
+  protected static AndroidApp.Builder buildClasses(Collection<Class<?>> programClasses)
+      throws IOException {
+    return buildClasses(programClasses, Collections.emptyList());
+  }
+
   protected static AndroidApp.Builder buildClasses(
       Collection<Class<?>> programClasses, Collection<Class<?>> libraryClasses) throws IOException {
     AndroidApp.Builder builder = AndroidApp.builder();
@@ -527,6 +556,73 @@
     }
   }
 
+  protected static AppView<AppInfoWithSubtyping> computeAppViewWithSubtyping(AndroidApp app)
+      throws Exception {
+    Timing timing = new Timing();
+    InternalOptions options = new InternalOptions();
+    DexApplication application = new ApplicationReader(app, options, timing).read().toDirect();
+    AppView<AppInfoWithSubtyping> appView =
+        AppView.createForR8(new AppInfoWithSubtyping(application), options);
+    appView.setAppServices(AppServices.builder(appView).build());
+    return appView;
+  }
+
+  protected static AppView<AppInfoWithLiveness> computeAppViewWithLiveness(
+      AndroidApp app, Class<?> mainClass) throws Exception {
+    AppView<AppInfoWithSubtyping> appView = computeAppViewWithSubtyping(app);
+    // Run the tree shaker to compute an instance of AppInfoWithLiveness.
+    ExecutorService executor = Executors.newSingleThreadExecutor();
+    DexApplication application = appView.appInfo().app();
+    RootSet rootSet =
+        new RootSetBuilder(
+                appView, application, buildKeepRuleForClass(mainClass, application.dexItemFactory))
+            .run(executor);
+    AppInfoWithLiveness appInfoWithLiveness =
+        EnqueuerFactory.createForInitialTreeShaking(appView)
+            .traceApplication(rootSet, ProguardClassFilter.empty(), executor, application.timing);
+    // We do not run the tree pruner to ensure that the hierarchy is as designed and not modified
+    // due to liveness.
+    return appView.setAppInfo(appInfoWithLiveness);
+  }
+
+  protected static DexType buildType(Class<?> clazz, DexItemFactory factory) {
+    return buildType(Reference.classFromClass(clazz), factory);
+  }
+
+  protected static DexType buildType(TypeReference type, DexItemFactory factory) {
+    return factory.createType(type.getDescriptor());
+  }
+
+  protected static DexMethod buildMethod(Method method, DexItemFactory factory) {
+    return buildMethod(Reference.methodFromMethod(method), factory);
+  }
+
+  protected static DexMethod buildMethod(MethodReference method, DexItemFactory factory) {
+    return factory.createMethod(
+        buildType(method.getHolderClass(), factory),
+        buildProto(method.getReturnType(), method.getFormalTypes(), factory),
+        method.getMethodName());
+  }
+
+  protected static DexProto buildProto(
+      TypeReference returnType, List<TypeReference> formalTypes, DexItemFactory factory) {
+    return factory.createProto(
+        returnType == null ? factory.voidType : buildType(returnType, factory),
+        ListUtils.map(formalTypes, type -> buildType(type, factory)));
+  }
+
+  private static List<ProguardConfigurationRule> buildKeepRuleForClass(
+      Class clazz, DexItemFactory factory) {
+    Builder keepRuleBuilder = ProguardKeepRule.builder();
+    keepRuleBuilder.setSource("buildKeepRuleForClass " + clazz.getTypeName());
+    keepRuleBuilder.setType(ProguardKeepRuleType.KEEP);
+    keepRuleBuilder.setClassNames(
+        ProguardClassNameList.singletonList(
+            ProguardTypeMatcher.create(
+                factory.createType(DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName())))));
+    return Collections.singletonList(keepRuleBuilder.build());
+  }
+
   /** Returns a list containing all the data resources in the given app. */
   public static List<DataEntryResource> getDataResources(AndroidApp app) throws ResourceException {
     List<DataEntryResource> dataResources = new ArrayList<>();
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 882459b..5d8e69b 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -33,6 +33,7 @@
 
   public static final Consumer<InternalOptions> DEFAULT_OPTIONS =
       options -> {
+        options.testing.allowClassInlinerGracefulExit = false;
         options.testing.allowUnusedProguardConfigurationRules = false;
         options.testing.reportUnusedProguardConfigurationRules = true;
       };
diff --git a/src/test/java/com/android/tools/r8/VersionTests.java b/src/test/java/com/android/tools/r8/VersionTests.java
new file mode 100644
index 0000000..ba5ee78
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/VersionTests.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import static com.android.tools.r8.Version.LABEL;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class VersionTests extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  private final TestParameters parameters;
+
+  public VersionTests(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testSemVerInfo() {
+    int majorVersion = Version.getMajorVersion();
+    int minorVersion = Version.getMinorVersion();
+    int patchVersion = Version.getPatchVersion();
+    String preReleaseString = Version.getPreReleaseString();
+    if (LABEL.equals("master")) {
+      assertEquals(-1, majorVersion);
+      assertEquals(-1, minorVersion);
+      assertEquals(-1, patchVersion);
+      assertNull(preReleaseString);
+      assertTrue(Version.getVersionString().startsWith("master"));
+    } else {
+      assertTrue(majorVersion > 0);
+      assertTrue(minorVersion >= 0);
+      assertTrue(patchVersion >= 0);
+      assertNotNull(preReleaseString);
+      assertTrue(
+          Version.getVersionString()
+              .startsWith(
+                  ""
+                      + majorVersion
+                      + "."
+                      + minorVersion
+                      + "."
+                      + patchVersion
+                      + (preReleaseString.isEmpty() ? "" : "-" + preReleaseString)));
+    }
+  }
+
+  @Test
+  public void testDevelopmentPredicate() {
+    assertEquals(LABEL.equals("master") || LABEL.contains("-dev"), Version.isDevelopmentVersion());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/access/NestMethodAccessTest.java b/src/test/java/com/android/tools/r8/graph/access/NestMethodAccessTest.java
new file mode 100644
index 0000000..786177c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/access/NestMethodAccessTest.java
@@ -0,0 +1,153 @@
+// Copyright (c) 2019, 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.access;
+
+import static com.android.tools.r8.TestRuntime.CfVm.JDK11;
+import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.ir.optimize.NestUtils;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NestMethodAccessTest extends TestBase {
+  static final String EXPECTED = StringUtils.lines("A::bar", "A::baz");
+
+  private final TestParameters parameters;
+  private final boolean inSameNest;
+
+  @Parameterized.Parameters(name = "{0}, in-same-nest:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters()
+            .withCfRuntimesStartingFromIncluding(JDK11)
+            .withDexRuntimes()
+            .withAllApiLevels()
+            .build(),
+        BooleanUtils.values());
+  }
+
+  public NestMethodAccessTest(TestParameters parameters, boolean inSameNest) {
+    this.parameters = parameters;
+    this.inSameNest = inSameNest;
+  }
+
+  public Collection<Class<?>> getClasses() {
+    return ImmutableList.of(Main.class);
+  }
+
+  public Collection<byte[]> getTransformedClasses() throws Exception {
+    return ImmutableList.of(
+        withNest(A.class)
+            .setPrivate(A.class.getDeclaredMethod("bar"))
+            .setPrivate(A.class.getDeclaredMethod("baz"))
+            .transform(),
+        withNest(B.class).transform());
+  }
+
+  @Test
+  public void testResolutionAccess() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(getClasses()).addClassProgramData(getTransformedClasses()).build(),
+            Main.class);
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexProgramClass bClass =
+        appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
+    DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
+    // TODO(b/145187573): Update to check the full access control once possible.
+    assertEquals(
+        inSameNest,
+        NestUtils.sameNest(bClass.type, resolutionResult.getSingleTarget().method.holder, appView));
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getClasses())
+        .addProgramClassFileData(getTransformedClasses())
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkExpectedResult);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(getClasses())
+        .addProgramClassFileData(getTransformedClasses())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .disassemble()
+        .apply(
+            result -> {
+              if (parameters.isDexRuntime() && inSameNest) {
+                // TODO(b/145187969): R8 incorrectly compiles the nest based access away.
+                result.assertFailureWithErrorThatMatches(
+                    containsString(NullPointerException.class.getName()));
+              } else {
+                checkExpectedResult(result);
+              }
+            });
+  }
+
+  private void checkExpectedResult(TestRunResult<?> result) {
+    if (inSameNest) {
+      result.assertSuccessWithOutput(EXPECTED);
+    } else {
+      result.assertFailureWithErrorThatMatches(containsString(IllegalAccessError.class.getName()));
+    }
+  }
+
+  private ClassFileTransformer withNest(Class<?> clazz) throws Exception {
+    if (inSameNest) {
+      // If in the same nest make A host and B a member.
+      return transformer(clazz).setNest(A.class, B.class);
+    }
+    // Otherwise, set the class to be its own host and no additional members.
+    return transformer(clazz).setNest(clazz);
+  }
+
+  static class A {
+    /* will be private */ void bar() {
+      System.out.println("A::bar");
+    }
+
+    /* will be private */ static void baz() {
+      System.out.println("A::baz");
+    }
+  }
+
+  static class B {
+    public void foo() {
+      // Virtual invoke to private method.
+      new A().bar();
+      // Static invoke to private method.
+      A.baz();
+    }
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      new B().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialForInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForInvokeVirtualTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/graph/InvokeSpecialForInvokeVirtualTest.java
rename to src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForInvokeVirtualTest.java
index 816288d..283f479 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSpecialForInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForInvokeVirtualTest.java
@@ -2,7 +2,7 @@
 // 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;
+package com.android.tools.r8.graph.invokespecial;
 
 import static org.junit.Assert.assertEquals;
 import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialForNonDeclaredInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForNonDeclaredInvokeVirtualTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/graph/InvokeSpecialForNonDeclaredInvokeVirtualTest.java
rename to src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForNonDeclaredInvokeVirtualTest.java
index a54fa1d..1477df4 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSpecialForNonDeclaredInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForNonDeclaredInvokeVirtualTest.java
@@ -2,7 +2,7 @@
 // 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;
+package com.android.tools.r8.graph.invokespecial;
 
 import static org.junit.Assert.assertEquals;
 import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceTest.java
similarity index 98%
rename from src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceTest.java
rename to src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceTest.java
index f49c764..24ae2fc 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceTest.java
@@ -2,7 +2,7 @@
 // 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;
+package com.android.tools.r8.graph.invokespecial;
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceWithBridgeTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridgeTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceWithBridgeTest.java
rename to src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridgeTest.java
index 1253df8..647881d 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceWithBridgeTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridgeTest.java
@@ -2,7 +2,7 @@
 // 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;
+package com.android.tools.r8.graph.invokespecial;
 
 import static org.junit.Assert.assertEquals;
 import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialMissingInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialMissingInvokeVirtualTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/graph/InvokeSpecialMissingInvokeVirtualTest.java
rename to src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialMissingInvokeVirtualTest.java
index e046730..5343c5a 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSpecialMissingInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialMissingInvokeVirtualTest.java
@@ -2,7 +2,7 @@
 // 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;
+package com.android.tools.r8.graph.invokespecial;
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialOnSameClassTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/graph/InvokeSpecialOnSameClassTest.java
rename to src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassTest.java
index 00f4bb3..7fae808 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSpecialOnSameClassTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassTest.java
@@ -2,7 +2,7 @@
 // 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;
+package com.android.tools.r8.graph.invokespecial;
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialTest.java
similarity index 90%
rename from src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java
rename to src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialTest.java
index 83d0b83..44b676a 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialTest.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2019, 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;
+package com.android.tools.r8.graph.invokespecial;
 
 import static com.android.tools.r8.utils.DescriptorUtils.javaTypeToDescriptor;
 import static org.hamcrest.CoreMatchers.containsString;
@@ -13,9 +13,6 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.graph.invokespecial.Main;
-import com.android.tools.r8.graph.invokespecial.TestClass;
-import com.android.tools.r8.graph.invokespecial.TestClassDump;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
 import org.junit.BeforeClass;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/HashCodeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/HashCodeTest.java
new file mode 100644
index 0000000..2a2ad92
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/HashCodeTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2019, 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.callsites;
+
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class HashCodeTest extends TestBase {
+  private static final Class<?> MAIN = TestClass.class;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public HashCodeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(HashCodeTest.class)
+        .addKeepMainRule(MAIN)
+        .enableMergeAnnotations()
+        .addOptionsModification(o -> {
+          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+        })
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines("10");
+  }
+
+  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+    // TODO(b/139246447): should avoid visiting A#<init>, which is trivial, default init!
+    assert encodedMethod.method.holder.toSourceString().endsWith("A")
+        && encodedMethod.toSourceString().contains("<init>")
+        : "Unexpected revisit: " + encodedMethod.toSourceString();
+  }
+
+  static class TestClass {
+    public static void main(String[] args) {
+      A obj = System.currentTimeMillis() > 0 ? new B() : new C();
+      System.out.println(obj.hashCode());
+    }
+  }
+
+  @NeverMerge
+  static class A {
+    @Override
+    public int hashCode() {
+      return 1;
+    }
+  }
+
+  @NeverMerge
+  static class B extends A {
+    @Override
+    public int hashCode() {
+      return super.hashCode() * 7 + 3;
+    }
+  }
+
+  static class C extends B {
+    @Override
+    public int hashCode() {
+      return super.hashCode() * 31 + 17;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceWithRefinedReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java
similarity index 98%
rename from src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceWithRefinedReceiverTest.java
rename to src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java
index 8e9e61f..b804cbe 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceWithRefinedReceiverTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2019, 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.callsites.nullability;
+package com.android.tools.r8.ir.optimize.callsites;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualWithRefinedReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java
similarity index 98%
rename from src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualWithRefinedReceiverTest.java
rename to src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java
index 354ba3b..c69f0c1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualWithRefinedReceiverTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2019, 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.callsites.nullability;
+package com.android.tools.r8.ir.optimize.callsites;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/KeptMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/KeptMethodTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/KeptMethodTest.java
rename to src/test/java/com/android/tools/r8/ir/optimize/callsites/KeptMethodTest.java
index 3822a49..3e87f06 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/KeptMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/KeptMethodTest.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2019, 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.callsites.nullability;
+package com.android.tools.r8.ir.optimize.callsites;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/LibraryMethodOverridesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java
similarity index 98%
rename from src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/LibraryMethodOverridesTest.java
rename to src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java
index ba8c1fd..19172e7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/LibraryMethodOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2019, 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.callsites.nullability;
+package com.android.tools.r8.ir.optimize.callsites;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/WithStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/WithStaticizerTest.java
rename to src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
index e600ebc..4fe7fde 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/WithStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2019, 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.callsites.nullability;
+package com.android.tools.r8.ir.optimize.callsites;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
index baea5e4..5f0a56c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
@@ -65,6 +65,7 @@
     if (methodName.equals("test")) {
       upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
     } else {
+      // TODO(b/139246447): should avoid visiting <init>, which is trivial, default init!
       // For testing purpose, `Base` is not merged and kept. The system correctly caught that, when
       // the default initializer is invoked, the receiver had a refined type, `Sub1`.
       upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(0);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
index e3bd783..f31919a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
@@ -60,6 +60,7 @@
         : "Unexpected revisit: " + encodedMethod.toSourceString();
     CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
     // `arg` for `test` or the receiver of `Base#<init>`.
+    // TODO(b/139246447): should avoid visiting <init>, which is trivial, default init!
     // For testing purpose, `Base` is not merged and kept. The system correctly caught that, when
     // the default initializer is invoked, the receiver had a refined type, `Sub1`.
     TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(0);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
index b6dae68..81da3d5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
@@ -65,6 +65,7 @@
     if (methodName.equals("m")) {
       upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
     } else {
+      // TODO(b/139246447): should avoid visiting <init>, which is trivial, default init!
       // For testing purpose, `Base` is not merged and kept. The system correctly caught that, when
       // the default initializer is invoked, the receiver had a refined type, `Sub1`.
       upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(0);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithInheritanceTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithInheritanceTest.java
index 37e5a31..a1042ce 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithInheritanceTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithInheritanceTest.java
@@ -5,12 +5,14 @@
 package com.android.tools.r8.ir.optimize.classinliner;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -21,29 +23,31 @@
 @RunWith(Parameterized.class)
 public class BuilderWithInheritanceTest extends TestBase {
 
-  private final Backend backend;
+  private final TestParameters parameters;
 
-  @Parameters(name = "Backend: {0}")
-  public static Backend[] data() {
-    return ToolHelper.getBackends();
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public BuilderWithInheritanceTest(Backend backend) {
-    this.backend = backend;
+  public BuilderWithInheritanceTest(TestParameters parameters) {
+    this.parameters = parameters;
   }
 
   @Test
   public void test() throws Exception {
     CodeInspector inspector =
-        testForR8(backend)
+        testForR8(parameters.getBackend())
             .addInnerClasses(BuilderWithInheritanceTest.class)
             .addKeepMainRule(TestClass.class)
+            // TODO(b/145090972): Should never need to exit gracefully during testing.
+            .allowClassInlinerGracefulExit()
             .enableInliningAnnotations()
             .enableMergeAnnotations()
-            .run(TestClass.class)
+            .run(parameters.getRuntime(), TestClass.class)
             .assertSuccessWithOutput("42")
             .inspector();
-    assertThat(inspector.clazz(Builder.class), isPresent());
+    assertThat(inspector.clazz(Builder.class), not(isPresent()));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/EscapeFromParentConstructorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/EscapeFromParentConstructorTest.java
new file mode 100644
index 0000000..2062667
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/EscapeFromParentConstructorTest.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2019, 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 com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EscapeFromParentConstructorTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public EscapeFromParentConstructorTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(EscapeFromParentConstructorTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    // The receiver escapes from BuilderBase.<init>(), and therefore, Builder is not considered
+    // eligible for class inlining.
+    assertThat(inspector.clazz(BuilderBase.class), isPresent());
+    assertThat(inspector.clazz(Builder.class), isPresent());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(new Builder().build());
+    }
+  }
+
+  @NeverMerge
+  static class BuilderBase {
+
+    String greeting;
+
+    BuilderBase() {
+      initialize();
+    }
+
+    @NeverInline
+    void initialize() {
+      this.greeting = "Hello world!";
+    }
+  }
+
+  static class Builder extends BuilderBase {
+
+    String build() {
+      return greeting;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/cost/NonMaterializingFieldAccessesAfterClassInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/cost/NonMaterializingFieldAccessesAfterClassInliningTest.java
new file mode 100644
index 0000000..c95fa85
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/cost/NonMaterializingFieldAccessesAfterClassInliningTest.java
@@ -0,0 +1,216 @@
+// Copyright (c) 2019, 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.cost;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NonMaterializingFieldAccessesAfterClassInliningTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public NonMaterializingFieldAccessesAfterClassInliningTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(NonMaterializingFieldAccessesAfterClassInliningTest.class)
+        .addKeepMainRule(TestClass.class)
+        // Should be able to class inline Builder even when the threshold is low.
+        .addOptionsModification(options -> options.classInliningInstructionAllowance = 3)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertThat(inspector.clazz(Greeter.class), isPresent());
+    assertThat(inspector.clazz(Builder.class), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(
+          new Builder()
+              .setC1('H')
+              .setC2('e')
+              .setC3('l')
+              .setC4('l')
+              .setC5('o')
+              .setC6(' ')
+              .setC7('w')
+              .setC8('o')
+              .setC9('r')
+              .setC10('l')
+              .setC11('d')
+              .setC12('!')
+              .build());
+    }
+  }
+
+  static class Greeter {
+
+    final char c1;
+    final char c2;
+    final char c3;
+    final char c4;
+    final char c5;
+    final char c6;
+    final char c7;
+    final char c8;
+    final char c9;
+    final char c10;
+    final char c11;
+    final char c12;
+
+    Greeter(
+        char c1,
+        char c2,
+        char c3,
+        char c4,
+        char c5,
+        char c6,
+        char c7,
+        char c8,
+        char c9,
+        char c10,
+        char c11,
+        char c12) {
+      this.c1 = c1;
+      this.c2 = c2;
+      this.c3 = c3;
+      this.c4 = c4;
+      this.c5 = c5;
+      this.c6 = c6;
+      this.c7 = c7;
+      this.c8 = c8;
+      this.c9 = c9;
+      this.c10 = c10;
+      this.c11 = c11;
+      this.c12 = c12;
+    }
+
+    @Override
+    public String toString() {
+      return Character.toString(c1) + c2 + c3 + c4 + c5 + c6 + c7 + c8 + c9 + c10 + c11 + c12;
+    }
+  }
+
+  static class Builder {
+
+    char c1;
+    char c2;
+    char c3;
+    char c4;
+    char c5;
+    char c6;
+    char c7;
+    char c8;
+    char c9;
+    char c10;
+    char c11;
+    char c12;
+
+    @NeverInline
+    Builder setC1(char c) {
+      this.c1 = c;
+      return this;
+    }
+
+    @NeverInline
+    Builder setC2(char c) {
+      this.c2 = c;
+      return this;
+    }
+
+    @NeverInline
+    Builder setC3(char c) {
+      this.c3 = c;
+      return this;
+    }
+
+    @NeverInline
+    Builder setC4(char c) {
+      this.c4 = c;
+      return this;
+    }
+
+    @NeverInline
+    Builder setC5(char c) {
+      this.c5 = c;
+      return this;
+    }
+
+    @NeverInline
+    Builder setC6(char c) {
+      this.c6 = c;
+      return this;
+    }
+
+    @NeverInline
+    Builder setC7(char c) {
+      this.c7 = c;
+      return this;
+    }
+
+    @NeverInline
+    Builder setC8(char c) {
+      this.c8 = c;
+      return this;
+    }
+
+    @NeverInline
+    Builder setC9(char c) {
+      this.c9 = c;
+      return this;
+    }
+
+    @NeverInline
+    Builder setC10(char c) {
+      this.c10 = c;
+      return this;
+    }
+
+    @NeverInline
+    Builder setC11(char c) {
+      this.c11 = c;
+      return this;
+    }
+
+    @NeverInline
+    Builder setC12(char c) {
+      this.c12 = c;
+      return this;
+    }
+
+    @NeverInline
+    Greeter build() {
+      return new Greeter(c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
new file mode 100644
index 0000000..cdb5538
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2019, 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.metadata;
+
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.utils.DescriptorUtils;
+
+abstract class KotlinMetadataTestBase extends KotlinTestBase {
+
+  KotlinMetadataTestBase(KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+  }
+
+  static final String PKG_PREFIX =
+      DescriptorUtils.getBinaryNameFromJavaType(
+          KotlinMetadataTestBase.class.getPackage().getName());
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java
similarity index 72%
rename from src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java
rename to src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java
index c7ba86e..3ba5ecd 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java
@@ -11,16 +11,13 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import java.nio.file.Path;
 import java.util.Collection;
@@ -30,7 +27,7 @@
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class MetadataRenameTest extends KotlinTestBase {
+public class MetadataRenameInExtensionTest extends KotlinMetadataTestBase {
 
   private final TestParameters parameters;
 
@@ -40,33 +37,19 @@
         getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
   }
 
-  public MetadataRenameTest(TestParameters parameters, KotlinTargetVersion targetVersion) {
+  public MetadataRenameInExtensionTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
     super(targetVersion);
     this.parameters = parameters;
   }
 
-  private static final String PKG_PREFIX =
-      DescriptorUtils.getBinaryNameFromJavaType(MetadataRenameTest.class.getPackage().getName());
-  private static Path supertypeLibJar;
   private static Path extLibJar;
 
   @BeforeClass
   public static void createLibJar() throws Exception {
-    String supertypeLibFolder = PKG_PREFIX + "/supertype_lib";
-    supertypeLibJar = getStaticTemp().newFile("supertype_lib.jar").toPath();
-    ProcessResult processResult =
-        ToolHelper.runKotlinc(
-            null,
-            supertypeLibJar,
-            null,
-            getKotlinFileInTest(supertypeLibFolder, "impl"),
-            getKotlinFileInTest(supertypeLibFolder + "/internal", "itf")
-        );
-    assertEquals(0, processResult.exitCode);
-
     String extLibFolder = PKG_PREFIX + "/extension_lib";
     extLibJar = getStaticTemp().newFile("ext_lib.jar").toPath();
-    processResult =
+    ProcessResult processResult =
         ToolHelper.runKotlinc(
             null,
             extLibJar,
@@ -77,55 +60,7 @@
   }
 
   @Test
-  public void b143687784() throws Exception {
-    assumeTrue(parameters.getRuntime().isCf());
-
-    R8TestCompileResult compileResult =
-        testForR8(parameters.getBackend())
-            .addProgramFiles(supertypeLibJar)
-            // Keep non-private members except for ones in `internal` definitions.
-            .addKeepRules("-keep public class !**.internal.**, * { !private *; }")
-            .addKeepAttributes("*Annotation*")
-            .compile();
-    String pkg = getClass().getPackage().getName();
-    final String itfClassName = pkg + ".supertype_lib.internal.Itf";
-    final String implClassName = pkg + ".supertype_lib.Impl";
-    compileResult.inspect(inspector -> {
-      ClassSubject itf = inspector.clazz(itfClassName);
-      assertThat(itf, not(isPresent()));
-
-      ClassSubject impl = inspector.clazz(implClassName);
-      assertThat(impl, isPresent());
-      assertThat(impl, not(isRenamed()));
-      // API entry is kept, hence the presence of Metadata.
-      DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
-      assertNotNull(metadata);
-      assertThat(metadata.toString(), not(containsString("internal")));
-      assertThat(metadata.toString(), not(containsString("Itf")));
-    });
-
-    Path r8ProcessedLibZip = temp.newFile("r8-lib.zip").toPath();
-    compileResult.writeToZip(r8ProcessedLibZip);
-
-    String appFolder = PKG_PREFIX + "/supertype_app";
-    Path output =
-        kotlinc(parameters.getRuntime().asCf())
-            .addClasspathFiles(r8ProcessedLibZip)
-            .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
-            .setOutputPath(temp.newFolder().toPath())
-            .compile();
-
-    testForJvm()
-        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), r8ProcessedLibZip)
-        .addClasspath(output)
-        .run(parameters.getRuntime(), pkg + ".supertype_app.MainKt")
-        .assertSuccessWithOutputLines("Impl::foo", "Program::foo");
-  }
-
-  @Test
-  public void testMetadataInExtension() throws Exception {
-    assumeTrue(parameters.getRuntime().isCf());
-
+  public void testMetadataInExtension_merged() throws Exception {
     R8TestCompileResult compileResult =
         testForR8(parameters.getBackend())
             .addProgramFiles(extLibJar)
@@ -161,11 +96,61 @@
         kotlinc(parameters.getRuntime().asCf())
             .addClasspathFiles(r8ProcessedLibZip)
             .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
-            // TODO(b/143687784): update to just .compile() once fixed.
             .setOutputPath(temp.newFolder().toPath())
+            // TODO(b/143687784): update to just .compile() once fixed.
             .compileRaw();
     // TODO(b/143687784): should be able to compile!
     assertNotEquals(0, kotlinTestCompileResult.exitCode);
     assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: doStuff"));
   }
+
+  @Test
+  public void testMetadataInExtension_renamed() throws Exception {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(extLibJar)
+            // Keep the B class and its interface (which has the doStuff method).
+            .addKeepRules("-keep class **.B")
+            .addKeepRules("-keep class **.I { <methods>; }")
+            // Keep Super, but allow minification.
+            .addKeepRules("-keep,allowobfuscation class **.Super")
+            // Keep the BKt extension method which requires metadata
+            // to be called with Kotlin syntax from other kotlin code.
+            .addKeepRules("-keep class **.BKt { <methods>; }")
+            .addKeepAttributes("*Annotation*")
+            .compile();
+    String pkg = getClass().getPackage().getName();
+    final String superClassName = pkg + ".extension_lib.Super";
+    final String bClassName = pkg + ".extension_lib.B";
+    compileResult.inspect(inspector -> {
+      ClassSubject sup = inspector.clazz(superClassName);
+      assertThat(sup, isPresent());
+      assertThat(sup, isRenamed());
+
+      ClassSubject impl = inspector.clazz(bClassName);
+      assertThat(impl, isPresent());
+      assertThat(impl, not(isRenamed()));
+      // API entry is kept, hence the presence of Metadata.
+      DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
+      assertNotNull(metadata);
+      assertThat(metadata.toString(), not(containsString("Super")));
+    });
+
+    Path r8ProcessedLibZip = temp.newFile("r8-lib.zip").toPath();
+    compileResult.writeToZip(r8ProcessedLibZip);
+
+    String appFolder = PKG_PREFIX + "/extension_app";
+    Path output =
+        kotlinc(parameters.getRuntime().asCf())
+            .addClasspathFiles(r8ProcessedLibZip)
+            .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), r8ProcessedLibZip)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), pkg + ".extension_app.MainKt")
+        .assertSuccessWithOutputLines("do stuff", "do stuff");
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSupertypeTest.java
similarity index 68%
copy from src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java
copy to src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSupertypeTest.java
index c7ba86e..7001909 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSupertypeTest.java
@@ -9,18 +9,14 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import java.nio.file.Path;
 import java.util.Collection;
@@ -30,7 +26,7 @@
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class MetadataRenameTest extends KotlinTestBase {
+public class MetadataRenameInSupertypeTest extends KotlinMetadataTestBase {
 
   private final TestParameters parameters;
 
@@ -40,15 +36,13 @@
         getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
   }
 
-  public MetadataRenameTest(TestParameters parameters, KotlinTargetVersion targetVersion) {
+  public MetadataRenameInSupertypeTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
     super(targetVersion);
     this.parameters = parameters;
   }
 
-  private static final String PKG_PREFIX =
-      DescriptorUtils.getBinaryNameFromJavaType(MetadataRenameTest.class.getPackage().getName());
   private static Path supertypeLibJar;
-  private static Path extLibJar;
 
   @BeforeClass
   public static void createLibJar() throws Exception {
@@ -63,23 +57,10 @@
             getKotlinFileInTest(supertypeLibFolder + "/internal", "itf")
         );
     assertEquals(0, processResult.exitCode);
-
-    String extLibFolder = PKG_PREFIX + "/extension_lib";
-    extLibJar = getStaticTemp().newFile("ext_lib.jar").toPath();
-    processResult =
-        ToolHelper.runKotlinc(
-            null,
-            extLibJar,
-            null,
-            getKotlinFileInTest(extLibFolder, "B")
-        );
-    assertEquals(0, processResult.exitCode);
   }
 
   @Test
-  public void b143687784() throws Exception {
-    assumeTrue(parameters.getRuntime().isCf());
-
+  public void b143687784_merged() throws Exception {
     R8TestCompileResult compileResult =
         testForR8(parameters.getBackend())
             .addProgramFiles(supertypeLibJar)
@@ -123,49 +104,50 @@
   }
 
   @Test
-  public void testMetadataInExtension() throws Exception {
-    assumeTrue(parameters.getRuntime().isCf());
-
+  public void b143687784_renamed() throws Exception {
     R8TestCompileResult compileResult =
         testForR8(parameters.getBackend())
-            .addProgramFiles(extLibJar)
-            // Keep the B class and its interface (which has the doStuff method).
-            .addKeepRules("-keep class **.B")
-            .addKeepRules("-keep class **.I { <methods>; }")
-            // Keep the BKt extension method which requires metadata
-            // to be called with Kotlin syntax from other kotlin code.
-            .addKeepRules("-keep class **.BKt { <methods>; }")
+            .addProgramFiles(supertypeLibJar)
+            // Keep non-private members except for ones in `internal` definitions.
+            .addKeepRules("-keep public class !**.internal.**, * { !private *; }")
+            // Keep `internal` definitions, but allow minification.
+            .addKeepRules("-keep,allowobfuscation class **.internal.** { *; }")
             .addKeepAttributes("*Annotation*")
             .compile();
     String pkg = getClass().getPackage().getName();
-    final String superClassName = pkg + ".extension_lib.Super";
-    final String bClassName = pkg + ".extension_lib.B";
+    final String itfClassName = pkg + ".supertype_lib.internal.Itf";
+    final String implClassName = pkg + ".supertype_lib.Impl";
     compileResult.inspect(inspector -> {
-      ClassSubject sup = inspector.clazz(superClassName);
-      assertThat(sup, not(isPresent()));
+      ClassSubject itf = inspector.clazz(itfClassName);
+      assertThat(itf, isPresent());
+      assertThat(itf, isRenamed());
 
-      ClassSubject impl = inspector.clazz(bClassName);
+      ClassSubject impl = inspector.clazz(implClassName);
       assertThat(impl, isPresent());
       assertThat(impl, not(isRenamed()));
       // API entry is kept, hence the presence of Metadata.
       DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
       assertNotNull(metadata);
-      assertThat(metadata.toString(), not(containsString("Super")));
+      assertThat(metadata.toString(), not(containsString("internal")));
+      assertThat(metadata.toString(), not(containsString("Itf")));
+      assertThat(metadata.toString(), containsString("a/a"));
     });
 
     Path r8ProcessedLibZip = temp.newFile("r8-lib.zip").toPath();
     compileResult.writeToZip(r8ProcessedLibZip);
 
-    String appFolder = PKG_PREFIX + "/extension_app";
-    ProcessResult kotlinTestCompileResult =
+    String appFolder = PKG_PREFIX + "/supertype_app";
+    Path output =
         kotlinc(parameters.getRuntime().asCf())
             .addClasspathFiles(r8ProcessedLibZip)
             .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
-            // TODO(b/143687784): update to just .compile() once fixed.
             .setOutputPath(temp.newFolder().toPath())
-            .compileRaw();
-    // TODO(b/143687784): should be able to compile!
-    assertNotEquals(0, kotlinTestCompileResult.exitCode);
-    assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: doStuff"));
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), r8ProcessedLibZip)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), pkg + ".supertype_app.MainKt")
+        .assertSuccessWithOutputLines("Impl::foo", "Program::foo");
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
index 155c91c..fa46140 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
@@ -15,8 +15,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.util.Collection;
@@ -53,6 +51,8 @@
             .addKeepMainRule(mainClassName)
             .addKeepAttributes("*Annotation*")
             .addKeepRules("-keep class kotlin.Metadata")
+            // TODO(b/145090972): Should never need to exit gracefully during testing.
+            .allowClassInlinerGracefulExit()
             .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), mainClassName);
     CodeInspector inspector = result.inspector();
@@ -67,5 +67,4 @@
     // All other classes can be renamed, hence the absence of Metadata;
     assertNull(retrieveMetadata(impl1.getDexClass()));
   }
-
 }
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index ec01f62..b1bcb52 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -6,17 +6,12 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.AsmTestBase;
-import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.AppServices;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
-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.graph.ResolutionResult;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.resolution.singletarget.Main;
 import com.android.tools.r8.resolution.singletarget.one.AbstractSubClass;
 import com.android.tools.r8.resolution.singletarget.one.AbstractTopClass;
@@ -34,26 +29,10 @@
 import com.android.tools.r8.resolution.singletarget.two.OtherSubSubClassOne;
 import com.android.tools.r8.resolution.singletarget.two.OtherSubSubClassTwo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.EnqueuerFactory;
-import com.android.tools.r8.shaking.ProguardClassFilter;
-import com.android.tools.r8.shaking.ProguardClassNameList;
-import com.android.tools.r8.shaking.ProguardConfigurationRule;
-import com.android.tools.r8.shaking.ProguardKeepRule;
-import com.android.tools.r8.shaking.ProguardKeepRule.Builder;
-import com.android.tools.r8.shaking.ProguardKeepRuleType;
-import com.android.tools.r8.shaking.ProguardTypeMatcher;
-import com.android.tools.r8.shaking.RootSetBuilder;
-import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.stream.Collectors;
 import org.junit.Assert;
 import org.junit.BeforeClass;
@@ -103,42 +82,11 @@
     this.allTargetHolders = allTargetHolders;
   }
 
-  public static AppInfoWithLiveness createAppInfoWithLiveness(AndroidApp app, Class<?> mainClass)
-      throws Exception {
-    // Run the tree shaker to compute an instance of AppInfoWithLiveness.
-    Timing timing = new Timing();
-    InternalOptions options = new InternalOptions();
-    DexApplication application = new ApplicationReader(app, options, timing).read().toDirect();
-    AppView<? extends AppInfoWithSubtyping> appView =
-        AppView.createForR8(new AppInfoWithSubtyping(application), options);
-    appView.setAppServices(AppServices.builder(appView).build());
-
-    ExecutorService executor = Executors.newSingleThreadExecutor();
-    RootSet rootSet =
-        new RootSetBuilder(
-                appView, application, buildKeepRuleForClass(mainClass, application.dexItemFactory))
-            .run(executor);
-    return EnqueuerFactory.createForInitialTreeShaking(appView)
-        .traceApplication(rootSet, ProguardClassFilter.empty(), executor, timing);
-    // We do not run the tree pruner to ensure that the hierarchy is as designed and not modified
-    // due to liveness.
-  }
-
   @BeforeClass
   public static void computeAppInfo() throws Exception {
-    appInfo = createAppInfoWithLiveness(readClassesAndAsmDump(CLASSES, ASM_CLASSES), Main.class);
-  }
-
-  private static List<ProguardConfigurationRule> buildKeepRuleForClass(Class clazz,
-      DexItemFactory factory) {
-    Builder keepRuleBuilder = ProguardKeepRule.builder();
-    keepRuleBuilder.setSource("buildKeepRuleForClass " + clazz.getTypeName());
-    keepRuleBuilder.setType(ProguardKeepRuleType.KEEP);
-    keepRuleBuilder.setClassNames(
-        ProguardClassNameList.singletonList(
-            ProguardTypeMatcher.create(
-                factory.createType(DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName())))));
-    return Collections.singletonList(keepRuleBuilder.build());
+    appInfo =
+        computeAppViewWithLiveness(readClassesAndAsmDump(CLASSES, ASM_CLASSES), Main.class)
+            .appInfo();
   }
 
   private static Object[] singleTarget(String name, Class<?> receiverAndTarget) {
@@ -244,19 +192,14 @@
         });
   }
 
-  public static DexMethod buildMethod(Class clazz, String name, AppInfo appInfo) {
-    return appInfo
-        .dexItemFactory()
-        .createMethod(
-            toType(clazz, appInfo),
-            appInfo.dexItemFactory().createProto(appInfo.dexItemFactory().voidType),
-            name);
+  public static DexMethod buildNullaryVoidMethod(Class clazz, String name, AppInfo appInfo) {
+    return buildMethod(
+        Reference.method(Reference.classFromClass(clazz), name, Collections.emptyList(), null),
+        appInfo.dexItemFactory());
   }
 
-  public static DexType toType(Class clazz, AppInfo appInfo) {
-    return appInfo
-        .dexItemFactory()
-        .createType(DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName()));
+  private static DexType toType(Class clazz, AppInfo appInfo) {
+    return buildType(clazz, appInfo.dexItemFactory());
   }
 
   private final String methodName;
@@ -266,7 +209,7 @@
 
   @Test
   public void lookupSingleTarget() {
-    DexMethod method = buildMethod(invokeReceiver, methodName, appInfo);
+    DexMethod method = buildNullaryVoidMethod(invokeReceiver, methodName, appInfo);
     Assert.assertNotNull(
         appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).getSingleTarget());
     DexEncodedMethod singleVirtualTarget = appInfo.lookupSingleVirtualTarget(method, method.holder);
@@ -281,7 +224,7 @@
 
   @Test
   public void lookupVirtualTargets() {
-    DexMethod method = buildMethod(invokeReceiver, methodName, appInfo);
+    DexMethod method = buildNullaryVoidMethod(invokeReceiver, methodName, appInfo);
     Assert.assertNotNull(
         appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).getSingleTarget());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
index ccdb49b..5fe17d5 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
@@ -59,11 +59,11 @@
 
   @BeforeClass
   public static void computeAppInfo() throws Exception {
-    appInfo = SingleTargetLookupTest.createAppInfoWithLiveness(readClasses(CLASSES), Main.class);
+    appInfo = computeAppViewWithLiveness(readClasses(CLASSES), Main.class).appInfo();
   }
 
   private static DexMethod buildMethod(Class clazz, String name) {
-    return SingleTargetLookupTest.buildMethod(clazz, name, appInfo);
+    return SingleTargetLookupTest.buildNullaryVoidMethod(clazz, name, appInfo);
   }
 
   @Parameters(name = "{0}")
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
index f3191ac..22445e6 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
@@ -108,12 +108,11 @@
   @BeforeClass
   public static void computeAppInfo() throws Exception {
     appInfo =
-        SingleTargetLookupTest.createAppInfoWithLiveness(
-            readClassesAndAsmDump(CLASSES, DUMPS), Main.class);
+        computeAppViewWithLiveness(readClassesAndAsmDump(CLASSES, DUMPS), Main.class).appInfo();
   }
 
   private static DexMethod buildMethod(Class clazz, String name) {
-    return SingleTargetLookupTest.buildMethod(clazz, name, appInfo);
+    return SingleTargetLookupTest.buildNullaryVoidMethod(clazz, name, appInfo);
   }
 
   @Parameters(name = "{0}")
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
index 7c435d3..63aa76f 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
@@ -95,12 +95,11 @@
   @BeforeClass
   public static void computeAppInfo() throws Exception {
     appInfo =
-        SingleTargetLookupTest.createAppInfoWithLiveness(
-            readClassesAndAsmDump(CLASSES, DUMPS), Main.class);
+        computeAppViewWithLiveness(readClassesAndAsmDump(CLASSES, DUMPS), Main.class).appInfo();
   }
 
   private static DexMethod buildMethod(Class clazz, String name) {
-    return SingleTargetLookupTest.buildMethod(clazz, name, appInfo);
+    return SingleTargetLookupTest.buildNullaryVoidMethod(clazz, name, appInfo);
   }
 
   @Parameters(name = "{0}")
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
index e876ce0..22a12f2 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
@@ -142,12 +142,11 @@
   @BeforeClass
   public static void computeAppInfo() throws Exception {
     appInfo =
-        SingleTargetLookupTest.createAppInfoWithLiveness(
-            readClassesAndAsmDump(CLASSES, DUMPS), Main.class);
+        computeAppViewWithLiveness(readClassesAndAsmDump(CLASSES, DUMPS), Main.class).appInfo();
   }
 
   private static DexMethod buildMethod(Class clazz, String name) {
-    return SingleTargetLookupTest.buildMethod(clazz, name, appInfo);
+    return SingleTargetLookupTest.buildNullaryVoidMethod(clazz, name, appInfo);
   }
 
   @Parameters(name = "{0}")
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
index c9a1c5e..2117ed6 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
@@ -15,8 +15,8 @@
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.resolution.SingleTargetLookupTest;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.AndroidApp;
 import com.google.common.collect.ImmutableList;
-import java.util.Collections;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -43,10 +43,9 @@
   public void testResolution() throws Exception {
     // The resolution is runtime independent, so just run it on the default CF VM.
     assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
-    AppInfoWithLiveness appInfo =
-        SingleTargetLookupTest.createAppInfoWithLiveness(
-            buildClasses(CLASSES, Collections.emptyList()).build(), Main.class);
-    DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
+    AndroidApp app = readClasses(CLASSES);
+    AppInfoWithLiveness appInfo = computeAppViewWithLiveness(app, Main.class).appInfo();
+    DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     // Currently R8 will resolve to L::f as that is the first in the topological search.
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
index 075e399..a9c79fc 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
@@ -44,12 +44,13 @@
     // The resolution is runtime independent, so just run it on the default CF VM.
     assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
     AppInfoWithLiveness appInfo =
-        SingleTargetLookupTest.createAppInfoWithLiveness(
-            buildClasses(CLASSES, Collections.emptyList())
-                .addClassProgramData(Collections.singletonList(transformB()))
-                .build(),
-            Main.class);
-    DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
+        computeAppViewWithLiveness(
+                buildClasses(CLASSES)
+                    .addClassProgramData(Collections.singletonList(transformB()))
+                    .build(),
+                Main.class)
+            .appInfo();
+    DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     assertEquals(L.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
index 58d0891..a65c188 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
@@ -44,12 +44,13 @@
     // The resolution is runtime independent, so just run it on the default CF VM.
     assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
     AppInfoWithLiveness appInfo =
-        SingleTargetLookupTest.createAppInfoWithLiveness(
-            buildClasses(CLASSES, Collections.emptyList())
-                .addClassProgramData(Collections.singletonList(transformB()))
-                .build(),
-            Main.class);
-    DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
+        computeAppViewWithLiveness(
+                buildClasses(CLASSES)
+                    .addClassProgramData(Collections.singletonList(transformB()))
+                    .build(),
+                Main.class)
+            .appInfo();
+    DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     assertEquals(R.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
index 30295fd..053fba0 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
@@ -47,12 +47,13 @@
     // The resolution is runtime independent, so just run it on the default CF VM.
     assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
     AppInfoWithLiveness appInfo =
-        SingleTargetLookupTest.createAppInfoWithLiveness(
-            buildClasses(CLASSES, Collections.emptyList())
-                .addClassProgramData(Collections.singletonList(transformB()))
-                .build(),
-            Main.class);
-    DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
+        computeAppViewWithLiveness(
+                buildClasses(CLASSES)
+                    .addClassProgramData(Collections.singletonList(transformB()))
+                    .build(),
+                Main.class)
+            .appInfo();
+    DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     assertEquals(L.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
index 6c4070e..5163bc2 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
@@ -47,12 +47,13 @@
     // The resolution is runtime independent, so just run it on the default CF VM.
     assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
     AppInfoWithLiveness appInfo =
-        SingleTargetLookupTest.createAppInfoWithLiveness(
-            buildClasses(CLASSES, Collections.emptyList())
-                .addClassProgramData(Collections.singletonList(transformB()))
-                .build(),
-            Main.class);
-    DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
+        computeAppViewWithLiveness(
+                buildClasses(CLASSES)
+                    .addClassProgramData(Collections.singletonList(transformB()))
+                    .build(),
+                Main.class)
+            .appInfo();
+    DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     assertEquals(R.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
index 581554b..75f689f 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
@@ -46,12 +46,13 @@
     // The resolution is runtime independent, so just run it on the default CF VM.
     assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
     AppInfoWithLiveness appInfo =
-        SingleTargetLookupTest.createAppInfoWithLiveness(
-            buildClasses(CLASSES, Collections.emptyList())
-                .addClassProgramData(Collections.singletonList(transformB()))
-                .build(),
-            Main.class);
-    DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
+        computeAppViewWithLiveness(
+                buildClasses(CLASSES)
+                    .addClassProgramData(Collections.singletonList(transformB()))
+                    .build(),
+                Main.class)
+            .appInfo();
+    DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     Set<String> holders = new HashSet<>();
     resolutionResult
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
index 7ca9525..5772bdc 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.resolution.SingleTargetLookupTest;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
-import java.util.Collections;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -44,9 +43,8 @@
     // The resolution is runtime independent, so just run it on the default CF VM.
     assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
     AppInfoWithLiveness appInfo =
-        SingleTargetLookupTest.createAppInfoWithLiveness(
-            buildClasses(CLASSES, Collections.emptyList()).build(), Main.class);
-    DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
+        computeAppViewWithLiveness(readClasses(CLASSES), Main.class).appInfo();
+    DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     assertEquals(L.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
index 1cbd668..50e0f8d 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.resolution.SingleTargetLookupTest;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
-import java.util.Collections;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -44,9 +43,8 @@
     // The resolution is runtime independent, so just run it on the default CF VM.
     assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
     AppInfoWithLiveness appInfo =
-        SingleTargetLookupTest.createAppInfoWithLiveness(
-            buildClasses(CLASSES, Collections.emptyList()).build(), Main.class);
-    DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
+        computeAppViewWithLiveness(readClasses(CLASSES), Main.class).appInfo();
+    DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     assertEquals(R.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
index 47d7d65..bb81c51 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
@@ -47,12 +47,13 @@
     // The resolution is runtime independent, so just run it on the default CF VM.
     assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
     AppInfoWithLiveness appInfo =
-        SingleTargetLookupTest.createAppInfoWithLiveness(
-            buildClasses(CLASSES, Collections.emptyList())
-                .addClassProgramData(Collections.singletonList(transformB()))
-                .build(),
-            Main.class);
-    DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
+        computeAppViewWithLiveness(
+                buildClasses(CLASSES)
+                    .addClassProgramData(Collections.singletonList(transformB()))
+                    .build(),
+                Main.class)
+            .appInfo();
+    DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     Set<String> holders = new HashSet<>();
     resolutionResult
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 428f9bb..d0d40bb 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -6,6 +6,8 @@
 import static org.objectweb.asm.Opcodes.ASM7;
 
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
@@ -13,6 +15,7 @@
 import com.android.tools.r8.transformers.MethodTransformer.MethodContext;
 import com.android.tools.r8.utils.DescriptorUtils;
 import java.io.IOException;
+import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -24,6 +27,8 @@
 
 public class ClassFileTransformer {
 
+  private static final int NEST_SUPPORTED_VERSION = 55;
+
   /**
    * Basic algorithm for transforming the content of a class file.
    *
@@ -161,6 +166,100 @@
         });
   }
 
+  public ClassFileTransformer setMinVersion(int minVersion) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public void visit(
+              int version,
+              int access,
+              String name,
+              String signature,
+              String superName,
+              String[] interfaces) {
+            super.visit(
+                Integer.max(version, minVersion), access, name, signature, superName, interfaces);
+          }
+        });
+  }
+
+  public ClassFileTransformer setNest(Class<?> host, Class<?>... members) {
+    assert !Arrays.asList(members).contains(host);
+    return setMinVersion(NEST_SUPPORTED_VERSION)
+        .addClassTransformer(
+            new ClassTransformer() {
+
+              final String hostName = DescriptorUtils.getBinaryNameFromJavaType(host.getTypeName());
+
+              final List<String> memberNames =
+                  Arrays.stream(members)
+                      .map(m -> DescriptorUtils.getBinaryNameFromJavaType(m.getTypeName()))
+                      .collect(Collectors.toList());
+
+              String className;
+
+              @Override
+              public void visit(
+                  int version,
+                  int access,
+                  String name,
+                  String signature,
+                  String superName,
+                  String[] interfaces) {
+                super.visit(version, access, name, signature, superName, interfaces);
+                className = name;
+              }
+
+              @Override
+              public void visitNestHost(String nestHost) {
+                // Ignore/remove existing nest information.
+              }
+
+              @Override
+              public void visitNestMember(String nestMember) {
+                // Ignore/remove existing nest information.
+              }
+
+              @Override
+              public void visitEnd() {
+                if (className.equals(hostName)) {
+                  for (String memberName : memberNames) {
+                    super.visitNestMember(memberName);
+                  }
+                } else {
+                  assert memberNames.contains(className);
+                  super.visitNestHost(hostName);
+                }
+                super.visitEnd();
+              }
+            });
+  }
+
+  public ClassFileTransformer setPrivate(Method method) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          final MethodReference methodReference = Reference.methodFromMethod(method);
+
+          @Override
+          public MethodVisitor visitMethod(
+              int access, String name, String descriptor, String signature, String[] exceptions) {
+            boolean isConstructor =
+                name.equals(Constants.INSTANCE_INITIALIZER_NAME)
+                    || name.equals(Constants.CLASS_INITIALIZER_NAME);
+            MethodAccessFlags accessFlags =
+                MethodAccessFlags.fromCfAccessFlags(access, isConstructor);
+            if (name.equals(methodReference.getMethodName())
+                && descriptor.equals(methodReference.getMethodDescriptor())) {
+              accessFlags.unsetPublic();
+              accessFlags.unsetProtected();
+              accessFlags.setPrivate();
+            }
+            return super.visitMethod(
+                accessFlags.getAsCfAccessFlags(), name, descriptor, signature, exceptions);
+          }
+        });
+  }
+
   /** Abstraction of the MethodVisitor.visitMethodInsn method with its continuation. */
   @FunctionalInterface
   public interface MethodInsnTransform {
diff --git a/tests/d8_api_usage_sample.jar b/tests/d8_api_usage_sample.jar
index 168d828..30b00cb 100644
--- a/tests/d8_api_usage_sample.jar
+++ b/tests/d8_api_usage_sample.jar
Binary files differ
diff --git a/tests/r8_api_usage_sample.jar b/tests/r8_api_usage_sample.jar
index 168d828..4c4801c 100644
--- a/tests/r8_api_usage_sample.jar
+++ b/tests/r8_api_usage_sample.jar
Binary files differ
diff --git a/tools/r8_release.py b/tools/r8_release.py
index a848beb..e661dc6 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -68,6 +68,11 @@
             'is the same as exiting release (%s).' % old_version
           sys.exit(1)
 
+        if args.dev_pre_cherry_pick:
+          for pre_commit in args.dev_pre_cherry_pick:
+            subprocess.check_call([
+                'git', 'cherry-pick', '--no-edit', pre_commit])
+
         # Merge the desired commit from master on to the branch.
         subprocess.check_call([
           'git', 'merge', '--no-ff', '--no-edit', commithash])
@@ -478,6 +483,12 @@
   group.add_argument('--dev-release',
                       metavar=('<master hash>'),
                       help='The hash to use for the new dev version of R8')
+  result.add_argument('--dev-pre-cherry-pick',
+                      metavar=('<master hash(s)>'),
+                      default=[],
+                      action='append',
+                      help='List of commits to cherry pick before doing full '
+                           'merge, mostly used for reverting cherry picks')
   group.add_argument('--version',
                       metavar=('<version>'),
                       help='The new version of R8 (e.g., 1.4.51) to release to selected channels')
@@ -563,4 +574,3 @@
 
 if __name__ == '__main__':
   sys.exit(main())
-
diff --git a/tools/utils.py b/tools/utils.py
index 495f455..2ff2ec2 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -560,8 +560,9 @@
   cmd = [jdk.GetJavaExecutable(), '-cp', path, 'com.android.tools.r8.R8',
         '--version']
   output = subprocess.check_output(cmd, stderr = subprocess.STDOUT)
-  # output is on form 'R8 <version>' so we just strip of 'R8 '.
-  return output.splitlines()[0][3:]
+  # output is of the form 'R8 <version> (with additional info)'
+  # so we split on '('; clean up tailing spaces; and strip off 'R8 '.
+  return output.split('(')[0].strip()[3:]
 
 def desugar_configuration_version():
   with open(DESUGAR_CONFIGURATION, 'r') as f: