Merge commit '8e6ffb0601c56268382f651f7877e2e8f01a2519' into dev-release
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 9dc1cc9..8323666 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -166,7 +166,7 @@
 
     private CompilationMode mode;
     private int minApiLevel = 0;
-    private DesugarState desugarState = DesugarState.ON;
+    protected DesugarState desugarState = DesugarState.ON;
     private List<StringResource> desugaredLibraryConfigurationResources = new ArrayList<>();
     private boolean includeClassesChecksum = false;
     private boolean lookupLibraryBeforeProgram = true;
diff --git a/src/main/java/com/android/tools/r8/CompatDxHelper.java b/src/main/java/com/android/tools/r8/CompatDxHelper.java
index fb186f4..beec3dc 100644
--- a/src/main/java/com/android/tools/r8/CompatDxHelper.java
+++ b/src/main/java/com/android/tools/r8/CompatDxHelper.java
@@ -6,15 +6,12 @@
 
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.InternalOptions.DesugarState;
 
 public class CompatDxHelper {
   public static void run(D8Command command, Boolean minimalMainDex)
       throws CompilationFailedException {
     AndroidApp app = command.getInputApp();
     InternalOptions options = command.getInternalOptions();
-    // DX does not desugar.
-    options.desugarState = DesugarState.OFF;
     // DX allows --multi-dex without specifying a main dex list for legacy devices.
     // That is broken, but for CompatDX we do the same to not break existing builds
     // that are trying to transition.
@@ -28,4 +25,8 @@
   public static void ignoreDexInArchive(BaseCommand.Builder builder) {
     builder.setIgnoreDexInArchive(true);
   }
+
+  public static void enableDesugarBackportStatics(D8Command.Builder builder) {
+    builder.enableDesugarBackportStatics();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 467570f..d421ec3 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -155,6 +155,12 @@
       return self();
     }
 
+    // Internal helper for compat tools to make them only desugar backports.
+    Builder enableDesugarBackportStatics() {
+      this.desugarState = DesugarState.ONLY_BACKPORT_STATICS;
+      return self();
+    }
+
     @Override
     Builder self() {
       return this;
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index a384ebc..ab5707a 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -91,7 +91,9 @@
 
     @Override
     public boolean registerInvokeVirtual(DexMethod method) {
-      DexEncodedMethod target = appInfo.lookupVirtualTarget(method.holder, method);
+      ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+      DexEncodedMethod target =
+          resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null;
       if (target != null && target.method != method) {
         addType(method.holder);
         addMethod(target.method);
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 1c7774d..77c4895 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -41,6 +41,8 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.kotlin.KotlinInfo;
+import com.android.tools.r8.kotlin.KotlinMemberInfo;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.Minifier;
@@ -881,7 +883,9 @@
     Kotlin kotlin = appView.dexItemFactory().kotlin;
     Reporter reporter = options.reporter;
     for (DexProgramClass programClass : application.classes()) {
-      programClass.setKotlinInfo(kotlin.getKotlinInfo(programClass, reporter));
+      KotlinInfo kotlinInfo = kotlin.getKotlinInfo(programClass, reporter);
+      programClass.setKotlinInfo(kotlinInfo);
+      KotlinMemberInfo.markKotlinMemberInfo(programClass, kotlinInfo);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
index 23dbc06..fc2a080 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
@@ -5,9 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
@@ -169,9 +167,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forBinop();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
index f22b956..53536d2 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
@@ -4,9 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
@@ -45,9 +43,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forArrayLength();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
index db6e106..1f667b4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -5,9 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
@@ -90,9 +88,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forArrayGet();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
index 3f155f4..d0eadef 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
@@ -5,9 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -80,9 +78,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forArrayPut();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
index ca75d03..e89c480 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -4,9 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -61,9 +59,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forCheckCast(type, invocationContext);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
index 10c8eab..dac3eca 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
@@ -5,9 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.Cmp;
 import com.android.tools.r8.ir.code.Cmp.Bias;
 import com.android.tools.r8.ir.code.NumericType;
@@ -94,9 +92,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forBinop();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
index a10e914..7f77ade 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
@@ -5,9 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -84,9 +82,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forConstClass(type, invocationContext);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
index 6e34d8d..a9a1d73 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
@@ -4,10 +4,8 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
@@ -60,9 +58,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forConstMethodHandle();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
index f3b7e3b..063b6fa 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
@@ -4,10 +4,8 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -60,9 +58,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forConstMethodType();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
index 69acb80..7ad901a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
@@ -4,9 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -37,9 +35,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forConstInstruction();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
index fddbba4..72e4638 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
@@ -5,9 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -135,9 +133,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forConstInstruction();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
index 45e5a13..d52d901 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
@@ -4,10 +4,8 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -67,9 +65,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forConstInstruction();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
index d4ec522..36ba9b4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
@@ -5,10 +5,8 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -83,9 +81,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forDexItemBasedConstString(item, invocationContext);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index 996355b..a000dcf 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -5,10 +5,8 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -125,9 +123,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     switch (opcode) {
       case Opcodes.GETSTATIC:
         return inliningConstraints.forStaticGet(field, invocationContext);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
index a9f440d..e5a3c19 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -7,10 +7,8 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -295,9 +293,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return ConstraintWithTarget.ALWAYS;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
index d5f5bc6..efe67ba 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
@@ -4,9 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -62,9 +60,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forJumpInstruction();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIf.java b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
index e23c0cc..1ba9c06 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
@@ -5,9 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.If.Type;
 import com.android.tools.r8.ir.code.ValueType;
@@ -95,9 +93,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forJumpInstruction();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
index 47f90b2..28a59d7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
@@ -5,9 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.If.Type;
 import com.android.tools.r8.ir.code.ValueType;
@@ -96,9 +94,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forJumpInstruction();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
index 951da4f..92f2094 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
@@ -4,9 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -53,9 +51,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return ConstraintWithTarget.ALWAYS;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
index 2cfdbaf..cb4baef 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
@@ -4,9 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -59,9 +57,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forInstanceOf(type, invocationContext);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 2801ca8..833e5e1 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -4,9 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -147,7 +145,5 @@
 
   public abstract ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView);
+      DexType invocationContext);
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 4656408..6311bfd 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.errors.CompilationError;
 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.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -25,7 +26,6 @@
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
-import com.android.tools.r8.jar.InliningConstraintVisitor;
 import com.android.tools.r8.naming.NamingLens;
 import java.util.Arrays;
 import org.objectweb.asm.MethodVisitor;
@@ -196,11 +196,78 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
-    return InliningConstraintVisitor.getConstraintForInvoke(
-        opcode, method, graphLense, appView, inliningConstraints, invocationContext);
+      DexType invocationContext) {
+    GraphLense graphLense = inliningConstraints.getGraphLense();
+    AppView<?> appView = inliningConstraints.getAppView();
+    DexMethod target = method;
+    // Find the DEX invocation type.
+    Type type;
+    switch (opcode) {
+      case Opcodes.INVOKEINTERFACE:
+        // Could have changed to an invoke-virtual instruction due to vertical class merging
+        // (if an interface is merged into a class).
+        type = graphLense.lookupMethod(target, null, Type.INTERFACE).getType();
+        assert type == Type.INTERFACE || type == Type.VIRTUAL;
+        break;
+
+      case Opcodes.INVOKESPECIAL:
+        if (appView.dexItemFactory().isConstructor(target)) {
+          type = Type.DIRECT;
+          assert noNeedToUseGraphLense(target, type, graphLense);
+        } else if (target.holder == invocationContext) {
+          // The method could have been publicized.
+          type = graphLense.lookupMethod(target, null, Type.DIRECT).getType();
+          assert type == Type.DIRECT || type == Type.VIRTUAL;
+        } else {
+          // This is a super call. Note that the vertical class merger translates some invoke-super
+          // instructions to invoke-direct. However, when that happens, the invoke instruction and
+          // the target method end up being in the same class, and therefore, we will allow inlining
+          // it. The result of using type=SUPER below will be the same, since it leads to the
+          // inlining constraint SAMECLASS.
+          // TODO(christofferqa): Consider using graphLense.lookupMethod (to do this, we need the
+          // context for the graph lense, though).
+          type = Type.SUPER;
+          assert noNeedToUseGraphLense(target, type, graphLense);
+        }
+        break;
+
+      case Opcodes.INVOKESTATIC: {
+        // Static invokes may have changed as a result of horizontal class merging.
+        GraphLenseLookupResult lookup = graphLense.lookupMethod(target, null, Type.STATIC);
+        target = lookup.getMethod();
+        type = lookup.getType();
+        break;
+      }
+
+      case Opcodes.INVOKEVIRTUAL: {
+        type = Type.VIRTUAL;
+        // Instructions that target a private method in the same class translates to
+        // invoke-direct.
+        if (target.holder == invocationContext) {
+          DexClass clazz = appView.definitionFor(target.holder);
+          if (clazz != null && clazz.lookupDirectMethod(target) != null) {
+            type = Type.DIRECT;
+          }
+        }
+
+        // Virtual invokes may have changed to interface invokes as a result of member rebinding.
+        GraphLenseLookupResult lookup = graphLense.lookupMethod(target, null, type);
+        target = lookup.getMethod();
+        type = lookup.getType();
+        break;
+      }
+
+      default:
+        throw new Unreachable("Unexpected opcode " + opcode);
+    }
+
+    return inliningConstraints.forInvoke(target, type, invocationContext);
+  }
+
+  private static boolean noNeedToUseGraphLense(
+      DexMethod method, Invoke.Type type, GraphLense graphLense) {
+    assert graphLense.lookupMethod(method, null, type).getType() == type;
+    return true;
   }
 
   private Type invokeTypeForInvokeSpecialToNonInitMethodOnHolder(
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index 301be60..231c115 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexString;
@@ -19,7 +18,6 @@
 import com.android.tools.r8.graph.DexValue.DexValueMethodType;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.DexValue.DexValueType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
@@ -118,9 +116,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forInvokeCustom();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
index c6b2bd0..df9d6bd 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
@@ -4,9 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -60,9 +58,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return ConstraintWithTarget.ALWAYS;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
index 4067a79..ae1fd85 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
@@ -5,9 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -89,9 +87,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forLoad();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
index c9ddad6..1d03017 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
@@ -5,9 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
@@ -141,9 +139,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forBinop();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
index 0fda291..dc117dd 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
@@ -4,9 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.Monitor.Type;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -54,9 +52,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forMonitor();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
index bb79f60..936f62d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
@@ -4,9 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -66,9 +64,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forInvokeMultiNewArray(type, invocationContext);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
index cbd67ae..254ee77 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
@@ -5,9 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
@@ -83,9 +81,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forUnop();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNew.java b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
index ab00d86..46821da 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNew.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
@@ -4,9 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -57,9 +55,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forNewInstance(type, invocationContext);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
index d4f26a6..10504d9 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
@@ -5,9 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -100,9 +98,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forNewArrayEmpty(type, invocationContext);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNop.java b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
index 75f6be9..0ae7685 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
@@ -4,9 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -41,9 +39,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return ConstraintWithTarget.ALWAYS;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
index 73d8f34..c1118e5 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
@@ -5,9 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
@@ -154,9 +152,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forUnop();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
index 9302711..035604b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
@@ -4,9 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -69,9 +67,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return ConstraintWithTarget.ALWAYS;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
index 88972fe..76475fa 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
@@ -5,9 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -77,9 +75,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forReturn();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
index 52d2ae3..fd55aa0 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
@@ -4,9 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -46,9 +44,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forReturn();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
index 33205c1..5886058 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
@@ -6,9 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -317,9 +315,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return ConstraintWithTarget.ALWAYS;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStore.java b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
index 45f274a..5f8a05d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
@@ -5,9 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -88,9 +86,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forStore();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
index f9c5f7e..47aff14 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
@@ -4,9 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
@@ -105,9 +103,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forJumpInstruction();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
index 8d2f92b..a7609c0 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
@@ -4,9 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
@@ -48,9 +46,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints,
-      DexType invocationContext,
-      GraphLense graphLense,
-      AppView<?> appView) {
+      DexType invocationContext) {
     return inliningConstraints.forJumpInstruction();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilder.java b/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilder.java
index f4f3c7e..69cbddb 100644
--- a/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilder.java
+++ b/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilder.java
@@ -60,6 +60,7 @@
   private String output = null;
   private int numberOfThreads = 8;
   private boolean noLocals = false;
+  private boolean backportStatics = false;
 
   public static void main(String[] args)
       throws IOException, InterruptedException, ExecutionException {
@@ -108,6 +109,9 @@
         case "--nolocals":
           noLocals = true;
           break;
+        case "--desugar-backport-statics":
+          backportStatics = true;
+          break;
         default:
           System.err.println("Unsupported option: " + flag);
           System.exit(1);
@@ -168,6 +172,9 @@
         .setMode(noLocals ? CompilationMode.RELEASE : CompilationMode.DEBUG)
         .setMinApiLevel(AndroidApiLevel.H_MR2.getLevel())
         .setDisableDesugaring(true);
+    if (backportStatics) {
+      CompatDxHelper.enableDesugarBackportStatics(builder);
+    }
     try (InputStream stream = zipFile.getInputStream(classEntry)) {
       builder.addClassProgramData(
           ByteStreams.toByteArray(stream),
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 a52c2ea..e98d5c0 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -90,6 +90,7 @@
     public final String mainDexList;
     public final boolean minimalMainDex;
     public final int minApiLevel;
+    public final boolean backportStatics;
     public final String inputList;
     public final ImmutableList<String> inputs;
     // Undocumented option
@@ -147,6 +148,7 @@
       final OptionSpec<String> mainDexList;
       final OptionSpec<Void> minimalMainDex;
       final OptionSpec<Integer> minApiLevel;
+      final OptionSpec<Void> backportStatics;
       final OptionSpec<String> inputList;
       final OptionSpec<String> inputs;
       final OptionSpec<Void> version;
@@ -226,6 +228,8 @@
         minApiLevel = parser
             .accepts("min-sdk-version", "Minimum Android API level compatibility.")
             .withRequiredArg().ofType(Integer.class);
+        backportStatics =
+            parser.accepts("desugar-backport-statics", "Backport additional Java 8 APIs");
         inputList = parser
             .accepts("input-list", "File listing input files")
             .withRequiredArg()
@@ -290,6 +294,7 @@
       } else {
         minApiLevel = AndroidApiLevel.getDefault().getLevel();
       }
+      backportStatics = options.has(spec.backportStatics);
       inputList = options.valueOf(spec.inputList);
       inputs = ImmutableList.copyOf(options.valuesOf(spec.inputs));
       maxIndexNumber = options.valueOf(spec.maxIndexNumber);
@@ -451,13 +456,16 @@
       CompatDxHelper.ignoreDexInArchive(builder);
       builder
           .addProgramFiles(inputs)
-          .setProgramConsumer(
-              createConsumer(inputs, output, singleDexFile, dexArgs.keepClasses))
+          .setProgramConsumer(createConsumer(inputs, output, singleDexFile, dexArgs.keepClasses))
           .setMode(mode)
+          .setDisableDesugaring(true) // DX does not desugar.
           .setMinApiLevel(dexArgs.minApiLevel);
       if (mainDexList != null) {
         builder.addMainDexListFiles(mainDexList);
       }
+      if (dexArgs.backportStatics) {
+        CompatDxHelper.enableDesugarBackportStatics(builder);
+      }
       CompatDxHelper.run(builder.build(), dexArgs.minimalMainDex);
     } finally {
       executor.shutdown();
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 7ffcf30..4e3df21 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -256,57 +256,6 @@
     return null;
   }
 
-  // TODO(b/147578480): RemoveDeprecation Use AppInfoWithClassHierarchy and
-  // lookupXX(DexMethod, DexProgramClass). The following 3 methods should either be removed or
-  // return null.
-
-  /**
-   * Lookup static method following the super chain from the holder of {@code method}.
-   *
-   * <p>This method will resolve the method on the holder of {@code method} and only return a
-   * non-null value if the result of resolution was a static, non-abstract method.
-   *
-   * @param method the method to lookup
-   * @return The actual target for {@code method} or {@code null} if none found.
-   */
-  @Deprecated // TODO(b/147578480): Remove
-  public DexEncodedMethod lookupStaticTarget(DexMethod method) {
-    assert checkIfObsolete();
-    ResolutionResult resolutionResult = resolveMethod(method.holder, method);
-    DexEncodedMethod target = resolutionResult.getSingleTarget();
-    return target == null || target.isStatic() ? target : null;
-  }
-
-  /**
-   * Lookup direct method following the super chain from the holder of {@code method}.
-   *
-   * <p>This method will lookup private and constructor methods.
-   *
-   * @param method the method to lookup
-   * @return The actual target for {@code method} or {@code null} if none found.
-   */
-  public DexEncodedMethod lookupDirectTarget(DexMethod method) {
-    assert checkIfObsolete();
-    ResolutionResult resolutionResult = resolveMethod(method.holder, method);
-    DexEncodedMethod target = resolutionResult.getSingleTarget();
-    return target == null || target.isDirectMethod() ? target : null;
-  }
-
-  /**
-   * Lookup virtual method starting in type and following the super chain.
-   *
-   * <p>This method will resolve the method on the holder of {@code method} and only return a
-   * non-null value if the result of resolution was a virtual target.
-   *
-   * <p>TODO(b/140204899): Delete this method as it does resolution and not a "lookup of targets".
-   */
-  public DexEncodedMethod lookupVirtualTarget(DexType type, DexMethod method) {
-    assert checkIfObsolete();
-    assert type.isClassType() || type.isArrayType();
-    ResolutionResult resolutionResult = resolveMethod(type, method);
-    return resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null;
-  }
-
   /**
    * Implements resolution of a method descriptor against a target type.
    *
@@ -447,28 +396,12 @@
   }
 
   /**
-   * Helper method used for emulated interface resolution (not in JVM specifications). The result
-   * may be abstract.
-   */
-  public ResolutionResult resolveMaximallySpecificMethods(DexClass clazz, DexMethod method) {
-    assert !clazz.type.isArrayType();
-    if (clazz.isInterface()) {
-      // Look for exact method on interface.
-      DexEncodedMethod result = clazz.lookupMethod(method);
-      if (result != null) {
-        return new SingleResolutionResult(clazz, clazz, result);
-      }
-    }
-    return resolveMethodStep3(clazz, method);
-  }
-
-  /**
    * Implements step 3 of <a
    * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">Section
    * 5.4.3.3 of the JVM Spec</a>. As this is the same for interfaces and classes, we share one
    * implementation.
    */
-  private ResolutionResult resolveMethodStep3(DexClass clazz, DexMethod method) {
+  ResolutionResult resolveMethodStep3(DexClass clazz, DexMethod method) {
     MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder(clazz);
     resolveMethodStep3Helper(clazz, method, builder);
     return builder.resolve();
@@ -563,32 +496,6 @@
   }
 
   /**
-   * Lookup instance field starting in type and following the interface and super chain.
-   * <p>
-   * The result is the field that will be hit at runtime, if such field is known. A result
-   * of null indicates that the field is either undefined or not an instance field.
-   */
-  public DexEncodedField lookupInstanceTarget(DexType type, DexField field) {
-    assert checkIfObsolete();
-    assert type.isClassType();
-    DexEncodedField result = resolveFieldOn(type, field);
-    return result == null || result.accessFlags.isStatic() ? null : result;
-  }
-
-  /**
-   * Lookup static field starting in type and following the interface and super chain.
-   * <p>
-   * The result is the field that will be hit at runtime, if such field is known. A result
-   * of null indicates that the field is either undefined or not a static field.
-   */
-  public DexEncodedField lookupStaticTarget(DexType type, DexField field) {
-    assert checkIfObsolete();
-    assert type.isClassType();
-    DexEncodedField result = resolveFieldOn(type, field);
-    return result == null || !result.accessFlags.isStatic() ? null : result;
-  }
-
-  /**
    * Implements resolution of a field descriptor against the holder of the field. See also {@link
    * #resolveFieldOn}.
    */
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 4344794..96fee36 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import java.util.ArrayDeque;
 import java.util.Deque;
 
@@ -89,6 +90,48 @@
   }
 
   /**
+   * Helper method used for emulated interface resolution (not in JVM specifications). The result
+   * may be abstract.
+   */
+  public ResolutionResult resolveMaximallySpecificMethods(DexClass clazz, DexMethod method) {
+    assert !clazz.type.isArrayType();
+    if (clazz.isInterface()) {
+      // Look for exact method on interface.
+      DexEncodedMethod result = clazz.lookupMethod(method);
+      if (result != null) {
+        return new SingleResolutionResult(clazz, clazz, result);
+      }
+    }
+    return resolveMethodStep3(clazz, method);
+  }
+
+  /**
+   * Lookup instance field starting in type and following the interface and super chain.
+   *
+   * <p>The result is the field that will be hit at runtime, if such field is known. A result of
+   * null indicates that the field is either undefined or not an instance field.
+   */
+  public DexEncodedField lookupInstanceTarget(DexType type, DexField field) {
+    assert checkIfObsolete();
+    assert type.isClassType();
+    DexEncodedField result = resolveFieldOn(type, field);
+    return result == null || result.accessFlags.isStatic() ? null : result;
+  }
+
+  /**
+   * Lookup static field starting in type and following the interface and super chain.
+   *
+   * <p>The result is the field that will be hit at runtime, if such field is known. A result of
+   * null indicates that the field is either undefined or not a static field.
+   */
+  public DexEncodedField lookupStaticTarget(DexType type, DexField field) {
+    assert checkIfObsolete();
+    assert type.isClassType();
+    DexEncodedField result = resolveFieldOn(type, field);
+    return result == null || !result.accessFlags.isStatic() ? null : result;
+  }
+
+  /**
    * Lookup static method following the super chain from the holder of {@code method}.
    *
    * <p>This method will resolve the method on the holder of {@code method} and only return a
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 1581a5e..1388051 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -351,6 +351,13 @@
   }
 
   @SuppressWarnings("unchecked")
+  public AppView<AppInfoWithClassHierarchy> withClassHierarchy() {
+    return appInfo.hasClassHierarchy()
+        ? (AppView<AppInfoWithClassHierarchy>) this
+        : null;
+  }
+
+  @SuppressWarnings("unchecked")
   public AppView<AppInfoWithSubtyping> withSubtyping() {
     return appInfo.hasSubtyping()
         ? (AppView<AppInfoWithSubtyping>) this
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 2753232..5402e3a 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -509,7 +509,7 @@
       constraint =
           ConstraintWithTarget.meet(
               constraint,
-              insn.inliningConstraint(inliningConstraints, invocationContext, graphLense, appView),
+              insn.inliningConstraint(inliningConstraints, invocationContext),
               appView);
       if (constraint == ConstraintWithTarget.NEVER) {
         return constraint;
diff --git a/src/main/java/com/android/tools/r8/graph/DelegatingUseRegistry.java b/src/main/java/com/android/tools/r8/graph/DelegatingUseRegistry.java
deleted file mode 100644
index 1375e9d..0000000
--- a/src/main/java/com/android/tools/r8/graph/DelegatingUseRegistry.java
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graph;
-
-public class DelegatingUseRegistry extends UseRegistry {
-    private final UseRegistry delegate;
-
-    public DelegatingUseRegistry(DexItemFactory factory, UseRegistry delegate) {
-      super(factory);
-      this.delegate = delegate;
-    }
-
-    @Override
-    public boolean registerInvokeVirtual(DexMethod method) {
-      return delegate.registerInvokeVirtual(method);
-    }
-
-    @Override
-    public boolean registerInvokeDirect(DexMethod method) {
-      return delegate.registerInvokeDirect(method);
-    }
-
-    @Override
-    public boolean registerInvokeStatic(DexMethod method) {
-      return delegate.registerInvokeStatic(method);
-    }
-
-    @Override
-    public boolean registerInvokeInterface(DexMethod method) {
-      return delegate.registerInvokeInterface(method);
-    }
-
-    @Override
-    public boolean registerInvokeSuper(DexMethod method) {
-      return delegate.registerInvokeSuper(method);
-    }
-
-    @Override
-    public boolean registerInstanceFieldWrite(DexField field) {
-      return delegate.registerInstanceFieldWrite(field);
-    }
-
-    @Override
-    public boolean registerInstanceFieldRead(DexField field) {
-      return delegate.registerInstanceFieldRead(field);
-    }
-
-    @Override
-    public boolean registerNewInstance(DexType type) {
-      return delegate.registerNewInstance(type);
-    }
-
-    @Override
-    public boolean registerStaticFieldRead(DexField field) {
-      return delegate.registerStaticFieldRead(field);
-    }
-
-    @Override
-    public boolean registerStaticFieldWrite(DexField field) {
-      return delegate.registerStaticFieldWrite(field);
-    }
-
-    @Override
-    public boolean registerTypeReference(DexType type) {
-      return delegate.registerTypeReference(type);
-    }
-
-}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 06f579c..358af04 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -13,8 +13,6 @@
 import com.android.tools.r8.utils.PredicateUtils;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Predicates;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Sets;
@@ -24,14 +22,10 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.ListIterator;
-import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
-import kotlinx.metadata.KmConstructor;
-import kotlinx.metadata.KmFunction;
-import kotlinx.metadata.KmProperty;
 
 public abstract class DexClass extends DexDefinition {
 
@@ -214,56 +208,6 @@
     return Arrays.asList(virtualMethods);
   }
 
-  public Map<DexEncodedMethod, KmConstructor> kotlinConstructors(
-      List<KmConstructor> constructors, AppView<?> appView) {
-    ImmutableMap.Builder<DexEncodedMethod, KmConstructor> builder = ImmutableMap.builder();
-    for (DexEncodedMethod method : directMethods) {
-      if (method.isInstanceInitializer()) {
-        KmConstructor constructor = method.findCompatibleKotlinConstructor(constructors, appView);
-        if (constructor != null) {
-          // Found a compatible constructor that is likely asked to keep.
-          builder.put(method, constructor);
-        }
-      }
-    }
-    return builder.build();
-  }
-
-  public Map<DexEncodedMethod, KmFunction> kotlinExtensions(
-      List<KmFunction> extensions, AppView<?> appView) {
-    ImmutableMap.Builder<DexEncodedMethod, KmFunction> builder = ImmutableMap.builder();
-    for (DexEncodedMethod method : directMethods) {
-      if (method.isInitializer()) {
-        continue;
-      }
-      KmFunction extension = method.findCompatibleKotlinExtension(extensions, appView);
-      if (extension != null) {
-        // Found a compatible extension that is likely asked to keep.
-        builder.put(method, extension);
-      }
-    }
-    return builder.build();
-  }
-
-  public List<DexEncodedMethod> kotlinFunctions(
-      List<KmFunction> functions, List<KmProperty> properties, AppView<?> appView) {
-    ImmutableList.Builder<DexEncodedMethod> builder = ImmutableList.builder();
-    for (DexEncodedMethod method : methods()) {
-      if (method.isInitializer()) {
-        continue;
-      }
-      KmFunction function = method.findCompatibleKotlinFunction(functions, appView);
-      if (function != null) {
-        // Found a compatible function that is likely asked to keep.
-        builder.add(method);
-      } else if (!method.isKotlinProperty(properties, appView)) {
-        // This could be a newly merged method that is not part of properties.
-        builder.add(method);
-      }
-    }
-    return builder.build();
-  }
-
   public void appendVirtualMethod(DexEncodedMethod method) {
     DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length + 1];
     System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length);
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinition.java b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
index 9127b94..82dfc12 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
@@ -12,16 +12,6 @@
  */
 public abstract class DexDefinition extends DexItem {
 
-  @Override
-  public boolean isDexDefinition() {
-    return true;
-  }
-
-  @Override
-  public DexDefinition asDexDefinition() {
-    return this;
-  }
-
   public boolean isDexClass() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 1764634..a920005 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.ir.optimize.info.DefaultFieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
+import com.android.tools.r8.kotlin.KotlinMemberInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public class DexEncodedField extends KeyedDexItem<DexField> {
@@ -27,6 +28,7 @@
   private DexValue staticValue;
 
   private FieldOptimizationInfo optimizationInfo = DefaultFieldOptimizationInfo.getInstance();
+  private KotlinMemberInfo kotlinMemberInfo = KotlinMemberInfo.getNoKotlinMemberInfo();
 
   public DexEncodedField(
       DexField field,
@@ -65,6 +67,19 @@
     optimizationInfo = info;
   }
 
+  public KotlinMemberInfo getKotlinMemberInfo() {
+    return kotlinMemberInfo;
+  }
+
+  public void setKotlinMemberInfo(KotlinMemberInfo kotlinMemberInfo) {
+    assert this.kotlinMemberInfo == KotlinMemberInfo.getNoKotlinMemberInfo();
+    this.kotlinMemberInfo = kotlinMemberInfo;
+  }
+
+  public boolean isKotlinBackingField() {
+    return kotlinMemberInfo.memberKind.isBackingField();
+  }
+
   @Override
   public void collectIndexedItems(
       IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 2ecc549..ff31e35 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -54,7 +54,7 @@
 import com.android.tools.r8.ir.synthetic.FieldAccessorSourceCode;
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
-import com.android.tools.r8.kotlin.KotlinMetadataSynthesizer;
+import com.android.tools.r8.kotlin.KotlinMemberInfo;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -74,9 +74,6 @@
 import java.util.Map;
 import java.util.function.Consumer;
 import java.util.function.IntPredicate;
-import kotlinx.metadata.KmConstructor;
-import kotlinx.metadata.KmFunction;
-import kotlinx.metadata.KmProperty;
 import org.objectweb.asm.Opcodes;
 
 public class DexEncodedMethod extends KeyedDexItem<DexMethod> {
@@ -138,6 +135,7 @@
   private MethodOptimizationInfo optimizationInfo = DefaultMethodOptimizationInfo.DEFAULT_INSTANCE;
   private CallSiteOptimizationInfo callSiteOptimizationInfo = CallSiteOptimizationInfo.BOTTOM;
   private int classFileVersion;
+  private KotlinMemberInfo kotlinMemberInfo = KotlinMemberInfo.getNoKotlinMemberInfo();
 
   private DexEncodedMethod defaultInterfaceMethodImplementation = null;
 
@@ -394,59 +392,42 @@
     return accessFlags.isSynthetic();
   }
 
-  // TODO(b/70169921): Handling JVM extensions as well.
-  KmConstructor findCompatibleKotlinConstructor(
-      List<KmConstructor> constructors, AppView<?> appView) {
-    if (!isInstanceInitializer()) {
-      return null;
-    }
-    for (KmConstructor constructor : constructors) {
-      if (KotlinMetadataSynthesizer.isCompatibleConstructor(constructor, this, appView)) {
-        return constructor;
-      }
-    }
-    return null;
+  public KotlinMemberInfo getKotlinMemberInfo() {
+    return kotlinMemberInfo;
   }
 
-  // TODO(b/70169921): Handling JVM extensions as well.
-  KmFunction findCompatibleKotlinExtension(List<KmFunction> extensions, AppView<?> appView) {
-    if (!isStaticMember()) {
-      return null;
+  public void setKotlinMemberInfo(KotlinMemberInfo kotlinMemberInfo) {
+    if (this.kotlinMemberInfo == KotlinMemberInfo.getNoKotlinMemberInfo()) {
+      // Initial setup or structure-changing optimizations that just need to copy metadata from the
+      // old instance of DexEncodedMethod to the new one.
+      this.kotlinMemberInfo = kotlinMemberInfo;
+    } else {
+      // Structure-changing optimizations, such as (vertical|horizontal) merger or inliner, that
+      // may need to redefine what this method is. Simply, the method merged/inlined by optimization
+      // is no longer what it used to be; it's safe to ignore metadata of that method, since it is
+      // not asked to be kept. But, the nature of the current one is not changed, hence keeping the
+      // original one as-is.
+      // E.g., originally the current method is extension function, and new information, say, from
+      // an inlinee, is extension property. Being merged here means:
+      //   * That inlinee is not an extension property anymore. We can ignore metadata from it.
+      //   * This method is still an extension function, just with a bigger body.
     }
-    for (KmFunction extension : extensions) {
-      if (KotlinMetadataSynthesizer.isCompatibleExtension(extension, this, appView)) {
-        return extension;
-      }
-    }
-    return null;
   }
 
-  KmFunction findCompatibleKotlinFunction(List<KmFunction> functions, AppView<?> appView) {
-    if (isStaticMember()) {
-      return null;
-    }
-    for (KmFunction function : functions) {
-      if (KotlinMetadataSynthesizer.isCompatibleFunction(function, this, appView)) {
-        return function;
-      }
-    }
-    return null;
+  public boolean isKotlinFunction() {
+    return kotlinMemberInfo.memberKind.isFunction();
   }
 
-  boolean isKotlinProperty(List<KmProperty> properties, AppView<?> appView) {
-    return findCompatibleKotlinProperty(properties, appView) != null;
+  public boolean isKotlinExtensionFunction() {
+    return kotlinMemberInfo.memberKind.isExtensionFunction();
   }
 
-  KmProperty findCompatibleKotlinProperty(List<KmProperty> properties, AppView<?> appView) {
-    if (isStaticMember()) {
-      return null;
-    }
-    for (KmProperty property : properties) {
-      if (KotlinMetadataSynthesizer.isCompatibleProperty(property, this, appView)) {
-        return property;
-      }
-    }
-    return null;
+  public boolean isKotlinProperty() {
+    return kotlinMemberInfo.memberKind.isProperty();
+  }
+
+  public boolean isKotlinExtensionProperty() {
+    return kotlinMemberInfo.memberKind.isExtensionProperty();
   }
 
   public boolean isOnlyInlinedIntoNestMembers() {
@@ -1255,6 +1236,7 @@
 
   public void copyMetadata(DexEncodedMethod from) {
     checkIfObsolete();
+    setKotlinMemberInfo(from.kotlinMemberInfo);
     // Record that the current method uses identifier name string if the inlinee did so.
     if (from.getOptimizationInfo().useIdentifierNameString()) {
       getMutableOptimizationInfo().markUseIdentifierNameString();
@@ -1281,6 +1263,7 @@
     private Code code;
     private CompilationState compilationState;
     private MethodOptimizationInfo optimizationInfo;
+    private KotlinMemberInfo kotlinMemberInfo;
     private final int classFileVersion;
     private boolean d8R8Synthesized;
 
@@ -1296,6 +1279,7 @@
       code = from.code;
       compilationState = from.compilationState;
       optimizationInfo = from.optimizationInfo.mutableCopy();
+      kotlinMemberInfo = from.kotlinMemberInfo;
       classFileVersion = from.classFileVersion;
       this.d8R8Synthesized = d8R8Synthesized;
 
@@ -1390,6 +1374,7 @@
               code,
               classFileVersion,
               d8R8Synthesized);
+      result.setKotlinMemberInfo(kotlinMemberInfo);
       result.compilationState = compilationState;
       result.optimizationInfo = optimizationInfo;
       return result;
diff --git a/src/main/java/com/android/tools/r8/graph/DexItem.java b/src/main/java/com/android/tools/r8/graph/DexItem.java
index 8aa09b6..eb8e150 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItem.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItem.java
@@ -64,22 +64,6 @@
     return toString();
   }
 
-  public boolean isDexReference() {
-    return false;
-  }
-
-  public DexReference asDexReference() {
-    return null;
-  }
-
-  public boolean isDexDefinition() {
-    return false;
-  }
-
-  public DexDefinition asDexDefinition() {
-    return null;
-  }
-
   static <T extends DexItem> Stream<T> filter(Stream<DexItem> stream, Class<T> clazz) {
     return stream.filter(clazz::isInstance).map(clazz::cast);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexReference.java b/src/main/java/com/android/tools/r8/graph/DexReference.java
index af7dc20..d51810e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexReference.java
+++ b/src/main/java/com/android/tools/r8/graph/DexReference.java
@@ -13,16 +13,6 @@
  */
 public abstract class DexReference extends IndexedDexItem {
 
-  @Override
-  public boolean isDexReference() {
-    return true;
-  }
-
-  @Override
-  public DexReference asDexReference() {
-    return this;
-  }
-
   public boolean isDexType() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index 7e98588..23f2a61 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -71,9 +71,6 @@
   public abstract DexEncodedMethod lookupInvokeDirectTarget(
       DexProgramClass context, AppInfoWithClassHierarchy appInfo);
 
-  @Deprecated
-  public abstract DexEncodedMethod lookupInvokeSuperTarget(DexClass context, AppInfo appInfo);
-
   /** Lookup the single target of an invoke-static on this resolution result if possible. */
   public abstract DexEncodedMethod lookupInvokeStaticTarget(
       DexProgramClass context, AppInfoWithClassHierarchy appInfo);
@@ -186,10 +183,19 @@
     @Override
     public DexEncodedMethod lookupInvokeSuperTarget(
         DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
+      // TODO(b/147848950): Investigate and remove the Compilation error. It could compile to
+      // throw IAE.
+      if (resolvedMethod.isInstanceInitializer()
+          || (appInfo.hasSubtyping()
+              && initialResolutionHolder != context
+              && !isSuperclass(initialResolutionHolder, context, appInfo.withSubtyping()))) {
+        throw new CompilationError(
+            "Illegal invoke-super to " + resolvedMethod.toSourceString(), context.getOrigin());
+      }
       if (!isAccessibleFrom(context, appInfo)) {
         return null;
       }
-      DexEncodedMethod target = lookupInvokeSuperTarget(context.asDexClass(), appInfo);
+      DexEncodedMethod target = internalInvokeSpecialOrSuper(context, appInfo, (sup, sub) -> true);
       if (target == null) {
         return null;
       }
@@ -244,21 +250,10 @@
       return null;
     }
 
-    @Override
-    public DexEncodedMethod lookupInvokeSuperTarget(DexClass context, AppInfo appInfo) {
-      assert context != null;
-      if (resolvedMethod.isInstanceInitializer()
-          || (appInfo.hasSubtyping()
-              && initialResolutionHolder != context
-              && !isSuperclass(initialResolutionHolder, context, appInfo.withSubtyping()))) {
-        throw new CompilationError(
-            "Illegal invoke-super to " + resolvedMethod.toSourceString(), context.getOrigin());
-      }
-      return internalInvokeSpecialOrSuper(context, appInfo, (sup, sub) -> true);
-    }
-
     private DexEncodedMethod internalInvokeSpecialOrSuper(
-        DexClass context, AppInfo appInfo, BiPredicate<DexClass, DexClass> isSuperclass) {
+        DexClass context,
+        AppInfoWithClassHierarchy appInfo,
+        BiPredicate<DexClass, DexClass> isSuperclass) {
 
       // Statics cannot be targeted by invoke-special/super.
       if (getResolvedMethod().isStatic()) {
@@ -455,11 +450,6 @@
     }
 
     @Override
-    public final DexEncodedMethod lookupInvokeSuperTarget(DexClass context, AppInfo appInfo) {
-      return null;
-    }
-
-    @Override
     public DexEncodedMethod lookupInvokeStaticTarget(DexProgramClass context,
         AppInfoWithClassHierarchy appInfo) {
       return null;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
index 17b91f3..e9060a4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
@@ -58,13 +58,13 @@
       OptimizationFeedback feedback,
       DexProgramClass clazz,
       DexEncodedMethod method) {
+    assert clazz != null;
     assert clazz.type == method.method.holder;
     this.appView = appView;
     this.clazz = clazz;
     this.code = code;
     this.feedback = feedback;
     this.method = method;
-    assert this.clazz != null;
   }
 
   public static void run(
@@ -261,9 +261,14 @@
         // Record that this block reads all fields.
         result.put(block, UnknownFieldSet.getInstance());
         changed = true;
-      } else if (knownReadSet.size() != oldSize) {
-        assert knownReadSet.size() > oldSize;
-        changed = true;
+      } else {
+        if (knownReadSet != readSet) {
+          result.put(block, knownReadSet.asConcreteFieldSet());
+        }
+        if (knownReadSet.size() != oldSize) {
+          assert knownReadSet.size() > oldSize;
+          changed = true;
+        }
       }
 
       if (changed) {
@@ -278,7 +283,12 @@
     // Abstract value.
     Value root = value.getAliasedValue();
     AbstractValue abstractValue = computeAbstractValue(root);
-    if (!abstractValue.isUnknown()) {
+    if (abstractValue.isUnknown()) {
+      if (field.isStatic()) {
+        feedback.recordFieldHasAbstractValue(
+            field, appView, appView.abstractValueFactory().createSingleFieldValue(field.field));
+      }
+    } else {
       feedback.recordFieldHasAbstractValue(field, appView, abstractValue);
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index d1cf2e5..e2881ad 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -4,14 +4,30 @@
 
 package com.android.tools.r8.ir.analysis.proto;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 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.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.code.CheckCast;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.optimize.CodeRewriter;
+import com.android.tools.r8.ir.optimize.Inliner;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.inliner.FixedInliningReasonStrategy;
+import com.android.tools.r8.utils.PredicateSet;
+import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -22,9 +38,12 @@
 //  references a dead proto builder.
 public class GeneratedMessageLiteBuilderShrinker {
 
+  private final AppView<? extends AppInfoWithSubtyping> appView;
   private final ProtoReferences references;
 
-  GeneratedMessageLiteBuilderShrinker(ProtoReferences references) {
+  GeneratedMessageLiteBuilderShrinker(
+      AppView<? extends AppInfoWithSubtyping> appView, ProtoReferences references) {
+    this.appView = appView;
     this.references = references;
   }
 
@@ -39,10 +58,19 @@
 
   public static void addInliningHeuristicsForBuilderInlining(
       AppView<? extends AppInfoWithSubtyping> appView,
+      PredicateSet<DexType> alwaysClassInline,
+      Set<DexType> neverMerge,
       Set<DexMethod> alwaysInline,
       Set<DexMethod> neverInline,
       Set<DexMethod> bypassClinitforInlining) {
-    new RootSetExtension(appView, alwaysInline, neverInline, bypassClinitforInlining).extend();
+    new RootSetExtension(
+            appView,
+            alwaysClassInline,
+            neverMerge,
+            alwaysInline,
+            neverInline,
+            bypassClinitforInlining)
+        .extend();
   }
 
   public void preprocessCallGraphBeforeCycleElimination(Map<DexMethod, Node> nodes) {
@@ -60,38 +88,147 @@
     }
   }
 
+  public void inlineCallsToDynamicMethod(
+      DexEncodedMethod method,
+      IRCode code,
+      CodeRewriter codeRewriter,
+      OptimizationFeedback feedback,
+      MethodProcessor methodProcessor,
+      Inliner inliner) {
+    if (method.method.toSourceString().contains("proto2.BuilderWithReusedSettersTestClass")) {
+      System.out.println();
+    }
+    strengthenCheckCastInstructions(code);
+
+    ProtoInliningReasonStrategy inliningReasonStrategy =
+        new ProtoInliningReasonStrategy(appView, new FixedInliningReasonStrategy(Reason.NEVER));
+    inliner.performInlining(method, code, feedback, methodProcessor, inliningReasonStrategy);
+
+    // Run the enum optimization to optimize all Enum.ordinal() invocations. This is required to
+    // get rid of the enum switch in dynamicMethod().
+    if (appView.options().enableEnumValueOptimization) {
+      codeRewriter.rewriteConstantEnumMethodCalls(code);
+    }
+  }
+
+  /**
+   * This method tries to strengthen the type of check-cast instructions that cast a value to
+   * GeneratedMessageLite.
+   *
+   * <p>New proto messages are created by calling dynamicMethod(MethodToInvoke.NEW_MUTABLE_INSTANCE)
+   * and casting the result to GeneratedMessageLite.
+   *
+   * <p>If we encounter the following pattern, then we cannot inline the second call to
+   * dynamicMethod, because we don't have a precise receiver type.
+   *
+   * <pre>
+   *   GeneratedMessageLite msg =
+   *       (GeneratedMessageLite)
+   *           Message.DEFAULT_INSTANCE.dynamicMethod(MethodToInvoke.NEW_MUTABLE_INSTANCE);
+   *   GeneratedMessageLite msg2 =
+   *       (GeneratedMessageLite) msg.dynamicMethod(MethodToInvoke.NEW_MUTABLE_INSTANCE);
+   * </pre>
+   *
+   * <p>This method therefore optimizes the code above into:
+   *
+   * <pre>
+   *   Message msg =
+   *       (Message) Message.DEFAULT_INSTANCE.dynamicMethod(MethodToInvoke.NEW_MUTABLE_INSTANCE);
+   *   Message msg2 = (Message) msg.dynamicMethod(MethodToInvoke.NEW_MUTABLE_INSTANCE);
+   * </pre>
+   *
+   * <p>This is assuming that calling dynamicMethod() on a proto message with
+   * MethodToInvoke.NEW_MUTABLE_INSTANCE will create an instance of the enclosing class.
+   */
+  private void strengthenCheckCastInstructions(IRCode code) {
+    Set<Value> affectedValues = Sets.newIdentityHashSet();
+    InstructionListIterator instructionIterator = code.instructionListIterator();
+    CheckCast checkCast;
+    while ((checkCast = instructionIterator.nextUntil(Instruction::isCheckCast)) != null) {
+      if (checkCast.getType() != references.generatedMessageLiteType) {
+        continue;
+      }
+      Value root = checkCast.object().getAliasedValue();
+      if (root.isPhi() || !root.definition.isInvokeVirtual()) {
+        continue;
+      }
+      InvokeVirtual invoke = root.definition.asInvokeVirtual();
+      DexMethod invokedMethod = invoke.getInvokedMethod();
+      if (!references.isDynamicMethod(invokedMethod)
+          && !references.isDynamicMethodBridge(invokedMethod)) {
+        continue;
+      }
+      assert invokedMethod.proto.parameters.values[0] == references.methodToInvokeType;
+      Value methodToInvokeValue = invoke.arguments().get(1);
+      if (!references.methodToInvokeMembers.isNewMutableInstanceEnum(methodToInvokeValue)) {
+        continue;
+      }
+      ClassTypeLatticeElement receiverType =
+          invoke.getReceiver().getDynamicUpperBoundType(appView).asClassTypeLatticeElement();
+      if (receiverType != null) {
+        AppInfoWithClassHierarchy appInfo = appView.appInfo();
+        DexType rawReceiverType = receiverType.getClassType();
+        if (appInfo.isStrictSubtypeOf(rawReceiverType, references.generatedMessageLiteType)) {
+          Value dest = code.createValue(receiverType.asMaybeNull(), checkCast.getLocalInfo());
+          CheckCast replacement = new CheckCast(dest, checkCast.object(), rawReceiverType);
+          instructionIterator.replaceCurrentInstruction(replacement, affectedValues);
+        }
+      }
+    }
+    if (!affectedValues.isEmpty()) {
+      new TypeAnalysis(appView).narrowing(affectedValues);
+    }
+  }
+
   private static class RootSetExtension {
 
     private final AppView<? extends AppInfoWithSubtyping> appView;
     private final ProtoReferences references;
 
+    private final PredicateSet<DexType> alwaysClassInline;
+    private final Set<DexType> neverMerge;
+
     private final Set<DexMethod> alwaysInline;
     private final Set<DexMethod> neverInline;
     private final Set<DexMethod> bypassClinitforInlining;
 
     RootSetExtension(
         AppView<? extends AppInfoWithSubtyping> appView,
+        PredicateSet<DexType> alwaysClassInline,
+        Set<DexType> neverMerge,
         Set<DexMethod> alwaysInline,
         Set<DexMethod> neverInline,
         Set<DexMethod> bypassClinitforInlining) {
       this.appView = appView;
       this.references = appView.protoShrinker().references;
+      this.alwaysClassInline = alwaysClassInline;
+      this.neverMerge = neverMerge;
       this.alwaysInline = alwaysInline;
       this.neverInline = neverInline;
       this.bypassClinitforInlining = bypassClinitforInlining;
     }
 
     void extend() {
+      alwaysClassInlineGeneratedMessageLiteBuilders();
+
       // GeneratedMessageLite heuristics.
       alwaysInlineCreateBuilderFromGeneratedMessageLite();
       neverInlineIsInitializedFromGeneratedMessageLite();
 
       // * extends GeneratedMessageLite heuristics.
       bypassClinitforInliningNewBuilderMethods();
-      alwaysInlineDynamicMethodFromGeneratedMessageLiteImplementations();
 
       // GeneratedMessageLite$Builder heuristics.
-      alwaysInlineBuildPartialFromGeneratedMessageLiteBuilder();
+      alwaysInlineBuildPartialFromGeneratedMessageLiteExtendableBuilder();
+      neverMergeGeneratedMessageLiteBuilder();
+    }
+
+    private void alwaysClassInlineGeneratedMessageLiteBuilders() {
+      alwaysClassInline.addPredicate(
+          type ->
+              appView
+                  .appInfo()
+                  .isStrictSubtypeOf(type, references.generatedMessageLiteBuilderType));
     }
 
     private void bypassClinitforInliningNewBuilderMethods() {
@@ -108,29 +245,20 @@
       }
     }
 
-    private void alwaysInlineBuildPartialFromGeneratedMessageLiteBuilder() {
-      alwaysInline.add(references.generatedMessageLiteBuilderMethods.buildPartialMethod);
+    private void alwaysInlineBuildPartialFromGeneratedMessageLiteExtendableBuilder() {
+      alwaysInline.add(references.generatedMessageLiteExtendableBuilderMethods.buildPartialMethod);
     }
 
     private void alwaysInlineCreateBuilderFromGeneratedMessageLite() {
       alwaysInline.add(references.generatedMessageLiteMethods.createBuilderMethod);
     }
 
-    private void alwaysInlineDynamicMethodFromGeneratedMessageLiteImplementations() {
-      // TODO(b/132600418): We should be able to determine that dynamicMethod() becomes 'SIMPLE'
-      //  when the MethodToInvoke argument is MethodToInvoke.NEW_BUILDER.
-      DexItemFactory dexItemFactory = appView.dexItemFactory();
-      for (DexType type : appView.appInfo().subtypes(references.generatedMessageLiteType)) {
-        alwaysInline.add(
-            dexItemFactory.createMethod(
-                type,
-                dexItemFactory.createProto(
-                    dexItemFactory.objectType,
-                    references.methodToInvokeType,
-                    dexItemFactory.objectType,
-                    dexItemFactory.objectType),
-                "dynamicMethod"));
-      }
+    private void neverMergeGeneratedMessageLiteBuilder() {
+      // For consistency, never merge the GeneratedMessageLite builders. These will only have a
+      // unique subtype if the application has a single proto message, which mostly happens during
+      // testing.
+      neverMerge.add(references.generatedMessageLiteBuilderType);
+      neverMerge.add(references.generatedMessageLiteExtendableBuilderType);
     }
 
     /**
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
index 4d04a73..de37f38 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.ir.analysis.proto.ProtoReferences.MethodToInvokeMembers;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeMethod;
@@ -22,19 +23,30 @@
 
   private static final int METHOD_TO_INVOKE_ARGUMENT_POSITION_IN_DYNAMIC_METHOD = 1;
 
+  private final AppView<?> appView;
   private final InliningReasonStrategy parent;
   private final ProtoReferences references;
 
   public ProtoInliningReasonStrategy(AppView<?> appView, InliningReasonStrategy parent) {
+    this.appView = appView;
     this.parent = parent;
     this.references = appView.protoShrinker().references;
   }
 
   @Override
-  public Reason computeInliningReason(InvokeMethod invoke, DexEncodedMethod target) {
-    return references.isDynamicMethod(target)
+  public Reason computeInliningReason(
+      InvokeMethod invoke, DexEncodedMethod target, DexEncodedMethod context) {
+    DexProgramClass enclosingClass = appView.definitionFor(context.method.holder).asProgramClass();
+    if (references.isAbstractGeneratedMessageLiteBuilder(enclosingClass)
+        && invoke.isInvokeSuper()) {
+      // Aggressively inline invoke-super calls inside the GeneratedMessageLite builders. Such
+      // instructions prohibit inlining of the enclosing method into other contexts, and therefore
+      // block class inlining of proto builders.
+      return Reason.ALWAYS;
+    }
+    return references.isDynamicMethod(target) || references.isDynamicMethodBridge(target)
         ? computeInliningReasonForDynamicMethod(invoke)
-        : parent.computeInliningReason(invoke, target);
+        : parent.computeInliningReason(invoke, target, context);
   }
 
   private Reason computeInliningReasonForDynamicMethod(InvokeMethod invoke) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
index c90eaf2..aacb2a0 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.Value;
 
 public class ProtoReferences {
 
@@ -20,12 +21,15 @@
   public final DexType generatedExtensionType;
   public final DexType generatedMessageLiteType;
   public final DexType generatedMessageLiteBuilderType;
+  public final DexType generatedMessageLiteExtendableBuilderType;
   public final DexType rawMessageInfoType;
   public final DexType messageLiteType;
   public final DexType methodToInvokeType;
 
   public final GeneratedMessageLiteMethods generatedMessageLiteMethods;
   public final GeneratedMessageLiteBuilderMethods generatedMessageLiteBuilderMethods;
+  public final GeneratedMessageLiteExtendableBuilderMethods
+      generatedMessageLiteExtendableBuilderMethods;
   public final MethodToInvokeMembers methodToInvokeMembers;
 
   public final DexString dynamicMethodName;
@@ -53,6 +57,9 @@
     generatedMessageLiteBuilderType =
         factory.createType(
             factory.createString("Lcom/google/protobuf/GeneratedMessageLite$Builder;"));
+    generatedMessageLiteExtendableBuilderType =
+        factory.createType(
+            factory.createString("Lcom/google/protobuf/GeneratedMessageLite$ExtendableBuilder;"));
     rawMessageInfoType =
         factory.createType(factory.createString("Lcom/google/protobuf/RawMessageInfo;"));
     messageLiteType = factory.createType(factory.createString("Lcom/google/protobuf/MessageLite;"));
@@ -88,9 +95,16 @@
 
     generatedMessageLiteMethods = new GeneratedMessageLiteMethods(factory);
     generatedMessageLiteBuilderMethods = new GeneratedMessageLiteBuilderMethods(factory);
+    generatedMessageLiteExtendableBuilderMethods =
+        new GeneratedMessageLiteExtendableBuilderMethods(factory);
     methodToInvokeMembers = new MethodToInvokeMembers(factory);
   }
 
+  public boolean isAbstractGeneratedMessageLiteBuilder(DexProgramClass clazz) {
+    return clazz.type == generatedMessageLiteBuilderType
+        || clazz.type == generatedMessageLiteExtendableBuilderType;
+  }
+
   public boolean isDynamicMethod(DexMethod method) {
     return method.name == dynamicMethodName && method.proto == dynamicMethodProto;
   }
@@ -113,7 +127,9 @@
   }
 
   public boolean isGeneratedMessageLiteBuilder(DexProgramClass clazz) {
-    return clazz.superType == generatedMessageLiteBuilderType;
+    return (clazz.superType == generatedMessageLiteBuilderType
+            || clazz.superType == generatedMessageLiteExtendableBuilderType)
+        && !isAbstractGeneratedMessageLiteBuilder(clazz);
   }
 
   public boolean isMessageInfoConstructionMethod(DexMethod method) {
@@ -164,6 +180,19 @@
     }
   }
 
+  class GeneratedMessageLiteExtendableBuilderMethods {
+
+    public final DexMethod buildPartialMethod;
+
+    private GeneratedMessageLiteExtendableBuilderMethods(DexItemFactory dexItemFactory) {
+      buildPartialMethod =
+          dexItemFactory.createMethod(
+              generatedMessageLiteExtendableBuilderType,
+              dexItemFactory.createProto(extendableMessageType),
+              "buildPartial");
+    }
+  }
+
   public class MethodToInvokeMembers {
 
     public final DexField buildMessageInfoField;
@@ -195,6 +224,17 @@
               methodToInvokeType, methodToInvokeType, "SET_MEMOIZED_IS_INITIALIZED");
     }
 
+    public boolean isNewMutableInstanceEnum(DexField field) {
+      return field == newMutableInstanceField;
+    }
+
+    public boolean isNewMutableInstanceEnum(Value value) {
+      Value root = value.getAliasedValue();
+      return !root.isPhi()
+          && root.definition.isStaticGet()
+          && isNewMutableInstanceEnum(root.definition.asStaticGet().getField());
+    }
+
     public boolean isMethodToInvokeWithSimpleBody(DexField field) {
       return field == getDefaultInstanceField
           || field == getMemoizedIsInitializedField
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
index d7ba0cc..b7b8ef1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
@@ -32,7 +32,7 @@
             : null;
     this.generatedMessageLiteBuilderShrinker =
         appView.options().protoShrinking().enableGeneratedMessageLiteBuilderShrinking
-            ? new GeneratedMessageLiteBuilderShrinker(references)
+            ? new GeneratedMessageLiteBuilderShrinker(appView, references)
             : null;
     this.references = references;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
index 91086e1..b888ae5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
@@ -83,6 +83,10 @@
     return getOrCreateVariant(Nullability.definitelyNotNull());
   }
 
+  public TypeLatticeElement asMaybeNull() {
+    return getOrCreateVariant(Nullability.maybeNull());
+  }
+
   @Override
   public boolean isReference() {
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index f560b29..2bd87e0 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -28,6 +28,14 @@
     return null;
   }
 
+  public boolean isSingleFieldValue() {
+    return false;
+  }
+
+  public SingleFieldValue asSingleFieldValue() {
+    return null;
+  }
+
   public boolean isSingleNumberValue() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
index 7e34815..887a171 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
@@ -11,6 +11,8 @@
 public class AbstractValueFactory {
 
   private ConcurrentHashMap<DexField, SingleEnumValue> singleEnumValues = new ConcurrentHashMap<>();
+  private ConcurrentHashMap<DexField, SingleFieldValue> singleFieldValues =
+      new ConcurrentHashMap<>();
   private ConcurrentHashMap<Long, SingleNumberValue> singleNumberValues = new ConcurrentHashMap<>();
   private ConcurrentHashMap<DexString, SingleStringValue> singleStringValues =
       new ConcurrentHashMap<>();
@@ -19,6 +21,10 @@
     return singleEnumValues.computeIfAbsent(field, SingleEnumValue::new);
   }
 
+  public SingleFieldValue createSingleFieldValue(DexField field) {
+    return singleFieldValues.computeIfAbsent(field, SingleFieldValue::new);
+  }
+
   public SingleNumberValue createSingleNumberValue(long value) {
     return singleNumberValues.computeIfAbsent(value, SingleNumberValue::new);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleEnumValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleEnumValue.java
index 1f7a76b..313f977 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleEnumValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleEnumValue.java
@@ -4,28 +4,13 @@
 
 package com.android.tools.r8.ir.analysis.value;
 
-import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
-import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isMemberVisibleFromOriginalContext;
-
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.StaticGet;
-import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
-import com.android.tools.r8.ir.code.Value;
 
-public class SingleEnumValue extends SingleValue {
-
-  private final DexField field;
+public class SingleEnumValue extends SingleFieldValue {
 
   /** Intentionally package private, use {@link AbstractValueFactory} instead. */
   SingleEnumValue(DexField field) {
-    this.field = field;
+    super(field);
   }
 
   @Override
@@ -39,35 +24,7 @@
   }
 
   @Override
-  public boolean equals(Object o) {
-    return this == o;
-  }
-
-  @Override
-  public int hashCode() {
-    return System.identityHashCode(this);
-  }
-
-  @Override
   public String toString() {
-    return "SingleEnumValue(" + field.toSourceString() + ")";
-  }
-
-  @Override
-  public Instruction createMaterializingInstruction(
-      AppView<? extends AppInfoWithSubtyping> appView,
-      IRCode code,
-      TypeAndLocalInfoSupplier info) {
-    TypeLatticeElement type = TypeLatticeElement.fromDexType(field.type, maybeNull(), appView);
-    assert type.lessThanOrEqual(info.getTypeLattice(), appView);
-    Value outValue = code.createValue(type, info.getLocalInfo());
-    return new StaticGet(outValue, field);
-  }
-
-  @Override
-  public boolean isMaterializableInContext(AppView<?> appView, DexType context) {
-    DexEncodedField encodedField = appView.appInfo().resolveField(field);
-    return isMemberVisibleFromOriginalContext(
-        appView, context, encodedField.field.holder, encodedField.accessFlags);
+    return "SingleEnumValue(" + getField().toSourceString() + ")";
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
new file mode 100644
index 0000000..0adfe65
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2020, 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.analysis.value;
+
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isMemberVisibleFromOriginalContext;
+
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
+import com.android.tools.r8.ir.code.Value;
+
+public class SingleFieldValue extends SingleValue {
+
+  private final DexField field;
+
+  /** Intentionally package private, use {@link AbstractValueFactory} instead. */
+  SingleFieldValue(DexField field) {
+    this.field = field;
+  }
+
+  public DexField getField() {
+    return field;
+  }
+
+  @Override
+  public boolean isSingleFieldValue() {
+    return true;
+  }
+
+  @Override
+  public SingleFieldValue asSingleFieldValue() {
+    return this;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    return this == o;
+  }
+
+  @Override
+  public int hashCode() {
+    return System.identityHashCode(this);
+  }
+
+  @Override
+  public String toString() {
+    return "SingleFieldValue(" + field.toSourceString() + ")";
+  }
+
+  @Override
+  public Instruction createMaterializingInstruction(
+      AppView<? extends AppInfoWithSubtyping> appView, IRCode code, TypeAndLocalInfoSupplier info) {
+    TypeLatticeElement type = TypeLatticeElement.fromDexType(field.type, maybeNull(), appView);
+    assert type.lessThanOrEqual(info.getTypeLattice(), appView);
+    Value outValue = code.createValue(type, info.getLocalInfo());
+    return new StaticGet(outValue, field);
+  }
+
+  @Override
+  public boolean isMaterializableInContext(AppView<?> appView, DexType context) {
+    DexEncodedField encodedField = appView.appInfo().resolveField(field);
+    return isMemberVisibleFromOriginalContext(
+        appView, context, encodedField.field.holder, encodedField.accessFlags);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/AliasedValueConfiguration.java b/src/main/java/com/android/tools/r8/ir/code/AliasedValueConfiguration.java
new file mode 100644
index 0000000..008d363
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/AliasedValueConfiguration.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, 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.code;
+
+public interface AliasedValueConfiguration {
+
+  boolean isIntroducingAnAlias(Instruction instruction);
+
+  Value getAliasForOutValue(Instruction instruction);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/AssumeAndCheckCastAliasedValueConfiguration.java b/src/main/java/com/android/tools/r8/ir/code/AssumeAndCheckCastAliasedValueConfiguration.java
new file mode 100644
index 0000000..8a6517b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/AssumeAndCheckCastAliasedValueConfiguration.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2020, 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.code;
+
+import com.android.tools.r8.utils.ListUtils;
+
+public class AssumeAndCheckCastAliasedValueConfiguration implements AliasedValueConfiguration {
+
+  private static final AssumeAndCheckCastAliasedValueConfiguration INSTANCE =
+      new AssumeAndCheckCastAliasedValueConfiguration();
+
+  private AssumeAndCheckCastAliasedValueConfiguration() {}
+
+  public static AssumeAndCheckCastAliasedValueConfiguration getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public boolean isIntroducingAnAlias(Instruction instruction) {
+    return instruction.isAssume() || instruction.isCheckCast();
+  }
+
+  @Override
+  public Value getAliasForOutValue(Instruction instruction) {
+    assert instruction.isAssume() || instruction.isCheckCast();
+    return ListUtils.first(instruction.inValues());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 085c936..301be93 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -181,7 +181,7 @@
   }
 
   @Override
-  public void replaceCurrentInstruction(Instruction newInstruction) {
+  public void replaceCurrentInstruction(Instruction newInstruction, Set<Value> affectedValues) {
     if (current == null) {
       throw new IllegalStateException();
     }
@@ -190,6 +190,9 @@
     }
     if (current.outValue() != null && current.outValue().isUsed()) {
       assert newInstruction.outValue() != null;
+      if (affectedValues != null) {
+        current.outValue().addAffectedValuesTo(affectedValues);
+      }
       current.outValue().replaceUsers(newInstruction.outValue());
     }
     current.moveDebugValues(newInstruction);
diff --git a/src/main/java/com/android/tools/r8/ir/code/DefaultAliasedValueConfiguration.java b/src/main/java/com/android/tools/r8/ir/code/DefaultAliasedValueConfiguration.java
new file mode 100644
index 0000000..b147deb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/DefaultAliasedValueConfiguration.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2020, 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.code;
+
+public class DefaultAliasedValueConfiguration implements AliasedValueConfiguration {
+
+  private static final DefaultAliasedValueConfiguration INSTANCE =
+      new DefaultAliasedValueConfiguration();
+
+  private DefaultAliasedValueConfiguration() {}
+
+  public static DefaultAliasedValueConfiguration getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public boolean isIntroducingAnAlias(Instruction instruction) {
+    return instruction.isAssume();
+  }
+
+  @Override
+  public Value getAliasForOutValue(Instruction instruction) {
+    assert instruction.isAssume();
+    return instruction.asAssume().src();
+  }
+}
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 1a34adf..9594d10 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
@@ -1114,25 +1114,27 @@
     return true;
   }
 
-  public void removeAllTrivialPhis() {
-    removeAllTrivialPhis(null, null);
+  public boolean removeAllTrivialPhis() {
+    return removeAllTrivialPhis(null, null);
   }
 
-  public void removeAllTrivialPhis(IRBuilder builder) {
-    removeAllTrivialPhis(builder, null);
+  public boolean removeAllTrivialPhis(IRBuilder builder) {
+    return removeAllTrivialPhis(builder, null);
   }
 
-  public void removeAllTrivialPhis(Set<Value> affectedValues) {
-    removeAllTrivialPhis(null, affectedValues);
+  public boolean removeAllTrivialPhis(Set<Value> affectedValues) {
+    return removeAllTrivialPhis(null, affectedValues);
   }
 
-  public void removeAllTrivialPhis(IRBuilder builder, Set<Value> affectedValues) {
+  public boolean removeAllTrivialPhis(IRBuilder builder, Set<Value> affectedValues) {
+    boolean anyTrivialPhisRemoved = false;
     for (BasicBlock block : blocks) {
       List<Phi> phis = new ArrayList<>(block.getPhis());
       for (Phi phi : phis) {
-        phi.removeTrivialPhi(builder, affectedValues);
+        anyTrivialPhisRemoved |= phi.removeTrivialPhi(builder, affectedValues);
       }
     }
+    return anyTrivialPhisRemoved;
   }
 
   public int reserveMarkingColor() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index fdea0f6..f070e0f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -147,8 +147,8 @@
   }
 
   @Override
-  public void replaceCurrentInstruction(Instruction newInstruction) {
-    instructionIterator.replaceCurrentInstruction(newInstruction);
+  public void replaceCurrentInstruction(Instruction newInstruction, Set<Value> affectedValues) {
+    instructionIterator.replaceCurrentInstruction(newInstruction, affectedValues);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index 315568e..a8b539e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -17,6 +17,11 @@
 public interface InstructionListIterator
     extends InstructionIterator, ListIterator<Instruction>, PreviousUntilIterator<Instruction> {
 
+  /** See {@link #replaceCurrentInstruction(Instruction, Set)}. */
+  default void replaceCurrentInstruction(Instruction newInstruction) {
+    replaceCurrentInstruction(newInstruction, null);
+  }
+
   /**
    * Replace the current instruction (aka the {@link Instruction} returned by the previous call to
    * {@link #next} with the passed in <code>newInstruction</code>.
@@ -31,8 +36,9 @@
    * <p>The debug information of the current instruction will be attached to the new instruction.
    *
    * @param newInstruction the instruction to insert instead of the current.
+   * @param affectedValues if non-null, all users of the out value will be added to this set.
    */
-  void replaceCurrentInstruction(Instruction newInstruction);
+  void replaceCurrentInstruction(Instruction newInstruction, Set<Value> affectedValues);
 
   // Do not show a deprecation warning for InstructionListIterator.remove().
   @SuppressWarnings("deprecation")
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index 6f79a82..2c7dcfb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -67,8 +67,8 @@
     if (!appView.appInfo().hasSubtyping()) {
       return returnTypeLattice;
     }
-
-    List<DexType> lambdaInterfaces = LambdaDescriptor.getInterfaces(callSite, appView.appInfo());
+    List<DexType> lambdaInterfaces =
+        LambdaDescriptor.getInterfaces(callSite, appView.appInfo().withSubtyping());
     if (lambdaInterfaces == null || lambdaInterfaces.isEmpty()) {
       return returnTypeLattice;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index c8f8b49..5d782c3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -32,7 +32,7 @@
 
   public InvokeStatic(DexMethod target, Value result, List<Value> arguments) {
     this(target, result, arguments, false);
-    assert target.asDexReference().asDexMethod().proto.parameters.size() == arguments.size();
+    assert target.proto.parameters.size() == arguments.size();
   }
 
   public InvokeStatic(DexMethod target, Value result, List<Value> arguments, boolean itf) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index 3a098ca..6de9e58 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -36,8 +36,8 @@
   }
 
   @Override
-  public void replaceCurrentInstruction(Instruction newInstruction) {
-    currentBlockIterator.replaceCurrentInstruction(newInstruction);
+  public void replaceCurrentInstruction(Instruction newInstruction, Set<Value> affectedValues) {
+    currentBlockIterator.replaceCurrentInstruction(newInstruction, affectedValues);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NextUntilIterator.java b/src/main/java/com/android/tools/r8/ir/code/NextUntilIterator.java
index d15376e..cd08f22 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NextUntilIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NextUntilIterator.java
@@ -11,14 +11,15 @@
   /**
    * Continue to call {@link #next} while {@code predicate} tests {@code false}.
    *
-   * @returns the item that matched the predicate or {@code null} if all items fail
-   * the predicate test
+   * @returns the item that matched the predicate or {@code null} if all items fail the predicate
+   *     test
    */
-  default T nextUntil(Predicate<T> predicate) {
+  @SuppressWarnings("unchecked")
+  default <S extends T> S nextUntil(Predicate<T> predicate) {
     while (hasNext()) {
       T item = next();
       if (predicate.test(item)) {
-        return item;
+        return (S) item;
       }
     }
     return null;
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 d6372b5..bba9148 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
@@ -45,6 +45,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 public class Value implements Comparable<Value> {
@@ -264,25 +265,31 @@
    * <p>This method is useful to find the "true" definition of a value inside the current method.
    */
   public Value getAliasedValue() {
-    return getAliasedValue(Predicates.alwaysFalse());
+    return getAliasedValue(
+        DefaultAliasedValueConfiguration.getInstance(), Predicates.alwaysFalse());
   }
 
-  public Value getAliasedValue(Predicate<Value> stoppingCriterion) {
+  public Value getAliasedValue(AliasedValueConfiguration configuration) {
+    return getAliasedValue(configuration, Predicates.alwaysFalse());
+  }
+
+  public Value getAliasedValue(
+      AliasedValueConfiguration configuration, Predicate<Value> stoppingCriterion) {
     assert stoppingCriterion != null;
     Set<Value> visited = Sets.newIdentityHashSet();
     Value lastAliasedValue;
     Value aliasedValue = this;
     do {
-      if (stoppingCriterion.test(aliasedValue)) {
-        return aliasedValue;
-      }
       lastAliasedValue = aliasedValue;
       if (aliasedValue.isPhi()) {
         return aliasedValue;
       }
+      if (stoppingCriterion.test(aliasedValue)) {
+        return aliasedValue;
+      }
       Instruction definitionOfAliasedValue = aliasedValue.definition;
-      if (definitionOfAliasedValue.isIntroducingAnAlias()) {
-        aliasedValue = definitionOfAliasedValue.getAliasForOutValue();
+      if (configuration.isIntroducingAnAlias(definitionOfAliasedValue)) {
+        aliasedValue = configuration.getAliasForOutValue(definitionOfAliasedValue);
 
         // There shouldn't be a cycle.
         assert visited.add(aliasedValue);
@@ -293,7 +300,8 @@
   }
 
   public Value getSpecificAliasedValue(Predicate<Value> stoppingCriterion) {
-    Value aliasedValue = getAliasedValue(stoppingCriterion);
+    Value aliasedValue =
+        getAliasedValue(DefaultAliasedValueConfiguration.getInstance(), stoppingCriterion);
     return stoppingCriterion.test(aliasedValue) ? aliasedValue : null;
   }
 
@@ -440,21 +448,29 @@
   }
 
   public Set<Instruction> aliasedUsers() {
+    return aliasedUsers(DefaultAliasedValueConfiguration.getInstance());
+  }
+
+  public Set<Instruction> aliasedUsers(AliasedValueConfiguration configuration) {
     Set<Instruction> users = SetUtils.newIdentityHashSet(uniqueUsers());
     Set<Instruction> visited = Sets.newIdentityHashSet();
-    collectAliasedUsersViaAssume(visited, uniqueUsers(), users);
+    collectAliasedUsersViaAssume(configuration, visited, uniqueUsers(), users);
     return users;
   }
 
   private static void collectAliasedUsersViaAssume(
-      Set<Instruction> visited, Set<Instruction> usersToTest, Set<Instruction> collectedUsers) {
+      AliasedValueConfiguration configuration,
+      Set<Instruction> visited,
+      Set<Instruction> usersToTest,
+      Set<Instruction> collectedUsers) {
     for (Instruction user : usersToTest) {
       if (!visited.add(user)) {
         continue;
       }
-      if (user.isAssume()) {
+      if (configuration.isIntroducingAnAlias(user)) {
         collectedUsers.addAll(user.outValue().uniqueUsers());
-        collectAliasedUsersViaAssume(visited, user.outValue().uniqueUsers(), collectedUsers);
+        collectAliasedUsersViaAssume(
+            configuration, visited, user.outValue().uniqueUsers(), collectedUsers);
       }
     }
   }
@@ -627,13 +643,21 @@
   // Returns the set of Value that are affected if the current value's type lattice is updated.
   public Set<Value> affectedValues() {
     ImmutableSet.Builder<Value> affectedValues = ImmutableSet.builder();
+    forEachAffectedValue(affectedValues::add);
+    return affectedValues.build();
+  }
+
+  public void addAffectedValuesTo(Set<Value> affectedValues) {
+    forEachAffectedValue(affectedValues::add);
+  }
+
+  public void forEachAffectedValue(Consumer<Value> consumer) {
     for (Instruction user : uniqueUsers()) {
-      if (user.outValue() != null) {
-        affectedValues.add(user.outValue());
+      if (user.hasOutValue()) {
+        consumer.accept(user.outValue());
       }
     }
-    affectedValues.addAll(uniquePhiUsers());
-    return affectedValues.build();
+    uniquePhiUsers().forEach(consumer::accept);
   }
 
   public void replaceUsers(Value newValue) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CodeOptimization.java b/src/main/java/com/android/tools/r8/ir/conversion/CodeOptimization.java
index 8a40db4..ce571d6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CodeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CodeOptimization.java
@@ -6,7 +6,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
-import com.google.common.collect.ImmutableList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.function.Consumer;
@@ -41,42 +40,4 @@
       }
     };
   }
-
-  /**
-   * Builder for {@link CodeOptimization}.
-   *
-   * Users can append either {@link CodeOptimization}, or simply {@link IRCode} consumer.
-   *
-   * Note that the order of everything that is appended through the builder matters.
-   */
-  public static class Builder {
-    private ImmutableList.Builder<CodeOptimization> processingQueue;
-
-    private Builder() {
-      processingQueue = ImmutableList.builder();
-    }
-
-    public static Builder builder() {
-      return new Builder();
-    }
-
-    public Builder addIRCodeConsumer(Consumer<IRCode> consumer) {
-      processingQueue.add(from(consumer));
-      return this;
-    }
-
-    public Builder addCodeOptimization(CodeOptimization optimization) {
-      processingQueue.add(optimization);
-      return this;
-    }
-
-    public CodeOptimization build() {
-      return (code, feedback, methodProcessor) -> {
-        processingQueue
-            .build()
-            .forEach(codeOptimization ->
-                codeOptimization.optimize(code, feedback, methodProcessor));
-      };
-    }
-  }
 }
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 ace131b..028a397 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
@@ -193,7 +193,7 @@
     this.printer = printer;
     this.mainDexClasses = mainDexClasses.getClasses();
     this.codeRewriter = new CodeRewriter(appView, this);
-    this.constantCanonicalizer = new ConstantCanonicalizer();
+    this.constantCanonicalizer = new ConstantCanonicalizer(codeRewriter);
     this.classInitializerDefaultsOptimization =
         options.debug ? null : new ClassInitializerDefaultsOptimization(appView, this);
     this.stringConcatRewriter = new StringConcatRewriter(appView);
@@ -255,10 +255,7 @@
         ((options.desugarState == DesugarState.ON) && enableTwrCloseResourceDesugaring())
             ? new TwrCloseResourceRewriter(appView, this)
             : null;
-    this.backportedMethodRewriter =
-        options.desugarState == DesugarState.ON
-            ? new BackportedMethodRewriter(appView, this)
-            : null;
+    this.backportedMethodRewriter = new BackportedMethodRewriter(appView, this);
     this.covariantReturnTypeAnnotationTransformer =
         options.processCovariantReturnTypeAnnotations
             ? new CovariantReturnTypeAnnotationTransformer(this, appView.dexItemFactory())
@@ -444,9 +441,7 @@
 
   private void synthesizeJava8UtilityClass(
       Builder<?> builder, ExecutorService executorService) throws ExecutionException {
-    if (backportedMethodRewriter != null) {
-      backportedMethodRewriter.synthesizeUtilityClasses(builder, executorService);
-    }
+    backportedMethodRewriter.synthesizeUtilityClasses(builder, executorService);
   }
 
   private void processCovariantReturnTypeAnnotations(Builder<?> builder) {
@@ -1295,9 +1290,7 @@
     if (options.desugarState == DesugarState.ON && enableTryWithResourcesDesugaring()) {
       codeRewriter.rewriteThrowableAddAndGetSuppressed(code);
     }
-    if (backportedMethodRewriter != null) {
-      backportedMethodRewriter.desugar(code);
-    }
+    backportedMethodRewriter.desugar(code);
 
     stringConcatRewriter.desugarStringConcats(method.method, code);
 
@@ -1317,6 +1310,7 @@
           stringOptimizer,
           method,
           code,
+          feedback,
           methodProcessor,
           inliner,
           Suppliers.memoize(
@@ -1350,7 +1344,8 @@
     previous = printMethod(code, "IR after interface method rewriting (SSA)", previous);
 
     // This pass has to be after interfaceMethodRewriter and BackportedMethodRewriter.
-    if (desugaredLibraryAPIConverter != null) {
+    if (desugaredLibraryAPIConverter != null
+        && (!appView.enableWholeProgramOptimizations() || methodProcessor.isPrimary())) {
       desugaredLibraryAPIConverter.desugar(code);
       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 dc425e4..b65b164 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
@@ -29,6 +29,7 @@
 import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
 import com.android.tools.r8.graph.GraphLense.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.GraphLense.RewrittenPrototypeDescription.RemovedArgumentsInfo;
+import com.android.tools.r8.graph.ResolutionResult;
 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;
@@ -626,7 +627,9 @@
       // Virtual invoke is already as specific as it can get.
       return target;
     }
-    DexEncodedMethod newTarget = appView.appInfo().lookupVirtualTarget(receiverType, target);
+    ResolutionResult resolutionResult = appView.appInfo().resolveMethod(receiverType, target);
+    DexEncodedMethod newTarget =
+        resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null;
     if (newTarget == null || newTarget.method == target) {
       // Most likely due to a missing class, or invoke is already as specific as it gets.
       return target;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
index 81f93a7..b283f96 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueType;
@@ -56,13 +55,6 @@
     return true;
   }
 
-  private boolean isPrivateMethod() {
-    // We should be able to find targets for all private impl-methods, so
-    // we can rely on knowing accessibility flags for them.
-    MethodAccessFlags flags = descriptor().getAccessibility();
-    return flags != null && flags.isPrivate();
-  }
-
   // Are we delegating to a constructor?
   private boolean delegatingToConstructor() {
     return descriptor().implHandle.type.isInvokeConstructor();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 584bfc4..fe87616 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -44,6 +44,7 @@
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
@@ -70,7 +71,7 @@
   private final IRConverter converter;
   private final DexItemFactory factory;
   private final RewritableMethods rewritableMethods;
-  private final boolean enable;
+  private final boolean enabled;
 
   private final Set<DexType> holders = Sets.newConcurrentHashSet();
   private final Map<DexMethod, MethodProvider> methodProviders = new ConcurrentHashMap<>();
@@ -84,8 +85,10 @@
     // the highest known API level when the compiler is built. This ensures that when this is used
     // by the Android Platform build (which normally use an API level of 10000) there will be
     // no rewriting of backported methods. See b/147480264.
-    this.enable =
-        !this.rewritableMethods.isEmpty()
+    this.enabled =
+        (appView.options().desugarState == DesugarState.ON
+                || appView.options().desugarState == DesugarState.ONLY_BACKPORT_STATICS)
+            && !this.rewritableMethods.isEmpty()
             && appView.options().minApiLevel <= AndroidApiLevel.LATEST.getLevel();
   }
 
@@ -101,7 +104,7 @@
   }
 
   public void desugar(IRCode code) {
-    if (!enable) {
+    if (!enabled) {
       return; // Nothing to do!
     }
 
@@ -113,6 +116,10 @@
       }
 
       InvokeMethod invoke = instruction.asInvokeMethod();
+      if (appView.options().desugarState == DesugarState.ONLY_BACKPORT_STATICS
+          && !invoke.isInvokeStatic()) {
+        continue;
+      }
       MethodProvider provider = getMethodProviderOrNull(invoke.getInvokedMethod());
       if (provider == null) {
         if (!rewritableMethods.matchesVirtualRewrite(invoke.getInvokedMethod())) {
@@ -183,6 +190,9 @@
 
   public void synthesizeUtilityClasses(Builder<?> builder, ExecutorService executorService)
       throws ExecutionException {
+    if (!enabled) {
+      return;
+    }
     if (appView.options().isDesugaredLibraryCompilation()) {
       synthesizeEmulatedDispatchMethods(builder);
     } else {
@@ -1626,6 +1636,8 @@
     }
 
     private void initializeRetargetCoreLibraryMembers(AppView<?> appView) {
+      assert appView.appInfo().hasClassHierarchy()
+          : "Class hierarchy required for desugared library.";
       Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
           appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
       for (DexString methodName : retargetCoreLibMember.keySet()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 0204455..2f54714 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexClass;
@@ -174,7 +175,7 @@
     }
   }
 
-  private final AppView<?> appView;
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final DexItemFactory dexItemFactory;
   private final InterfaceMethodRewriter rewriter;
   private final Consumer<DexEncodedMethod> newSynthesizedMethodConsumer;
@@ -195,7 +196,7 @@
       new IdentityHashMap<>();
 
   ClassProcessor(
-      AppView<?> appView,
+      AppView<? extends AppInfoWithClassHierarchy> appView,
       InterfaceMethodRewriter rewriter,
       Consumer<DexEncodedMethod> newSynthesizedMethodConsumer) {
     this.appView = appView;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index ab215b6..09cd2aa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -97,7 +98,7 @@
   public static final String DEFAULT_METHOD_PREFIX = "$default$";
   public static final String PRIVATE_METHOD_PREFIX = "$private$";
 
-  private final AppView<?> appView;
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final IRConverter converter;
   private final InternalOptions options;
   final DexItemFactory factory;
@@ -132,7 +133,9 @@
 
   public InterfaceMethodRewriter(AppView<?> appView, IRConverter converter) {
     assert converter != null;
-    this.appView = appView;
+    assert appView.appInfo().hasClassHierarchy()
+        : "Cannot desugar interfaces without class hierarchy";
+    this.appView = appView.withClassHierarchy();
     this.converter = converter;
     this.options = appView.options();
     this.factory = appView.dexItemFactory();
@@ -280,7 +283,6 @@
               DexEncodedMethod dexEncodedMethod =
                   appView
                       .appInfo()
-                      .withClassHierarchy()
                       .lookupSuperTarget(invokeSuper.getInvokedMethod(), code.method.method.holder);
               if (dexEncodedMethod != null) {
                 DexClass dexClass = appView.definitionFor(dexEncodedMethod.method.holder);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 6bb06d5..e7d2f34 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -25,6 +25,8 @@
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
@@ -364,11 +366,15 @@
 
     // Lambda$ method. We must always find it.
     assert implMethod.holder == accessedFrom;
-    assert descriptor.targetFoundInClass(accessedFrom);
-    assert descriptor.getAccessibility() != null;
-
+    assert descriptor.verifyTargetFoundInClass(accessedFrom);
     if (implHandle.type.isInvokeStatic()) {
-      return new StaticLambdaImplTarget();
+      SingleResolutionResult resolution =
+          rewriter.getAppInfo().resolveMethod(implMethod.holder, implMethod).asSingleResolution();
+      assert resolution.getResolvedMethod().isStatic();
+      assert resolution.getResolvedHolder().isProgramClass();
+      return new StaticLambdaImplTarget(
+          new ProgramMethod(
+              resolution.getResolvedHolder().asProgramClass(), resolution.getResolvedMethod()));
     }
 
     assert implHandle.type.isInvokeInstance() || implHandle.type.isInvokeDirect();
@@ -575,18 +581,19 @@
   // Used for static private lambda$ methods. Only needs access relaxation.
   private final class StaticLambdaImplTarget extends Target {
 
-    StaticLambdaImplTarget() {
+    final ProgramMethod target;
+
+    StaticLambdaImplTarget(ProgramMethod target) {
       super(descriptor.implHandle.asMethod(), Invoke.Type.STATIC);
+      this.target = target;
     }
 
     @Override
     DexEncodedMethod ensureAccessibility() {
       // We already found the static method to be called, just relax its accessibility.
-      assert descriptor.getAccessibility() != null;
-      descriptor.getAccessibility().unsetPrivate();
-      DexClass implMethodHolder = definitionFor(descriptor.implHandle.asMethod().holder);
-      if (implMethodHolder.isInterface()) {
-        descriptor.getAccessibility().setPublic();
+      target.method.accessFlags.unsetPrivate();
+      if (target.holder.isInterface()) {
+        target.method.accessFlags.setPublic();
       }
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index 20becfb..2eb7b03 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -5,12 +5,13 @@
 package com.android.tools.r8.ir.desugar;
 
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.DexCallSite;
 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.DexMethodHandle;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -44,7 +45,8 @@
   final DexTypeList captures;
 
   // Used for accessibility analysis and few assertions only.
-  private final DexEncodedMethod targetMethod;
+  private final MethodAccessFlags targetAccessFlags;
+  private final DexType targetHolder;
 
   private LambdaDescriptor() {
     uniqueId = null;
@@ -53,12 +55,20 @@
     enforcedProto = null;
     implHandle = null;
     captures = null;
-    targetMethod = null;
+    targetAccessFlags = null;
+    targetHolder = null;
   }
 
-  private LambdaDescriptor(AppInfo appInfo, DexCallSite callSite,
-      DexString name, DexProto erasedProto, DexProto enforcedProto,
-      DexMethodHandle implHandle, DexType mainInterface, DexTypeList captures) {
+  private LambdaDescriptor(
+      AppInfoWithClassHierarchy appInfo,
+      DexType invocationContext,
+      DexCallSite callSite,
+      DexString name,
+      DexProto erasedProto,
+      DexProto enforcedProto,
+      DexMethodHandle implHandle,
+      DexType mainInterface,
+      DexTypeList captures) {
     assert appInfo != null;
     assert callSite != null;
     assert name != null;
@@ -76,7 +86,15 @@
     this.captures = captures;
 
     this.interfaces.add(mainInterface);
-    this.targetMethod = lookupTargetMethod(appInfo);
+    DexEncodedMethod targetMethod =
+        invocationContext == null ? null : lookupTargetMethod(appInfo, invocationContext);
+    if (targetMethod != null) {
+      targetAccessFlags = targetMethod.accessFlags.copy();
+      targetHolder = targetMethod.method.holder;
+    } else {
+      targetAccessFlags = null;
+      targetHolder = null;
+    }
   }
 
   final DexType getImplReceiverType() {
@@ -88,15 +106,18 @@
     return captures.length > 0 ? captures[0] : params[0];
   }
 
-  private DexEncodedMethod lookupTargetMethod(AppInfo appInfo) {
+  private DexEncodedMethod lookupTargetMethod(
+      AppInfoWithClassHierarchy appInfo, DexType invocationContext) {
+    assert invocationContext != null;
     // Find the lambda's impl-method target.
     DexMethod method = implHandle.asMethod();
     switch (implHandle.type) {
       case INVOKE_DIRECT:
       case INVOKE_INSTANCE: {
-        DexEncodedMethod target = appInfo.lookupVirtualTarget(getImplReceiverType(), method);
+          DexEncodedMethod target =
+              appInfo.resolveMethod(getImplReceiverType(), method).getSingleTarget();
         if (target == null) {
-          target = appInfo.lookupDirectTarget(method);
+            target = appInfo.lookupDirectTarget(method, invocationContext);
         }
         assert target == null
             || (implHandle.type.isInvokeInstance() && isInstanceMethod(target))
@@ -106,19 +127,20 @@
       }
 
       case INVOKE_STATIC: {
-        DexEncodedMethod target = appInfo.lookupStaticTarget(method);
+          DexEncodedMethod target = appInfo.lookupStaticTarget(method, invocationContext);
         assert target == null || target.accessFlags.isStatic();
         return target;
       }
 
       case INVOKE_CONSTRUCTOR: {
-        DexEncodedMethod target = appInfo.lookupDirectTarget(method);
+          DexEncodedMethod target = appInfo.lookupDirectTarget(method, invocationContext);
         assert target == null || target.accessFlags.isConstructor();
         return target;
       }
 
       case INVOKE_INTERFACE: {
-        DexEncodedMethod target = appInfo.lookupVirtualTarget(getImplReceiverType(), method);
+          DexEncodedMethod target =
+              appInfo.resolveMethod(getImplReceiverType(), method).getSingleTarget();
         assert target == null || isInstanceMethod(target);
         return target;
       }
@@ -143,12 +165,8 @@
     return encodedMethod.isPublicized() && isInstanceMethod(encodedMethod);
   }
 
-  final MethodAccessFlags getAccessibility() {
-    return targetMethod == null ? null : targetMethod.accessFlags;
-  }
-
-  final boolean targetFoundInClass(DexType type) {
-    return targetMethod != null && targetMethod.method.holder == type;
+  public final boolean verifyTargetFoundInClass(DexType type) {
+    return targetHolder == type;
   }
 
   /** If the lambda delegates to lambda$ method. */
@@ -177,9 +195,12 @@
     boolean instanceTarget = implHandle.type.isInvokeInstance() || implHandle.type.isInvokeDirect();
     boolean initTarget = implHandle.type.isInvokeConstructor();
     assert instanceTarget || staticTarget || initTarget;
-    assert !implHandle.type.isInvokeDirect() || isPrivateInstanceMethod(targetMethod);
+    assert !implHandle.type.isInvokeDirect()
+        || (targetAccessFlags.isPrivate()
+            && !targetAccessFlags.isConstructor()
+            && !targetAccessFlags.isStatic());
 
-    if (targetMethod == null) {
+    if (targetAccessFlags == null) {
       // The target cannot be a private method, since otherwise it
       // should have been found.
 
@@ -200,7 +221,7 @@
       return true;
     }
 
-    MethodAccessFlags flags = targetMethod.accessFlags;
+    MethodAccessFlags flags = targetAccessFlags;
 
     // Private methods always need accessors.
     if (flags.isPrivate()) {
@@ -211,26 +232,27 @@
     }
 
     boolean accessedFromSamePackage =
-        accessedFrom.getPackageDescriptor().equals(
-            targetMethod.method.holder.getPackageDescriptor());
+        accessedFrom.getPackageDescriptor().equals(targetHolder.getPackageDescriptor());
     assert flags.isProtected() || accessedFromSamePackage;
     return flags.isProtected() && !accessedFromSamePackage;
   }
 
   /**
-   * Matches call site for lambda metafactory invocation pattern and
-   * returns extracted match information, or null if match failed.
+   * Matches call site for lambda metafactory invocation pattern and returns extracted match
+   * information, or null if match failed.
    */
-  public static LambdaDescriptor tryInfer(DexCallSite callSite, AppInfo appInfo) {
-    LambdaDescriptor descriptor = infer(callSite, appInfo);
+  public static LambdaDescriptor tryInfer(
+      DexCallSite callSite, AppInfoWithClassHierarchy appInfo, DexProgramClass invocationContext) {
+    LambdaDescriptor descriptor = infer(callSite, appInfo, invocationContext.type);
     return descriptor == MATCH_FAILED ? null : descriptor;
   }
 
   /**
-   * Matches call site for lambda metafactory invocation pattern and
-   * returns extracted match information, or MATCH_FAILED if match failed.
+   * Matches call site for lambda metafactory invocation pattern and returns extracted match
+   * information, or MATCH_FAILED if match failed.
    */
-  static LambdaDescriptor infer(DexCallSite callSite, AppInfo appInfo) {
+  static LambdaDescriptor infer(
+      DexCallSite callSite, AppInfoWithClassHierarchy appInfo, DexType invocationContext) {
     // We expect bootstrap method to be either `metafactory` or `altMetafactory` method
     // of `java.lang.invoke.LambdaMetafactory` class. Both methods are static.
     if (!callSite.bootstrapMethod.type.isInvokeStatic()) {
@@ -277,9 +299,17 @@
     DexTypeList captures = lambdaFactoryProto.parameters;
 
     // Create a match.
-    LambdaDescriptor match = new LambdaDescriptor(appInfo, callSite,
-        funcMethodName, funcErasedSignature.value, funcEnforcedSignature.value,
-        lambdaImplMethodHandle, mainFuncInterface, captures);
+    LambdaDescriptor match =
+        new LambdaDescriptor(
+            appInfo,
+            invocationContext,
+            callSite,
+            funcMethodName,
+            funcErasedSignature.value,
+            funcEnforcedSignature.value,
+            lambdaImplMethodHandle,
+            mainFuncInterface,
+            captures);
 
     if (bootstrapMethod == factory.metafactoryMethod) {
       if (callSite.bootstrapArgs.size() != 3) {
@@ -342,8 +372,10 @@
     }
   }
 
-  public static List<DexType> getInterfaces(DexCallSite callSite, AppInfo appInfo) {
-    LambdaDescriptor descriptor = infer(callSite, appInfo);
+  public static List<DexType> getInterfaces(
+      DexCallSite callSite, AppInfoWithClassHierarchy appInfo) {
+    // No need for the invocationContext to figure out only the interfaces.
+    LambdaDescriptor descriptor = infer(callSite, appInfo, null);
     if (descriptor == LambdaDescriptor.MATCH_FAILED) {
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 2fd29e1..6ce1f5f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.ir.desugar;
 
-import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DefaultUseRegistry;
@@ -63,7 +63,7 @@
   static final String EXPECTED_LAMBDA_METHOD_PREFIX = "lambda$";
   private static final String LAMBDA_INSTANCE_FIELD_NAME = "INSTANCE";
 
-  private final AppView<?> appView;
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
 
   final DexString instanceFieldName;
 
@@ -87,7 +87,9 @@
   }
 
   public LambdaRewriter(AppView<?> appView) {
-    this.appView = appView;
+    assert appView.appInfo().hasClassHierarchy()
+        : "Lambda desugaring is not available without class hierarchy.";
+    this.appView = appView.withClassHierarchy();
     this.graphLens = new LambdaRewriterGraphLense(appView);
     this.instanceFieldName = getFactory().createString(LAMBDA_INSTANCE_FIELD_NAME);
   }
@@ -96,8 +98,8 @@
     return appView;
   }
 
-  public AppInfo getAppInfo() {
-    return getAppView().appInfo();
+  public AppInfoWithClassHierarchy getAppInfo() {
+    return appView.appInfo();
   }
 
   public DexItemFactory getFactory() {
@@ -214,7 +216,8 @@
           @Override
           public void registerCallSite(DexCallSite callSite) {
             LambdaDescriptor descriptor =
-                inferLambdaDescriptor(lensCodeRewriter.rewriteCallSite(callSite, method));
+                inferLambdaDescriptor(
+                    lensCodeRewriter.rewriteCallSite(callSite, method), method.method.holder);
             if (descriptor != LambdaDescriptor.MATCH_FAILED) {
               consumer.accept(getOrCreateLambdaClass(descriptor, method.method.holder));
             }
@@ -238,7 +241,8 @@
         Instruction instruction = instructions.next();
         if (instruction.isInvokeCustom()) {
           InvokeCustom invoke = instruction.asInvokeCustom();
-          LambdaDescriptor descriptor = inferLambdaDescriptor(invoke.getCallSite());
+          LambdaDescriptor descriptor =
+              inferLambdaDescriptor(invoke.getCallSite(), encodedMethod.method.holder);
           if (descriptor == LambdaDescriptor.MATCH_FAILED) {
             continue;
           }
@@ -333,7 +337,7 @@
   // corresponding to this lambda invocation point.
   //
   // Returns the lambda descriptor or `MATCH_FAILED`.
-  private LambdaDescriptor inferLambdaDescriptor(DexCallSite callSite) {
+  private LambdaDescriptor inferLambdaDescriptor(DexCallSite callSite, DexType invocationContext) {
     // We check the map before and after inferring lambda descriptor to minimize time
     // spent in synchronized block. As a result we may throw away calculated descriptor
     // in rare case when another thread has same call site processed concurrently,
@@ -341,7 +345,10 @@
     LambdaDescriptor descriptor = getKnown(knownCallSites, callSite);
     return descriptor != null
         ? descriptor
-        : putIfAbsent(knownCallSites, callSite, LambdaDescriptor.infer(callSite, getAppInfo()));
+        : putIfAbsent(
+            knownCallSites,
+            callSite,
+            LambdaDescriptor.infer(callSite, getAppInfo(), invocationContext));
   }
 
   private boolean isInMainDexList(DexType type) {
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 1382317..eff0d07 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
@@ -12,6 +12,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.ConstNumber;
@@ -29,8 +30,10 @@
 import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenCustomHashMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
 import it.unimi.dsi.fastutil.objects.Object2ObjectSortedMap.FastSortedEntrySet;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 
 /**
@@ -40,14 +43,17 @@
   // Threshold to limit the number of constant canonicalization.
   private static final int MAX_CANONICALIZED_CONSTANT = 22;
 
+  private final CodeRewriter codeRewriter;
+
   private int numberOfConstNumberCanonicalization = 0;
   private int numberOfConstStringCanonicalization = 0;
   private int numberOfDexItemBasedConstStringCanonicalization = 0;
   private int numberOfConstClassCanonicalization = 0;
-  private int numberOfEnumCanonicalization = 0;
+  private int numberOfEffectivelyFinalFieldCanonicalization = 0;
   private final Object2IntMap<Long> histogramOfCanonicalizationCandidatesPerMethod;
 
-  public ConstantCanonicalizer() {
+  public ConstantCanonicalizer(CodeRewriter codeRewriter) {
+    this.codeRewriter = codeRewriter;
     if (Log.ENABLED) {
       histogramOfCanonicalizationCandidatesPerMethod = new Object2IntArrayMap<>();
     } else {
@@ -66,7 +72,10 @@
         numberOfDexItemBasedConstStringCanonicalization);
     Log.info(getClass(),
         "# const-class canonicalization: %s", numberOfConstClassCanonicalization);
-    Log.info(getClass(), "# enum canonicalization: %s", numberOfEnumCanonicalization);
+    Log.info(
+        getClass(),
+        "# effectively final field canonicalization: %s",
+        numberOfEffectivelyFinalFieldCanonicalization);
     assert histogramOfCanonicalizationCandidatesPerMethod != null;
     Log.info(getClass(), "------ histogram of constant canonicalization candidates ------");
     histogramOfCanonicalizationCandidatesPerMethod.forEach((length, count) -> {
@@ -129,9 +138,11 @@
           && code.metadata().mayHaveMonitorInstruction()) {
         continue;
       }
-      // Only canonicalize enum values. This is only OK if the instruction cannot have side effects.
+      // Canonicalize effectively final fields that are guaranteed to be written before they are
+      // read. This is only OK if the instruction cannot have side effects.
       if (current.isStaticGet()) {
-        if (!current.outValue().getAbstractValue(appView, context).isSingleEnumValue()) {
+        AbstractValue abstractValue = current.outValue().getAbstractValue(appView, context);
+        if (!abstractValue.isSingleFieldValue()) {
           continue;
         }
         if (current.instructionMayHaveSideEffects(appView, context)) {
@@ -174,59 +185,69 @@
         histogramOfCanonicalizationCandidatesPerMethod.put(numOfCandidates, count + 1);
       }
     }
-    entries.stream()
-        .filter(a -> a.getValue().size() > 1)
-        .sorted((a, b) -> Integer.compare(b.getValue().size(), a.getValue().size()))
-        .limit(MAX_CANONICALIZED_CONSTANT)
-        .forEach(
-            (entry) -> {
-              Instruction canonicalizedConstant = entry.getKey();
-              assert canonicalizedConstant.instructionTypeCanBeCanonicalized();
-              Instruction newConst;
-              switch (canonicalizedConstant.opcode()) {
-                case CONST_CLASS:
-                  if (Log.ENABLED) {
-                    numberOfConstClassCanonicalization++;
-                  }
-                  newConst = ConstClass.copyOf(code, canonicalizedConstant.asConstClass());
-                  break;
-                case CONST_NUMBER:
-                  if (Log.ENABLED) {
-                    numberOfConstNumberCanonicalization++;
-                  }
-                  newConst = ConstNumber.copyOf(code, canonicalizedConstant.asConstNumber());
-                  break;
-                case CONST_STRING:
-                  if (Log.ENABLED) {
-                    numberOfConstStringCanonicalization++;
-                  }
-                  newConst = ConstString.copyOf(code, canonicalizedConstant.asConstString());
-                  break;
-                case DEX_ITEM_BASED_CONST_STRING:
-                  if (Log.ENABLED) {
-                    numberOfDexItemBasedConstStringCanonicalization++;
-                  }
-                  newConst =
-                      DexItemBasedConstString.copyOf(
-                          code, canonicalizedConstant.asDexItemBasedConstString());
-                  break;
-                case STATIC_GET:
-                  if (Log.ENABLED) {
-                    numberOfEnumCanonicalization++;
-                  }
-                  newConst = StaticGet.copyOf(code, canonicalizedConstant.asStaticGet());
-                  break;
-                default:
-                  throw new Unreachable();
-              }
-              newConst.setPosition(firstNonNonePosition);
-              insertCanonicalizedConstant(code, newConst);
-              for (Value outValue : entry.getValue()) {
-                outValue.replaceUsers(newConst.outValue());
-              }
-            });
 
-    code.removeAllTrivialPhis();
+    Iterator<Object2ObjectMap.Entry<Instruction, List<Value>>> iterator =
+        entries.stream()
+            .filter(a -> a.getValue().size() > 1)
+            .sorted((a, b) -> Integer.compare(b.getValue().size(), a.getValue().size()))
+            .limit(MAX_CANONICALIZED_CONSTANT)
+            .iterator();
+
+    if (!iterator.hasNext()) {
+      return;
+    }
+    do {
+      Object2ObjectMap.Entry<Instruction, List<Value>> entry = iterator.next();
+      Instruction canonicalizedConstant = entry.getKey();
+      assert canonicalizedConstant.instructionTypeCanBeCanonicalized();
+      Instruction newConst;
+      switch (canonicalizedConstant.opcode()) {
+        case CONST_CLASS:
+          if (Log.ENABLED) {
+            numberOfConstClassCanonicalization++;
+          }
+          newConst = ConstClass.copyOf(code, canonicalizedConstant.asConstClass());
+          break;
+        case CONST_NUMBER:
+          if (Log.ENABLED) {
+            numberOfConstNumberCanonicalization++;
+          }
+          newConst = ConstNumber.copyOf(code, canonicalizedConstant.asConstNumber());
+          break;
+        case CONST_STRING:
+          if (Log.ENABLED) {
+            numberOfConstStringCanonicalization++;
+          }
+          newConst = ConstString.copyOf(code, canonicalizedConstant.asConstString());
+          break;
+        case DEX_ITEM_BASED_CONST_STRING:
+          if (Log.ENABLED) {
+            numberOfDexItemBasedConstStringCanonicalization++;
+          }
+          newConst =
+              DexItemBasedConstString.copyOf(
+                  code, canonicalizedConstant.asDexItemBasedConstString());
+          break;
+        case STATIC_GET:
+          if (Log.ENABLED) {
+            numberOfEffectivelyFinalFieldCanonicalization++;
+          }
+          newConst = StaticGet.copyOf(code, canonicalizedConstant.asStaticGet());
+          break;
+        default:
+          throw new Unreachable();
+      }
+      newConst.setPosition(firstNonNonePosition);
+      insertCanonicalizedConstant(code, newConst);
+      for (Value outValue : entry.getValue()) {
+        outValue.replaceUsers(newConst.outValue());
+      }
+    } while (iterator.hasNext());
+
+    if (code.removeAllTrivialPhis()) {
+      codeRewriter.simplifyControlFlow(code);
+    }
+
     assert code.isConsistentSSA();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index c7e511a..8370b69 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
-import com.android.tools.r8.ir.analysis.proto.ProtoInliningReasonStrategy;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstancePut;
@@ -32,7 +31,6 @@
 import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
-import com.android.tools.r8.ir.optimize.inliner.DefaultInliningReasonStrategy;
 import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.logging.Log;
@@ -64,6 +62,7 @@
   DefaultInliningOracle(
       AppView<AppInfoWithLiveness> appView,
       Inliner inliner,
+      InliningReasonStrategy inliningReasonStrategy,
       DexEncodedMethod method,
       IRCode code,
       MethodProcessor methodProcessor,
@@ -71,20 +70,13 @@
       int inliningInstructionAllowance) {
     this.appView = appView;
     this.inliner = inliner;
+    this.reasonStrategy = inliningReasonStrategy;
     this.method = method;
     this.code = code;
     this.methodProcessor = methodProcessor;
     this.isProcessedConcurrently = methodProcessor::isProcessedConcurrently;
     this.inliningInstructionLimit = inliningInstructionLimit;
     this.instructionAllowance = inliningInstructionAllowance;
-
-    DefaultInliningReasonStrategy defaultInliningReasonStrategy =
-        new DefaultInliningReasonStrategy(
-            appView, methodProcessor.getCallSiteInformation(), inliner);
-    this.reasonStrategy =
-        appView.withGeneratedMessageLiteShrinker(
-            ignore -> new ProtoInliningReasonStrategy(appView, defaultInliningReasonStrategy),
-            defaultInliningReasonStrategy);
   }
 
   @Override
@@ -333,6 +325,7 @@
   public InlineAction computeInlining(
       InvokeMethod invoke,
       DexEncodedMethod singleTarget,
+      DexEncodedMethod context,
       ClassInitializationAnalysis classInitializationAnalysis,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     if (isSingleTargetInvalid(invoke, singleTarget, whyAreYouNotInliningReporter)) {
@@ -343,7 +336,7 @@
       return null;
     }
 
-    Reason reason = reasonStrategy.computeInliningReason(invoke, singleTarget);
+    Reason reason = reasonStrategy.computeInliningReason(invoke, singleTarget, context);
     if (reason == Reason.NEVER) {
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index 15281a5..26c28d7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -61,6 +61,7 @@
   public InlineAction computeInlining(
       InvokeMethod invoke,
       DexEncodedMethod singleTarget,
+      DexEncodedMethod context,
       ClassInitializationAnalysis classInitializationAnalysis,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     return computeForInvoke(invoke, whyAreYouNotInliningReporter);
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 ed35f73..5bbc662 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
@@ -19,6 +19,7 @@
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
+import com.android.tools.r8.ir.analysis.proto.ProtoInliningReasonStrategy;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -44,7 +45,9 @@
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
+import com.android.tools.r8.ir.optimize.inliner.DefaultInliningReasonStrategy;
 import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
+import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
 import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
@@ -830,12 +833,25 @@
     performInliningImpl(
         oracle, oracle, method, code, OptimizationFeedbackIgnore.getInstance(), inliningIRProvider);
   }
-
   public void performInlining(
       DexEncodedMethod method,
       IRCode code,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor) {
+    performInlining(
+        method,
+        code,
+        feedback,
+        methodProcessor,
+        createDefaultInliningReasonStrategy(methodProcessor));
+  }
+
+  public void performInlining(
+      DexEncodedMethod method,
+      IRCode code,
+      OptimizationFeedback feedback,
+      MethodProcessor methodProcessor,
+      InliningReasonStrategy inliningReasonStrategy) {
     InternalOptions options = appView.options();
     DefaultInliningOracle oracle =
         createDefaultOracle(
@@ -843,21 +859,48 @@
             code,
             methodProcessor,
             options.inliningInstructionLimit,
-            options.inliningInstructionAllowance - numberOfInstructions(code));
+            options.inliningInstructionAllowance - numberOfInstructions(code),
+            inliningReasonStrategy);
     InliningIRProvider inliningIRProvider = new InliningIRProvider(appView, method, code);
     assert inliningIRProvider.verifyIRCacheIsEmpty();
     performInliningImpl(oracle, oracle, method, code, feedback, inliningIRProvider);
   }
 
+  public InliningReasonStrategy createDefaultInliningReasonStrategy(
+      MethodProcessor methodProcessor) {
+    DefaultInliningReasonStrategy defaultInliningReasonStrategy =
+        new DefaultInliningReasonStrategy(appView, methodProcessor.getCallSiteInformation(), this);
+    return appView.withGeneratedMessageLiteShrinker(
+        ignore -> new ProtoInliningReasonStrategy(appView, defaultInliningReasonStrategy),
+        defaultInliningReasonStrategy);
+  }
+
   public DefaultInliningOracle createDefaultOracle(
       DexEncodedMethod method,
       IRCode code,
       MethodProcessor methodProcessor,
       int inliningInstructionLimit,
       int inliningInstructionAllowance) {
+    return createDefaultOracle(
+        method,
+        code,
+        methodProcessor,
+        inliningInstructionLimit,
+        inliningInstructionAllowance,
+        createDefaultInliningReasonStrategy(methodProcessor));
+  }
+
+  public DefaultInliningOracle createDefaultOracle(
+      DexEncodedMethod method,
+      IRCode code,
+      MethodProcessor methodProcessor,
+      int inliningInstructionLimit,
+      int inliningInstructionAllowance,
+      InliningReasonStrategy inliningReasonStrategy) {
     return new DefaultInliningOracle(
         appView,
         this,
+        inliningReasonStrategy,
         method,
         code,
         methodProcessor,
@@ -905,7 +948,11 @@
                   : WhyAreYouNotInliningReporter.createFor(singleTarget, appView, context);
           InlineAction action =
               oracle.computeInlining(
-                  invoke, singleTarget, classInitializationAnalysis, whyAreYouNotInliningReporter);
+                  invoke,
+                  singleTarget,
+                  context,
+                  classInitializationAnalysis,
+                  whyAreYouNotInliningReporter);
           if (action == null) {
             assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
             continue;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index fd4b0ca..e50011a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -11,15 +11,18 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Collection;
+import java.util.function.BiFunction;
 
 // Computes the inlining constraint for a given instruction.
 public class InliningConstraints {
@@ -47,10 +50,22 @@
     this.graphLense = graphLense; // Note: Intentionally *not* appView.graphLense().
   }
 
+  public AppView<AppInfoWithLiveness> getAppView() {
+    return appView;
+  }
+
+  public GraphLense getGraphLense() {
+    return graphLense;
+  }
+
   public void disallowStaticInterfaceMethodCalls() {
     allowStaticInterfaceMethodCalls = false;
   }
 
+  private boolean isVerticalClassMerging() {
+    return !graphLense.isIdentityLense();
+  }
+
   public ConstraintWithTarget forAlwaysMaterializingUser() {
     return ConstraintWithTarget.ALWAYS;
   }
@@ -154,10 +169,47 @@
     return ConstraintWithTarget.NEVER;
   }
 
+  private DexEncodedMethod lookupWhileVerticalClassMerging(
+      DexMethod method,
+      DexType invocationContext,
+      BiFunction<SingleResolutionResult, DexProgramClass, DexEncodedMethod> lookupFunction) {
+    SingleResolutionResult singleResolutionResult =
+        appView.appInfo().resolveMethod(method.holder, method).asSingleResolution();
+    if (singleResolutionResult == null) {
+      return null;
+    }
+    DexProgramClass context = appView.definitionForProgramType(invocationContext);
+    if (context == null) {
+      return null;
+    }
+    DexEncodedMethod dexEncodedMethod = lookupFunction.apply(singleResolutionResult, context);
+    if (dexEncodedMethod != null) {
+      return dexEncodedMethod;
+    }
+    assert graphLense.lookupType(context.superType) == context.type;
+    DexProgramClass superContext = appView.definitionForProgramType(context.superType);
+    if (superContext == null) {
+      return null;
+    }
+    DexEncodedMethod alternativeDexEncodedMethod =
+        lookupFunction.apply(singleResolutionResult, superContext);
+    if (alternativeDexEncodedMethod != null
+        && alternativeDexEncodedMethod.method.holder == superContext.type) {
+      return alternativeDexEncodedMethod;
+    }
+    return null;
+  }
+
   public ConstraintWithTarget forInvokeDirect(DexMethod method, DexType invocationContext) {
     DexMethod lookup = graphLense.lookupMethod(method);
-    return forSingleTargetInvoke(
-        lookup, appView.appInfo().lookupDirectTarget(lookup), invocationContext);
+    DexEncodedMethod target =
+        isVerticalClassMerging()
+            ? lookupWhileVerticalClassMerging(
+                lookup,
+                invocationContext,
+                (res, ctxt) -> res.lookupInvokeDirectTarget(ctxt, appView.appInfo()))
+            : appView.appInfo().lookupDirectTarget(lookup, invocationContext);
+    return forSingleTargetInvoke(lookup, target, invocationContext);
   }
 
   public ConstraintWithTarget forInvokeInterface(DexMethod method, DexType invocationContext) {
@@ -182,8 +234,14 @@
 
   public ConstraintWithTarget forInvokeStatic(DexMethod method, DexType invocationContext) {
     DexMethod lookup = graphLense.lookupMethod(method);
-    return forSingleTargetInvoke(
-        lookup, appView.appInfo().lookupStaticTarget(lookup, invocationContext), invocationContext);
+    DexEncodedMethod target =
+        isVerticalClassMerging()
+            ? lookupWhileVerticalClassMerging(
+                lookup,
+                invocationContext,
+                (res, ctxt) -> res.lookupInvokeStaticTarget(ctxt, appView.appInfo()))
+            : appView.appInfo().lookupStaticTarget(lookup, invocationContext);
+    return forSingleTargetInvoke(lookup, target, invocationContext);
   }
 
   public ConstraintWithTarget forInvokeSuper(DexMethod method, DexType invocationContext) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index 3fbb76f..6f4570f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -31,6 +31,7 @@
   InlineAction computeInlining(
       InvokeMethod invoke,
       DexEncodedMethod singleTarget,
+      DexEncodedMethod context,
       ClassInitializationAnalysis classInitializationAnalysis,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
 }
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 c71006f..c4a761b 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
@@ -4,10 +4,14 @@
 
 package com.android.tools.r8.ir.optimize.classinliner;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 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.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -18,6 +22,7 @@
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.InliningOracle;
 import com.android.tools.r8.ir.optimize.classinliner.InlineCandidateProcessor.IllegalClassInlinerStateException;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
 import com.android.tools.r8.ir.optimize.string.StringOptimizer;
 import com.android.tools.r8.logging.Log;
@@ -161,6 +166,7 @@
       StringOptimizer stringOptimizer,
       DexEncodedMethod method,
       IRCode code,
+      OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
       Inliner inliner,
       Supplier<InliningOracle> defaultOracle) {
@@ -174,7 +180,7 @@
     // We loop inlining iterations until there was no inlining, but still use same set
     // of roots to avoid infinite inlining. Looping makes possible for some roots to
     // become eligible after other roots are inlined.
-
+    boolean anyInlinedGeneratedMessageLiteBuilders = false;
     boolean anyInlinedMethods = false;
     boolean repeat;
     do {
@@ -233,6 +239,15 @@
           continue;
         }
 
+        if (appView.protoShrinker() != null && root.isNewInstance()) {
+          DexType instantiatedType = root.asNewInstance().clazz;
+          DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(instantiatedType));
+          if (clazz != null
+              && appView.protoShrinker().references.isGeneratedMessageLiteBuilder(clazz)) {
+            anyInlinedGeneratedMessageLiteBuilders = true;
+          }
+        }
+
         // Inline the class instance.
         try {
           anyInlinedMethods |= processor.processInlining(code, defaultOracle, inliningIRProvider);
@@ -262,6 +277,14 @@
       }
     } while (repeat);
 
+    if (anyInlinedGeneratedMessageLiteBuilders) {
+      // Inline all calls to dynamicMethod() where the given MethodToInvoke is considered simple.
+      appView.withGeneratedMessageLiteBuilderShrinker(
+          shrinker ->
+              shrinker.inlineCallsToDynamicMethod(
+                  method, code, codeRewriter, feedback, methodProcessor, inliner));
+    }
+
     if (anyInlinedMethods) {
       // If a method was inlined we may be able to remove check-cast instructions because we may
       // have more information about the types of the arguments at the call site. This is
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 17510c2..a5b8d52 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
@@ -20,8 +20,10 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.code.Assume;
+import com.android.tools.r8.ir.code.AliasedValueConfiguration;
+import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
 import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.If;
@@ -29,6 +31,7 @@
 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;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
@@ -50,6 +53,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.StringUtils;
 import com.google.common.collect.ImmutableSet;
@@ -76,6 +80,8 @@
 
   private static final ImmutableSet<If.Type> ALLOWED_ZERO_TEST_TYPES =
       ImmutableSet.of(If.Type.EQ, If.Type.NE);
+  private static final AliasedValueConfiguration aliasesThroughAssumeAndCheckCasts =
+      AssumeAndCheckCastAliasedValueConfiguration.getInstance();
 
   private final AppView<AppInfoWithLiveness> appView;
   private final Inliner inliner;
@@ -89,6 +95,8 @@
 
   private final Map<InvokeMethodWithReceiver, InliningInfo> methodCallsOnInstance =
       new IdentityHashMap<>();
+
+  private final Set<DexEncodedMethod> indirectMethodCallsOnInstance = Sets.newIdentityHashSet();
   private final Map<InvokeMethod, InliningInfo> extraMethodCalls
       = new IdentityHashMap<>();
   private final List<Pair<InvokeMethod, Integer>> unusedArguments
@@ -338,10 +346,7 @@
     boolean anyInlinedMethods = forceInlineExtraMethodInvocations(code, inliningIRProvider);
     if (anyInlinedMethods) {
       // Reset the collections.
-      methodCallsOnInstance.clear();
-      extraMethodCalls.clear();
-      unusedArguments.clear();
-      receivers.reset();
+      clear();
 
       // Repeat user analysis
       InstructionOrPhi ineligibleUser = areInstanceUsersEligible(defaultOracle);
@@ -355,7 +360,8 @@
     }
 
     anyInlinedMethods |= forceInlineDirectMethodInvocations(code, inliningIRProvider);
-    removeAssumeInstructionsLinkedToEligibleInstance();
+    anyInlinedMethods |= forceInlineIndirectMethodInvocations(code, inliningIRProvider);
+    removeAliasIntroducingInstructionsLinkedToEligibleInstance();
     removeMiscUsages(code);
     removeFieldReads(code);
     removeFieldWrites();
@@ -363,6 +369,14 @@
     return anyInlinedMethods;
   }
 
+  private void clear() {
+    methodCallsOnInstance.clear();
+    indirectMethodCallsOnInstance.clear();
+    extraMethodCalls.clear();
+    unusedArguments.clear();
+    receivers.reset();
+  }
+
   private void replaceUsagesAsUnusedArgument(IRCode code) {
     for (Pair<InvokeMethod, Integer> unusedArgument : unusedArguments) {
       InvokeMethod invoke = unusedArgument.getFirst();
@@ -448,21 +462,73 @@
     return true;
   }
 
-  private void removeAssumeInstructionsLinkedToEligibleInstance() {
-    for (Instruction user : eligibleInstance.aliasedUsers()) {
-      if (!user.isAssume()) {
-        continue;
-      }
-      Assume<?> assumeInstruction = user.asAssume();
-      Value src = assumeInstruction.src();
-      Value dest = assumeInstruction.outValue();
-      assert receivers.isReceiverAlias(dest);
-      assert !dest.hasPhiUsers();
-      dest.replaceUsers(src);
-      removeInstruction(user);
+  private boolean forceInlineIndirectMethodInvocations(
+      IRCode code, InliningIRProvider inliningIRProvider) throws IllegalClassInlinerStateException {
+    if (indirectMethodCallsOnInstance.isEmpty()) {
+      return false;
     }
-    // Verify that no more assume instructions are left as users.
+
+    Map<InvokeMethodWithReceiver, InliningInfo> methodCallsOnInstance = new IdentityHashMap<>();
+
+    Set<Instruction> currentUsers = eligibleInstance.uniqueUsers();
+    while (!currentUsers.isEmpty()) {
+      Set<Instruction> indirectOutValueUsers = Sets.newIdentityHashSet();
+      for (Instruction instruction : currentUsers) {
+        if (instruction.isAssume() || instruction.isCheckCast()) {
+          indirectOutValueUsers.addAll(instruction.outValue().uniqueUsers());
+          continue;
+        }
+
+        if (instruction.isInvokeMethodWithReceiver()) {
+          InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
+          DexMethod invokedMethod = invoke.getInvokedMethod();
+          if (invokedMethod == appView.dexItemFactory().objectMethods.constructor) {
+            continue;
+          }
+
+          Value receiver = invoke.getReceiver().getAliasedValue(aliasesThroughAssumeAndCheckCasts);
+          if (receiver != eligibleInstance) {
+            continue;
+          }
+
+          DexEncodedMethod singleTarget =
+              invoke.lookupSingleTarget(appView, code.method.method.holder);
+          if (singleTarget == null || !indirectMethodCallsOnInstance.contains(singleTarget)) {
+            throw new IllegalClassInlinerStateException();
+          }
+
+          methodCallsOnInstance.put(invoke, new InliningInfo(singleTarget, null));
+        }
+      }
+      currentUsers = indirectOutValueUsers;
+    }
+
+    assert !methodCallsOnInstance.isEmpty();
+
+    inliner.performForcedInlining(method, code, methodCallsOnInstance, inliningIRProvider);
+    return true;
+  }
+
+  private void removeAliasIntroducingInstructionsLinkedToEligibleInstance() {
+    Set<Instruction> currentUsers = eligibleInstance.uniqueUsers();
+    while (!currentUsers.isEmpty()) {
+      Set<Instruction> indirectOutValueUsers = Sets.newIdentityHashSet();
+      for (Instruction instruction : currentUsers) {
+        if (instruction.isAssume() || instruction.isCheckCast()) {
+          Value src = ListUtils.first(instruction.inValues());
+          Value dest = instruction.outValue();
+          indirectOutValueUsers.addAll(dest.uniqueUsers());
+          assert !dest.hasPhiUsers();
+          dest.replaceUsers(src);
+          removeInstruction(instruction);
+        }
+      }
+      currentUsers = indirectOutValueUsers;
+    }
+
+    // Verify that no more assume or check-cast instructions are left as users.
     assert eligibleInstance.aliasedUsers().stream().noneMatch(Instruction::isAssume);
+    assert eligibleInstance.aliasedUsers().stream().noneMatch(Instruction::isCheckCast);
   }
 
   // Remove miscellaneous users before handling field reads.
@@ -700,7 +766,13 @@
     while (!currentUsers.isEmpty()) {
       Set<Instruction> indirectOutValueUsers = Sets.newIdentityHashSet();
       for (Instruction instruction : currentUsers) {
-        if (instruction.isAssume()) {
+        if (instruction.isAssume() || instruction.isCheckCast()) {
+          if (instruction.isCheckCast()) {
+            CheckCast checkCast = instruction.asCheckCast();
+            if (!appView.appInfo().isSubtype(eligibleClass.type, checkCast.getType())) {
+              return false; // Unsafe cast.
+            }
+          }
           Value outValueAlias = instruction.outValue();
           if (outValueAlias.hasPhiUsers() || outValueAlias.hasDebugUsers()) {
             return false;
@@ -714,11 +786,12 @@
 
         if (instruction.isInvokeMethodWithReceiver()) {
           InvokeMethodWithReceiver user = instruction.asInvokeMethodWithReceiver();
-          if (user.getReceiver().getAliasedValue() != outValue) {
+          if (user.getReceiver().getAliasedValue(aliasesThroughAssumeAndCheckCasts) != outValue) {
             return false;
           }
           for (int i = 1; i < user.inValues().size(); i++) {
-            if (user.inValues().get(i).getAliasedValue() == outValue) {
+            Value inValue = user.inValues().get(i);
+            if (inValue.getAliasedValue(aliasesThroughAssumeAndCheckCasts) == outValue) {
               return false;
             }
           }
@@ -767,15 +840,45 @@
       return null;
     }
 
+    ClassInlinerEligibilityInfo eligibility =
+        singleTarget.getOptimizationInfo().getClassInlinerEligibility();
+    if (eligibility.callsReceiver.size() > 1) {
+      return null;
+    }
+    if (!eligibility.callsReceiver.isEmpty()) {
+      assert eligibility.callsReceiver.get(0).getFirst() == Invoke.Type.VIRTUAL;
+      DexMethod indirectlyInvokedMethod = eligibility.callsReceiver.get(0).getSecond();
+      DexEncodedMethod indirectSingleTarget =
+          appView.appInfo().resolveMethod(eligibleClass, indirectlyInvokedMethod).getSingleTarget();
+      if (indirectSingleTarget == null) {
+        return null;
+      }
+      if (!isEligibleIndirectVirtualMethodCall(indirectlyInvokedMethod, indirectSingleTarget)) {
+        return null;
+      }
+      indirectMethodCallsOnInstance.add(indirectSingleTarget);
+    }
+
     return new InliningInfo(singleTarget, eligibleClass.type);
   }
 
-  private boolean isEligibleIndirectVirtualMethodCall(DexMethod callee) {
+  private boolean isEligibleIndirectVirtualMethodCall(DexMethod invokedMethod) {
     DexEncodedMethod singleTarget =
-        appView.appInfo().resolveMethod(eligibleClass, callee).getSingleTarget();
-    return isEligibleSingleTarget(singleTarget)
-        && isEligibleVirtualMethodCall(
-            null, callee, singleTarget, eligibility -> eligibility.returnsReceiver.isFalse());
+        appView.appInfo().resolveMethod(eligibleClass, invokedMethod).getSingleTarget();
+    return isEligibleIndirectVirtualMethodCall(invokedMethod, singleTarget);
+  }
+
+  private boolean isEligibleIndirectVirtualMethodCall(
+      DexMethod invokedMethod, DexEncodedMethod singleTarget) {
+    if (!isEligibleSingleTarget(singleTarget)) {
+      return false;
+    }
+    return isEligibleVirtualMethodCall(
+        null,
+        invokedMethod,
+        singleTarget,
+        eligibility ->
+            eligibility.callsReceiver.isEmpty() && eligibility.returnsReceiver.isFalse());
   }
 
   private boolean isEligibleVirtualMethodCall(
@@ -803,7 +906,7 @@
 
     MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
     ClassInlinerEligibilityInfo eligibility = optimizationInfo.getClassInlinerEligibility();
-    if (eligibility == null || !eligibility.callsReceiver.isEmpty()) {
+    if (eligibility == null) {
       return false;
     }
 
@@ -1009,6 +1112,7 @@
           oracle.computeInlining(
               invoke,
               singleTarget,
+              method,
               ClassInitializationAnalysis.trivial(),
               NopWhyAreYouNotInliningReporter.getInstance());
       if (inlineAction == null) {
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 8238723..b97cc0d 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
@@ -27,6 +27,7 @@
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_NEW_ARRAY;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
+import static com.android.tools.r8.ir.code.Opcodes.MONITOR;
 import static com.android.tools.r8.ir.code.Opcodes.MUL;
 import static com.android.tools.r8.ir.code.Opcodes.NEW_ARRAY_EMPTY;
 import static com.android.tools.r8.ir.code.Opcodes.NEW_INSTANCE;
@@ -59,6 +60,8 @@
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.code.AliasedValueConfiguration;
+import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.DominatorTree;
 import com.android.tools.r8.ir.code.FieldInstruction;
@@ -99,6 +102,7 @@
 import java.util.List;
 import java.util.Set;
 import java.util.function.BiFunction;
+import java.util.function.Predicate;
 
 public class MethodOptimizationInfoCollector {
   private final AppView<AppInfoWithLiveness> appView;
@@ -167,80 +171,79 @@
     List<Pair<Invoke.Type, DexMethod>> callsReceiver = new ArrayList<>();
     boolean seenSuperInitCall = false;
     boolean seenMonitor = false;
-    for (Instruction insn : receiver.aliasedUsers()) {
-      if (insn.isAssume()) {
-        continue;
-      }
 
-      if (insn.isMonitor()) {
-        seenMonitor = true;
-        continue;
-      }
+    AliasedValueConfiguration configuration =
+        AssumeAndCheckCastAliasedValueConfiguration.getInstance();
+    Predicate<Value> isReceiverAlias = value -> value.getAliasedValue(configuration) == receiver;
+    for (Instruction insn : receiver.aliasedUsers(configuration)) {
+      switch (insn.opcode()) {
+        case ASSUME:
+        case CHECK_CAST:
+        case RETURN:
+          break;
 
-      if (insn.isInstanceGet() || insn.isInstancePut()) {
-        if (insn.isInstancePut()) {
-          InstancePut instancePutInstruction = insn.asInstancePut();
-          // Only allow field writes to the receiver.
-          if (instancePutInstruction.object().getAliasedValue() != receiver) {
+        case MONITOR:
+          seenMonitor = true;
+          break;
+
+        case INSTANCE_GET:
+        case INSTANCE_PUT:
+          {
+            if (insn.isInstancePut()) {
+              InstancePut instancePutInstruction = insn.asInstancePut();
+              // Only allow field writes to the receiver.
+              if (!isReceiverAlias.test(instancePutInstruction.object())) {
+                return;
+              }
+              // Do not allow the receiver to escape via a field write.
+              if (isReceiverAlias.test(instancePutInstruction.value())) {
+                return;
+              }
+            }
+            DexField field = insn.asFieldInstruction().getField();
+            if (appView.appInfo().resolveFieldOn(clazz, field) != null) {
+              // Require only accessing direct or indirect instance fields of the current class.
+              break;
+            }
             return;
           }
-          // Do not allow the receiver to escape via a field write.
-          if (instancePutInstruction.value().getAliasedValue() == receiver) {
+
+        case INVOKE_DIRECT:
+          {
+            InvokeDirect invoke = insn.asInvokeDirect();
+            DexMethod invokedMethod = invoke.getInvokedMethod();
+            if (dexItemFactory.isConstructor(invokedMethod)
+                && invokedMethod.holder == clazz.superType
+                && ListUtils.lastIndexMatching(invoke.arguments(), isReceiverAlias) == 0
+                && !seenSuperInitCall
+                && instanceInitializer) {
+              seenSuperInitCall = true;
+              break;
+            }
+            // We don't support other direct calls yet.
             return;
           }
-        }
-        DexField field = insn.asFieldInstruction().getField();
-        if (appView.appInfo().resolveFieldOn(clazz, field) != null) {
-          // Require only accessing direct or indirect instance fields of the current class.
-          continue;
-        }
-        return;
-      }
 
-      // If this is an instance initializer allow one call to superclass instance initializer.
-      if (insn.isInvokeDirect()) {
-        InvokeDirect invokedDirect = insn.asInvokeDirect();
-        DexMethod invokedMethod = invokedDirect.getInvokedMethod();
-        if (dexItemFactory.isConstructor(invokedMethod)
-            && invokedMethod.holder == clazz.superType
-            && ListUtils.lastIndexMatching(
-                invokedDirect.inValues(), v -> v.getAliasedValue() == receiver) == 0
-            && !seenSuperInitCall
-            && instanceInitializer) {
-          seenSuperInitCall = true;
-          continue;
-        }
-        // We don't support other direct calls yet.
-        return;
-      }
-
-      if (insn.isInvokeVirtual()) {
-        InvokeVirtual invoke = insn.asInvokeVirtual();
-        if (invoke.getReceiver().getAliasedValue() != receiver) {
-          return; // Not allowed.
-        }
-        for (int i = 1; i < invoke.arguments().size(); i++) {
-          Value argument = invoke.arguments().get(i);
-          if (argument.getAliasedValue() == receiver) {
-            return; // Not allowed.
+        case INVOKE_VIRTUAL:
+          {
+            InvokeVirtual invoke = insn.asInvokeVirtual();
+            if (ListUtils.lastIndexMatching(invoke.arguments(), isReceiverAlias) != 0) {
+              return; // Not allowed.
+            }
+            DexMethod invokedMethod = invoke.getInvokedMethod();
+            DexType returnType = invokedMethod.proto.returnType;
+            if (returnType.isClassType()
+                && appView.appInfo().isRelatedBySubtyping(returnType, method.method.holder)) {
+              return; // Not allowed, could introduce an alias of the receiver.
+            }
+            callsReceiver.add(new Pair<>(Invoke.Type.VIRTUAL, invokedMethod));
           }
-        }
-        DexMethod invokedMethod = invoke.getInvokedMethod();
-        DexType returnType = invokedMethod.proto.returnType;
-        if (returnType.isClassType()
-            && appView.appInfo().isRelatedBySubtyping(returnType, method.method.holder)) {
-          return; // Not allowed, could introduce an alias of the receiver.
-        }
-        callsReceiver.add(new Pair<>(Invoke.Type.VIRTUAL, invokedMethod));
-        continue;
-      }
+          break;
 
-      if (insn.isReturn()) {
-        continue;
+        default:
+          // Other receiver usages make the method not eligible.
+          return;
       }
-
-      // Other receiver usages make the method not eligible.
-      return;
     }
 
     if (instanceInitializer && !seenSuperInitCall) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
index f28afbe..5c45a37 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
@@ -28,7 +28,8 @@
   }
 
   @Override
-  public Reason computeInliningReason(InvokeMethod invoke, DexEncodedMethod target) {
+  public Reason computeInliningReason(
+      InvokeMethod invoke, DexEncodedMethod target, DexEncodedMethod context) {
     if (target.getOptimizationInfo().forceInline()
         || (appView.appInfo().hasLiveness()
             && appView.withLiveness().appInfo().forceInline.contains(target.method))) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/FixedInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/FixedInliningReasonStrategy.java
new file mode 100644
index 0000000..2b4c072
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/FixedInliningReasonStrategy.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2020, 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.inliner;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+
+public class FixedInliningReasonStrategy implements InliningReasonStrategy {
+
+  private final Reason reason;
+
+  public FixedInliningReasonStrategy(Reason reason) {
+    this.reason = reason;
+  }
+
+  @Override
+  public Reason computeInliningReason(
+      InvokeMethod invoke, DexEncodedMethod target, DexEncodedMethod context) {
+    return reason;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java
index 5450988..6e8e1aa 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java
@@ -10,5 +10,6 @@
 
 public interface InliningReasonStrategy {
 
-  Reason computeInliningReason(InvokeMethod invoke, DexEncodedMethod target);
+  Reason computeInliningReason(
+      InvokeMethod invoke, DexEncodedMethod target, DexEncodedMethod context);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index 8682fc1..5af6c6f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.optimize.staticizer;
 
-import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
@@ -16,6 +15,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -458,7 +458,7 @@
     // Check constructor.
     InvokeDirect invoke = instruction.asInvokeDirect();
     DexEncodedMethod methodInvoked =
-        appView.appInfo().lookupDirectTarget(invoke.getInvokedMethod());
+        appView.appInfo().lookupDirectTarget(invoke.getInvokedMethod(), info.candidate);
     List<Value> values = invoke.inValues();
 
     if (ListUtils.lastIndexMatching(values, v -> v.getAliasedValue() == candidateValue) != 0
@@ -599,10 +599,13 @@
             }
             return candidateInfo.invalidate();
           }
-          AppInfo appInfo = appView.appInfo();
-          DexEncodedMethod methodInvoked = user.isInvokeDirect()
-              ? appInfo.lookupDirectTarget(methodReferenced)
-              : appInfo.lookupVirtualTarget(methodReferenced.holder, methodReferenced);
+          AppInfoWithLiveness appInfo = appView.appInfo();
+          ResolutionResult resolutionResult =
+              appInfo.resolveMethod(methodReferenced.holder, methodReferenced);
+          DexEncodedMethod methodInvoked =
+              user.isInvokeDirect()
+                  ? resolutionResult.lookupInvokeDirectTarget(candidateInfo.candidate, appInfo)
+                  : resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null;
           if (ListUtils.lastIndexMatching(invoke.inValues(), isAliasedValue) == 0
               && methodInvoked != null
               && methodInvoked.method.holder == candidateInfo.candidate.type) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
index 02b6e64..8e8d2da 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
@@ -415,7 +415,10 @@
             Map<Instruction, BuilderState> perInstrState = getBuilderState(builder);
             BuilderState dominantState = findDominantState(dominatorTree, perInstrState, instr);
             if (dominantState != null) {
-              perInstrState.put(instr, dominantState);
+              // Instead of using the dominant state directly, treat this retrieval point as a new
+              // state without an addition so that dominant state can account for dependent states.
+              BuilderState currentState = dominantState.createChild("");
+              perInstrState.put(instr, currentState);
             } else {
               // TODO(b/114002137): if we want to utilize partial results, don't remove it here.
               candidateBuilders.remove(builder);
diff --git a/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
deleted file mode 100644
index 88f89c3..0000000
--- a/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
+++ /dev/null
@@ -1,268 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.jar;
-
-import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
-
-import com.android.tools.r8.errors.CompilationError;
-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.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
-import com.android.tools.r8.graph.JarApplicationReader;
-import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import org.objectweb.asm.ConstantDynamic;
-import org.objectweb.asm.Handle;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-import org.objectweb.asm.tree.TryCatchBlockNode;
-
-// This visitor can be used to determine if a piece of jar code has any instructions that the
-// inliner would not be willing to inline. This can be used to determine if a method can be force
-// inlined although its IR is still not available.
-//
-// Note that this class has only been implemented for the hooks in InliningConstraints that may
-// return a non-ALWAYS inlining constraint (e.g., InliningConstraints.forReturn is not called).
-public class InliningConstraintVisitor extends MethodVisitor {
-
-  private final JarApplicationReader application;
-  private final AppView<AppInfoWithLiveness> appView;
-  private final GraphLense graphLense;
-  private final InliningConstraints inliningConstraints;
-  private final DexEncodedMethod method;
-  private final DexType invocationContext;
-
-  private ConstraintWithTarget constraint;
-
-  public InliningConstraintVisitor(
-      JarApplicationReader application,
-      AppView<AppInfoWithLiveness> appView,
-      GraphLense graphLense,
-      DexEncodedMethod method,
-      DexType invocationContext) {
-    super(ASM_VERSION);
-    assert graphLense.isContextFreeForMethods();
-    this.application = application;
-    this.appView = appView;
-    this.graphLense = graphLense;
-    this.inliningConstraints = new InliningConstraints(appView, graphLense);
-    this.method = method;
-    this.invocationContext = invocationContext;
-
-    // Model a synchronized method as having a monitor instruction.
-    this.constraint = method.accessFlags.isSynchronized()
-        ? inliningConstraints.forMonitor() : ConstraintWithTarget.ALWAYS;
-  }
-
-  public void disallowStaticInterfaceMethodCalls() {
-    inliningConstraints.disallowStaticInterfaceMethodCalls();
-  }
-
-  public ConstraintWithTarget getConstraint() {
-    return constraint;
-  }
-
-  private void updateConstraint(ConstraintWithTarget other) {
-    constraint = ConstraintWithTarget.meet(constraint, other, appView);
-  }
-
-  // Used to signal that the result is ready, such that we do not need to visit all instructions of
-  // the method, if we can see early on that it cannot be inlined anyway.
-  public boolean isFinished() {
-    return constraint == ConstraintWithTarget.NEVER;
-  }
-
-  public void accept(TryCatchBlockNode tryCatchBlock) {
-    // Model a try-catch as a move-exception instruction.
-    updateConstraint(inliningConstraints.forMoveException());
-  }
-
-  @Override
-  public void visitFieldInsn(int opcode, String owner, String name, String desc) {
-    DexField field = application.getField(owner, name, desc);
-    switch (opcode) {
-      case Opcodes.GETFIELD:
-        updateConstraint(inliningConstraints.forInstanceGet(field, invocationContext));
-        break;
-
-      case Opcodes.PUTFIELD:
-        updateConstraint(inliningConstraints.forInstancePut(field, invocationContext));
-        break;
-
-      case Opcodes.GETSTATIC:
-        updateConstraint(inliningConstraints.forStaticGet(field, invocationContext));
-        break;
-
-      case Opcodes.PUTSTATIC:
-        updateConstraint(inliningConstraints.forStaticPut(field, invocationContext));
-        break;
-
-      default:
-        throw new Unreachable("Unexpected opcode " + opcode);
-    }
-  }
-
-  @Override
-  public void visitLdcInsn(Object cst) {
-    if (cst instanceof Type && ((Type) cst).getSort() != Type.METHOD) {
-      DexType type = application.getType((Type) cst);
-      updateConstraint(inliningConstraints.forConstClass(type, invocationContext));
-    } else if (cst instanceof Handle) {
-      updateConstraint(inliningConstraints.forConstMethodHandle());
-    } else if (cst instanceof ConstantDynamic) {
-      throw new CompilationError("Unsupported dynamic constant: " + cst.toString());
-    } else {
-      updateConstraint(inliningConstraints.forConstInstruction());
-    }
-  }
-
-  @Override
-  public void visitInvokeDynamicInsn(
-      String name,
-      String descriptor,
-      Handle bootstrapMethodHandle,
-      Object... bootstrapMethodArguments) {
-    updateConstraint(inliningConstraints.forInvokeCustom());
-  }
-
-  @Override
-  public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
-    DexType ownerType = application.getTypeFromName(owner);
-    DexMethod target = application.getMethod(ownerType, name, desc);
-    updateConstraint(
-        getConstraintForInvoke(
-            opcode, target, graphLense, appView, inliningConstraints, invocationContext));
-  }
-
-  public static ConstraintWithTarget getConstraintForInvoke(
-      int opcode,
-      DexMethod target,
-      GraphLense graphLense,
-      AppView<?> appView,
-      InliningConstraints inliningConstraints,
-      DexType invocationContext) {
-    // Find the DEX invocation type.
-    Invoke.Type type;
-    switch (opcode) {
-      case Opcodes.INVOKEINTERFACE:
-        // Could have changed to an invoke-virtual instruction due to vertical class merging
-        // (if an interface is merged into a class).
-        type = graphLense.lookupMethod(target, null, Invoke.Type.INTERFACE).getType();
-        assert type == Invoke.Type.INTERFACE || type == Invoke.Type.VIRTUAL;
-        break;
-
-      case Opcodes.INVOKESPECIAL:
-        if (appView.dexItemFactory().isConstructor(target)) {
-          type = Invoke.Type.DIRECT;
-          assert noNeedToUseGraphLense(target, type, graphLense);
-        } else if (target.holder == invocationContext) {
-          // The method could have been publicized.
-          type = graphLense.lookupMethod(target, null, Invoke.Type.DIRECT).getType();
-          assert type == Invoke.Type.DIRECT || type == Invoke.Type.VIRTUAL;
-        } else {
-          // This is a super call. Note that the vertical class merger translates some invoke-super
-          // instructions to invoke-direct. However, when that happens, the invoke instruction and
-          // the target method end up being in the same class, and therefore, we will allow inlining
-          // it. The result of using type=SUPER below will be the same, since it leads to the
-          // inlining constraint SAMECLASS.
-          // TODO(christofferqa): Consider using graphLense.lookupMethod (to do this, we need the
-          // context for the graph lense, though).
-          type = Invoke.Type.SUPER;
-          assert noNeedToUseGraphLense(target, type, graphLense);
-        }
-        break;
-
-      case Opcodes.INVOKESTATIC: {
-        // Static invokes may have changed as a result of horizontal class merging.
-        GraphLenseLookupResult lookup = graphLense.lookupMethod(target, null, Invoke.Type.STATIC);
-        target = lookup.getMethod();
-        type = lookup.getType();
-        break;
-      }
-
-      case Opcodes.INVOKEVIRTUAL: {
-        type = Invoke.Type.VIRTUAL;
-          // Instructions that target a private method in the same class translates to
-          // invoke-direct.
-          if (target.holder == invocationContext) {
-            DexClass clazz = appView.definitionFor(target.holder);
-          if (clazz != null && clazz.lookupDirectMethod(target) != null) {
-            type = Invoke.Type.DIRECT;
-          }
-        }
-
-        // Virtual invokes may have changed to interface invokes as a result of member rebinding.
-        GraphLenseLookupResult lookup = graphLense.lookupMethod(target, null, type);
-        target = lookup.getMethod();
-        type = lookup.getType();
-        break;
-      }
-
-      default:
-        throw new Unreachable("Unexpected opcode " + opcode);
-    }
-
-    return inliningConstraints.forInvoke(target, type, invocationContext);
-  }
-
-  private static boolean noNeedToUseGraphLense(
-      DexMethod method, Invoke.Type type, GraphLense graphLense) {
-    assert graphLense.lookupMethod(method, null, type).getType() == type;
-    return true;
-  }
-
-  @Override
-  public void visitInsn(int opcode) {
-    switch (opcode) {
-      case Opcodes.MONITORENTER:
-      case Opcodes.MONITOREXIT:
-        updateConstraint(inliningConstraints.forMonitor());
-        break;
-
-      default:
-        // All instructions here lead to the inlining constraint ALWAYS.
-    }
-  }
-
-  @Override
-  public void visitMultiANewArrayInsn(String desc, int dims) {
-    DexType type = application.getTypeFromDescriptor(desc);
-    updateConstraint(inliningConstraints.forInvokeMultiNewArray(type, invocationContext));
-  }
-
-  @Override
-  public void visitTypeInsn(int opcode, String typeName) {
-    DexType type = application.getTypeFromName(typeName);
-    switch (opcode) {
-      case Opcodes.ANEWARRAY:
-        updateConstraint(inliningConstraints.forNewArrayEmpty(type, invocationContext));
-        break;
-
-      case Opcodes.CHECKCAST:
-        updateConstraint(inliningConstraints.forCheckCast(type, invocationContext));
-        break;
-
-      case Opcodes.INSTANCEOF:
-        updateConstraint(inliningConstraints.forInstanceOf(type, invocationContext));
-        break;
-
-      case Opcodes.NEW:
-        updateConstraint(inliningConstraints.forNewInstance(type, invocationContext));
-        break;
-
-      default:
-        throw new Unreachable("Unexpected opcode " + opcode);
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/jar/JarArgumentUseVisitor.java b/src/main/java/com/android/tools/r8/jar/JarArgumentUseVisitor.java
deleted file mode 100644
index b46df5f..0000000
--- a/src/main/java/com/android/tools/r8/jar/JarArgumentUseVisitor.java
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.jar;
-
-import static org.objectweb.asm.Opcodes.ALOAD;
-import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
-import static org.objectweb.asm.Opcodes.DLOAD;
-import static org.objectweb.asm.Opcodes.FLOAD;
-import static org.objectweb.asm.Opcodes.ILOAD;
-import static org.objectweb.asm.Opcodes.LLOAD;
-
-import com.android.tools.r8.graph.ArgumentUse;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexType;
-import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
-import it.unimi.dsi.fastutil.ints.Int2IntMap;
-import org.objectweb.asm.MethodVisitor;
-
-public class JarArgumentUseVisitor extends MethodVisitor {
-  private final ArgumentUse registry;
-  private final Int2IntMap slotToArgument;
-  private final int arguments;
-
-  public JarArgumentUseVisitor(DexEncodedMethod method, ArgumentUse registry) {
-    super(ASM_VERSION);
-    this.registry = registry;
-
-    DexProto proto = method.method.proto;
-    boolean isStatic = method.accessFlags.isStatic();
-
-    boolean hasLongOrDouble = false;
-    for (DexType value : proto.parameters.values) {
-      if (value.isLongType() || value.isDoubleType()) {
-        hasLongOrDouble = true;
-        break;
-      }
-    }
-    if (hasLongOrDouble) {
-      this.slotToArgument = new Int2IntArrayMap();
-      int nextSlot = 0;
-      int nextArgument = 0;
-      if (!isStatic) {
-        slotToArgument.put(nextSlot++, nextArgument++);
-      }
-      for (DexType value : proto.parameters.values) {
-        slotToArgument.put(nextSlot++, nextArgument++);
-        if (value.isLongType() || value.isDoubleType()) {
-          nextSlot++;
-        }
-      }
-      this.arguments = nextSlot;
-    } else {
-      this.slotToArgument = null;
-      this.arguments = proto.parameters.values.length + (isStatic ? 0 : 1);
-    }
-  }
-
-  @Override
-  public void visitIincInsn(final int var, final int increment) {
-    if (var < arguments) {
-      registry.register(slotToArgument == null ? var : slotToArgument.get(var));
-    }
-  }
-
-  @Override
-  public void visitVarInsn(final int opcode, final int var) {
-    switch (opcode) {
-      case ILOAD:
-      case LLOAD:
-      case FLOAD:
-      case DLOAD:
-      case ALOAD:
-        if (var < arguments) {
-          registry.register(slotToArgument == null ? var : slotToArgument.get(var));
-        }
-        break;
-
-      default:
-        // Ignore
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
deleted file mode 100644
index fc9ea8b..0000000
--- a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.jar;
-
-import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
-
-import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.DexCallSite;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexMethodHandle;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.JarApplicationReader;
-import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
-import org.objectweb.asm.ConstantDynamic;
-import org.objectweb.asm.Handle;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-
-public class JarRegisterEffectsVisitor extends MethodVisitor {
-  private final DexType clazz;
-  private final UseRegistry registry;
-  private final JarApplicationReader application;
-
-  public JarRegisterEffectsVisitor(DexType clazz, UseRegistry registry,
-      JarApplicationReader application) {
-    super(ASM_VERSION);
-    this.clazz = clazz;
-    this.registry = registry;
-    this.application = application;
-  }
-
-  @Override
-  public void visitTypeInsn(int opcode, String name) {
-    DexType type = application.getTypeFromName(name);
-    if (opcode == org.objectweb.asm.Opcodes.NEW) {
-      registry.registerNewInstance(type);
-    } else if (opcode == Opcodes.CHECKCAST) {
-      registry.registerCheckCast(type);
-    } else {
-      registry.registerTypeReference(type);
-    }
-  }
-
-  @Override
-  public void visitMultiANewArrayInsn(String desc, int dims) {
-    registry.registerTypeReference(application.getTypeFromDescriptor(desc));
-  }
-
-  @Override
-  public void visitLdcInsn(Object cst) {
-    if (cst instanceof Type) {
-      if (((Type) cst).getSort() == Type.METHOD) {
-        String descriptor = ((Type) cst).getDescriptor();
-        assert descriptor.charAt(0) == '(';
-        registry.registerProto(application.getProto(descriptor));
-      } else {
-        registry.registerConstClass(application.getType((Type) cst));
-      }
-    } else if (cst instanceof ConstantDynamic) {
-      throw new CompilationError("Unsupported dynamic constant: " + cst.toString());
-    } else if (cst instanceof Handle) {
-      DexMethodHandle handle = DexMethodHandle.fromAsmHandle((Handle) cst, application, clazz);
-      registry.registerMethodHandle(
-          handle, MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
-    }
-  }
-
-  @Override
-  public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
-    DexType ownerType = application.getTypeFromName(owner);
-    DexMethod method = application.getMethod(ownerType, name, desc);
-    switch (opcode) {
-      case Opcodes.INVOKEVIRTUAL:
-        registry.registerInvokeVirtual(method);
-        break;
-      case Opcodes.INVOKESTATIC:
-        registry.registerInvokeStatic(method);
-        break;
-      case Opcodes.INVOKEINTERFACE:
-        registry.registerInvokeInterface(method);
-        break;
-      case Opcodes.INVOKESPECIAL:
-        if (name.equals(Constants.INSTANCE_INITIALIZER_NAME) || ownerType == clazz) {
-          registry.registerInvokeDirect(method);
-        } else {
-          registry.registerInvokeSuper(method);
-        }
-        break;
-      default:
-        throw new Unreachable("Unexpected opcode " + opcode);
-    }
-  }
-
-  @Override
-  public void visitFieldInsn(int opcode, String owner, String name, String desc) {
-    DexField field = application.getField(owner, name, desc);
-    switch (opcode) {
-      case Opcodes.GETFIELD:
-        registry.registerInstanceFieldRead(field);
-        break;
-      case Opcodes.PUTFIELD:
-        registry.registerInstanceFieldWrite(field);
-        break;
-      case Opcodes.GETSTATIC:
-        registry.registerStaticFieldRead(field);
-        break;
-      case Opcodes.PUTSTATIC:
-        registry.registerStaticFieldWrite(field);
-        break;
-      default:
-        throw new Unreachable("Unexpected opcode " + opcode);
-    }
-  }
-
-  @Override
-  public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
-    registry.registerCallSite(
-        DexCallSite.fromAsmInvokeDynamic(application, clazz, name, desc, bsm, bsmArgs));
-  }
-
-}
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 ff97c60..c63f22e 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -15,9 +15,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
 import kotlinx.metadata.KmClass;
 import kotlinx.metadata.KmConstructor;
 import kotlinx.metadata.KmType;
@@ -26,7 +24,7 @@
 
 public class KotlinClass extends KotlinInfo<KotlinClassMetadata.Class> {
 
-  private KmClass kmClass;
+  KmClass kmClass;
 
   static KotlinClass fromKotlinClassMetadata(
       KotlinClassMetadata kotlinClassMetadata, DexClass clazz) {
@@ -70,12 +68,12 @@
       return;
     }
     List<KmConstructor> constructors = kmClass.getConstructors();
-    List<KmConstructor> originalConstructors = new ArrayList<>(constructors);
     constructors.clear();
-    for (Map.Entry<DexEncodedMethod, KmConstructor> entry :
-        clazz.kotlinConstructors(originalConstructors, appView).entrySet()) {
-      KmConstructor constructor =
-          toRenamedKmConstructor(entry.getKey(), entry.getValue(), appView, lens);
+    for (DexEncodedMethod method : clazz.directMethods()) {
+      if (!method.isInstanceInitializer()) {
+        continue;
+      }
+      KmConstructor constructor = toRenamedKmConstructor(method, appView, lens);
       if (constructor != null) {
         constructors.add(constructor);
       }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
index a9a4b9c..94a993b 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -14,7 +14,7 @@
 
 public final class KotlinClassPart extends KotlinInfo<KotlinClassMetadata.MultiFileClassPart> {
 
-  private KmPackage kmPackage;
+  KmPackage kmPackage;
 
   static KotlinClassPart fromKotlinClassMetadata(
       KotlinClassMetadata kotlinClassMetadata, DexClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
index 855a428..9eb70f8 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -14,7 +14,7 @@
 
 public final class KotlinFile extends KotlinInfo<KotlinClassMetadata.FileFacade> {
 
-  private KmPackage kmPackage;
+  KmPackage kmPackage;
 
   static KotlinFile fromKotlinClassMetadata(
       KotlinClassMetadata kotlinClassMetadata, DexClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
index 9cd71c3..ebd5524 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.isExtension;
 import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmFunction;
 import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmFunctionAsExtension;
 
@@ -14,15 +13,12 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
 import kotlinx.metadata.KmDeclarationContainer;
 import kotlinx.metadata.KmFunction;
-import kotlinx.metadata.KmProperty;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
-// Provides access to kotlin information.
+// Provides access to package/class-level Kotlin information.
 public abstract class KotlinInfo<MetadataKind extends KotlinClassMetadata> {
   final MetadataKind metadata;
   final DexClass clazz;
@@ -91,6 +87,10 @@
     return null;
   }
 
+  boolean hasDeclarations() {
+    return isClass() || isFile() || isClassPart();
+  }
+
   @Override
   public String toString() {
     return (clazz != null ? clazz.toSourceString() : "<null class?!>")
@@ -106,30 +106,38 @@
     assert clazz != null;
 
     List<KmFunction> functions = kmDeclarationContainer.getFunctions();
-    List<KmFunction> originalFunctions =
-        functions.stream()
-            .filter(kmFunction -> !isExtension(kmFunction))
-            .collect(Collectors.toList());
-    List<KmFunction> originalExtensions =
-        functions.stream()
-            .filter(KotlinMetadataSynthesizer::isExtension)
-            .collect(Collectors.toList());
     functions.clear();
+    // TODO(b/70169921): clear property
+    for (DexEncodedMethod method : clazz.methods()) {
+      if (method.isInitializer()) {
+        continue;
+      }
 
-    List<KmProperty> properties = kmDeclarationContainer.getProperties();
-    for (DexEncodedMethod method : clazz.kotlinFunctions(originalFunctions, properties, appView)) {
-      KmFunction function = toRenamedKmFunction(method, null, appView, lens);
-      if (function != null) {
-        functions.add(function);
+      if (method.isKotlinExtensionFunction()) {
+        KmFunction extension = toRenamedKmFunctionAsExtension(method, appView, lens);
+        if (extension != null) {
+          functions.add(extension);
+        }
+        continue;
       }
-    }
-    for (Map.Entry<DexEncodedMethod, KmFunction> entry :
-        clazz.kotlinExtensions(originalExtensions, appView).entrySet()) {
-      KmFunction extension =
-          toRenamedKmFunctionAsExtension(entry.getKey(), entry.getValue(), appView, lens);
-      if (extension != null) {
-        functions.add(extension);
+      if (method.isKotlinFunction()) {
+        KmFunction function = toRenamedKmFunction(method, appView, lens);
+        if (function != null) {
+          functions.add(function);
+        }
+        continue;
       }
+      if (method.isKotlinExtensionProperty()) {
+        // TODO(b/70169921): (extension) property
+        continue;
+      }
+      if (method.isKotlinProperty()) {
+        // TODO(b/70169921): (extension) property
+        continue;
+      }
+
+      // TODO(b/70169921): What should we do for methods that fall into this category---no mark?
     }
+    // TODO(b/70169921): (extension) companion
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
new file mode 100644
index 0000000..82bafe6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
@@ -0,0 +1,171 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.kotlin;
+
+import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmFieldSignature;
+import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmMethodSignature;
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.isExtension;
+
+import com.android.tools.r8.errors.Unreachable;
+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.kotlin.KotlinMetadataJvmExtensionUtils.KmFunctionProcessor;
+import com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.KmPropertyProcessor;
+import java.util.HashMap;
+import java.util.Map;
+import kotlinx.metadata.KmDeclarationContainer;
+import kotlinx.metadata.KmFunction;
+import kotlinx.metadata.KmProperty;
+
+// Provides access to field/method-level Kotlin information.
+public class KotlinMemberInfo {
+  private static KotlinMemberInfo NO_KOTLIN_MEMBER_INFO = new KotlinMemberInfo(MemberKind.NONE, 0);
+
+  public static KotlinMemberInfo getNoKotlinMemberInfo() {
+    return NO_KOTLIN_MEMBER_INFO;
+  }
+
+  public final MemberKind memberKind;
+  public final int flag;
+  // TODO(b/70169921): for (extension) property, we may need to group/track the original shape of
+  //  the property. Then, at then end, metadata for only live ones will be synthesized.
+
+  private KotlinMemberInfo(MemberKind memberKind, int flag) {
+    this.memberKind = memberKind;
+    this.flag = flag;
+  }
+
+  public enum MemberKind {
+    NONE,
+
+    FUNCTION,
+    EXTENSION_FUNCTION,
+
+    PROPERTY_BACKING_FIELD,
+    PROPERTY_GETTER,
+    PROPERTY_SETTER,
+    PROPERTY_ANNOTATIONS,
+
+    // No backing field for extension property.
+    EXTENSION_PROPERTY_GETTER,
+    EXTENSION_PROPERTY_SETTER,
+    EXTENSION_PROPERTY_ANNOTATIONS;
+
+    // TODO(b/70169921): (extension) companion
+
+    public boolean isFunction() {
+      return this == FUNCTION || isExtensionFunction();
+    }
+
+    public boolean isExtensionFunction() {
+      return this == EXTENSION_FUNCTION;
+    }
+
+    public boolean isBackingField() {
+      return this == PROPERTY_BACKING_FIELD;
+    }
+
+    public boolean isProperty() {
+      return isBackingField()
+          || this == PROPERTY_GETTER
+          || this == PROPERTY_SETTER
+          || this == PROPERTY_ANNOTATIONS
+          || isExtensionProperty();
+    }
+
+    public boolean isExtensionProperty() {
+      return this == EXTENSION_PROPERTY_GETTER
+          || this == EXTENSION_PROPERTY_SETTER
+          || this == EXTENSION_PROPERTY_ANNOTATIONS;
+    }
+  }
+
+  public static void markKotlinMemberInfo(DexClass clazz, KotlinInfo kotlinInfo) {
+    if (kotlinInfo == null || !kotlinInfo.hasDeclarations()) {
+      return;
+    }
+    if (kotlinInfo.isClass()) {
+      markKotlinMemberInfo(clazz, kotlinInfo.asClass().kmClass);
+    } else if (kotlinInfo.isFile()) {
+      markKotlinMemberInfo(clazz, kotlinInfo.asFile().kmPackage);
+    } else if (kotlinInfo.isClassPart()) {
+      markKotlinMemberInfo(clazz, kotlinInfo.asClassPart().kmPackage);
+    } else {
+      throw new Unreachable("Unexpected KotlinInfo: " + kotlinInfo);
+    }
+  }
+
+  private static void markKotlinMemberInfo(
+      DexClass clazz, KmDeclarationContainer kmDeclarationContainer) {
+    Map<String, KmFunction> kmFunctionMap = new HashMap<>();
+    Map<String, KmProperty> kmPropertyFieldMap = new HashMap<>();
+    Map<String, KmProperty> kmPropertyGetterMap = new HashMap<>();
+    Map<String, KmProperty> kmPropertySetterMap = new HashMap<>();
+
+    kmDeclarationContainer.getFunctions().forEach(kmFunction -> {
+      KmFunctionProcessor functionProcessor = new KmFunctionProcessor(kmFunction);
+      if (functionProcessor.signature() != null) {
+        kmFunctionMap.put(functionProcessor.signature().asString(), kmFunction);
+      }
+    });
+    kmDeclarationContainer.getProperties().forEach(kmProperty -> {
+      KmPropertyProcessor propertyProcessor = new KmPropertyProcessor(kmProperty);
+      if (propertyProcessor.fieldSignature() != null) {
+        kmPropertyFieldMap.put(propertyProcessor.fieldSignature().asString(), kmProperty);
+      }
+      if (propertyProcessor.getterSignature() != null) {
+        kmPropertyGetterMap.put(propertyProcessor.getterSignature().asString(), kmProperty);
+      }
+      if (propertyProcessor.setterSignature() != null) {
+        kmPropertySetterMap.put(propertyProcessor.setterSignature().asString(), kmProperty);
+      }
+      // TODO(b/70169921): property annotations
+    });
+
+    for (DexEncodedField field : clazz.fields()) {
+      String key = toJvmFieldSignature(field.field).asString();
+      if (kmPropertyFieldMap.containsKey(key)) {
+        KmProperty kmProperty = kmPropertyFieldMap.get(key);
+        field.setKotlinMemberInfo(
+            new KotlinMemberInfo(MemberKind.PROPERTY_BACKING_FIELD, kmProperty.getFlags()));
+      }
+    }
+
+    for (DexEncodedMethod method : clazz.methods()) {
+      if (method.isInitializer()) {
+        continue;
+      }
+      String key = toJvmMethodSignature(method.method).asString();
+      if (kmFunctionMap.containsKey(key)) {
+        KmFunction kmFunction = kmFunctionMap.get(key);
+        if (isExtension(kmFunction)) {
+          method.setKotlinMemberInfo(
+              new KotlinMemberInfo(MemberKind.EXTENSION_FUNCTION, kmFunction.getFlags()));
+        } else {
+          method.setKotlinMemberInfo(
+              new KotlinMemberInfo(MemberKind.FUNCTION, kmFunction.getFlags()));
+        }
+      } else if (kmPropertyGetterMap.containsKey(key)) {
+        KmProperty kmProperty = kmPropertyGetterMap.get(key);
+        if (isExtension(kmProperty)) {
+          method.setKotlinMemberInfo(
+              new KotlinMemberInfo(MemberKind.EXTENSION_PROPERTY_GETTER, kmProperty.getFlags()));
+        } else {
+          method.setKotlinMemberInfo(
+              new KotlinMemberInfo(MemberKind.PROPERTY_GETTER, kmProperty.getFlags()));
+        }
+      } else if (kmPropertySetterMap.containsKey(key)) {
+        KmProperty kmProperty = kmPropertySetterMap.get(key);
+        if (isExtension(kmProperty)) {
+          method.setKotlinMemberInfo(
+              new KotlinMemberInfo(MemberKind.EXTENSION_PROPERTY_SETTER, kmProperty.getFlags()));
+        } else {
+          method.setKotlinMemberInfo(
+              new KotlinMemberInfo(MemberKind.PROPERTY_SETTER, kmProperty.getFlags()));
+        }
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
index 85e3540..bdd7f60 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
@@ -3,9 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
-import com.google.common.collect.ImmutableList;
-import java.util.Arrays;
-import java.util.List;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
 import kotlinx.metadata.KmConstructor;
 import kotlinx.metadata.KmConstructorExtensionVisitor;
 import kotlinx.metadata.KmConstructorVisitor;
@@ -24,57 +24,19 @@
 
 class KotlinMetadataJvmExtensionUtils {
 
-  private static boolean isValidJvmMethodSignature(String desc) {
-    return desc != null
-        && !desc.isEmpty()
-        && desc.charAt(0) == '('
-        && desc.lastIndexOf('(') == 0
-        && desc.indexOf(')') != -1
-        && desc.indexOf(')') == desc.lastIndexOf(')')
-        && desc.lastIndexOf(')') < desc.length();
+  static JvmFieldSignature toJvmFieldSignature(DexField field) {
+    return new JvmFieldSignature(field.name.toString(), field.type.toDescriptorString());
   }
 
-  /**
-   * Extract return type from {@link JvmMethodSignature}.
-   *
-   * Example of JVM signature is: `JvmMethodSignature("getX", "()Ljava/lang/Object;").`
-   * In this case, the return type is "Ljava/lang/Object;".
-   */
-  static String returnTypeFromJvmMethodSignature(JvmMethodSignature signature) {
-    if (signature == null) {
-      return null;
+  static JvmMethodSignature toJvmMethodSignature(DexMethod method) {
+    StringBuilder descBuilder = new StringBuilder();
+    descBuilder.append("(");
+    for (DexType argType : method.proto.parameters.values) {
+      descBuilder.append(argType.toDescriptorString());
     }
-    String desc = signature.getDesc();
-    if (!isValidJvmMethodSignature(desc)) {
-      return null;
-    }
-    int index = desc.lastIndexOf(')');
-    assert desc.charAt(0) == '(' && 0 < index && index < desc.length() : signature.asString();
-    return desc.substring(index + 1);
-  }
-
-  /**
-   * Extract parameters from {@link JvmMethodSignature}.
-   *
-   * Example of JVM signature is: `JvmMethodSignature("setX", "(Ljava/lang/Object;)V").`
-   * In this case, the parameter is the list with "Ljava/lang/Object;" as the first element.
-   */
-  static List<String> parameterTypesFromJvmMethodSignature(JvmMethodSignature signature) {
-    if (signature == null) {
-      return null;
-    }
-    String desc = signature.getDesc();
-    if (!isValidJvmMethodSignature(desc)) {
-      return null;
-    }
-    int index = desc.lastIndexOf(')');
-    assert desc.charAt(0) == '(' && 0 < index && index < desc.length() : signature.asString();
-    String params = desc.substring(1, index);
-    if (params.isEmpty()) {
-      return ImmutableList.of();
-    } else {
-      return Arrays.asList(params.split(","));
-    }
+    descBuilder.append(")");
+    descBuilder.append(method.proto.returnType.toDescriptorString());
+    return new JvmMethodSignature(method.name.toString(), descBuilder.toString());
   }
 
   static class KmConstructorProcessor {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
index 7e4f7ca..a3e62c5 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -4,10 +4,8 @@
 package com.android.tools.r8.kotlin;
 
 import static com.android.tools.r8.kotlin.Kotlin.addKotlinPrefix;
-import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.parameterTypesFromJvmMethodSignature;
-import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.returnTypeFromJvmMethodSignature;
+import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmMethodSignature;
 import static com.android.tools.r8.utils.DescriptorUtils.descriptorToInternalName;
-import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKmType;
 import static kotlinx.metadata.FlagsKt.flagsOf;
 
 import com.android.tools.r8.graph.AppView;
@@ -16,9 +14,6 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.KmConstructorProcessor;
-import com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.KmFunctionProcessor;
-import com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.KmPropertyProcessor;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.List;
@@ -27,14 +22,18 @@
 import kotlinx.metadata.KmProperty;
 import kotlinx.metadata.KmType;
 import kotlinx.metadata.KmValueParameter;
-import kotlinx.metadata.jvm.JvmMethodSignature;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
 
-public class KotlinMetadataSynthesizer {
+class KotlinMetadataSynthesizer {
 
   static boolean isExtension(KmFunction kmFunction) {
     return kmFunction.getReceiverParameterType() != null;
   }
 
+  static boolean isExtension(KmProperty kmProperty) {
+    return kmProperty.getReceiverParameterType() != null;
+  }
+
   static KmType toKmType(String descriptor) {
     KmType kmType = new KmType(flagsOf());
     kmType.visitClass(descriptorToInternalName(descriptor));
@@ -72,185 +71,8 @@
     return kmType;
   }
 
-  private static boolean isCompatible(String desc, DexType type) {
-    if (desc == null || type == null) {
-      return false;
-    }
-    return desc.equals(type.toDescriptorString());
-  }
-
-  private static boolean isCompatible(KmType kmType, DexType type, AppView<?> appView) {
-    if (kmType == null || type == null) {
-      return false;
-    }
-    String descriptor = null;
-    if (appView.dexItemFactory().kotlin.knownTypeConversion.containsKey(type)) {
-      DexType convertedType = appView.dexItemFactory().kotlin.knownTypeConversion.get(type);
-      descriptor = convertedType.toDescriptorString();
-    }
-    if (descriptor == null) {
-      descriptor = type.toDescriptorString();
-    }
-    assert descriptor != null;
-    return descriptor.equals(getDescriptorFromKmType(kmType));
-  }
-
-  private static boolean isCompatibleJvmMethodSignature(
-      JvmMethodSignature signature, DexEncodedMethod method) {
-    String methodName = method.method.name.toString();
-    if (!signature.getName().equals(methodName)) {
-      return false;
-    }
-    if (!isCompatible(
-        returnTypeFromJvmMethodSignature(signature), method.method.proto.returnType)) {
-      return false;
-    }
-    List<String> parameterTypes = parameterTypesFromJvmMethodSignature(signature);
-    if (parameterTypes == null || parameterTypes.size() != method.method.proto.parameters.size()) {
-      return false;
-    }
-    for (int i = 0; i < method.method.proto.parameters.size(); i++) {
-      if (!isCompatible(parameterTypes.get(i), method.method.proto.parameters.values[i])) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  public static boolean isCompatibleConstructor(
-      KmConstructor constructor, DexEncodedMethod method, AppView<?> appView) {
-    // Note that targets for @JvmName don't include constructor. So, it's not necessary to process
-    // JvmMethodSignature inside JvmConstructorExtension.
-    List<KmValueParameter> parameters = constructor.getValueParameters();
-    if (method.method.proto.parameters.size() != parameters.size()) {
-      return false;
-    }
-    for (int i = 0; i < method.method.proto.parameters.size(); i++) {
-      KmType kmType = parameters.get(i).getType();
-      if (!isCompatible(kmType, method.method.proto.parameters.values[i], appView)) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  public static boolean isCompatibleFunction(
-      KmFunction function, DexEncodedMethod method, AppView<?> appView) {
-    // Check if a custom name is set to avoid name clash.
-    KmFunctionProcessor kmFunctionProcessor = new KmFunctionProcessor(function);
-    JvmMethodSignature jvmMethodSignature = kmFunctionProcessor.signature();
-    if (jvmMethodSignature != null && isCompatibleJvmMethodSignature(jvmMethodSignature, method)) {
-      return true;
-    }
-
-    if (!function.getName().equals(method.method.name.toString())) {
-      return false;
-    }
-    if (!isCompatible(function.getReturnType(), method.method.proto.returnType, appView)) {
-      return false;
-    }
-    List<KmValueParameter> parameters = function.getValueParameters();
-    if (method.method.proto.parameters.size() != parameters.size()) {
-      return false;
-    }
-    for (int i = 0; i < method.method.proto.parameters.size(); i++) {
-      KmType kmType = parameters.get(i).getType();
-      if (!isCompatible(kmType, method.method.proto.parameters.values[i], appView)) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  public static boolean isCompatibleExtension(
-      KmFunction extension, DexEncodedMethod method, AppView<?> appView) {
-    // Check if a custom name is set to avoid name clash.
-    KmFunctionProcessor kmFunctionProcessor = new KmFunctionProcessor(extension);
-    JvmMethodSignature jvmMethodSignature = kmFunctionProcessor.signature();
-    if (jvmMethodSignature != null && isCompatibleJvmMethodSignature(jvmMethodSignature, method)) {
-      return true;
-    }
-
-    if (!extension.getName().equals(method.method.name.toString())) {
-      return false;
-    }
-    if (!isCompatible(extension.getReturnType(), method.method.proto.returnType, appView)) {
-      return false;
-    }
-    List<KmValueParameter> parameters = extension.getValueParameters();
-    if (method.method.proto.parameters.size() != parameters.size() + 1) {
-      return false;
-    }
-    assert method.method.proto.parameters.size() > 0;
-    assert extension.getReceiverParameterType() != null;
-    if (!isCompatible(
-        extension.getReceiverParameterType(), method.method.proto.parameters.values[0], appView)) {
-      return false;
-    }
-    for (int i = 1; i < method.method.proto.parameters.size(); i++) {
-      KmType kmType = parameters.get(i - 1).getType();
-      if (!isCompatible(kmType, method.method.proto.parameters.values[i], appView)) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  public static boolean isCompatibleProperty(
-      KmProperty kmProperty, DexEncodedMethod method, AppView<?> appView) {
-    KmPropertyProcessor kmPropertyProcessor = new KmPropertyProcessor(kmProperty);
-    // Check if a custom getter is defined via @get:JvmName("myGetter").
-    JvmMethodSignature getterSignature = kmPropertyProcessor.getterSignature();
-    if (getterSignature != null && isCompatibleJvmMethodSignature(getterSignature, method)) {
-      return true;
-    }
-    // Check if a custom setter is defined via @set:JvmName("mySetter").
-    JvmMethodSignature setterSignature = kmPropertyProcessor.setterSignature();
-    if (setterSignature != null && isCompatibleJvmMethodSignature(setterSignature, method)) {
-      return true;
-    }
-
-    // E.g., property `prop: T` is mapped to `getProp()T`, `setProp(T)V`, `prop$annotations()V`.
-    // For boolean property, though, getter is mapped to `isProp()Z`.
-    // TODO(b/70169921): Avoid decoding.
-    String methodName = method.method.name.toString();
-    if (!methodName.startsWith("is")
-        && !methodName.startsWith("get")
-        && !methodName.startsWith("set")
-        && !methodName.endsWith("$annotations")) {
-      return false;
-    }
-
-    String propertyName = kmProperty.getName();
-    assert propertyName.length() > 0;
-    String annotations = propertyName + "$annotations";
-    if (methodName.equals(annotations)) {
-      return true;
-    }
-    String capitalized =
-        Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
-    String returnTypeDescriptor = getDescriptorFromKmType(kmProperty.returnType);
-    String getterPrefix =
-        returnTypeDescriptor != null && returnTypeDescriptor.endsWith("Boolean;") ? "is" : "get";
-    String getter = getterPrefix + capitalized;
-    if (methodName.equals(getter)
-        && method.method.proto.parameters.size() == 0
-        && isCompatible(kmProperty.returnType, method.method.proto.returnType, appView)) {
-      return true;
-    }
-    String setter = "set" + capitalized;
-    if (methodName.equals(setter)
-        && method.method.proto.returnType.isVoidType()
-        && method.method.proto.parameters.size() == 1
-        && isCompatible(kmProperty.returnType, method.method.proto.parameters.values[0], appView)) {
-      return true;
-    }
-    return false;
-  }
-
   static KmConstructor toRenamedKmConstructor(
       DexEncodedMethod method,
-      KmConstructor original,
       AppView<AppInfoWithLiveness> appView,
       NamingLens lens) {
     // Make sure it is an instance initializer and live.
@@ -258,17 +80,9 @@
         || !appView.appInfo().liveMethods.contains(method.method)) {
       return null;
     }
-    // TODO(b/70169921): {@link KmConstructor.extensions} is private, i.e., no way to alter!
-    //   Thus, we rely on original metadata for now.
-    KmConstructorProcessor kmConstructorProcessor = new KmConstructorProcessor(original);
-    JvmMethodSignature jvmMethodSignature = kmConstructorProcessor.signature();
-    KmConstructor kmConstructor =
-        jvmMethodSignature != null
-            ? original
-            // TODO(b/70169921): Consult kotlinx.metadata.Flag.Constructor to set IS_PRIMARY.
-            : new KmConstructor(method.accessFlags.getAsKotlinFlags());
+    KmConstructor kmConstructor = new KmConstructor(method.accessFlags.getAsKotlinFlags());
+    JvmExtensionsKt.setSignature(kmConstructor, toJvmMethodSignature(method.method));
     List<KmValueParameter> parameters = kmConstructor.getValueParameters();
-    parameters.clear();
     if (!populateKmValueParameters(parameters, method, appView, lens, false)) {
       return null;
     }
@@ -277,23 +91,20 @@
 
   static KmFunction toRenamedKmFunction(
       DexEncodedMethod method,
-      KmFunction original,
       AppView<AppInfoWithLiveness> appView,
       NamingLens lens) {
-    return toRenamedKmFunctionHelper(method, original, appView, lens, false);
+    return toRenamedKmFunctionHelper(method, appView, lens, false);
   }
 
   static KmFunction toRenamedKmFunctionAsExtension(
       DexEncodedMethod method,
-      KmFunction original,
       AppView<AppInfoWithLiveness> appView,
       NamingLens lens) {
-    return toRenamedKmFunctionHelper(method, original, appView, lens, true);
+    return toRenamedKmFunctionHelper(method, appView, lens, true);
   }
 
   private static KmFunction toRenamedKmFunctionHelper(
       DexEncodedMethod method,
-      KmFunction original,
       AppView<AppInfoWithLiveness> appView,
       NamingLens lens,
       boolean isExtension) {
@@ -307,21 +118,17 @@
     // For a library method override, we should not have renamed it.
     assert !method.isLibraryMethodOverride().isTrue() || renamedMethod == method.method
         : method.toSourceString() + " -> " + renamedMethod.toSourceString();
-    // TODO(b/70169921): {@link KmFunction.extensions} is private, i.e., no way to alter!
-    //   Thus, we rely on original metadata for now.
-    assert !isExtension || original != null;
     KmFunction kmFunction =
-        isExtension
-            ? original
-            // TODO(b/70169921): Consult kotlinx.metadata.Flag.Function for kind (e.g., suspend).
-            : new KmFunction(method.accessFlags.getAsKotlinFlags(), renamedMethod.name.toString());
+        new KmFunction(method.accessFlags.getAsKotlinFlags(), renamedMethod.name.toString());
+    JvmExtensionsKt.setSignature(kmFunction, toJvmMethodSignature(method.method));
     KmType kmReturnType = toRenamedKmType(method.method.proto.returnType, appView, lens);
     if (kmReturnType == null) {
       return null;
     }
     kmFunction.setReturnType(kmReturnType);
     if (isExtension) {
-      assert method.method.proto.parameters.values.length > 0;
+      assert method.method.proto.parameters.values.length > 0
+          : method.method.toSourceString();
       KmType kmReceiverType =
           toRenamedKmType(method.method.proto.parameters.values[0], appView, lens);
       if (kmReceiverType == null) {
@@ -330,7 +137,6 @@
       kmFunction.setReceiverParameterType(kmReceiverType);
     }
     List<KmValueParameter> parameters = kmFunction.getValueParameters();
-    parameters.clear();
     if (!populateKmValueParameters(parameters, method, appView, lens, isExtension)) {
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index d0240e9..d13cc3b 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -13,6 +13,7 @@
 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.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -24,12 +25,14 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
+import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
@@ -132,7 +135,15 @@
     timing.end();
 
     timing.begin("rename-generic");
-    new GenericSignatureRewriter(appView, renaming).run(classes);
+    new GenericSignatureRewriter(appView, renaming)
+        .run(
+            new Iterable<DexProgramClass>() {
+              @Override
+              public Iterator<DexProgramClass> iterator() {
+                return IteratorUtils.<DexClass, DexProgramClass>filter(
+                    classes.iterator(), DexClass::isProgramClass);
+              }
+            });
     timing.end();
 
     timing.begin("rename-arrays");
diff --git a/src/main/java/com/android/tools/r8/naming/ClassRenamingMapper.java b/src/main/java/com/android/tools/r8/naming/ClassRenamingMapper.java
deleted file mode 100644
index 8609fff..0000000
--- a/src/main/java/com/android/tools/r8/naming/ClassRenamingMapper.java
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.naming;
-
-import com.android.tools.r8.utils.BiMapContainer;
-import com.google.common.collect.ImmutableBiMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-import java.util.Map;
-
-/**
- * Provides a translation between class names based on a source and target proguard map.
- *
- * A mapping consists of:
- *
- * <ul>
- *   <li> {@link #translation} a bidirectional mapping between obfuscated names on the source
- *   proguard map to the corresponding class in the target proguard map
- *   <li> {@link #newClasses} a set of the unobfuscated names of classes that are in the source but
- *   not the target map
- *   <li> {@link #unusedNames} a set of names in the target map that are not used by the source map
- * </ul>
- */
-public class ClassRenamingMapper {
-
-  public static ClassRenamingMapper from(ClassNameMapper originalMap, ClassNameMapper targetMap) {
-    ImmutableBiMap.Builder<String, String> translationBuilder = ImmutableBiMap.builder();
-    ImmutableSet.Builder<String> newClasses = ImmutableSet.builder();
-
-    Map<String, String> sourceOriginalToObfuscated =
-        originalMap.getObfuscatedToOriginalMapping().inverse;
-
-    BiMapContainer<String, String> targetMapping = targetMap.getObfuscatedToOriginalMapping();
-    Map<String, String> targetOriginalToObfuscated = targetMapping.inverse;
-
-    for (String originalName : sourceOriginalToObfuscated.keySet()) {
-      String sourceObfuscatedName = sourceOriginalToObfuscated.get(originalName);
-      String targetObfuscatedName = targetOriginalToObfuscated.get(originalName);
-      if (targetObfuscatedName == null) {
-        newClasses.add(originalName);
-        continue;
-      }
-      translationBuilder.put(sourceObfuscatedName, targetObfuscatedName);
-    }
-
-    ImmutableBiMap<String, String> translation = translationBuilder.build();
-    ImmutableSet<String> unusedNames =
-        ImmutableSet.copyOf(Sets.difference(targetMapping.original.keySet(), translation.values()));
-
-    return new ClassRenamingMapper(translation, newClasses.build(), unusedNames);
-  }
-
-  /**
-   * Mapping from obfuscated class names in the source map to their counterpart in the target name
-   * map.
-   */
-  public final ImmutableBiMap<String, String> translation;
-
-  /**
-   * Set of (unobfuscated) class names that are present in the source map but not in the target map.
-   */
-  public final ImmutableSet<String> newClasses;
-
-  /**
-   * Set of (obfuscated) class names that are present in the target map but not in the source map.
-   */
-  public final ImmutableSet<String> unusedNames;
-
-  private ClassRenamingMapper(ImmutableBiMap<String, String> translation,
-      ImmutableSet<String> newClasses, ImmutableSet<String> unusedNames) {
-    this.translation = translation;
-    this.newClasses = newClasses;
-    this.unusedNames = unusedNames;
-  }
-
-  @Override
-  public String toString() {
-    StringBuilder builder = new StringBuilder();
-    builder.append("Translation:\n\n");
-    for (String name : translation.keySet()) {
-      String newName = translation.get(name);
-      builder.append(name.equals(newName) ? "    " : " --- ")
-          .append(name)
-          .append(" -> ")
-          .append(newName)
-          .append('\n');
-    }
-    builder.append("\nNew classes:\n\n");
-    for (String name : newClasses) {
-      builder.append("    ")
-          .append(name)
-          .append('\n');
-    }
-    builder.append("\nUnused names:\n\n");
-    for (String unused : unusedNames) {
-      builder.append("    ")
-          .append(unused)
-          .append('\n');
-    }
-    return builder.toString();
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index 8ea3d93..b95f778 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
 import com.android.tools.r8.naming.FieldNameMinifier.FieldRenaming;
 import com.android.tools.r8.naming.MethodNameMinifier.MethodRenaming;
@@ -175,24 +176,11 @@
     if (holder == null || holder.isNotProgramClass()) {
       return true;
     }
-    // We don't know which invoke type this method is used for, so checks that it has been
-    // rebound either way.
-    DexEncodedMethod staticTarget = appView.appInfo().lookupStaticTarget(item);
-    DexEncodedMethod directTarget = appView.appInfo().lookupDirectTarget(item);
-    DexEncodedMethod virtualTarget = appView.appInfo().lookupVirtualTarget(item.holder, item);
-    DexClass staticTargetHolder =
-        staticTarget != null ? appView.definitionFor(staticTarget.method.holder) : null;
-    DexClass directTargetHolder =
-        directTarget != null ? appView.definitionFor(directTarget.method.holder) : null;
-    DexClass virtualTargetHolder =
-        virtualTarget != null ? appView.definitionFor(virtualTarget.method.holder) : null;
-    assert (directTarget == null && staticTarget == null && virtualTarget == null)
-        || (virtualTarget != null && virtualTarget.method == item)
-        || (directTarget != null && directTarget.method == item)
-        || (staticTarget != null && staticTarget.method == item)
-        || (directTargetHolder != null && directTargetHolder.isNotProgramClass())
-        || (virtualTargetHolder != null && virtualTargetHolder.isNotProgramClass())
-        || (staticTargetHolder != null && staticTargetHolder.isNotProgramClass())
+    SingleResolutionResult resolution =
+        appView.appInfo().resolveMethod(item.holder, item).asSingleResolution();
+    // The resolution is either unknown or resolved to the item or a visibility bridge.
+    assert resolution == null
+        || resolution.getResolvedMethod().method == item
         || appView.unneededVisibilityBridgeMethods().contains(item);
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index fab5f50..4fd6274 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -495,7 +495,7 @@
       DexString reservedName = getReservedName(definition, name, holder);
       if (reservedName != null) {
         if (!isAvailable.test(reservedName, reference)) {
-          reportReservationError(definition.asDexReference(), reservedName);
+          reportReservationError(definition.toReference(), reservedName);
         }
         return reservedName;
       }
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureAction.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureAction.java
index 6753e06..7cb3506 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureAction.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureAction.java
@@ -9,11 +9,17 @@
  */
 public interface GenericSignatureAction<T> {
 
+  enum ParserPosition {
+    CLASS_SUPER_OR_INTERFACE_ANNOTATION,
+    ENCLOSING_INNER_OR_TYPE_ANNOTATION,
+    MEMBER_ANNOTATION
+  }
+
   public void parsedSymbol(char symbol);
 
   public void parsedIdentifier(String identifier);
 
-  public T parsedTypeName(String name);
+  public T parsedTypeName(String name, ParserPosition isTopLevel);
 
   public T parsedInnerTypeName(T enclosingType, String name);
 
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java
index c1f6a7d..74b17d6 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.naming.signature;
 
+import com.android.tools.r8.naming.signature.GenericSignatureAction.ParserPosition;
 import java.lang.reflect.GenericSignatureFormatError;
 import java.nio.CharBuffer;
 
@@ -50,7 +51,7 @@
  */
 public class GenericSignatureParser<T> {
 
-  private final GenericSignatureAction<T> actions;
+  private GenericSignatureAction<T> actions;
 
   /*
    * Parser:
@@ -110,7 +111,7 @@
     try {
       actions.start();
       setInput(signature);
-      parseFieldTypeSignature();
+      parseFieldTypeSignature(ParserPosition.MEMBER_ANNOTATION);
       actions.stop();
     } catch (GenericSignatureFormatError e) {
       throw e;
@@ -141,11 +142,11 @@
     parseOptFormalTypeParameters();
 
     // SuperclassSignature ::= ClassTypeSignature.
-    parseClassTypeSignature();
+    parseClassTypeSignature(ParserPosition.CLASS_SUPER_OR_INTERFACE_ANNOTATION);
 
     while (symbol > 0) {
       // SuperinterfaceSignature ::= ClassTypeSignature.
-      parseClassTypeSignature();
+      parseClassTypeSignature(ParserPosition.CLASS_SUPER_OR_INTERFACE_ANNOTATION);
     }
   }
 
@@ -178,28 +179,28 @@
     expect(':');
 
     if (symbol == 'L' || symbol == '[' || symbol == 'T') {
-      parseFieldTypeSignature();
+      parseFieldTypeSignature(ParserPosition.MEMBER_ANNOTATION);
     }
 
     while (symbol == ':') {
       // InterfaceBound ::= ":" FieldTypeSignature.
       actions.parsedSymbol(symbol);
       scanSymbol();
-      parseFieldTypeSignature();
+      parseFieldTypeSignature(ParserPosition.MEMBER_ANNOTATION);
     }
   }
 
-  private void parseFieldTypeSignature() {
+  private void parseFieldTypeSignature(ParserPosition parserPosition) {
     // FieldTypeSignature ::= ClassTypeSignature | ArrayTypeSignature | TypeVariableSignature.
     switch (symbol) {
       case 'L':
-        parseClassTypeSignature();
+        parseClassTypeSignature(parserPosition);
         break;
       case '[':
-        // ArrayTypeSignature ::= "[" TypSignature.
+        // ArrayTypeSignature ::= "[" TypeSignature.
         actions.parsedSymbol(symbol);
         scanSymbol();
-        updateTypeSignature();
+        updateTypeSignature(parserPosition);
         break;
       case 'T':
         updateTypeVariableSignature();
@@ -209,7 +210,7 @@
     }
   }
 
-  private void parseClassTypeSignature() {
+  private void parseClassTypeSignature(ParserPosition parserPosition) {
     // ClassTypeSignature ::= "L" {Ident "/"} Ident OptTypeArguments {"." Ident OptTypeArguments}
     //  ";".
     actions.parsedSymbol(symbol);
@@ -226,12 +227,12 @@
     }
 
     qualIdent.append(this.identifier);
-    T parsedEnclosingType = actions.parsedTypeName(qualIdent.toString());
+    T parsedEnclosingType = actions.parsedTypeName(qualIdent.toString(), parserPosition);
 
     updateOptTypeArguments();
 
     while (symbol == '.') {
-      // Deal with Member Classes:
+      // Deal with Member Classes.
       actions.parsedSymbol(symbol);
       scanSymbol();
       scanIdentifier();
@@ -268,13 +269,13 @@
     } else if (symbol == '+') {
       actions.parsedSymbol(symbol);
       scanSymbol();
-      parseFieldTypeSignature();
+      parseFieldTypeSignature(ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION);
     } else if (symbol == '-') {
       actions.parsedSymbol(symbol);
       scanSymbol();
-      parseFieldTypeSignature();
+      parseFieldTypeSignature(ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION);
     } else {
-      parseFieldTypeSignature();
+      parseFieldTypeSignature(ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION);
     }
   }
 
@@ -291,7 +292,7 @@
     expect(';');
   }
 
-  private void updateTypeSignature() {
+  private void updateTypeSignature(ParserPosition parserPosition) {
     switch (symbol) {
       case 'B':
       case 'C':
@@ -306,7 +307,7 @@
         break;
       default:
         // Not an elementary type, but a FieldTypeSignature.
-        parseFieldTypeSignature();
+        parseFieldTypeSignature(parserPosition);
     }
   }
 
@@ -319,7 +320,7 @@
     expect('(');
 
     while (symbol != ')' && (symbol > 0)) {
-      updateTypeSignature();
+      updateTypeSignature(ParserPosition.MEMBER_ANNOTATION);
     }
 
     actions.parsedSymbol(symbol);
@@ -336,7 +337,7 @@
         if (symbol == 'T') {
           updateTypeVariableSignature();
         } else {
-          parseClassTypeSignature();
+          parseClassTypeSignature(ParserPosition.MEMBER_ANNOTATION);
         }
       } while (symbol == '^');
     }
@@ -345,7 +346,7 @@
   private void updateReturnType() {
     // ReturnType ::= TypeSignature | "V".
     if (symbol != 'V') {
-      updateTypeSignature();
+      updateTypeSignature(ParserPosition.MEMBER_ANNOTATION);
     } else {
       actions.parsedSymbol(symbol);
       scanSymbol();
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
index c3b7188..45c59a8 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -10,8 +10,8 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.origin.Origin;
@@ -24,6 +24,7 @@
 import java.util.Map;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
+import java.util.function.Predicate;
 import java.util.function.Supplier;
 
 public class GenericSignatureRewriter {
@@ -43,15 +44,16 @@
     this.reporter = appView.options().reporter;
   }
 
-  public void run(Iterable<? extends DexClass> classes) {
+  public void run(Iterable<? extends DexProgramClass> classes) {
     final GenericSignatureCollector genericSignatureCollector = new GenericSignatureCollector();
     final GenericSignatureParser<DexType> genericSignatureParser =
         new GenericSignatureParser<>(genericSignatureCollector);
-    // classes may not be the same as appInfo().classes() if applymapping is used on classpath
+    // Classes may not be the same as appInfo().classes() if applymapping is used on classpath
     // arguments. If that is the case, the ProguardMapMinifier will pass in all classes that is
     // either ProgramClass or has a mapping. This is then transitively called inside the
     // ClassNameMinifier.
-    for (DexClass clazz : classes) {
+    for (DexProgramClass clazz : classes) {
+      genericSignatureCollector.setCurrentClassContext(clazz);
       clazz.annotations =
           rewriteGenericSignatures(
               clazz.annotations,
@@ -152,13 +154,28 @@
 
   private class GenericSignatureCollector implements GenericSignatureAction<DexType> {
     private StringBuilder renamedSignature;
+    private DexProgramClass currentClassContext;
+    private DexType lastWrittenType = null;
 
-    public String getRenamedSignature() {
+    String getRenamedSignature() {
       return renamedSignature.toString();
     }
 
+    void setCurrentClassContext(DexProgramClass clazz) {
+      currentClassContext = clazz;
+    }
+
     @Override
     public void parsedSymbol(char symbol) {
+      if (symbol == ';' && lastWrittenType == null) {
+        // The type was never written (maybe because it was merged with it's subtype).
+        return;
+      }
+      // If the super-class or interface has been merged, we will stop writing out type
+      // arguments, resulting in a signature on the form '<>' if we do not remove it.
+      if (symbol == '>' && removeWrittenCharacter(c -> c == '<')) {
+        return;
+      }
       renamedSignature.append(symbol);
     }
 
@@ -168,19 +185,56 @@
     }
 
     @Override
-    public DexType parsedTypeName(String name) {
-      DexType type = appView.dexItemFactory().createType(getDescriptorFromClassBinaryName(name));
-      type = appView.graphLense().lookupType(type);
+    public DexType parsedTypeName(String name, ParserPosition parserPosition) {
+      if (parserPosition == ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION
+          && lastWrittenType == null) {
+        // We are writing type-arguments for a merged class.
+        removeWrittenClassCharacter();
+        return null;
+      }
+      String originalDescriptor = getDescriptorFromClassBinaryName(name);
+      DexType type =
+          appView.graphLense().lookupType(appView.dexItemFactory().createType(originalDescriptor));
       if (appView.appInfo().wasPruned(type)) {
         type = appView.dexItemFactory().objectType;
       }
       DexString renamedDescriptor = renaming.getOrDefault(type, type.descriptor);
+      if (parserPosition == ParserPosition.CLASS_SUPER_OR_INTERFACE_ANNOTATION
+          && currentClassContext != null) {
+        // We may have merged the type down to the current class type.
+        DexString classDescriptor = currentClassContext.type.descriptor;
+        if (!originalDescriptor.equals(classDescriptor.toString())
+            && renamedDescriptor.equals(classDescriptor)) {
+          lastWrittenType = null;
+          removeWrittenClassCharacter();
+          return type;
+        }
+      }
       renamedSignature.append(getClassBinaryNameFromDescriptor(renamedDescriptor.toString()));
+      lastWrittenType = type;
       return type;
     }
 
+    private boolean removeWrittenCharacter(Predicate<Character> removeIf) {
+      int index = renamedSignature.length() - 1;
+      if (index < 0 || !removeIf.test(renamedSignature.charAt(index))) {
+        return false;
+      }
+      renamedSignature.deleteCharAt(index);
+      return true;
+    }
+
+    private void removeWrittenClassCharacter() {
+      removeWrittenCharacter(c -> c == 'L');
+    }
+
     @Override
     public DexType parsedInnerTypeName(DexType enclosingType, String name) {
+      if (enclosingType == null) {
+        // We are writing inner type names
+        removeWrittenClassCharacter();
+        return null;
+      }
       assert enclosingType.isClassType();
       String enclosingDescriptor = enclosingType.toDescriptorString();
       DexType type =
@@ -197,6 +251,7 @@
       type = appView.graphLense().lookupType(type);
       DexString renamedDescriptor = renaming.get(type);
       if (renamedDescriptor != null) {
+        // TODO(b/147504070): If this is a merged class equal to the class context, do not add.
         // Pick the renamed inner class from the fully renamed binary name.
         String fullRenamedBinaryName =
             getClassBinaryNameFromDescriptor(renamedDescriptor.toString());
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 33e4eed..ac4f95f 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -29,6 +29,7 @@
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.utils.CollectionUtils;
+import com.android.tools.r8.utils.PredicateSet;
 import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -139,7 +140,7 @@
   /** All methods that may not have any unused arguments removed. */
   public final Set<DexMethod> keepUnusedArguments;
   /** All types that should be inlined if possible due to a configuration directive. */
-  public final Set<DexType> alwaysClassInline;
+  public final PredicateSet<DexType> alwaysClassInline;
   /** All types that *must* never be inlined due to a configuration directive (testing only). */
   public final Set<DexType> neverClassInline;
   /** All types that *must* never be merged due to a configuration directive (testing only). */
@@ -207,7 +208,7 @@
       Set<DexMethod> whyAreYouNotInlining,
       Set<DexMethod> keepConstantArguments,
       Set<DexMethod> keepUnusedArguments,
-      Set<DexType> alwaysClassInline,
+      PredicateSet<DexType> alwaysClassInline,
       Set<DexType> neverClassInline,
       Set<DexType> neverMerge,
       Set<DexReference> neverPropagateValue,
@@ -286,7 +287,7 @@
       Set<DexMethod> whyAreYouNotInlining,
       Set<DexMethod> keepConstantArguments,
       Set<DexMethod> keepUnusedArguments,
-      Set<DexType> alwaysClassInline,
+      PredicateSet<DexType> alwaysClassInline,
       Set<DexType> neverClassInline,
       Set<DexType> neverMerge,
       Set<DexReference> neverPropagateValue,
@@ -496,7 +497,7 @@
             .map(this::definitionFor)
             .filter(Objects::nonNull)
             .collect(Collectors.toList()));
-    this.alwaysClassInline = rewriteItems(previous.alwaysClassInline, lense::lookupType);
+    this.alwaysClassInline = previous.alwaysClassInline.rewriteItems(lense::lookupType);
     this.neverClassInline = rewriteItems(previous.neverClassInline, lense::lookupType);
     this.neverMerge = rewriteItems(previous.neverMerge, lense::lookupType);
     this.neverPropagateValue = lense.rewriteReferencesConservatively(previous.neverPropagateValue);
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 2884fcd..4a8cdf3 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -612,7 +612,7 @@
       bootstrapMethods.add(callSite.bootstrapMethod.asMethod());
     }
 
-    LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callSite, appInfo);
+    LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callSite, appInfo, context.holder);
     if (descriptor == null) {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 8555b93..f27bd26 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -32,6 +32,7 @@
 import com.android.tools.r8.utils.Consumer3;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.PredicateSet;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.base.Equivalence.Wrapper;
@@ -82,7 +83,7 @@
   private final Set<DexMethod> whyAreYouNotInlining = Sets.newIdentityHashSet();
   private final Set<DexMethod> keepParametersWithConstantValue = Sets.newIdentityHashSet();
   private final Set<DexMethod> keepUnusedArguments = Sets.newIdentityHashSet();
-  private final Set<DexType> alwaysClassInline = Sets.newIdentityHashSet();
+  private final PredicateSet<DexType> alwaysClassInline = new PredicateSet<>();
   private final Set<DexType> neverClassInline = Sets.newIdentityHashSet();
   private final Set<DexType> neverMerge = Sets.newIdentityHashSet();
   private final Set<DexReference> neverPropagateValue = Sets.newIdentityHashSet();
@@ -295,7 +296,12 @@
     }
     if (appView.options().protoShrinking().enableGeneratedMessageLiteBuilderShrinking) {
       GeneratedMessageLiteBuilderShrinker.addInliningHeuristicsForBuilderInlining(
-          appView, alwaysInline, neverInline, bypassClinitforInlining);
+          appView,
+          alwaysClassInline,
+          neverMerge,
+          alwaysInline,
+          neverInline,
+          bypassClinitforInlining);
     }
     assert Sets.intersection(neverInline, alwaysInline).isEmpty()
             && Sets.intersection(neverInline, forceInline).isEmpty()
@@ -1123,7 +1129,7 @@
       }
       switch (classInlineRule.getType()) {
         case ALWAYS:
-          alwaysClassInline.add(item.asDexClass().type);
+          alwaysClassInline.addElement(item.asDexClass().type);
           break;
         case NEVER:
           neverClassInline.add(item.asDexClass().type);
@@ -1202,7 +1208,7 @@
     public final Set<DexMethod> whyAreYouNotInlining;
     public final Set<DexMethod> keepConstantArguments;
     public final Set<DexMethod> keepUnusedArguments;
-    public final Set<DexType> alwaysClassInline;
+    public final PredicateSet<DexType> alwaysClassInline;
     public final Set<DexType> neverClassInline;
     public final Set<DexType> neverMerge;
     public final Set<DexReference> neverPropagateValue;
@@ -1229,7 +1235,7 @@
         Set<DexMethod> whyAreYouNotInlining,
         Set<DexMethod> keepConstantArguments,
         Set<DexMethod> keepUnusedArguments,
-        Set<DexType> alwaysClassInline,
+        PredicateSet<DexType> alwaysClassInline,
         Set<DexType> neverClassInline,
         Set<DexType> neverMerge,
         Set<DexReference> neverPropagateValue,
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 69b77cd..62241d5 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -383,7 +383,7 @@
       }
     }
     if (clazz.getEnclosingMethod() != null || !clazz.getInnerClasses().isEmpty()) {
-      // TODO(herhut): Consider supporting merging of enclosing-method and inner-class attributes.
+      // TODO(b/147504070): Consider merging of enclosing-method and inner-class attributes.
       if (Log.ENABLED) {
         AbortReason.UNSUPPORTED_ATTRIBUTES.printLogMessageForClass(clazz);
       }
@@ -440,7 +440,7 @@
       return false;
     }
     if (targetClass.getEnclosingMethod() != null || !targetClass.getInnerClasses().isEmpty()) {
-      // TODO(herhut): Consider supporting merging of enclosing-method and inner-class attributes.
+      // TODO(b/147504070): Consider merging of enclosing-method and inner-class attributes.
       if (Log.ENABLED) {
         AbortReason.UNSUPPORTED_ATTRIBUTES.printLogMessageForClass(clazz);
       }
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
index 5a50f1b..a66d383 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
@@ -28,6 +28,7 @@
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipException;
 import java.util.zip.ZipFile;
@@ -136,4 +137,43 @@
   private boolean isProgramResourceName(String name) {
     return ZipUtils.isClassFile(name) || (ZipUtils.isDexFile(name) && !ignoreDexInArchive);
   }
+
+  public void accept(Consumer<ProgramResource> visitor) throws ResourceException {
+    try (ZipFile zipFile =
+        FileUtils.createZipFile(archive.getPath().toFile(), StandardCharsets.UTF_8)) {
+      final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+      while (entries.hasMoreElements()) {
+        ZipEntry entry = entries.nextElement();
+        String name = entry.getName();
+        if (archive.matchesFile(name) && isProgramResourceName(name)) {
+          Origin entryOrigin = new ArchiveEntryOrigin(name, origin);
+          try (InputStream stream = zipFile.getInputStream(entry)) {
+            if (ZipUtils.isDexFile(name)) {
+              OneShotByteResource resource =
+                  OneShotByteResource.create(
+                      Kind.DEX, entryOrigin, ByteStreams.toByteArray(stream), null);
+              visitor.accept(resource);
+            } else if (ZipUtils.isClassFile(name)) {
+              OneShotByteResource resource =
+                  OneShotByteResource.create(
+                      Kind.CF,
+                      entryOrigin,
+                      ByteStreams.toByteArray(stream),
+                      Collections.singleton(DescriptorUtils.guessTypeDescriptor(name)));
+              visitor.accept(resource);
+            }
+          }
+        }
+      }
+    } catch (ZipException e) {
+      throw new ResourceException(
+          origin,
+          new CompilationError("Zip error while reading '" + archive + "': " + e.getMessage(), e));
+    } catch (IOException e) {
+      throw new ResourceException(
+          origin,
+          new CompilationError(
+              "I/O exception while reading '" + archive + "': " + e.getMessage(), e));
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java b/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java
index 94024ee..2e34cd7 100644
--- a/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java
@@ -27,7 +27,7 @@
   }
 
   public String lookupNameAndDescriptor(String binaryName, int lineNumber)
-      throws IOException, ResourceException {
+      throws ResourceException {
     if (sourceMethodMapping.isEmpty()) {
       readLineNumbersFromClassFiles();
     }
@@ -35,13 +35,35 @@
     return lineMappings == null ? null : lineMappings.get(lineNumber);
   }
 
-  private void readLineNumbersFromClassFiles() throws ResourceException, IOException {
+  private void readLineNumbersFromClassFiles() throws ResourceException {
     ClassVisitor classVisitor = new ClassVisitor();
     for (ProgramResourceProvider resourceProvider : inputApp.getProgramResourceProviders()) {
-      for (ProgramResource programResource : resourceProvider.getProgramResources()) {
-        if (programResource.getKind() == Kind.CF) {
-          new ClassReader(StreamUtils.StreamToByteArrayClose(programResource.getByteStream()))
-              .accept(classVisitor, ClassReader.SKIP_FRAMES);
+      if (resourceProvider instanceof ArchiveResourceProvider) {
+        ArchiveResourceProvider provider = (ArchiveResourceProvider) resourceProvider;
+        provider.accept(
+            programResource -> {
+              if (programResource.getKind() != Kind.CF) {
+                return;
+              }
+              try {
+                new ClassReader(StreamUtils.StreamToByteArrayClose(programResource.getByteStream()))
+                    .accept(classVisitor, ClassReader.SKIP_FRAMES);
+              } catch (IOException | ResourceException e) {
+                // Intentionally left empty because the addition of inline info for kotlin inline
+                // functions is a best effort.
+              }
+            });
+      } else {
+        for (ProgramResource programResource : resourceProvider.getProgramResources()) {
+          if (programResource.getKind() == Kind.CF) {
+            try {
+              new ClassReader(StreamUtils.StreamToByteArrayClose(programResource.getByteStream()))
+                  .accept(classVisitor, ClassReader.SKIP_FRAMES);
+            } catch (IOException e) {
+              // Intentionally left empty because the addition of inline info for kotlin inline
+              // functions is a best effort.
+            }
+          }
         }
       }
     }
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 c233df4..d168a64 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -76,6 +76,8 @@
 
   public enum DesugarState {
     OFF,
+    // This is for use when desugar has run before, and backports have still not been desugared.
+    ONLY_BACKPORT_STATICS,
     ON
   }
 
@@ -218,8 +220,7 @@
   public boolean enablePropagationOfDynamicTypesAtCallSites = true;
   // TODO(b/69963623): enable if everything is ready, including signature rewriting at call sites.
   public boolean enablePropagationOfConstantsAtCallSites = false;
-  // TODO(b/70169921): enable after branching.
-  public boolean enableKotlinMetadataRewriting = false;
+  public boolean enableKotlinMetadataRewriting = true;
   public boolean encodeChecksums = false;
   public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
   public boolean enableCfInterfaceMethodDesugaring = false;
@@ -246,8 +247,7 @@
   public boolean enableInitializedClassesInInstanceMethodsAnalysis = true;
   public boolean enableRedundantFieldLoadElimination = true;
   public boolean enableValuePropagation = true;
-  // TODO(b/125282093): Enable member value propagation for instance fields.
-  public boolean enableValuePropagationForInstanceFields = false;
+  public boolean enableValuePropagationForInstanceFields = true;
   public boolean enableUninstantiatedTypeOptimization = true;
   // Currently disabled, see b/146957343.
   public boolean enableUninstantiatedTypeOptimizationForInterfaces = false;
@@ -1083,13 +1083,6 @@
     enablePropagationOfConstantsAtCallSites = true;
   }
 
-  // TODO(b/70169921): Remove this once enabled.
-  @VisibleForTesting
-  public void enableKotlinMetadataRewriting() {
-    assert !enableKotlinMetadataRewriting;
-    enableKotlinMetadataRewriting = true;
-  }
-
   private boolean hasMinApi(AndroidApiLevel level) {
     assert isGeneratingDex();
     return minApiLevel >= level.getLevel();
diff --git a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
index 7ffce35..f38c02a 100644
--- a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
@@ -16,7 +16,8 @@
 
 public class IteratorUtils {
 
-  public static <T, S extends T> Iterator<S> filter(Iterator<T> iterator, Predicate<T> predicate) {
+  public static <T, S extends T> Iterator<S> filter(
+      Iterator<? extends T> iterator, Predicate<T> predicate) {
     return new Iterator<S>() {
 
       private S next = advance();
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index c7324da..df13ac6 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -48,7 +48,6 @@
 import com.android.tools.r8.naming.Range;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.google.common.base.Suppliers;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -120,7 +119,6 @@
 
     private KotlinInlineFunctionPositionRemapper(
         AppView<?> appView,
-        AndroidApp inputApp,
         PositionRemapper baseRemapper,
         CfLineToMethodMapper lineToMethodMapper) {
       this.appView = appView;
@@ -172,7 +170,7 @@
         }
         // This is the same position, so we should really not mark this as an inline position. Fall
         // through to the default case.
-      } catch (IOException | ResourceException ignored) {
+      } catch (ResourceException ignored) {
         // Intentionally left empty. Remapping of kotlin functions utility is a best effort mapping.
       }
       return baseRemapper.createRemappedPosition(position);
@@ -322,7 +320,7 @@
         // remapper to allow for remapping original positions to kotlin inline positions.
         KotlinInlineFunctionPositionRemapper kotlinRemapper =
             new KotlinInlineFunctionPositionRemapper(
-                appView, inputApp, positionRemapper, cfLineToMethodMapper);
+                appView, positionRemapper, cfLineToMethodMapper);
 
         for (DexEncodedMethod method : methods) {
           kotlinRemapper.currentMethod = method;
diff --git a/src/main/java/com/android/tools/r8/utils/PredicateSet.java b/src/main/java/com/android/tools/r8/utils/PredicateSet.java
new file mode 100644
index 0000000..da15974
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/PredicateSet.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2020, 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.utils;
+
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+public class PredicateSet<T> {
+
+  private final Set<T> elements = Sets.newIdentityHashSet();
+  private final List<Predicate<T>> predicates = new ArrayList<>();
+
+  public boolean addElement(T element) {
+    return elements.add(element);
+  }
+
+  public void addPredicate(Predicate<T> predicate) {
+    predicates.add(predicate);
+  }
+
+  public PredicateSet<T> rewriteItems(Function<T, T> mapping) {
+    PredicateSet<T> set = new PredicateSet<>();
+    for (T item : elements) {
+      set.elements.add(mapping.apply(item));
+    }
+    // It is assumed that the predicates do not need rewriting. Otherwise, this method must be
+    // overwritten.
+    set.predicates.addAll(predicates);
+    return set;
+  }
+
+  public boolean contains(T element) {
+    if (elements.contains(element)) {
+      return true;
+    }
+    for (Predicate<T> predicate : predicates) {
+      if (predicate.test(element)) {
+        return true;
+      }
+    }
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/SimpleHashMap.java b/src/main/java/com/android/tools/r8/utils/SimpleHashMap.java
deleted file mode 100644
index 73f607a..0000000
--- a/src/main/java/com/android/tools/r8/utils/SimpleHashMap.java
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.utils;
-
-abstract class SimpleHashMap {
-
-  private int size = 0;  // number of elements in this hash map.
-  private int limit;  // resize when size reaches limit.
-  private int mask;  // length - 1.
-
-  // Constants for capacity.
-  static final int MIN_CAPACITY = 2;
-  static final int DEFAULT_CAPACITY = 50;
-
-  // Constant for loadfactor.
-  static final double MIN_LOAD_FACTOR = 0.2;
-  static final double MAX_LOAD_FACTOR = 0.8;
-  static final double DEFAULT_LOAD_FACTOR = 0.6;
-
-  SimpleHashMap() {
-    this(DEFAULT_CAPACITY);
-  }
-
-  SimpleHashMap(int initialCapacity) {
-    this(initialCapacity, DEFAULT_LOAD_FACTOR);
-  }
-
-  SimpleHashMap(int initialCapacity, double loadFactor) {
-    initialCapacity = Math.max(MIN_CAPACITY, initialCapacity);
-    loadFactor = Math.min(MAX_LOAD_FACTOR, Math.max(MIN_LOAD_FACTOR, loadFactor));
-    final int initialLength = roundToPow2((int) ((double) initialCapacity / loadFactor));
-    initialize(initialLength, (int) ((double) initialLength * loadFactor));
-  }
-
-  public int size() {
-    return size;
-  }
-
-  @Override
-  public String toString() {
-    return this.getClass().getName() + ", " + size + "(length " + length() + ")";
-  }
-
-  int length() {
-    return mask + 1;
-  }
-
-  void initialize(final int length, final int limit) {
-    size = 0;
-    mask = length - 1;
-    this.limit = limit;
-  }
-
-  void resize() {
-    // Double length and limit.
-    initialize(length() << 1, limit << 1);
-  }
-
-  void ensureCapacity() {
-    if (size >= limit) {
-      resize();
-    }
-  }
-
-  void incrementSize() {
-    size++;
-  }
-
-  private int roundToPow2(int number) {
-    number--;
-    number |= number >> 1;
-    number |= number >> 2;
-    number |= number >> 4;
-    number |= number >> 8;
-    number |= number >> 16;
-    return number + 1;
-  }
-
-  int firstProbe(final int hash) {
-    return hash & mask;
-  }
-
-  int nextProbe(final int last, final int index) {
-    return (last + index) & mask;
-  }
-}
diff --git a/src/test/examplesProto/proto2/HasFlaggedOffExtensionBuilderTestClass.java b/src/test/examplesProto/proto2/HasFlaggedOffExtensionBuilderTestClass.java
new file mode 100644
index 0000000..9cc9e84
--- /dev/null
+++ b/src/test/examplesProto/proto2/HasFlaggedOffExtensionBuilderTestClass.java
@@ -0,0 +1,45 @@
+// 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 proto2;
+
+import com.android.tools.r8.proto2.Shrinking.HasFlaggedOffExtension;
+import com.google.protobuf.ExtensionRegistryLite;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.WireFormat;
+import java.nio.ByteBuffer;
+
+public class HasFlaggedOffExtensionBuilderTestClass {
+
+  // A protobuf payload indicating that varint field 1 is set to 42.
+  // See https://developers.google.com/protocol-buffers/docs/encoding
+  //
+  // Since serialization and deserialization use the same schema (which we're modifying), testing
+  // against wire-format data is preferred.
+  private static final byte[] FIELD1_SET_TO_42 =
+      new byte[] {(1 << 3) | WireFormat.WIRETYPE_VARINT, 42};
+
+  // A protobuf payload indicating that field 10 is a message whose field 1 is set to 42.
+  private static final byte[] MESSAGE10_WITH_FIELD1_SET_TO_42 =
+      ByteBuffer.allocate(4)
+          .put(
+              new byte[] {
+                (10 << 3) | WireFormat.WIRETYPE_LENGTH_DELIMITED, (byte) FIELD1_SET_TO_42.length
+              })
+          .put(FIELD1_SET_TO_42)
+          .array();
+
+  public static void main(String[] args) {
+    HasFlaggedOffExtension msg;
+    try {
+      msg =
+          HasFlaggedOffExtension.parseFrom(
+              MESSAGE10_WITH_FIELD1_SET_TO_42, ExtensionRegistryLite.getGeneratedRegistry());
+    } catch (InvalidProtocolBufferException e) {
+      System.out.println("Unexpected exception: " + e);
+      throw new RuntimeException(e);
+    }
+    Printer.print(HasFlaggedOffExtension.newBuilder(msg).build());
+  }
+}
diff --git a/src/test/examplesProto/proto2/Printer.java b/src/test/examplesProto/proto2/Printer.java
index fd87ef0..0d6f506 100644
--- a/src/test/examplesProto/proto2/Printer.java
+++ b/src/test/examplesProto/proto2/Printer.java
@@ -4,20 +4,25 @@
 
 package proto2;
 
+import com.android.tools.r8.proto2.Shrinking.HasFlaggedOffExtension;
 import com.android.tools.r8.proto2.TestProto.Primitives;
 
 public class Printer {
 
-  static void print(Primitives primitives) {
-    System.out.println(primitives.hasFooInt32());
-    System.out.println(primitives.getFooInt32());
-    System.out.println(primitives.hasOneofString());
-    System.out.println(primitives.getOneofString());
-    System.out.println(primitives.hasOneofUint32());
-    System.out.println(primitives.getOneofUint32());
-    System.out.println(primitives.hasBarInt64());
-    System.out.println(primitives.getBarInt64());
-    System.out.println(primitives.hasQuxString());
-    System.out.println(primitives.getQuxString());
+  static void print(HasFlaggedOffExtension msg) {
+    System.out.println(msg.getSerializedSize());
+  }
+
+  static void print(Primitives msg) {
+    System.out.println(msg.hasFooInt32());
+    System.out.println(msg.getFooInt32());
+    System.out.println(msg.hasOneofString());
+    System.out.println(msg.getOneofString());
+    System.out.println(msg.hasOneofUint32());
+    System.out.println(msg.getOneofUint32());
+    System.out.println(msg.hasBarInt64());
+    System.out.println(msg.getBarInt64());
+    System.out.println(msg.hasQuxString());
+    System.out.println(msg.getQuxString());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/VersionTests.java b/src/test/java/com/android/tools/r8/VersionTests.java
index dffaa2b..9db9512 100644
--- a/src/test/java/com/android/tools/r8/VersionTests.java
+++ b/src/test/java/com/android/tools/r8/VersionTests.java
@@ -61,7 +61,13 @@
 
   @Test
   public void testDevelopmentPredicate() {
-    assertEquals(LABEL.equals("master") || LABEL.contains("-dev"), Version.isDevelopmentVersion());
+   if (LABEL.equals("master") || LABEL.contains("-dev")) {
+      assertTrue(Version.isDevelopmentVersion());
+    } else {
+      // This is a release branch, but Version.isDevelopmentVersion will still return true
+      // since this is not the release archive with the r8-version.properties file.
+      assertFalse(Version.isDevelopmentVersion(LABEL, false));
+    }
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/DesugarStaticBackportsOnly.java b/src/test/java/com/android/tools/r8/desugar/backports/DesugarStaticBackportsOnly.java
new file mode 100644
index 0000000..cd6d160
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/DesugarStaticBackportsOnly.java
@@ -0,0 +1,136 @@
+// Copyright (c) 2020, 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.desugar.backports;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions.DesugarState;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.DexInstructionSubject;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DesugarStaticBackportsOnly extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public DesugarStaticBackportsOnly(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private void checkLongHashCodeDesugared(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(TestClass.class);
+    assertThat(classSubject, isPresent());
+    assertEquals(
+        parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
+        classSubject
+            .uniqueMethodWithName("main")
+            .streamInstructions()
+            .anyMatch(
+                instructionSubject ->
+                    instructionSubject.isInvokeStatic()
+                        && instructionSubject
+                            .toString()
+                            .contains("$r8$backportedMethods$utility$Long$1$hashCode")));
+    assertEquals(
+        parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N),
+        classSubject
+            .uniqueMethodWithName("main")
+            .streamInstructions()
+            .anyMatch(
+                instructionSubject ->
+                    instructionSubject.isInvokeStatic()
+                        && instructionSubject.toString().contains("java/lang/Long")));
+  }
+
+  @Test
+  public void testBackportDesugared() throws Exception {
+    String expectedOutput = StringUtils.lines("1234");
+    testForD8()
+        .addProgramClasses(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(
+            options -> options.desugarState = DesugarState.ONLY_BACKPORT_STATICS)
+        .compile()
+        .inspect(this::checkLongHashCodeDesugared)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(expectedOutput);
+  }
+
+  private void checkLambdaNotDesugared(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(TestClassLambda.class);
+    assertThat(classSubject, isPresent());
+    assertTrue(
+        classSubject
+            .uniqueMethodWithName("main")
+            .streamInstructions()
+            .anyMatch(
+                instructionSubject ->
+                    ((DexInstructionSubject) instructionSubject).isInvokeCustom()));
+  }
+
+  @Test
+  public void testLambdaNotDesugared() throws Exception {
+    D8TestBuilder builder =
+        testForD8()
+            .addProgramClasses(TestClassLambda.class)
+            .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(
+                options -> options.desugarState = DesugarState.ONLY_BACKPORT_STATICS);
+    if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O)) {
+      builder.compile().inspect(this::checkLambdaNotDesugared);
+    } else {
+      try {
+        builder.compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertOnlyErrors();
+              diagnostics.assertErrorsCount(1);
+              Diagnostic diagnostic = diagnostics.getErrors().get(0);
+              assertThat(
+                  diagnostic.getDiagnosticMessage(),
+                  containsString("Invoke-customs are only supported starting with Android O"));
+            });
+      } catch (CompilationFailedException e) {
+        // Expected compilation failed.
+        return;
+      }
+      fail("Expected test to fail with CompilationFailedException");
+    }
+  }
+
+  static class TestClass {
+    public static void main(String[] args) {
+      System.out.println(Long.hashCode(1234));
+    }
+  }
+
+  static class TestClassLambda {
+    public static void main(String[] args) {
+      Arrays.asList(args).forEach(s -> System.out.println(s));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index e49d9a5..07db6bd 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.graph;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
@@ -77,13 +78,17 @@
     );
 
     AndroidApp application = buildApplication(builder);
-    AppInfo appInfo = computeAppInfo(application);
+    AppInfoWithClassHierarchy appInfo = computeAppInfoWithClassHierarchy(application);
     CodeInspector inspector = new CodeInspector(appInfo.app());
     DexEncodedMethod method = getMethod(inspector, DEFAULT_CLASS_NAME, "int", "x",
         ImmutableList.of());
-    assertNull(appInfo.lookupVirtualTarget(method.method.holder, method.method));
-    assertNull(appInfo.lookupDirectTarget(method.method));
-    assertNotNull(appInfo.lookupStaticTarget(method.method));
+    assertFalse(
+        appInfo
+            .resolveMethod(method.method.holder, method.method)
+            .getSingleTarget()
+            .isVirtualMethod());
+    assertNull(appInfo.lookupDirectTarget(method.method, method.method.holder));
+    assertNotNull(appInfo.lookupStaticTarget(method.method, method.method.holder));
 
     if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(DexVm.Version.V4_4_4)) {
       // Dalvik rejects at verification time instead of producing the
@@ -147,7 +152,7 @@
     );
 
     AndroidApp application = buildApplication(builder);
-    AppInfo appInfo = computeAppInfo(application);
+    AppInfoWithClassHierarchy appInfo = computeAppInfoWithClassHierarchy(application);
     CodeInspector inspector = new CodeInspector(appInfo.app());
 
     DexMethod methodXOnTestSuper =
@@ -162,16 +167,20 @@
     DexMethod methodXOnTest =
         appInfo.dexItemFactory().createMethod(classTest, methodXProto, methodXName);
 
-    assertNull(appInfo.lookupVirtualTarget(classTestSuper, methodXOnTestSuper));
-    assertNull(appInfo.lookupVirtualTarget(classTest, methodXOnTestSuper));
-    assertNull(appInfo.lookupVirtualTarget(classTest, methodXOnTest));
+    assertFalse(
+        appInfo
+            .resolveMethod(classTestSuper, methodXOnTestSuper)
+            .getSingleTarget()
+            .isVirtualMethod());
+    assertNull(appInfo.resolveMethod(classTest, methodXOnTestSuper).getSingleTarget());
+    assertNull(appInfo.resolveMethod(classTest, methodXOnTest).getSingleTarget());
 
-    assertNull(appInfo.lookupDirectTarget(methodXOnTestSuper));
-    assertNull(appInfo.lookupDirectTarget(methodXOnTest));
+    assertNull(appInfo.lookupDirectTarget(methodXOnTestSuper, methodXOnTestSuper.holder));
+    assertNull(appInfo.lookupDirectTarget(methodXOnTest, methodXOnTest.holder));
 
-    assertNotNull(appInfo.lookupStaticTarget(methodXOnTestSuper));
+    assertNotNull(appInfo.lookupStaticTarget(methodXOnTestSuper, methodXOnTestSuper.holder));
     // Accessing a private target on a different type will fail resolution outright.
-    assertNull(appInfo.lookupStaticTarget(methodXOnTest));
+    assertNull(appInfo.lookupStaticTarget(methodXOnTest, methodXOnTest.holder));
 
     assertEquals("OK", runArt(application));
   }
@@ -197,7 +206,7 @@
     );
 
     AndroidApp application = buildApplication(builder);
-    AppInfo appInfo = computeAppInfo(application);
+    AppInfoWithClassHierarchy appInfo = computeAppInfoWithClassHierarchy(application);
     DexItemFactory factory = appInfo.dexItemFactory();
 
     DexField aFieldOnSubClass = factory
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
index c2a5c6a..cffd09c 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -56,7 +56,7 @@
   private void testVirtualLookup(DexProgramClass clazz, DexEncodedMethod method) {
     // Check lookup will produce the same result.
     DexMethod id = method.method;
-    assertEquals(appInfo.lookupVirtualTarget(id.holder, method.method), method);
+    assertEquals(appInfo.resolveMethod(id.holder, method.method).getSingleTarget(), method);
 
     // Check lookup targets with include method.
     Set<DexEncodedMethod> targets =
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
index 7a158d3..6bf5d8f 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
@@ -14,6 +15,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
@@ -27,7 +29,6 @@
 @RunWith(Parameterized.class)
 public class Proto2BuilderShrinkingTest extends ProtoShrinkingTestBase {
 
-  private static final String LITE_BUILDER = "com.google.protobuf.GeneratedMessageLite$Builder";
   private static final String METHOD_TO_INVOKE_ENUM =
       "com.google.protobuf.GeneratedMessageLite$MethodToInvoke";
 
@@ -46,12 +47,14 @@
             ImmutableList.of("proto2.BuilderWithProtoBuilderSetterTestClass"),
             ImmutableList.of("proto2.BuilderWithProtoSetterTestClass"),
             ImmutableList.of("proto2.BuilderWithReusedSettersTestClass"),
+            ImmutableList.of("proto2.HasFlaggedOffExtensionBuilderTestClass"),
             ImmutableList.of(
                 "proto2.BuilderWithOneofSetterTestClass",
                 "proto2.BuilderWithPrimitiveSettersTestClass",
                 "proto2.BuilderWithProtoBuilderSetterTestClass",
                 "proto2.BuilderWithProtoSetterTestClass",
-                "proto2.BuilderWithReusedSettersTestClass")),
+                "proto2.BuilderWithReusedSettersTestClass",
+                "proto2.HasFlaggedOffExtensionBuilderTestClass")),
         getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
@@ -69,11 +72,22 @@
             .addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
             .addOptionsModification(
                 options -> {
+                  assert !options.applyInliningToInlinee;
                   options.applyInliningToInlinee = true;
+
+                  assert !options.enableFieldBitAccessAnalysis;
                   options.enableFieldBitAccessAnalysis = true;
+
+                  assert !options.protoShrinking().enableGeneratedExtensionRegistryShrinking;
                   options.protoShrinking().enableGeneratedExtensionRegistryShrinking = true;
+
+                  assert !options.protoShrinking().enableGeneratedMessageLiteShrinking;
                   options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
+
+                  assert !options.protoShrinking().enableGeneratedMessageLiteBuilderShrinking;
                   options.protoShrinking().enableGeneratedMessageLiteBuilderShrinking = true;
+
+                  assert !options.enableStringSwitchConversion;
                   options.enableStringSwitchConversion = true;
                 })
             .allowAccessModification()
@@ -153,6 +167,8 @@
             "0",
             "true",
             "qux");
+      case "proto2.HasFlaggedOffExtensionBuilderTestClass":
+        return StringUtils.lines("4");
       default:
         throw new Unreachable();
     }
@@ -164,14 +180,15 @@
   }
 
   private void verifyBuildersAreAbsent(CodeInspector outputInspector) {
-    boolean primitivesBuilderShouldBeLive =
-        mains.contains("proto2.BuilderWithReusedSettersTestClass");
     assertThat(
-        outputInspector.clazz(LITE_BUILDER),
-        primitivesBuilderShouldBeLive ? isPresent() : not(isPresent()));
+        outputInspector.clazz(
+            "com.android.tools.r8.proto2.Shrinking$HasFlaggedOffExtension$Builder"),
+        mains.equals(ImmutableList.of("proto2.HasFlaggedOffExtensionBuilderTestClass"))
+            ? isPresent()
+            : not(isPresent()));
     assertThat(
         outputInspector.clazz("com.android.tools.r8.proto2.TestProto$Primitives$Builder"),
-        primitivesBuilderShouldBeLive ? isPresent() : not(isPresent()));
+        not(isPresent()));
     assertThat(
         outputInspector.clazz("com.android.tools.r8.proto2.TestProto$OuterMessage$Builder"),
         not(isPresent()));
@@ -185,13 +202,14 @@
     for (String main : mains) {
       MethodSubject mainMethodSubject = outputInspector.clazz(main).mainMethod();
       assertThat(mainMethodSubject, isPresent());
-      // TODO(christofferqa): Enable assertion.
-      // assertTrue(
-      //     mainMethodSubject
-      //         .streamInstructions()
-      //         .filter(InstructionSubject::isStaticGet)
-      //         .map(instruction -> instruction.getField().type)
-      //         .noneMatch(methodToInvokeType::equals));
+      assertTrue(
+          main,
+          mainMethodSubject
+              .streamInstructions()
+              .filter(InstructionSubject::isStaticGet)
+              .map(instruction -> instruction.getField().type)
+              .noneMatch(methodToInvokeType::equals));
+      assertTrue(mainMethodSubject.streamInstructions().noneMatch(InstructionSubject::isSwitch));
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
index 111e8f1..4b0d1ed 100644
--- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
+++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
@@ -8,7 +8,7 @@
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
@@ -61,7 +61,8 @@
 
     InternalOptions options = new InternalOptions();
     DexApplication application = buildApplication(builder, options);
-    AppView<?> appView = AppView.createForD8(new AppInfo(application), options);
+    AppView<AppInfoWithClassHierarchy> appView =
+        AppView.createForD8(new AppInfoWithClassHierarchy(application), options);
 
     // Return the processed method for inspection.
     MethodSubject methodSubject = getMethodSubject(application, signature);
@@ -177,7 +178,8 @@
 
     InternalOptions options = new InternalOptions();
     DexApplication application = buildApplication(builder, options);
-    AppView<?> appView = AppView.createForD8(new AppInfo(application), options);
+    AppView<AppInfoWithClassHierarchy> appView =
+        AppView.createForD8(new AppInfoWithClassHierarchy(application), options);
 
     // Return the processed method for inspection.
     MethodSubject methodSubject = getMethodSubject(application, signature);
@@ -301,7 +303,8 @@
 
     InternalOptions options = new InternalOptions();
     DexApplication application = buildApplication(builder, options);
-    AppView<?> appView = AppView.createForD8(new AppInfo(application), options);
+    AppView<AppInfoWithClassHierarchy> appView =
+        AppView.createForD8(new AppInfoWithClassHierarchy(application), options);
 
     // Return the processed method for inspection.
     MethodSubject methodSubject = getMethodSubject(application, signature);
@@ -425,7 +428,8 @@
 
     InternalOptions options = new InternalOptions();
     DexApplication application = buildApplication(builder, options);
-    AppView<?> appView = AppView.createForD8(new AppInfo(application), options);
+    AppView<AppInfoWithClassHierarchy> appView =
+        AppView.createForD8(new AppInfoWithClassHierarchy(application), options);
 
     // Return the processed method for inspection.
     MethodSubject methodSubject = getMethodSubject(application, signature);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EffectivelyFinalFieldCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EffectivelyFinalFieldCanonicalizationTest.java
new file mode 100644
index 0000000..4e73445
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EffectivelyFinalFieldCanonicalizationTest.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2020, 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.canonicalization;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class EffectivelyFinalFieldCanonicalizationTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public EffectivelyFinalFieldCanonicalizationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(EffectivelyFinalFieldCanonicalizationTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("[R8] Foo", "[R8] Bar");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+
+    MethodSubject mainMethodSubject = testClassSubject.mainMethod();
+    assertThat(mainMethodSubject, isPresent());
+    assertEquals(
+        1, mainMethodSubject.streamInstructions().filter(InstructionSubject::isStaticGet).count());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Printer.INSTANCE.print("Foo");
+      Printer.INSTANCE.print("Bar");
+    }
+  }
+
+  @NeverClassInline
+  static class Printer {
+
+    static Printer INSTANCE = new Printer("[R8]");
+
+    private String tag;
+
+    Printer(String tag) {
+      this.tag = tag;
+    }
+
+    @NeverInline
+    void print(String message) {
+      System.out.println(tag + " " + message);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ifs/SemiTrivialPhiBranchTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ifs/SemiTrivialPhiBranchTest.java
index 8b84dab..a8bccea 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ifs/SemiTrivialPhiBranchTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ifs/SemiTrivialPhiBranchTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.ifs;
 
 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;
@@ -50,8 +51,7 @@
     assertThat(testClassSubject, isPresent());
     assertThat(testClassSubject.mainMethod(), isPresent());
     assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
-    // TODO(christofferqa): Should not be present.
-    assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+    assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeInstanceFieldValueTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeInstanceFieldValueTest.java
new file mode 100644
index 0000000..32d6962
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeInstanceFieldValueTest.java
@@ -0,0 +1,112 @@
+// Copyright (c) 2020, 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.membervaluepropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class AssumeInstanceFieldValueTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public AssumeInstanceFieldValueTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(AssumeInstanceFieldValueTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules(
+            "-assumevalues class " + Config.class.getTypeName() + " {",
+            "  boolean alwaysTrue return true;",
+            "}",
+            "-assumenosideeffects class " + Config.class.getTypeName() + " {",
+            "  boolean alwaysTrueNoSideEffects return true;",
+            "}")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        // TODO(b/147835946): Should be "Hello world!".
+        .assertSuccessWithOutput("");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject configClassSubject = inspector.clazz(Config.class);
+    assertThat(configClassSubject, isPresent());
+
+    FieldSubject alwaysTrueFieldSubject = configClassSubject.uniqueFieldWithName("alwaysTrue");
+    assertThat(alwaysTrueFieldSubject, isPresent());
+
+    FieldSubject alwaysTrueNoSideEffectsFieldSubject =
+        configClassSubject.uniqueFieldWithName("alwaysTrueNoSideEffects");
+    assertThat(alwaysTrueNoSideEffectsFieldSubject, isPresent());
+
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+
+    MethodSubject mainMethodSubject = testClassSubject.mainMethod();
+    assertThat(mainMethodSubject, isPresent());
+    assertEquals(
+        1,
+        mainMethodSubject
+            .streamInstructions()
+            .filter(InstructionSubject::isInstanceGet)
+            .map(InstructionSubject::getField)
+            .filter(alwaysTrueFieldSubject.getField().field::equals)
+            .count());
+    // TODO(b/147835946): Should be true.
+    assertFalse(
+        mainMethodSubject
+            .streamInstructions()
+            .filter(InstructionSubject::isInstanceGet)
+            .map(InstructionSubject::getField)
+            .noneMatch(alwaysTrueNoSideEffectsFieldSubject.getField().field::equals));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      if (new Config().alwaysTrue) {
+        System.out.print("Hello");
+      }
+      Config nullableConfig = System.currentTimeMillis() >= 0 ? new Config() : null;
+      if (nullableConfig.alwaysTrue) {
+        System.out.print(" world");
+      }
+      if (nullableConfig.alwaysTrueNoSideEffects) {
+        System.out.println("!");
+      }
+    }
+  }
+
+  static class Config {
+
+    boolean alwaysTrue;
+    boolean alwaysTrueNoSideEffects;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FinalFieldWithDefaultValueAssignmentPropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FinalFieldWithDefaultValueAssignmentPropagationTest.java
new file mode 100644
index 0000000..0f714ef
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FinalFieldWithDefaultValueAssignmentPropagationTest.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2020, 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.membervaluepropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+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 FinalFieldWithDefaultValueAssignmentPropagationTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public FinalFieldWithDefaultValueAssignmentPropagationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(FinalFieldWithDefaultValueAssignmentPropagationTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("false");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+    // TODO(b/147799637): Should be absent.
+    assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+
+    ClassSubject configClassSubject = inspector.clazz(Config.class);
+    assertThat(configClassSubject, isPresent());
+
+    MethodSubject configConstructorSubject = configClassSubject.init();
+    assertThat(configConstructorSubject, isPresent());
+    // TODO(b/147799637): Should be true.
+    assertFalse(
+        configConstructorSubject.streamInstructions().noneMatch(InstructionSubject::isInstancePut));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      if (new Config().alwaysFalse) {
+        dead();
+      }
+    }
+
+    @NeverInline
+    static void dead() {
+      System.out.println("Dead!");
+    }
+  }
+
+  static class Config {
+
+    final boolean alwaysFalse;
+
+    Config() {
+      // An instruction that causes alwaysFalse to be read.
+      System.out.println(this);
+      // Since alwaysFalse is final we can remove the assignment, even if the receiver escapes.
+      alwaysFalse = false;
+    }
+
+    @Override
+    public String toString() {
+      return Boolean.toString(alwaysFalse);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
index 5b4c540..a74ac5f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
@@ -42,12 +42,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InstanceFieldValuePropagationTest.class)
         .addKeepMainRule(TestClass.class)
-        .addOptionsModification(
-            options -> {
-              // TODO(b/125282093): Remove options modification once landed.
-              assert !options.enableValuePropagationForInstanceFields;
-              options.enableValuePropagationForInstanceFields = true;
-            })
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonFinalFieldWithDefaultValueAssignmentPropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonFinalFieldWithDefaultValueAssignmentPropagationTest.java
new file mode 100644
index 0000000..2a37168
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonFinalFieldWithDefaultValueAssignmentPropagationTest.java
@@ -0,0 +1,96 @@
+// Copyright (c) 2020, 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.membervaluepropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.NeverClassInline;
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+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 NonFinalFieldWithDefaultValueAssignmentPropagationTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public NonFinalFieldWithDefaultValueAssignmentPropagationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(NonFinalFieldWithDefaultValueAssignmentPropagationTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+    // TODO(b/147799637): Should be absent.
+    assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+
+    ClassSubject configClassSubject = inspector.clazz(Config.class);
+    assertThat(configClassSubject, isPresent());
+
+    MethodSubject configConstructorSubject = configClassSubject.init();
+    assertThat(configConstructorSubject, isPresent());
+    // TODO(b/147799637): Should be true.
+    assertFalse(
+        configConstructorSubject.streamInstructions().noneMatch(InstructionSubject::isInstancePut));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      if (new Config().alwaysFalse) {
+        dead();
+      }
+    }
+
+    @NeverInline
+    static void dead() {
+      System.out.println("Dead!");
+    }
+  }
+
+  @NeverClassInline
+  static class Config {
+
+    boolean alwaysFalse;
+
+    Config() {
+      // An instruction that cannot read alwaysFalse, because the receiver has not escaped
+      // (except into Object.<init>()).
+      System.out.println("Hello world!");
+      // Since the receiver has not escaped, we can remove the assignment.
+      alwaysFalse = false;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonFinalFieldWithNonDefaultValueAssignmentPropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonFinalFieldWithNonDefaultValueAssignmentPropagationTest.java
new file mode 100644
index 0000000..c69ddd6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonFinalFieldWithNonDefaultValueAssignmentPropagationTest.java
@@ -0,0 +1,96 @@
+// Copyright (c) 2020, 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.membervaluepropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.NeverClassInline;
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+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 NonFinalFieldWithNonDefaultValueAssignmentPropagationTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public NonFinalFieldWithNonDefaultValueAssignmentPropagationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(NonFinalFieldWithNonDefaultValueAssignmentPropagationTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+    // TODO(b/147799637): Should be absent.
+    assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+
+    ClassSubject configClassSubject = inspector.clazz(Config.class);
+    assertThat(configClassSubject, isPresent());
+
+    MethodSubject configConstructorSubject = configClassSubject.init();
+    assertThat(configConstructorSubject, isPresent());
+    // TODO(b/147799637): Should be true.
+    assertFalse(
+        configConstructorSubject.streamInstructions().noneMatch(InstructionSubject::isInstancePut));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      if (!new Config().alwaysTrue) {
+        dead();
+      }
+    }
+
+    @NeverInline
+    static void dead() {
+      System.out.println("Dead!");
+    }
+  }
+
+  @NeverClassInline
+  static class Config {
+
+    boolean alwaysTrue;
+
+    Config() {
+      // An instruction that cannot read alwaysTrue, because the receiver has not escaped
+      // (except into Object.<init>()).
+      System.out.println("Hello world!");
+      // Since the receiver has not escaped, we are guaranteed that all reads will evaluate to true.
+      alwaysTrue = true;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
index d0a4064..5ab6053 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -170,6 +170,7 @@
             "STATIC: SimpleWithThrowingGetter SimpleWithThrowingGetter.getInstance()",
             "STATIC: SimpleWithThrowingGetter SimpleWithThrowingGetter.getInstance()",
             "STATIC: String TrivialTestClass.next()",
+            "SimpleWithThrowingGetter SimpleWithThrowingGetter.INSTANCE",
             "VIRTUAL: String SimpleWithThrowingGetter.bar(String)",
             "VIRTUAL: String SimpleWithThrowingGetter.foo()"),
         references(clazz, "testSimpleWithThrowingGetter", "void"));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
index f186803..e40b4e0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
@@ -225,6 +225,21 @@
   }
 
   @Test
+  public void testConditionalPhiWithoutAppend() throws Exception {
+    buildAndCheckIR(
+        "conditionalPhiWithoutAppend",
+        checkOptimizerStates(appView, optimizer -> {
+          assertEquals(1, optimizer.analysis.builderStates.size());
+          for (Value builder : optimizer.analysis.builderStates.keySet()) {
+            Map<Instruction, BuilderState> perBuilderState =
+                optimizer.analysis.builderStates.get(builder);
+            checkBuilderState(optimizer, perBuilderState, null, true);
+          }
+          assertEquals(0, optimizer.analysis.simplifiedBuilders.size());
+        }));
+  }
+
+  @Test
   public void testLoop() throws Exception {
     buildAndCheckIR(
         "loop",
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
index 1b16aa6..0efe652 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
@@ -51,6 +51,8 @@
       "Hello,R8",
       // phiWithDifferentInits
       "Hello,R8",
+      // conditionalPhiWithoutAppend
+      "initial:suffix",
       // loop
       "na;na;na;na;na;na;na;na;Batman!",
       // loopWithBuilder
@@ -59,7 +61,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -152,6 +154,10 @@
     assertThat(method, isPresent());
     assertEquals(3, countConstString(method));
 
+    method = mainClass.uniqueMethodWithName("conditionalPhiWithoutAppend");
+    assertThat(method, isPresent());
+    assertEquals(3, countConstString(method));
+
     method = mainClass.uniqueMethodWithName("loop");
     assertThat(method, isPresent());
     assertEquals(3, countConstString(method));
@@ -173,7 +179,7 @@
         testForD8()
             .debug()
             .addProgramClasses(MAIN)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
     test(result, false, false);
@@ -182,7 +188,7 @@
         testForD8()
             .release()
             .addProgramClasses(MAIN)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
     test(result, false, true);
@@ -198,7 +204,7 @@
             .enableInliningAnnotations()
             .addKeepMainRule(MAIN)
             .noMinification()
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
     test(result, true, true);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTestClass.java
index 980148b..535fee9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTestClass.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTestClass.java
@@ -138,6 +138,16 @@
   }
 
   @NeverInline
+  public static void conditionalPhiWithoutAppend() {
+    StringBuilder b = new StringBuilder("initial");
+    String suffix = "suffix";
+    if (!suffix.isEmpty()) {
+      b.append(":").append(suffix);
+    }
+    System.out.println(b.toString());
+  }
+
+  @NeverInline
   public static void loop() {
     String r = "";
     for (int i = 0; i < 8; i++) {
@@ -169,6 +179,7 @@
     simplePhi();
     phiAtInit();
     phiWithDifferentInits();
+    conditionalPhiWithoutAppend();
     loop();
     loopWithBuilder();
   }
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index 555b94c..a449c5d 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -44,7 +44,7 @@
     }
 
     @Override
-    public void replaceCurrentInstruction(Instruction newInstruction) {
+    public void replaceCurrentInstruction(Instruction newInstruction, Set<Value> affectedValues) {
       throw new Unimplemented();
     }
 
diff --git a/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java b/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
index c55b3a5..b4dc614 100644
--- a/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
+++ b/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
@@ -293,7 +293,8 @@
         "  invokespecial SubClass/<init>()V",
         "  invokespecial SubClass/aMethod()V",
         "  return");
-    ensureIAE(builder);
+    ensureExceptionOrCompilerError(builder, IllegalAccessError.class,
+        compiler -> compiler.equals(CompilerUnderTest.R8));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInClasspathTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInClasspathTypeTest.java
index ab702b1..e785965 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInClasspathTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInClasspathTypeTest.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8.kotlin.metadata;
 
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isExtension;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
 import static org.hamcrest.CoreMatchers.not;
@@ -16,7 +16,6 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
 import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
@@ -76,7 +75,6 @@
             // to be called with Kotlin syntax from other kotlin code.
             .addKeepRules("-keep class **.ImplKt { <methods>; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
-            .addOptionsModification(InternalOptions::enableKotlinMetadataRewriting)
             .compile();
     String pkg = getClass().getPackage().getName();
     final String implClassName = pkg + ".classpath_lib_ext.Impl";
@@ -142,7 +140,6 @@
             // to be called with Kotlin syntax from other kotlin code.
             .addKeepRules("-keep class **.ImplKt { <methods>; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
-            .addOptionsModification(InternalOptions::enableKotlinMetadataRewriting)
             .compile();
     String pkg = getClass().getPackage().getName();
     final String implClassName = pkg + ".classpath_lib_ext.Impl";
@@ -160,7 +157,7 @@
       assertThat(kmPackage, isPresent());
 
       KmFunctionSubject kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("fooExt");
-      assertThat(kmFunction, isExtension());
+      assertThat(kmFunction, isExtensionFunction());
 
       ClassSubject extra = inspector.clazz(extraClassName);
       assertThat(extra, isPresent());
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionFunctionTest.java
index 8e1e6b7..0f124e4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionFunctionTest.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8.kotlin.metadata;
 
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isExtension;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
 import static org.hamcrest.CoreMatchers.not;
@@ -16,7 +16,6 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
 import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
@@ -69,7 +68,6 @@
             // to be called with Kotlin syntax from other kotlin code.
             .addKeepRules("-keep class **.BKt { <methods>; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
-            .addOptionsModification(InternalOptions::enableKotlinMetadataRewriting)
             .compile();
     String pkg = getClass().getPackage().getName();
     final String superClassName = pkg + ".extension_function_lib.Super";
@@ -96,7 +94,7 @@
       assertThat(kmPackage, isPresent());
 
       KmFunctionSubject kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("extension");
-      assertThat(kmFunction, isExtension());
+      assertThat(kmFunction, isExtensionFunction());
     });
 
     Path libJar = compileResult.writeToZip();
@@ -130,7 +128,6 @@
             // to be called with Kotlin syntax from other kotlin code.
             .addKeepRules("-keep class **.BKt { <methods>; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
-            .addOptionsModification(InternalOptions::enableKotlinMetadataRewriting)
             .compile();
     String pkg = getClass().getPackage().getName();
     final String superClassName = pkg + ".extension_function_lib.Super";
@@ -161,7 +158,7 @@
       assertThat(kmPackage, isPresent());
 
       KmFunctionSubject kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("extension");
-      assertThat(kmFunction, isExtension());
+      assertThat(kmFunction, isExtensionFunction());
     });
 
     Path libJar = compileResult.writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionPropertyTest.java
index 0b327c9..7ffa9ac 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionPropertyTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.kotlin.metadata;
 
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionProperty;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
 import static org.hamcrest.CoreMatchers.containsString;
@@ -17,10 +18,10 @@
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
 import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
+import com.android.tools.r8.utils.codeinspector.KmPropertySubject;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.List;
@@ -69,7 +70,6 @@
             // to be called with Kotlin syntax from other kotlin code.
             .addKeepRules("-keep class **.BKt { <methods>; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
-            .addOptionsModification(InternalOptions::enableKotlinMetadataRewriting)
             .compile();
     String pkg = getClass().getPackage().getName();
     final String superClassName = pkg + ".extension_property_lib.Super";
@@ -95,7 +95,8 @@
       KmPackageSubject kmPackage = bKt.getKmPackage();
       assertThat(kmPackage, isPresent());
 
-      // TODO(b/70169921): test property details.
+      KmPropertySubject kmProperty = kmPackage.kmPropertyExtensionWithUniqueName("asI");
+      assertThat(kmProperty, isExtensionProperty());
     });
 
     Path libJar = compileResult.writeToZip();
@@ -126,7 +127,6 @@
             // to be called with Kotlin syntax from other kotlin code.
             .addKeepRules("-keep class **.BKt { <methods>; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
-            .addOptionsModification(InternalOptions::enableKotlinMetadataRewriting)
             .compile();
     String pkg = getClass().getPackage().getName();
     final String superClassName = pkg + ".extension_property_lib.Super";
@@ -156,7 +156,8 @@
       KmPackageSubject kmPackage = bKt.getKmPackage();
       assertThat(kmPackage, isPresent());
 
-      // TODO(b/70169921): test property details.
+      KmPropertySubject kmProperty = kmPackage.kmPropertyExtensionWithUniqueName("asI");
+      assertThat(kmProperty, isExtensionProperty());
     });
 
     Path libJar = compileResult.writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInFunctionTest.java
index c7e5db4..c0a1092 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInFunctionTest.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8.kotlin.metadata;
 
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isExtension;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
 import static org.hamcrest.CoreMatchers.not;
@@ -16,7 +16,6 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
 import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
@@ -68,7 +67,6 @@
             // Keep the BKt method, which will be called from other kotlin code.
             .addKeepRules("-keep class **.BKt { <methods>; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
-            .addOptionsModification(InternalOptions::enableKotlinMetadataRewriting)
             .compile();
     String pkg = getClass().getPackage().getName();
     final String superClassName = pkg + ".function_lib.Super";
@@ -96,7 +94,7 @@
 
       KmFunctionSubject kmFunction = kmPackage.kmFunctionWithUniqueName("fun");
       assertThat(kmFunction, isPresent());
-      assertThat(kmFunction, not(isExtension()));
+      assertThat(kmFunction, not(isExtensionFunction()));
     });
 
     Path libJar = compileResult.writeToZip();
@@ -129,7 +127,6 @@
             // Keep the BKt method, which will be called from other kotlin code.
             .addKeepRules("-keep class **.BKt { <methods>; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
-            .addOptionsModification(InternalOptions::enableKotlinMetadataRewriting)
             .compile();
     String pkg = getClass().getPackage().getName();
     final String superClassName = pkg + ".function_lib.Super";
@@ -160,7 +157,7 @@
 
       KmFunctionSubject kmFunction = kmPackage.kmFunctionWithUniqueName("fun");
       assertThat(kmFunction, isPresent());
-      assertThat(kmFunction, not(isExtension()));
+      assertThat(kmFunction, not(isExtensionFunction()));
     });
 
     Path libJar = compileResult.writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInLibraryTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInLibraryTypeTest.java
index 603dd6e..8917ebe 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInLibraryTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInLibraryTypeTest.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
 import java.nio.file.Path;
@@ -82,7 +81,6 @@
             // Keep the main entry.
             .addKeepMainRule(main)
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
-            .addOptionsModification(InternalOptions::enableKotlinMetadataRewriting)
             // -dontoptimize so that basic code structure is kept.
             .noOptimization()
             .compile();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInMultifileClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInMultifileClassTest.java
index 80a2641..327878a 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInMultifileClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInMultifileClassTest.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -66,7 +65,6 @@
             .addKeepRules("-keep class **.UtilKt")
             .addKeepRules("-keepclassmembers class * { ** comma*Join*(...); }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
-            .addOptionsModification(InternalOptions::enableKotlinMetadataRewriting)
             .compile();
     String pkg = getClass().getPackage().getName();
     final String utilClassName = pkg + ".multifileclass_lib.UtilKt";
@@ -125,7 +123,6 @@
             .addKeepRules(
                 "-keepclassmembers,allowobfuscation class * { ** joinOf*(...); }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
-            .addOptionsModification(InternalOptions::enableKotlinMetadataRewriting)
             .compile();
     String pkg = getClass().getPackage().getName();
     final String utilClassName = pkg + ".multifileclass_lib.UtilKt";
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInParameterTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInParameterTypeTest.java
index 61ede88..d75ec09 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInParameterTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInParameterTypeTest.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
 import java.nio.file.Path;
@@ -64,7 +63,6 @@
             // Keep Itf, but allow minification.
             .addKeepRules("-keep,allowobfuscation class **.Itf")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
-            .addOptionsModification(InternalOptions::enableKotlinMetadataRewriting)
             .compile();
     String pkg = getClass().getPackage().getName();
     final String itfClassName = pkg + ".parametertype_lib.Itf";
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTest.java
index c671ea5..d0708f8 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTest.java
@@ -4,19 +4,22 @@
 package com.android.tools.r8.kotlin.metadata;
 
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionProperty;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 
 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.shaking.ProguardKeepAttributes;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmPropertySubject;
 import java.nio.file.Path;
 import java.util.Collection;
 import org.junit.BeforeClass;
@@ -61,7 +64,6 @@
             .addKeepRules("-keep class **.Person { <init>(...); }")
             .addKeepRules("-keepclassmembers class **.Person { *** get*(); }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
-            .addOptionsModification(InternalOptions::enableKotlinMetadataRewriting)
             .compile();
     String pkg = getClass().getPackage().getName();
     final String personClassName = pkg + ".fragile_property_lib.Person";
@@ -69,10 +71,35 @@
       ClassSubject person = inspector.clazz(personClassName);
       assertThat(person, isPresent());
       assertThat(person, not(isRenamed()));
+
       // API entry is kept, hence the presence of Metadata.
       KmClassSubject kmClass = person.getKmClass();
       assertThat(kmClass, isPresent());
-      // TODO(b/70169921): test property details.
+
+      KmPropertySubject name = kmClass.kmPropertyWithUniqueName("name");
+      assertThat(name, isPresent());
+      assertThat(name, not(isExtensionProperty()));
+      assertNotNull(name.fieldSignature());
+      assertNotNull(name.getterSignature());
+      // TODO(b/70169921): Can remove setter.
+      assertNotNull(name.setterSignature());
+
+      KmPropertySubject familyName = kmClass.kmPropertyWithUniqueName("familyName");
+      assertThat(familyName, isPresent());
+      assertThat(familyName, not(isExtensionProperty()));
+      // No backing field for property `familyName`
+      assertNull(familyName.fieldSignature());
+      assertNotNull(familyName.getterSignature());
+      // No setter for property `familyName`
+      assertNull(familyName.setterSignature());
+
+      KmPropertySubject age = kmClass.kmPropertyWithUniqueName("age");
+      assertThat(age, isPresent());
+      assertThat(age, not(isExtensionProperty()));
+      assertNotNull(age.fieldSignature());
+      assertNotNull(age.getterSignature());
+      // TODO(b/70169921): Can remove setter.
+      assertNotNull(name.setterSignature());
     });
 
     Path libJar = compileResult.writeToZip();
@@ -105,7 +132,6 @@
             // Keep LibKt extension methods
             .addKeepRules("-keep class **.LibKt { <methods>; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
-            .addOptionsModification(InternalOptions::enableKotlinMetadataRewriting)
             .compile();
     String pkg = getClass().getPackage().getName();
     final String personClassName = pkg + ".fragile_property_lib.Person";
@@ -116,7 +142,31 @@
       // API entry is kept, hence the presence of Metadata.
       KmClassSubject kmClass = person.getKmClass();
       assertThat(kmClass, isPresent());
-      // TODO(b/70169921): test property details.
+
+      KmPropertySubject name = kmClass.kmPropertyWithUniqueName("name");
+      assertThat(name, isPresent());
+      assertThat(name, not(isExtensionProperty()));
+      assertNotNull(name.fieldSignature());
+      // TODO(b/70169921): Either remove getter or rewrite renamed signature.
+      assertNotNull(name.getterSignature());
+      assertNotNull(name.setterSignature());
+
+      KmPropertySubject familyName = kmClass.kmPropertyWithUniqueName("familyName");
+      assertThat(familyName, isPresent());
+      assertThat(familyName, not(isExtensionProperty()));
+      // No backing field for property `familyName`
+      assertNull(familyName.fieldSignature());
+      assertNotNull(familyName.getterSignature());
+      // No setter for property `familyName`
+      assertNull(familyName.setterSignature());
+
+      KmPropertySubject age = kmClass.kmPropertyWithUniqueName("age");
+      assertThat(age, isPresent());
+      assertThat(age, not(isExtensionProperty()));
+      assertNotNull(age.fieldSignature());
+      // TODO(b/70169921): Either remove getter or rewrite renamed signature.
+      assertNotNull(age.getterSignature());
+      assertNotNull(name.setterSignature());
     });
 
     Path libJar = compileResult.writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTypeTest.java
index 696cd03..60a9732 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTypeTest.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
 import java.nio.file.Path;
@@ -64,7 +63,6 @@
             // Keep non-private members of Impl
             .addKeepRules("-keep public class **.Impl { !private *; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
-            .addOptionsModification(InternalOptions::enableKotlinMetadataRewriting)
             .compile();
     String pkg = getClass().getPackage().getName();
     final String itfClassName = pkg + ".propertytype_lib.Itf";
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInReturnTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInReturnTypeTest.java
index 25ee2f8..4f24442 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInReturnTypeTest.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
 import java.nio.file.Path;
@@ -63,7 +62,6 @@
             // Keep Itf, but allow minification.
             .addKeepRules("-keep,allowobfuscation class **.Itf")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
-            .addOptionsModification(InternalOptions::enableKotlinMetadataRewriting)
             .compile();
     String pkg = getClass().getPackage().getName();
     final String itfClassName = pkg + ".returntype_lib.Itf";
diff --git a/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java b/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
index b264ef5..4489b75 100644
--- a/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
+++ b/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
@@ -22,6 +22,7 @@
 import org.junit.Test;
 
 public class GenericSignatureParserTest extends TestBase {
+
   private static class ReGenerateGenericSignatureRewriter
       implements GenericSignatureAction<String> {
 
@@ -42,7 +43,7 @@
     }
 
     @Override
-    public String parsedTypeName(String name) {
+    public String parsedTypeName(String name, ParserPosition parserPosition) {
       renamedSignature.append(name);
       return name;
     }
@@ -389,7 +390,7 @@
       }
 
       @Override
-      public String parsedTypeName(String name) {
+      public String parsedTypeName(String name, ParserPosition parserPosition) {
         throw exceptionSupplier.get();
       }
     }
diff --git a/src/test/java/com/android/tools/r8/naming/signature/SignatureOfMergedClassesTest.java b/src/test/java/com/android/tools/r8/naming/signature/SignatureOfMergedClassesTest.java
new file mode 100644
index 0000000..01a95ba
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/SignatureOfMergedClassesTest.java
@@ -0,0 +1,138 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.signature;
+
+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.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.signature.merging.I;
+import com.android.tools.r8.naming.signature.merging.ImplI;
+import com.android.tools.r8.naming.signature.merging.ImplK;
+import com.android.tools.r8.naming.signature.merging.ImplL;
+import com.android.tools.r8.naming.signature.merging.InterfaceToKeep;
+import com.android.tools.r8.naming.signature.merging.J;
+import com.android.tools.r8.naming.signature.merging.K;
+import com.android.tools.r8.naming.signature.merging.L;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.concurrent.ExecutionException;
+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 SignatureOfMergedClassesTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public SignatureOfMergedClassesTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRemovalOfMergedInterfaceOnSameClass()
+      throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(
+            ImplI.class,
+            ImplK.class,
+            I.class,
+            J.class,
+            InterfaceToKeep.class,
+            K.class,
+            Main.class,
+            L.class,
+            ImplL.class)
+        .addKeepMainRule(Main.class)
+        .addKeepClassRules(InterfaceToKeep.class)
+        .addKeepAttributes("Signature, InnerClasses, EnclosingMethod, *Annotation*")
+        .setMinApi(parameters.getApiLevel())
+        .noMinification()
+        .addOptionsModification(
+            internalOptions -> {
+              internalOptions.enableUnusedInterfaceRemoval = false;
+            })
+        .compileWithExpectedDiagnostics(TestDiagnosticMessages::assertNoMessages)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(
+            "ImplI.foo",
+            "ImplI: com.android.tools.r8.naming.signature.merging.InterfaceToKeep<java.lang.Void>",
+            "K: com.android.tools.r8.naming.signature.merging.InterfaceToKeep<java.lang.Void>",
+            "ImplK.foo",
+            "ImplK.bar",
+            "ImplK: interface com.android.tools.r8.naming.signature.merging.K",
+            "ImplL.print")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(I.class), not(isPresent()));
+              assertThat(codeInspector.clazz(J.class), not(isPresent()));
+            });
+  }
+
+  @Test
+  public void testKeepingOneSelfOnInterface()
+      throws ExecutionException, CompilationFailedException, IOException {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Foo.class, InterfaceToKeep.class)
+        .addKeepMainRule(Foo.class)
+        .addKeepClassRules(InterfaceToKeep.class)
+        .addKeepAttributes("Signature, InnerClasses, EnclosingMethod, *Annotation*")
+        .setMinApi(parameters.getApiLevel())
+        .noMinification()
+        .addOptionsModification(
+            internalOptions -> {
+              internalOptions.enableUnusedInterfaceRemoval = false;
+            })
+        .run(parameters.getRuntime(), Foo.class)
+        .assertSuccessWithOutputLines(
+            "com.android.tools.r8.naming.signature.merging.InterfaceToKeep"
+                + "<com.android.tools.r8.naming.signature.SignatureOfMergedClassesTest$Foo>");
+  }
+
+  public static class Foo implements InterfaceToKeep<Foo> {
+
+    public static void main(String[] args) {
+      for (Type genericInterface : Foo.class.getGenericInterfaces()) {
+        System.out.println(genericInterface);
+      }
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new ImplI().foo();
+      for (Type genericInterface : ImplI.class.getGenericInterfaces()) {
+        System.out.println("ImplI: " + genericInterface);
+      }
+      for (Type genericInterface : K.class.getGenericInterfaces()) {
+        System.out.println("K: " + genericInterface);
+      }
+      K k = new ImplK();
+      k.foo();
+      k.bar();
+      for (Type genericInterface : ImplK.class.getGenericInterfaces()) {
+        System.out.println("ImplK: " + genericInterface);
+      }
+      L<ImplL> l = new ImplL();
+      l.print((ImplL) l);
+      for (Type genericInterface : ImplL.class.getGenericInterfaces()) {
+        System.out.println("ImplL: " + genericInterface);
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/signature/merging/I.java b/src/test/java/com/android/tools/r8/naming/signature/merging/I.java
new file mode 100644
index 0000000..237786c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/merging/I.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.signature.merging;
+
+public interface I {
+  void foo();
+}
diff --git a/src/test/java/com/android/tools/r8/naming/signature/merging/ImplI.java b/src/test/java/com/android/tools/r8/naming/signature/merging/ImplI.java
new file mode 100644
index 0000000..b842386
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/merging/ImplI.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.signature.merging;
+
+public class ImplI implements InterfaceToKeep<Void>, I {
+
+  @Override
+  public void foo() {
+    System.out.println("ImplI.foo");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/signature/merging/ImplK.java b/src/test/java/com/android/tools/r8/naming/signature/merging/ImplK.java
new file mode 100644
index 0000000..3c23d56
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/merging/ImplK.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.signature.merging;
+
+public class ImplK implements K {
+
+  @Override
+  public void foo() {
+    System.out.println("ImplK.foo");
+  }
+
+  @Override
+  public void bar() {
+    System.out.println("ImplK.bar");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/signature/merging/ImplL.java b/src/test/java/com/android/tools/r8/naming/signature/merging/ImplL.java
new file mode 100644
index 0000000..e50bdb3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/merging/ImplL.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.signature.merging;
+
+public class ImplL implements L<ImplL> {
+
+  @Override
+  public void print(ImplL implL) {
+    System.out.println("ImplL.print");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/signature/merging/InterfaceToKeep.java b/src/test/java/com/android/tools/r8/naming/signature/merging/InterfaceToKeep.java
new file mode 100644
index 0000000..6bebcd0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/merging/InterfaceToKeep.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.signature.merging;
+
+public interface InterfaceToKeep<T> {}
diff --git a/src/test/java/com/android/tools/r8/naming/signature/merging/J.java b/src/test/java/com/android/tools/r8/naming/signature/merging/J.java
new file mode 100644
index 0000000..444a5e8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/merging/J.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.signature.merging;
+
+public interface J {
+  void foo();
+}
diff --git a/src/test/java/com/android/tools/r8/naming/signature/merging/K.java b/src/test/java/com/android/tools/r8/naming/signature/merging/K.java
new file mode 100644
index 0000000..4b8ed50
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/merging/K.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.signature.merging;
+
+public interface K extends InterfaceToKeep<Void>, J {
+  void bar();
+}
diff --git a/src/test/java/com/android/tools/r8/naming/signature/merging/L.java b/src/test/java/com/android/tools/r8/naming/signature/merging/L.java
new file mode 100644
index 0000000..65656dd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/merging/L.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.signature.merging;
+
+public interface L<T> {
+
+  void print(T t);
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
index d8944f1..6162b15 100644
--- a/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
@@ -49,17 +49,23 @@
           factory.createArrayType(2, fooType)
         };
     DexEncodedMethod langObjectNotifyMethod =
-        appInfo.lookupVirtualTarget(
-            fooType,
-            factory.createMethod(fooType, factory.createProto(factory.voidType), "notify"));
+        appInfo
+            .resolveMethod(
+                fooType,
+                factory.createMethod(fooType, factory.createProto(factory.voidType), "notify"))
+            .getSingleTarget();
     for (DexType arrType : arrayTypes) {
       assertNull(
-          appInfo.lookupVirtualTarget(
-              arrType, factory.createMethod(arrType, factory.createProto(arrType), "clone")));
+          appInfo
+              .resolveMethod(
+                  arrType, factory.createMethod(arrType, factory.createProto(arrType), "clone"))
+              .getSingleTarget());
       DexEncodedMethod target =
-          appInfo.lookupVirtualTarget(
-              arrType,
-              factory.createMethod(arrType, factory.createProto(factory.voidType), "notify"));
+          appInfo
+              .resolveMethod(
+                  arrType,
+                  factory.createMethod(arrType, factory.createProto(factory.voidType), "notify"))
+              .getSingleTarget();
       assertEquals(langObjectNotifyMethod, target);
     }
   }
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
index 8443f06..2f9a1ec 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
@@ -48,10 +48,6 @@
 
   private void configure(InternalOptions options) {
     options.enableEnumValueOptimization = enableOptimization;
-
-    // TODO(b/125282093): Remove options modification once landed.
-    assert !options.enableValuePropagationForInstanceFields;
-    options.enableValuePropagationForInstanceFields = true;
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java b/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java
index 8ae759f..66c8152 100644
--- a/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java
@@ -51,12 +51,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(EffectivelyFinalInstanceFieldsTest.class)
         .addKeepMainRule(MAIN)
-        .addOptionsModification(
-            options -> {
-              // TODO(b/125282093): Remove options modification once landed.
-              assert !options.enableValuePropagationForInstanceFields;
-              options.enableValuePropagationForInstanceFields = true;
-            })
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableMergeAnnotations()
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
index 564902a..f28f242 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
@@ -40,12 +40,7 @@
         TreeShaking18Test::unusedRemoved,
         null,
         null,
-        ImmutableList.of("src/test/examples/shaking18/keep-rules.txt"),
-        options -> {
-          // TODO(b/125282093): Remove options modification once landed.
-          assert !options.enableValuePropagationForInstanceFields;
-          options.enableValuePropagationForInstanceFields = true;
-        });
+        ImmutableList.of("src/test/examples/shaking18/keep-rules.txt"));
   }
 
   private static void unusedRemoved(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
index ba1c576..1a61008 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
@@ -12,12 +12,10 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NeverPropagateValue;
-import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersBuilder;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
@@ -51,28 +49,24 @@
 
   @Test
   public void testStaticMethod() throws Exception {
-    test(FooStaticMethod.class, TestStaticMethod.class, null);
+    test(FooStaticMethod.class, TestStaticMethod.class);
   }
 
   @Test
   public void testStaticField() throws Exception {
-    test(
-        FooStaticField.class,
-        TestStaticField.class,
-        builder -> builder.enableInliningAnnotations().enableMemberValuePropagationAnnotations());
+    test(FooStaticField.class, TestStaticField.class);
   }
 
-  private void test(
-      Class<?> fooClass, Class<?> testClass, ThrowableConsumer<R8FullTestBuilder> configuration)
-      throws Exception {
+  private void test(Class<?> fooClass, Class<?> testClass) throws Exception {
     WhyAreYouKeepingConsumer whyAreYouKeepingConsumer = new WhyAreYouKeepingConsumer(null);
     GraphInspector inspector =
         testForR8(parameters.getBackend())
+            .enableInliningAnnotations()
+            .enableMemberValuePropagationAnnotations()
             .enableNeverClassInliningAnnotations()
             .enableGraphInspector(whyAreYouKeepingConsumer)
             .addProgramClasses(testClass, fooClass)
             .addKeepMainRule(testClass)
-            .apply(configuration)
             .run(parameters.getRuntime(), testClass)
             .assertSuccessWithOutput(EXPECTED)
             .graphInspector();
@@ -122,6 +116,8 @@
 
     private FooStaticMethod() {}
 
+    @NeverInline
+    @NeverPropagateValue
     @Override
     public String toString() {
       return "Foo!";
@@ -130,6 +126,8 @@
 
   @NeverClassInline
   public static class TestStaticMethod {
+
+    @NeverPropagateValue
     public FooStaticMethod foo;
 
     public TestStaticMethod() {
@@ -159,6 +157,8 @@
 
   @NeverClassInline
   public static class TestStaticField {
+
+    @NeverPropagateValue
     public FooStaticField foo;
 
     public TestStaticField() {
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSubclassKeepsSuperTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSubclassKeepsSuperTest.java
index 72bd54e..5e7786f 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSubclassKeepsSuperTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSubclassKeepsSuperTest.java
@@ -5,7 +5,10 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersBuilder;
@@ -42,7 +45,10 @@
     GraphInspector inspector =
         testForR8(parameters.getBackend())
             .enableGraphInspector()
+            .enableInliningAnnotations()
+            .enableMemberValuePropagationAnnotations()
             .enableMergeAnnotations()
+            .enableNeverClassInliningAnnotations()
             .addProgramClasses(CLASS, Foo.class, Bar.class)
             .addKeepMainRule(CLASS)
             .run(parameters.getRuntime(), CLASS)
@@ -63,6 +69,7 @@
   @NeverMerge
   public abstract static class Bar {}
 
+  @NeverClassInline
   public static final class Foo extends Bar {
 
     static final Foo INSTANCE = new Foo();
@@ -73,6 +80,8 @@
 
     private Foo() {}
 
+    @NeverInline
+    @NeverPropagateValue
     @Override
     public String toString() {
       return "Foo!";
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
index 212dd9f..6a2f504 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
@@ -69,6 +69,21 @@
   }
 
   @Override
+  public KmPropertySubject kmPropertyWithUniqueName(String name) {
+    return null;
+  }
+
+  @Override
+  public KmPropertySubject kmPropertyExtensionWithUniqueName(String name) {
+    return null;
+  }
+
+  @Override
+  public List<KmPropertySubject> getProperties() {
+    return null;
+  }
+
+  @Override
   public List<ClassSubject> getReturnTypesInProperties() {
     return null;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java
index fb7466b..e869207 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils.codeinspector;
 
+import kotlinx.metadata.jvm.JvmMethodSignature;
+
 public class AbsentKmFunctionSubject extends KmFunctionSubject {
 
   @Override
@@ -24,4 +26,9 @@
   public boolean isExtension() {
     return false;
   }
+
+  @Override
+  public JvmMethodSignature signature() {
+    return null;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPackageSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPackageSubject.java
index 1fd6506..aef78fc 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPackageSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPackageSubject.java
@@ -69,6 +69,21 @@
   }
 
   @Override
+  public KmPropertySubject kmPropertyWithUniqueName(String name) {
+    return null;
+  }
+
+  @Override
+  public KmPropertySubject kmPropertyExtensionWithUniqueName(String name) {
+    return null;
+  }
+
+  @Override
+  public List<KmPropertySubject> getProperties() {
+    return null;
+  }
+
+  @Override
   public List<ClassSubject> getReturnTypesInProperties() {
     return null;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPropertySubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPropertySubject.java
new file mode 100644
index 0000000..70f6e52
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPropertySubject.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2020, 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.utils.codeinspector;
+
+import kotlinx.metadata.jvm.JvmFieldSignature;
+import kotlinx.metadata.jvm.JvmMethodSignature;
+
+public class AbsentKmPropertySubject extends KmPropertySubject {
+
+  @Override
+  public boolean isPresent() {
+    return false;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    return false;
+  }
+
+  @Override
+  public boolean isSynthetic() {
+    return false;
+  }
+
+  @Override
+  public boolean isExtension() {
+    return false;
+  }
+
+  @Override
+  public JvmFieldSignature fieldSignature() {
+    return null;
+  }
+
+  @Override
+  public JvmMethodSignature getterSignature() {
+    return null;
+  }
+
+  @Override
+  public JvmMethodSignature setterSignature() {
+    return null;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 4aec544..1a4dcc16 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -423,7 +423,7 @@
     }
 
     @Override
-    public String parsedTypeName(String name) {
+    public String parsedTypeName(String name, ParserPosition parserPosition) {
       String type = name;
       if (obfuscatedToOriginalMapping != null) {
         String original = mapType(obfuscatedToOriginalMapping, name);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index af63ce8..e487726 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -46,6 +46,8 @@
 import com.android.tools.r8.code.IgetWide;
 import com.android.tools.r8.code.InstanceOf;
 import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.InvokeCustom;
+import com.android.tools.r8.code.InvokeCustomRange;
 import com.android.tools.r8.code.InvokeDirect;
 import com.android.tools.r8.code.InvokeDirectRange;
 import com.android.tools.r8.code.InvokeInterface;
@@ -197,6 +199,10 @@
     return false;
   }
 
+  public boolean isInvokeCustom() {
+    return instruction instanceof InvokeCustom || instruction instanceof InvokeCustomRange;
+  }
+
   public boolean isInvokeSuper() {
     return instruction instanceof InvokeSuper || instruction instanceof InvokeSuperRange;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
index f30a0e5..34bc771 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
@@ -15,10 +15,15 @@
 import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmFunctionExtensionVisitor;
 import kotlinx.metadata.KmFunctionVisitor;
+import kotlinx.metadata.KmProperty;
+import kotlinx.metadata.KmPropertyExtensionVisitor;
+import kotlinx.metadata.KmPropertyVisitor;
 import kotlinx.metadata.KmType;
 import kotlinx.metadata.KmTypeVisitor;
+import kotlinx.metadata.jvm.JvmFieldSignature;
 import kotlinx.metadata.jvm.JvmFunctionExtensionVisitor;
 import kotlinx.metadata.jvm.JvmMethodSignature;
+import kotlinx.metadata.jvm.JvmPropertyExtensionVisitor;
 
 public interface FoundKmDeclarationContainerSubject extends KmDeclarationContainerSubject {
 
@@ -74,7 +79,7 @@
   // TODO(b/145824437): This is a dup of KotlinMetadataJvmExtensionUtils$KmFunctionProcessor
   class KmFunctionProcessor {
     // Custom name via @JvmName("..."). Otherwise, null.
-    private JvmMethodSignature signature = null;
+    JvmMethodSignature signature = null;
 
     KmFunctionProcessor(KmFunction kmFunction) {
       kmFunction.accept(new KmFunctionVisitor() {
@@ -93,26 +98,28 @@
         }
       });
     }
-
-    JvmMethodSignature signature() {
-      return signature;
-    }
   }
 
   default KmFunctionSubject kmFunctionOrExtensionWithUniqueName(String name, boolean isExtension) {
+    KmFunction foundFunction = null;
     for (KmFunction kmFunction : getKmDeclarationContainer().getFunctions()) {
       if (KmFunctionSubject.isExtension(kmFunction) != isExtension) {
         continue;
       }
       if (kmFunction.getName().equals(name)) {
-        return new FoundKmFunctionSubject(codeInspector(), kmFunction);
+        foundFunction = kmFunction;
+        break;
       }
       KmFunctionProcessor kmFunctionProcessor = new KmFunctionProcessor(kmFunction);
-      if (kmFunctionProcessor.signature() != null
-          && kmFunctionProcessor.signature().getName().equals(name)) {
-        return new FoundKmFunctionSubject(codeInspector(), kmFunction);
+      if (kmFunctionProcessor.signature != null
+          && kmFunctionProcessor.signature.getName().equals(name)) {
+        foundFunction = kmFunction;
+        break;
       }
     }
+    if (foundFunction != null) {
+      return new FoundKmFunctionSubject(codeInspector(), foundFunction);
+    }
     return new AbsentKmFunctionSubject();
   }
 
@@ -159,6 +166,91 @@
         .collect(Collectors.toList());
   }
 
+  // TODO(b/145824437): This is a dup of KotlinMetadataJvmExtensionUtils$KmPropertyProcessor
+  static class KmPropertyProcessor {
+    JvmFieldSignature fieldSignature = null;
+    // Custom getter via @get:JvmName("..."). Otherwise, null.
+    JvmMethodSignature getterSignature = null;
+    // Custom getter via @set:JvmName("..."). Otherwise, null.
+    JvmMethodSignature setterSignature = null;
+
+    KmPropertyProcessor(KmProperty kmProperty) {
+      kmProperty.accept(new KmPropertyVisitor() {
+        @Override
+        public KmPropertyExtensionVisitor visitExtensions(KmExtensionType type) {
+          if (type != JvmPropertyExtensionVisitor.TYPE) {
+            return null;
+          }
+          return new JvmPropertyExtensionVisitor() {
+            @Override
+            public void visit(
+                int flags,
+                JvmFieldSignature fieldDesc,
+                JvmMethodSignature getterDesc,
+                JvmMethodSignature setterDesc) {
+              assert fieldSignature == null : fieldSignature.asString();
+              fieldSignature = fieldDesc;
+              assert getterSignature == null : getterSignature.asString();
+              getterSignature = getterDesc;
+              assert setterSignature == null : setterSignature.asString();
+              setterSignature = setterDesc;
+            }
+          };
+        }
+      });
+    }
+  }
+
+  default KmPropertySubject kmPropertyOrExtensionWithUniqueName(String name, boolean isExtension) {
+    KmProperty foundProperty = null;
+    for (KmProperty kmProperty : getKmDeclarationContainer().getProperties()) {
+      if (KmPropertySubject.isExtension(kmProperty) != isExtension) {
+        continue;
+      }
+      if (kmProperty.getName().equals(name)) {
+        foundProperty = kmProperty;
+        break;
+      }
+      KmPropertyProcessor kmPropertyProcessor = new KmPropertyProcessor(kmProperty);
+      if (kmPropertyProcessor.fieldSignature != null
+          && kmPropertyProcessor.fieldSignature.getName().equals(name)) {
+        foundProperty = kmProperty;
+        break;
+      }
+      if (kmPropertyProcessor.getterSignature != null
+          && kmPropertyProcessor.getterSignature.getName().equals(name)) {
+        foundProperty = kmProperty;
+        break;
+      }
+      if (kmPropertyProcessor.setterSignature != null
+          && kmPropertyProcessor.setterSignature.getName().equals(name)) {
+        foundProperty = kmProperty;
+        break;
+      }
+    }
+    if (foundProperty != null) {
+      return new FoundKmPropertySubject(codeInspector(), foundProperty);
+    }
+    return new AbsentKmPropertySubject();
+  }
+
+  @Override
+  default KmPropertySubject kmPropertyWithUniqueName(String name) {
+    return kmPropertyOrExtensionWithUniqueName(name, false);
+  }
+
+  @Override
+  default KmPropertySubject kmPropertyExtensionWithUniqueName(String name) {
+    return kmPropertyOrExtensionWithUniqueName(name, true);
+  }
+
+  @Override
+  default List<KmPropertySubject> getProperties() {
+    return getKmDeclarationContainer().getProperties().stream()
+        .map(kmProperty -> new FoundKmPropertySubject(codeInspector(), kmProperty))
+        .collect(Collectors.toList());
+  }
+
   @Override
   default List<ClassSubject> getReturnTypesInProperties() {
     return getKmDeclarationContainer().getProperties().stream()
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
index 4ee0f82..7c970c2 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
@@ -3,15 +3,20 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils.codeinspector;
 
+import com.android.tools.r8.utils.codeinspector.FoundKmDeclarationContainerSubject.KmFunctionProcessor;
 import kotlinx.metadata.KmFunction;
+import kotlinx.metadata.jvm.JvmMethodSignature;
 
 public class FoundKmFunctionSubject extends KmFunctionSubject {
   private final CodeInspector codeInspector;
   private final KmFunction kmFunction;
+  private final JvmMethodSignature signature;
 
   FoundKmFunctionSubject(CodeInspector codeInspector, KmFunction kmFunction) {
     this.codeInspector = codeInspector;
     this.kmFunction = kmFunction;
+    KmFunctionProcessor kmFunctionProcessor = new KmFunctionProcessor(kmFunction);
+    this.signature = kmFunctionProcessor.signature;
   }
 
   @Override
@@ -36,4 +41,9 @@
   public boolean isExtension() {
     return isExtension(kmFunction);
   }
+
+  @Override
+  public JvmMethodSignature signature() {
+    return signature;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java
new file mode 100644
index 0000000..4f9cc94
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2020, 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.utils.codeinspector;
+
+import com.android.tools.r8.utils.codeinspector.FoundKmDeclarationContainerSubject.KmPropertyProcessor;
+import kotlinx.metadata.KmProperty;
+import kotlinx.metadata.jvm.JvmFieldSignature;
+import kotlinx.metadata.jvm.JvmMethodSignature;
+
+public class FoundKmPropertySubject extends KmPropertySubject {
+  private final CodeInspector codeInspector;
+  private final KmProperty kmProperty;
+  private final JvmFieldSignature fieldSignature;
+  private final JvmMethodSignature getterSignature;
+  private final JvmMethodSignature setterSignature;
+
+  FoundKmPropertySubject(CodeInspector codeInspector, KmProperty kmProperty) {
+    this.codeInspector = codeInspector;
+    this.kmProperty = kmProperty;
+    KmPropertyProcessor kmPropertyProcessor = new KmPropertyProcessor(kmProperty);
+    this.fieldSignature = kmPropertyProcessor.fieldSignature;
+    this.getterSignature = kmPropertyProcessor.getterSignature;
+    this.setterSignature = kmPropertyProcessor.setterSignature;
+  }
+
+  @Override
+  public boolean isPresent() {
+    return true;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    // TODO(b/70169921): How to determine it is renamed?
+    //   backing field renamed? If no backing field exists, then examine getter/setter?
+    return false;
+  }
+
+  @Override
+  public boolean isSynthetic() {
+    // TODO(b/70169921): This should return `true` conditionally if we start synthesizing @Metadata
+    //   from scratch.
+    return false;
+  }
+
+  @Override
+  public boolean isExtension() {
+    return isExtension(kmProperty);
+  }
+
+  @Override
+  public JvmFieldSignature fieldSignature() {
+    return fieldSignature;
+  }
+
+  @Override
+  public JvmMethodSignature getterSignature() {
+    return getterSignature;
+  }
+
+  @Override
+  public JvmMethodSignature setterSignature() {
+    return setterSignature;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmDeclarationContainerSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmDeclarationContainerSubject.java
index 677ad3d..0893077 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmDeclarationContainerSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmDeclarationContainerSubject.java
@@ -22,5 +22,11 @@
 
   List<ClassSubject> getReturnTypesInFunctions();
 
+  KmPropertySubject kmPropertyWithUniqueName(String name);
+
+  KmPropertySubject kmPropertyExtensionWithUniqueName(String name);
+
+  List<KmPropertySubject> getProperties();
+
   List<ClassSubject> getReturnTypesInProperties();
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java
index ca91912..2e9eeda 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import kotlinx.metadata.KmFunction;
+import kotlinx.metadata.jvm.JvmMethodSignature;
 
 public abstract class KmFunctionSubject extends Subject {
   // TODO(b/145824437): This is a dup of KotlinMetadataSynthesizer#isExtension
@@ -12,4 +13,6 @@
   }
 
   public abstract boolean isExtension();
+
+  public abstract JvmMethodSignature signature();
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmPropertySubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmPropertySubject.java
new file mode 100644
index 0000000..9fb5a0a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmPropertySubject.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2020, 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.utils.codeinspector;
+
+import kotlinx.metadata.KmProperty;
+import kotlinx.metadata.jvm.JvmFieldSignature;
+import kotlinx.metadata.jvm.JvmMethodSignature;
+
+public abstract class KmPropertySubject extends Subject {
+  // TODO(b/145824437): This is a dup of KotlinMetadataSynthesizer#isExtension
+  static boolean isExtension(KmProperty kmProperty) {
+    return kmProperty.getReceiverParameterType() != null;
+  }
+
+  public abstract boolean isExtension();
+
+  public abstract JvmFieldSignature fieldSignature();
+
+  public abstract JvmMethodSignature getterSignature();
+
+  public abstract JvmMethodSignature setterSignature();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index b1c6958..d8d219e 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -65,6 +65,8 @@
       type = "@Metadata.KmPackage";
     } else if (subject instanceof KmFunctionSubject) {
       type = "@Metadata.KmFunction";
+    } else if (subject instanceof KmPropertySubject) {
+      type = "@Metadata.KmProperty";
     }
     return type;
   }
@@ -85,6 +87,8 @@
       name = ((KmPackageSubject) subject).getDexClass().toSourceString();
     } else if (subject instanceof KmFunctionSubject) {
       name = ((KmFunctionSubject) subject).toString();
+    } else if (subject instanceof KmPropertySubject) {
+      name = ((KmPropertySubject) subject).toString();
     }
     return name;
   }
@@ -365,7 +369,7 @@
     };
   }
 
-  public static Matcher<KmFunctionSubject> isExtension() {
+  public static Matcher<KmFunctionSubject> isExtensionFunction() {
     return new TypeSafeMatcher<KmFunctionSubject>() {
       @Override
       protected boolean matchesSafely(KmFunctionSubject kmFunction) {
@@ -388,6 +392,29 @@
     };
   }
 
+  public static Matcher<KmPropertySubject> isExtensionProperty() {
+    return new TypeSafeMatcher<KmPropertySubject>() {
+      @Override
+      protected boolean matchesSafely(KmPropertySubject kmProperty) {
+        return kmProperty.isPresent() && kmProperty.isExtension();
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("is extension property");
+      }
+
+      @Override
+      public void describeMismatchSafely(
+          final KmPropertySubject kmProperty, Description description) {
+        description
+            .appendText("kmProperty ")
+            .appendValue(kmProperty)
+            .appendText(" was not");
+      }
+    };
+  }
+
   public static Matcher<RetraceMethodResult> isInlineFrame() {
     return new TypeSafeMatcher<RetraceMethodResult>() {
       @Override
diff --git a/tools/r8_release.py b/tools/r8_release.py
index a694ae0..f74fd57 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -19,7 +19,7 @@
 import update_prebuilds_in_android
 import utils
 
-R8_DEV_BRANCH = '2.0'
+R8_DEV_BRANCH = '2.1'
 R8_VERSION_FILE = os.path.join(
     'src', 'main', 'java', 'com', 'android', 'tools', 'r8', 'Version.java')
 THIS_FILE_RELATIVE = os.path.join('tools', 'r8_release.py')