Merge commit 'a5cd84a21f27683a45068a86549f441586c922a6' into dev-release

Change-Id: I30e45ebb967331f6c5e64cc83ece3cd760284472
diff --git a/build.gradle b/build.gradle
index 7f2f61d..f7b3a44 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1709,6 +1709,9 @@
     if (project.hasProperty('command_cache_dir')) {
         systemProperty 'command_cache_dir', project.property('command_cache_dir')
     }
+    if (project.hasProperty('command_cache_stats_dir')) {
+        systemProperty 'command_cache_stats_dir', project.property('command_cache_stats_dir')
+    }
     if (project.hasProperty('r8lib')) {
         dependsOn configureTestForR8Lib
         // R8lib should be used instead of the main output and all the tests in
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
index c0412fa..e2d1fd8 100644
--- a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
+++ b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
@@ -207,18 +207,35 @@
 }
 
 fun Project.getJavaHome(jdk : Jdk) : File {
-  // TODO(b/270105162): Make sure this works on other platforms.
-  return getRoot().resolveAll("third_party", "openjdk", jdk.folder, "linux")
+  val os: OperatingSystem = DefaultNativePlatform.getCurrentOperatingSystem()
+  var osFolder = "linux"
+  if (os.isWindows()) {
+    osFolder = "windows"
+  }
+  if (os.isMacOsX) {
+    osFolder = "osx"
+  }
+  return getRoot().resolveAll("third_party", "openjdk", jdk.folder, osFolder)
 }
 
 fun Project.getCompilerPath(jdk : Jdk) : String {
-  // TODO(b/270105162): Make sure this works on other platforms.
-  return getJavaHome(jdk).resolveAll("bin", "javac").toString()
+  val os: OperatingSystem = DefaultNativePlatform.getCurrentOperatingSystem()
+  val binary = if (os.isWindows()) "javac.exe" else "javac"
+  return getJavaHome(jdk).resolveAll("bin", binary).toString()
 }
 
 fun Project.getJavaPath(jdk : Jdk) : String {
-  // TODO(b/270105162): Make sure this works on other platforms.
-  return getJavaHome(jdk).resolveAll("bin", "java").toString()
+  val os: OperatingSystem = DefaultNativePlatform.getCurrentOperatingSystem()
+  val binary = if (os.isWindows()) "java.exe" else "java"
+  return getJavaHome(jdk).resolveAll("bin", binary).toString()
+}
+
+fun Project.getClasspath(vararg paths: File) : String {
+  val os: OperatingSystem = DefaultNativePlatform.getCurrentOperatingSystem()
+  assert (!paths.isEmpty())
+  val separator = if (os.isWindows()) ";"  else ":"
+  var classpath = paths.joinToString(separator = separator) { it -> it.toString() }
+  return classpath
 }
 
 fun Project.baseCompilerCommandLine(
@@ -231,7 +248,7 @@
     "-Xmx8g",
     "-ea",
     "-cp",
-    "$jar:$deps",
+    getClasspath(jar, deps),
     "com.android.tools.r8.SwissArmyKnife",
     compiler) + args
 }
@@ -349,6 +366,11 @@
     Paths.get("third_party", "chrome", "clank_google3_prebuilt").toFile(),
     Paths.get("third_party", "chrome", "clank_google3_prebuilt.tar.gz.sha1").toFile(),
     DependencyType.X20)
+  val chrome = ThirdPartyDependency(
+    "chrome",
+    Paths.get("third_party", "chrome", "chrome_200430").toFile(),
+    Paths.get("third_party", "chrome", "chrome_200430.tar.gz.sha1").toFile(),
+    DependencyType.X20)
   val compilerApi = ThirdPartyDependency(
     "compiler-api",
     Paths.get(
@@ -420,6 +442,10 @@
     "google-java-format",
     Paths.get("third_party", "google-java-format").toFile(),
     Paths.get("third_party", "google-java-format.tar.gz.sha1").toFile())
+  val googleJavaFormat_1_14 = ThirdPartyDependency(
+    "google-java-format-1.14",
+    Paths.get("third_party", "google", "google-java-format", "1.14.0").toFile(),
+    Paths.get("third_party", "google", "google-java-format", "1.14.0.tar.gz.sha1").toFile())
   val gson = ThirdPartyDependency(
     "gson",
     Paths.get("third_party", "gson", "gson-2.10.1").toFile(),
@@ -468,6 +494,10 @@
     "kotlinR8TestResources",
     Paths.get("third_party", "kotlinR8TestResources").toFile(),
     Paths.get("third_party", "kotlinR8TestResources.tar.gz.sha1").toFile())
+  val kotlinxCoroutines = ThirdPartyDependency(
+    "kotlinx-coroutines-1.3.6",
+    Paths.get("third_party", "kotlinx-coroutines-1.3.6").toFile(),
+    Paths.get("third_party", "kotlinx-coroutines-1.3.6.tar.gz.sha1").toFile())
   val multidex = ThirdPartyDependency(
     "multidex",
     Paths.get("third_party", "multidex").toFile(),
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/TestConfigurationHelper.kt b/d8_r8/commonBuildSrc/src/main/kotlin/TestConfigurationHelper.kt
index 9a8c17f..943feea 100644
--- a/d8_r8/commonBuildSrc/src/main/kotlin/TestConfigurationHelper.kt
+++ b/d8_r8/commonBuildSrc/src/main/kotlin/TestConfigurationHelper.kt
@@ -90,7 +90,8 @@
         "desugar_jdk_json_dir",
         "desugar_jdk_libs",
         "test_dir",
-        "command_cache_dir").forEach {
+        "command_cache_dir",
+        "command_cache_stats_dir").forEach {
         val propertyName = it
         if (project.hasProperty(propertyName)) {
           project.property(propertyName)?.let { v -> test.systemProperty(propertyName, v) }
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 4a8b427..a5fe760 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -343,6 +343,10 @@
     return abstractValueParameterJoiner;
   }
 
+  public void clearMethodResolutionOptimizationInfoCollection() {
+    methodResolutionOptimizationInfoCollection = MethodResolutionOptimizationInfoCollection.empty();
+  }
+
   public MethodResolutionOptimizationInfoCollection
       getMethodResolutionOptimizationInfoCollection() {
     return methodResolutionOptimizationInfoCollection;
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 d4db53d..87740a8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -621,6 +621,12 @@
     return accessFlags.isSynthetic();
   }
 
+  /** Returns true if this method is synthetic and a bridge method. */
+  public boolean isSyntheticBridgeMethod() {
+    checkIfObsolete();
+    return accessFlags.isSynthetic() && accessFlags.isBridge();
+  }
+
   public boolean belongsToDirectPool() {
     return accessFlags.belongsToDirectPool();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index 0d5f834..38b5634 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -460,11 +460,6 @@
     public DexValueNumber asDexValueNumber() {
       return this;
     }
-
-    @Override
-    public AbstractValue toAbstractValue(AbstractValueFactory factory) {
-      return factory.createSingleNumberValue(getRawValue());
-    }
   }
 
   public static class DexValueByte extends DexValueNumber {
@@ -537,6 +532,11 @@
     }
 
     @Override
+    public AbstractValue toAbstractValue(AbstractValueFactory factory) {
+      return factory.createSingleNumberValue(getRawValue(), TypeElement.getByte());
+    }
+
+    @Override
     public int hashCode() {
       return value * 3;
     }
@@ -629,6 +629,11 @@
     }
 
     @Override
+    public AbstractValue toAbstractValue(AbstractValueFactory factory) {
+      return factory.createSingleNumberValue(getRawValue(), TypeElement.getShort());
+    }
+
+    @Override
     public int hashCode() {
       return value * 7;
     }
@@ -711,6 +716,11 @@
     }
 
     @Override
+    public AbstractValue toAbstractValue(AbstractValueFactory factory) {
+      return factory.createSingleNumberValue(getRawValue(), TypeElement.getChar());
+    }
+
+    @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
       dest.forward(1);
       int length = dest.putUnsignedEncodedValue(value, 2);
@@ -817,6 +827,11 @@
     }
 
     @Override
+    public AbstractValue toAbstractValue(AbstractValueFactory factory) {
+      return factory.createSingleNumberValue(getRawValue(), TypeElement.getInt());
+    }
+
+    @Override
     public int hashCode() {
       return value * 11;
     }
@@ -909,6 +924,11 @@
     }
 
     @Override
+    public AbstractValue toAbstractValue(AbstractValueFactory factory) {
+      return factory.createSingleNumberValue(getRawValue(), TypeElement.getLong());
+    }
+
+    @Override
     public int hashCode() {
       return (int) value * 13;
     }
@@ -1011,6 +1031,11 @@
     }
 
     @Override
+    public AbstractValue toAbstractValue(AbstractValueFactory factory) {
+      return factory.createSingleNumberValue(getRawValue(), TypeElement.getFloat());
+    }
+
+    @Override
     public int hashCode() {
       return (int) (value * 19);
     }
@@ -1109,6 +1134,11 @@
     }
 
     @Override
+    public AbstractValue toAbstractValue(AbstractValueFactory factory) {
+      return factory.createSingleNumberValue(getRawValue(), TypeElement.getDouble());
+    }
+
+    @Override
     public int hashCode() {
       return (int) (value * 29);
     }
@@ -1853,6 +1883,11 @@
     }
 
     @Override
+    public AbstractValue toAbstractValue(AbstractValueFactory factory) {
+      return factory.createUncheckedNullValue();
+    }
+
+    @Override
     public int hashCode() {
       return 42;
     }
@@ -1949,6 +1984,11 @@
     }
 
     @Override
+    public AbstractValue toAbstractValue(AbstractValueFactory factory) {
+      return factory.createSingleNumberValue(getRawValue(), TypeElement.getBoolean());
+    }
+
+    @Override
     public int hashCode() {
       return value ? 1234 : 4321;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index 6ef6ae4..53e4ddc 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -116,7 +116,7 @@
     getDefinition().setApiLevelForCode(appView.computedMinApiLevel());
     setCode(ThrowNullCode.get(), appView);
     getSimpleFeedback().markProcessed(getDefinition(), ConstraintWithTarget.ALWAYS);
-    getSimpleFeedback().unsetOptimizationInfoForThrowNullMethod(this);
+    getSimpleFeedback().unsetOptimizationInfoForThrowNullMethod(appView, this);
   }
 
   public void registerCodeReferences(UseRegistry<?> registry) {
diff --git a/src/main/java/com/android/tools/r8/graph/proto/RemovedArgumentInfo.java b/src/main/java/com/android/tools/r8/graph/proto/RemovedArgumentInfo.java
index 839756a..8d767ad 100644
--- a/src/main/java/com/android/tools/r8/graph/proto/RemovedArgumentInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/proto/RemovedArgumentInfo.java
@@ -88,9 +88,11 @@
   @SuppressWarnings("ReferenceEquality")
   public RemovedArgumentInfo rewrittenWithLens(
       AppView<AppInfoWithLiveness> appView, GraphLens graphLens, GraphLens codeLens) {
-    SingleValue rewrittenSingleValue =
-        hasSingleValue() ? singleValue.rewrittenWithLens(appView, graphLens, codeLens) : null;
     DexType rewrittenType = graphLens.lookupType(type, codeLens);
+    SingleValue rewrittenSingleValue =
+        hasSingleValue()
+            ? singleValue.rewrittenWithLens(appView, rewrittenType, graphLens, codeLens)
+            : null;
     if (rewrittenSingleValue != singleValue || rewrittenType != type) {
       return new RemovedArgumentInfo(rewrittenSingleValue, rewrittenType);
     }
diff --git a/src/main/java/com/android/tools/r8/graph/proto/RemovedReceiverInfo.java b/src/main/java/com/android/tools/r8/graph/proto/RemovedReceiverInfo.java
index 7a1391c..ee7aa41 100644
--- a/src/main/java/com/android/tools/r8/graph/proto/RemovedReceiverInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/proto/RemovedReceiverInfo.java
@@ -26,9 +26,11 @@
   @SuppressWarnings("ReferenceEquality")
   public RemovedReceiverInfo rewrittenWithLens(
       AppView<AppInfoWithLiveness> appView, GraphLens graphLens, GraphLens codeLens) {
-    SingleValue rewrittenSingleValue =
-        hasSingleValue() ? getSingleValue().rewrittenWithLens(appView, graphLens, codeLens) : null;
     DexType rewrittenType = graphLens.lookupType(getType(), codeLens);
+    SingleValue rewrittenSingleValue =
+        hasSingleValue()
+            ? getSingleValue().rewrittenWithLens(appView, rewrittenType, graphLens, codeLens)
+            : null;
     if (rewrittenSingleValue != getSingleValue() || rewrittenType != getType()) {
       return new RemovedReceiverInfo(rewrittenSingleValue, rewrittenType);
     }
diff --git a/src/main/java/com/android/tools/r8/graph/proto/RewrittenPrototypeDescription.java b/src/main/java/com/android/tools/r8/graph/proto/RewrittenPrototypeDescription.java
index cde2daf..af66b41 100644
--- a/src/main/java/com/android/tools/r8/graph/proto/RewrittenPrototypeDescription.java
+++ b/src/main/java/com/android/tools/r8/graph/proto/RewrittenPrototypeDescription.java
@@ -175,7 +175,7 @@
     SingleValue rewrittenSingleValue =
         rewrittenReturnInfo
             .getSingleValue()
-            .rewrittenWithLens(appView, appView.graphLens(), codeLens);
+            .rewrittenWithLens(appView, method.getReturnType(), appView.graphLens(), codeLens);
     assert rewrittenSingleValue.isMaterializableInContext(appView, method);
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/proto/RewrittenTypeInfo.java b/src/main/java/com/android/tools/r8/graph/proto/RewrittenTypeInfo.java
index 8537d1d..fb10019 100644
--- a/src/main/java/com/android/tools/r8/graph/proto/RewrittenTypeInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/proto/RewrittenTypeInfo.java
@@ -97,7 +97,9 @@
     DexType rewrittenCastType = castType != null ? graphLens.lookupType(castType, codeLens) : null;
     DexType rewrittenNewType = graphLens.lookupType(newType, codeLens);
     SingleValue rewrittenSingleValue =
-        hasSingleValue() ? getSingleValue().rewrittenWithLens(appView, graphLens, codeLens) : null;
+        hasSingleValue()
+            ? getSingleValue().rewrittenWithLens(appView, rewrittenNewType, graphLens, codeLens)
+            : null;
     if (rewrittenCastType != castType
         || rewrittenNewType != newType
         || rewrittenSingleValue != singleValue) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index 507396f..06117ec 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -134,7 +134,10 @@
         newMethodReference.withName("$r8$clinit$synthetic", dexItemFactory);
     lensBuilder.recordNewMethodSignature(syntheticMethodReference, newMethodReference, true);
 
-    ComputedApiLevel apiReferenceLevel = classInitializerMerger.getApiReferenceLevel(appView);
+    ComputedApiLevel apiReferenceLevel =
+        appView.options().apiModelingOptions().isApiLibraryModelingEnabled()
+            ? classInitializerMerger.getApiReferenceLevel(appView)
+            : ComputedApiLevel.notSet();
     DexEncodedMethod definition =
         DexEncodedMethod.syntheticBuilder()
             .setMethod(newMethodReference)
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteMergedInstanceInitializerCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteMergedInstanceInitializerCode.java
index 15f0d57..a77873f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteMergedInstanceInitializerCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteMergedInstanceInitializerCode.java
@@ -254,17 +254,16 @@
           new CfDexItemBasedConstString(
               dexItemBasedStringValue.getItem(), dexItemBasedStringValue.getNameComputationInfo()));
       return 1;
+    } else if (singleConstValue.isNull()) {
+      assert type.isReferenceType();
+      instructionBuilder.add(new CfConstNull());
+      return 1;
     } else if (singleConstValue.isSingleNumberValue()) {
-      if (type.isReferenceType()) {
-        assert singleConstValue.isNull();
-        instructionBuilder.add(new CfConstNull());
-        return 1;
-      } else {
-        instructionBuilder.add(
-            new CfConstNumber(
-                singleConstValue.asSingleNumberValue().getValue(), ValueType.fromDexType(type)));
-        return type.getRequiredRegisters();
-      }
+      assert type.isPrimitiveType();
+      instructionBuilder.add(
+          new CfConstNumber(
+              singleConstValue.asSingleNumberValue().getValue(), ValueType.fromDexType(type)));
+      return type.getRequiredRegisters();
     } else {
       assert singleConstValue.isSingleStringValue();
       instructionBuilder.add(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
index 583d6be..29db86f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstancePut;
@@ -193,9 +194,13 @@
           .createArgumentInitializationInfo(root.getDefinition().asArgument().getIndex());
     }
     if (definition.isConstNumber()) {
-      return appView
-          .abstractValueFactory()
-          .createSingleNumberValue(definition.asConstNumber().getRawValue());
+      AbstractValueFactory abstractValueFactory = appView.abstractValueFactory();
+      if (definition.getOutType().isReferenceType()) {
+        return abstractValueFactory.createNullValue(definition.getOutType());
+      } else {
+        return abstractValueFactory.createSingleNumberValue(
+            definition.asConstNumber().getRawValue(), definition.getOutType());
+      }
     }
     if (definition.isConstString()) {
       return appView
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
index 49ecbf0..712fa0d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -197,7 +197,9 @@
     // value for fields that are not definitely null, we need to prove that the given field is never
     // read before it is written.
     AbstractValue abstractValue =
-        value.isZero() ? abstractValueFactory.createZeroValue() : AbstractValue.unknown();
+        value.isZero()
+            ? abstractValueFactory.createDefaultValue(field.getType())
+            : AbstractValue.unknown();
     fieldStates.compute(
         field.getDefinition(),
         (f, fieldState) -> {
@@ -328,7 +330,8 @@
   private void recordAllFieldPutsProcessed(
       ProgramField field, OptimizationFeedbackDelayed feedback) {
     FieldState fieldState = fieldStates.getOrDefault(field.getDefinition(), FieldState.bottom());
-    AbstractValue abstractValue = fieldState.getAbstractValue(appView.abstractValueFactory());
+    AbstractValue abstractValue =
+        fieldState.getAbstractValue(appView.abstractValueFactory(), field);
     if (abstractValue.isNonTrivial()) {
       feedback.recordFieldHasAbstractValue(field.getDefinition(), appView, abstractValue);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index 5f8e465..2161735 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -119,7 +119,7 @@
     markFieldAsDead(field);
     getSimpleFeedback()
         .recordFieldHasAbstractValue(
-            field, appView, appView.abstractValueFactory().createNullValue());
+            field, appView, appView.abstractValueFactory().createDefaultValue(field.getType()));
   }
 
   private void markFieldAsDead(DexEncodedField field) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/BottomFieldState.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/BottomFieldState.java
index c3474af..8a67c43 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/BottomFieldState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/BottomFieldState.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.analysis.fieldaccess.state;
 
+import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 
@@ -19,8 +20,9 @@
   }
 
   @Override
-  public AbstractValue getAbstractValue(AbstractValueFactory abstractValueFactory) {
-    return abstractValueFactory.createNullValue();
+  public AbstractValue getAbstractValue(
+      AbstractValueFactory abstractValueFactory, ProgramField field) {
+    return abstractValueFactory.createDefaultValue(field.getType());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcretePrimitiveTypeFieldState.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcretePrimitiveTypeFieldState.java
index 1f329f5..d8759cf 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcretePrimitiveTypeFieldState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcretePrimitiveTypeFieldState.java
@@ -26,7 +26,8 @@
   }
 
   @Override
-  public AbstractValue getAbstractValue(AbstractValueFactory abstractValueFactory) {
+  public AbstractValue getAbstractValue(
+      AbstractValueFactory abstractValueFactory, ProgramField field) {
     return abstractValue;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteReferenceTypeFieldState.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteReferenceTypeFieldState.java
index 64d0674..4021c55 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteReferenceTypeFieldState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteReferenceTypeFieldState.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.analysis.fieldaccess.state;
 
+import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 
@@ -17,7 +18,8 @@
   }
 
   @Override
-  public AbstractValue getAbstractValue(AbstractValueFactory abstractValueFactory) {
+  public AbstractValue getAbstractValue(
+      AbstractValueFactory abstractValueFactory, ProgramField field) {
     return abstractValue;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/FieldState.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/FieldState.java
index 6e0b203..bcf3716 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/FieldState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/FieldState.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.analysis.fieldaccess.state;
 
+import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 
@@ -18,7 +19,8 @@
     return UnknownFieldState.getInstance();
   }
 
-  public abstract AbstractValue getAbstractValue(AbstractValueFactory abstractValueFactory);
+  public abstract AbstractValue getAbstractValue(
+      AbstractValueFactory abstractValueFactory, ProgramField field);
 
   public boolean isArray() {
     return false;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/UnknownFieldState.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/UnknownFieldState.java
index e408f09..bf65dea 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/UnknownFieldState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/UnknownFieldState.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.analysis.fieldaccess.state;
 
+import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 
@@ -19,7 +20,8 @@
   }
 
   @Override
-  public AbstractValue getAbstractValue(AbstractValueFactory abstractValueFactory) {
+  public AbstractValue getAbstractValue(
+      AbstractValueFactory abstractValueFactory, ProgramField field) {
     return AbstractValue.unknown();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index e8c03c3..468274c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -99,16 +99,8 @@
           }
 
           AbstractValueFactory factory = appView.abstractValueFactory();
-          if (value.isDexValueNumber()) {
-            feedback.recordFieldHasAbstractValue(
-                field,
-                appView,
-                factory.createSingleNumberValue(value.asDexValueNumber().getRawValue()));
-          } else if (value.isDexValueString()) {
-            feedback.recordFieldHasAbstractValue(
-                field,
-                appView,
-                factory.createSingleStringValue(value.asDexValueString().getValue()));
+          if (value.isDexValueNumber() || value.isDexValueString()) {
+            feedback.recordFieldHasAbstractValue(field, appView, value.toAbstractValue(factory));
           } else if (value.isDexItemBasedValueString()) {
             // TODO(b/150835624): Extend to dex item based const strings.
           } else {
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 f84fb16..a831c7e 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
@@ -6,6 +6,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.graph.lens.GraphLens;
 import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -38,8 +39,8 @@
     return false;
   }
 
-  public final boolean isNull() {
-    return isFalse();
+  public boolean isNull() {
+    return false;
   }
 
   public final boolean isZero() {
@@ -197,7 +198,7 @@
   }
 
   public abstract AbstractValue rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens);
+      AppView<AppInfoWithLiveness> appView, DexType newType, GraphLens lens, GraphLens codeLens);
 
   @Override
   public abstract boolean equals(Object o);
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 7b87326..cdd91ef 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
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.objectstate.KnownLengthArrayState;
 import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
 import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
@@ -25,12 +26,17 @@
   private ConcurrentHashMap<Integer, KnownLengthArrayState> knownArrayLengthStates =
       new ConcurrentHashMap<>();
 
+  public SingleConstValue createDefaultValue(DexType type) {
+    assert type.isPrimitiveType() || type.isReferenceType();
+    return type.isPrimitiveType() ? createZeroValue() : createNullValue(type);
+  }
+
   public AbstractValue createDefiniteBitsNumberValue(
       int definitelySetBits, int definitelyUnsetBits) {
     if (definitelySetBits != 0 || definitelyUnsetBits != 0) {
       // If all bits are known, then create a single number value.
       if ((definitelySetBits | definitelyUnsetBits) == ALL_BITS_SET_MASK) {
-        return createSingleNumberValue(definitelySetBits);
+        return createUncheckedSingleNumberValue(definitelySetBits);
       }
       return new DefiniteBitsNumberValue(definitelySetBits, definitelyUnsetBits);
     }
@@ -56,16 +62,31 @@
         : new SingleStatefulFieldValue(field, state);
   }
 
-  public SingleNumberValue createSingleNumberValue(long value) {
+  public SingleNumberValue createSingleNumberValue(long value, TypeElement type) {
+    assert type.isPrimitiveType();
+    return createUncheckedSingleNumberValue(value);
+  }
+
+  public SingleNumberValue createUncheckedSingleNumberValue(long value) {
     return singleNumberValues.computeIfAbsent(value, SingleNumberValue::new);
   }
 
-  public SingleNumberValue createNullValue() {
-    return createSingleNumberValue(0);
+  public SingleNullValue createNullValue(DexType type) {
+    assert type.isReferenceType() : type;
+    return createUncheckedNullValue();
+  }
+
+  public SingleNullValue createNullValue(TypeElement type) {
+    assert type.isReferenceType();
+    return createUncheckedNullValue();
+  }
+
+  public SingleNullValue createUncheckedNullValue() {
+    return SingleNullValue.get();
   }
 
   public SingleNumberValue createZeroValue() {
-    return createSingleNumberValue(0);
+    return createUncheckedSingleNumberValue(0);
   }
 
   public SingleStringValue createSingleStringValue(DexString string) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/BottomValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/BottomValue.java
index 0653a7c..f5875b0 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/BottomValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/BottomValue.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.analysis.value;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
@@ -25,7 +26,7 @@
 
   @Override
   public AbstractValue rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
+      AppView<AppInfoWithLiveness> appView, DexType newType, GraphLens lens, GraphLens codeLens) {
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/DefiniteBitsNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/DefiniteBitsNumberValue.java
index 83ea1a0..778e61b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/DefiniteBitsNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/DefiniteBitsNumberValue.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.analysis.value;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.OptionalBool;
@@ -114,7 +115,7 @@
 
   @Override
   public AbstractValue rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
+      AppView<AppInfoWithLiveness> appView, DexType newType, GraphLens lens, GraphLens codeLens) {
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/NullOrAbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/NullOrAbstractValue.java
index 875943f..6eaf130 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/NullOrAbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/NullOrAbstractValue.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.analysis.value;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
@@ -44,8 +45,8 @@
 
   @Override
   public NullOrAbstractValue rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
-    return new NullOrAbstractValue(value.rewrittenWithLens(appView, lens, codeLens));
+      AppView<AppInfoWithLiveness> appView, DexType newType, GraphLens lens, GraphLens codeLens) {
+    return new NullOrAbstractValue(value.rewrittenWithLens(appView, newType, lens, codeLens));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java
index 9581c42..161f1a1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.analysis.value;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.OptionalBool;
@@ -87,7 +88,7 @@
 
   @Override
   public AbstractValue rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
+      AppView<AppInfoWithLiveness> appView, DexType newType, GraphLens lens, GraphLens codeLens) {
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromSetValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromSetValue.java
index 91b53e1..6678c17 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromSetValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromSetValue.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.analysis.value;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ArrayUtils;
@@ -103,7 +104,7 @@
 
   @Override
   public AbstractValue rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
+      AppView<AppInfoWithLiveness> appView, DexType newType, GraphLens lens, GraphLens codeLens) {
     return this;
   }
 
@@ -170,7 +171,8 @@
         assert !numbers.isEmpty();
         assert numbers.size() <= MAX_SIZE;
         if (numbers.size() == 1) {
-          return abstractValueFactory.createSingleNumberValue(numbers.iterator().nextInt());
+          return abstractValueFactory.createUncheckedSingleNumberValue(
+              numbers.iterator().nextInt());
         }
         return new NumberFromSetValue(numbers);
       }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
index d5047ac..ebe0e66 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
@@ -128,7 +128,7 @@
   @Override
   @SuppressWarnings("ReferenceEquality")
   public SingleValue rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
+      AppView<AppInfoWithLiveness> appView, DexType newType, GraphLens lens, GraphLens codeLens) {
     assert lens.lookupType(type, codeLens) == type;
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
index c5f258a..e75ea41 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
@@ -116,7 +117,7 @@
 
   @Override
   public SingleValue rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
+      AppView<AppInfoWithLiveness> appView, DexType newType, GraphLens lens, GraphLens codeLens) {
     return appView
         .abstractValueFactory()
         .createSingleDexItemBasedStringValue(
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
index 52f0af1..05d232e 100644
--- 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
@@ -144,11 +144,12 @@
   @Override
   @SuppressWarnings("ReferenceEquality")
   public SingleValue rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
+      AppView<AppInfoWithLiveness> appView, DexType newType, GraphLens lens, GraphLens codeLens) {
     AbstractValueFactory factory = appView.abstractValueFactory();
     EnumDataMap enumDataMap = appView.unboxedEnums();
     if (enumDataMap.hasUnboxedValueFor(field)) {
-      return factory.createSingleNumberValue(enumDataMap.getUnboxedValue(field));
+      return factory.createSingleNumberValue(
+          enumDataMap.getUnboxedValue(field), TypeElement.getInt());
     }
     DexField rewrittenField = lens.lookupField(field, codeLens);
     ObjectState rewrittenObjectState = getObjectState().rewrittenWithLens(appView, lens, codeLens);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNullValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNullValue.java
new file mode 100644
index 0000000..eb49ec6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNullValue.java
@@ -0,0 +1,91 @@
+// Copyright (c) 2023, 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 com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.NumberGenerator;
+import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class SingleNullValue extends SingleConstValue {
+
+  private static final SingleNullValue INSTANCE = new SingleNullValue();
+
+  /** Intentionally package private, use {@link AbstractValueFactory} instead. */
+  SingleNullValue() {}
+
+  static SingleNullValue get() {
+    return INSTANCE;
+  }
+
+  @Override
+  public boolean isNull() {
+    return true;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    return this == o;
+  }
+
+  @Override
+  public int hashCode() {
+    return System.identityHashCode(this);
+  }
+
+  @Override
+  public String toString() {
+    return "SingleNullValue";
+  }
+
+  @Override
+  public Instruction createMaterializingInstruction(
+      AppView<?> appView,
+      ProgramMethod context,
+      NumberGenerator valueNumberGenerator,
+      TypeAndLocalInfoSupplier info) {
+    assert info.getOutType().isReferenceType() : info.getOutType();
+    DebugLocalInfo localInfo = info.getLocalInfo();
+    Value returnedValue = new Value(valueNumberGenerator.next(), TypeElement.getNull(), localInfo);
+    return new ConstNumber(returnedValue, 0);
+  }
+
+  @Override
+  boolean internalIsMaterializableInContext(
+      AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
+    return true;
+  }
+
+  @Override
+  public boolean isMaterializableInAllContexts(AppView<AppInfoWithLiveness> appView) {
+    return true;
+  }
+
+  @Override
+  public InstanceFieldInitializationInfo fixupAfterParametersChanged(
+      ArgumentInfoCollection argumentInfoCollection) {
+    return this;
+  }
+
+  @Override
+  public SingleValue rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, DexType newType, GraphLens lens, GraphLens codeLens) {
+    if (newType.isIntType()) {
+      // Enum unboxing.
+      return appView.abstractValueFactory().createZeroValue();
+    }
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
index 0708da5..c76a0db 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
@@ -154,14 +155,10 @@
       ProgramMethod context,
       NumberGenerator valueNumberGenerator,
       TypeAndLocalInfoSupplier info) {
-    TypeElement typeLattice = info.getOutType();
-    DebugLocalInfo debugLocalInfo = info.getLocalInfo();
-    assert !typeLattice.isReferenceType() || value == 0;
-    Value returnedValue =
-        new Value(
-            valueNumberGenerator.next(),
-            typeLattice.isReferenceType() ? TypeElement.getNull() : typeLattice,
-            debugLocalInfo);
+    TypeElement type = info.getOutType();
+    assert type.isPrimitiveType() : type;
+    DebugLocalInfo localInfo = info.getLocalInfo();
+    Value returnedValue = new Value(valueNumberGenerator.next(), type, localInfo);
     return new ConstNumber(returnedValue, value);
   }
 
@@ -184,7 +181,7 @@
 
   @Override
   public SingleValue rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
+      AppView<AppInfoWithLiveness> appView, DexType newType, GraphLens lens, GraphLens codeLens) {
     return this;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
index 78e547c..ff503f2 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
@@ -102,7 +103,7 @@
 
   @Override
   public SingleValue rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
+      AppView<AppInfoWithLiveness> appView, DexType newType, GraphLens lens, GraphLens codeLens) {
     return this;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
index 51bc690..ba1dab9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.ir.code.IRCode;
@@ -63,5 +64,5 @@
 
   @Override
   public abstract SingleValue rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens);
+      AppView<AppInfoWithLiveness> appView, DexType newType, GraphLens lens, GraphLens codeLens);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/StatefulObjectValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/StatefulObjectValue.java
index 25deb94..e735de5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/StatefulObjectValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/StatefulObjectValue.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.ir.analysis.value;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -51,7 +52,7 @@
 
   @Override
   public AbstractValue rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
+      AppView<AppInfoWithLiveness> appView, DexType newType, GraphLens lens, GraphLens codeLens) {
     return create(getObjectState().rewrittenWithLens(appView, lens, codeLens));
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/UnknownValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/UnknownValue.java
index e2ef4e0..990afee 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/UnknownValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/UnknownValue.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.analysis.value;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
@@ -30,7 +31,7 @@
 
   @Override
   public AbstractValue rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
+      AppView<AppInfoWithLiveness> appView, DexType newType, GraphLens lens, GraphLens codeLens) {
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java
index e5f5b5e..3b73b9d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java
@@ -46,10 +46,12 @@
       AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
     Map<DexField, AbstractValue> rewrittenState = new IdentityHashMap<>();
     state.forEach(
-        (field, value) ->
-            rewrittenState.put(
-                lens.lookupField(field, codeLens),
-                value.rewrittenWithLens(appView, lens, codeLens)));
+        (field, value) -> {
+          DexField rewrittenField = lens.lookupField(field, codeLens);
+          rewrittenState.put(
+              rewrittenField,
+              value.rewrittenWithLens(appView, rewrittenField.getType(), lens, codeLens));
+        });
     return new NonEmptyObjectState(rewrittenState);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/And.java b/src/main/java/com/android/tools/r8/ir/code/And.java
index ef15dfa..03085c1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/And.java
+++ b/src/main/java/com/android/tools/r8/ir/code/And.java
@@ -109,7 +109,7 @@
       int result =
           foldIntegers(
               left.asSingleNumberValue().getIntValue(), right.asSingleNumberValue().getIntValue());
-      return appView.abstractValueFactory().createSingleNumberValue(result);
+      return appView.abstractValueFactory().createSingleNumberValue(result, getOutType());
     }
     if (left.hasDefinitelySetAndUnsetBitsInformation()
         && right.hasDefinitelySetAndUnsetBitsInformation()) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java b/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
index a926e50..654aff1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
@@ -154,7 +154,7 @@
         double result = foldDouble(leftConst.getDoubleValue(), rightConst.getDoubleValue());
         newConst = Double.doubleToLongBits(result);
       }
-      return appView.abstractValueFactory().createSingleNumberValue(newConst);
+      return appView.abstractValueFactory().createSingleNumberValue(newConst, getOutType());
     }
     return AbstractValue.unknown();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Cmp.java b/src/main/java/com/android/tools/r8/ir/code/Cmp.java
index 5c64fc6..fae64e1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Cmp.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Cmp.java
@@ -191,7 +191,7 @@
           result = (int) Math.signum(left - right);
         }
       }
-      return appView.abstractValueFactory().createSingleNumberValue(result);
+      return appView.abstractValueFactory().createSingleNumberValue(result, getOutType());
     } else if (leftAbstractValue.isConstantOrNonConstantNumberValue()
         && rightAbstractValue.isConstantOrNonConstantNumberValue()) {
       return buildLatticeResult(
@@ -212,7 +212,7 @@
     // Use min value as representative when values cannot overlap.
     int result =
         Integer.signum(Long.compare(leftRange.getMinInclusive(), rightRange.getMinInclusive()));
-    return appView.abstractValueFactory().createSingleNumberValue(result);
+    return appView.abstractValueFactory().createSingleNumberValue(result, getOutType());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index 72b2952..55f4136 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.lightir.LirBuilder;
@@ -337,7 +338,10 @@
     if (outValue.hasLocalInfo()) {
       return AbstractValue.unknown();
     }
-    return appView.abstractValueFactory().createSingleNumberValue(value);
+    AbstractValueFactory factory = appView.abstractValueFactory();
+    return getOutType().isReferenceType()
+        ? factory.createNullValue(getOutType())
+        : factory.createSingleNumberValue(value, getOutType());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index fa5469f..aad507a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -17,6 +18,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.LookupResult;
 import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
@@ -30,6 +32,7 @@
 import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -37,7 +40,6 @@
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.ImmutableList;
 import java.util.Arrays;
-import java.util.BitSet;
 import java.util.Collections;
 import java.util.List;
 
@@ -113,6 +115,11 @@
     return this;
   }
 
+  public MethodResolutionResult resolveMethod(
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return appView.appInfo().resolveMethod(method, getInterfaceBit());
+  }
+
   // In subclasses, e.g., invoke-virtual or invoke-super, use a narrower receiver type by using
   // receiver type and calling context---the holder of the method where the current invocation is.
   // TODO(b/140204899): Refactor lookup methods to be defined in a single place.
@@ -266,13 +273,25 @@
 
   @Override
   public boolean throwsNpeIfValueIsNull(Value value, AppView<?> appView, ProgramMethod context) {
+    if (!appView.hasClassHierarchy()) {
+      return false;
+    }
+    AppView<? extends AppInfoWithClassHierarchy> appViewWithClassHierarchy =
+        appView.withClassHierarchy();
+    SingleResolutionResult<?> resolutionResult =
+        resolveMethod(appViewWithClassHierarchy).asSingleResolution();
+    if (resolutionResult == null) {
+      return false;
+    }
     DexClassAndMethod singleTarget = lookupSingleTarget(appView, context);
-    if (singleTarget != null) {
-      BitSet nonNullParamOrThrow =
-          singleTarget.getDefinition().getOptimizationInfo().getNonNullParamOrThrow();
-      if (nonNullParamOrThrow != null) {
-        int argumentIndex = inValues.indexOf(value);
-        return argumentIndex >= 0 && nonNullParamOrThrow.get(argumentIndex);
+    MethodOptimizationInfo optimizationInfo =
+        resolutionResult.getOptimizationInfo(appView, this, singleTarget);
+    if (optimizationInfo.hasNonNullParamOrThrow()) {
+      for (int argumentIndex = 0; argumentIndex < arguments().size(); argumentIndex++) {
+        if (value == getArgument(argumentIndex)
+            && optimizationInfo.getNonNullParamOrThrow().get(argumentIndex)) {
+          return true;
+        }
       }
     }
     return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 27e709e..2521043 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -250,10 +250,7 @@
         appView.withClassHierarchy();
 
     SingleResolutionResult<?> resolutionResult =
-        appViewWithClassHierarchy
-            .appInfo()
-            .resolveMethodLegacy(getInvokedMethod(), getInterfaceBit())
-            .asSingleResolution();
+        resolveMethod(appViewWithClassHierarchy).asSingleResolution();
     if (resolutionResult == null) {
       return true;
     }
@@ -275,6 +272,12 @@
 
     // Find the target and check if the invoke may have side effects.
     DexClassAndMethod singleTarget = lookupSingleTarget(appView, context);
+    MethodOptimizationInfo optimizationInfo =
+        resolutionResult.getOptimizationInfo(appView, this, singleTarget);
+    if (!optimizationInfo.mayHaveSideEffects(this, appView.options())) {
+      return false;
+    }
+
     if (singleTarget == null) {
       return true;
     }
@@ -291,10 +294,8 @@
       return false;
     }
 
-    DexEncodedMethod singleTargetDefinition = singleTarget.getDefinition();
-    MethodOptimizationInfo optimizationInfo = singleTargetDefinition.getOptimizationInfo();
     if (assumption.canIgnoreInstanceFieldAssignmentsToReceiver()
-        && singleTargetDefinition.isInstanceInitializer()) {
+        && singleTarget.getDefinition().isInstanceInitializer()) {
       assert isInvokeDirect();
       InstanceInitializerInfo initializerInfo =
           optimizationInfo.getInstanceInitializerInfo(asInvokeDirect());
@@ -303,6 +304,6 @@
       }
     }
 
-    return optimizationInfo.mayHaveSideEffects(this, appView.options());
+    return true;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java b/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
index 3bc6933..a197004 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
@@ -132,7 +132,7 @@
         }
         newConst = foldLongs(leftConst.getLongValue(), right);
       }
-      return appView.abstractValueFactory().createSingleNumberValue(newConst);
+      return appView.abstractValueFactory().createSingleNumberValue(newConst, getOutType());
     } else {
       if (type == NumericType.INT) {
         return foldIntegers(leftAbstractValue, rightAbstractValue, appView);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Neg.java b/src/main/java/com/android/tools/r8/ir/code/Neg.java
index 222973a..46b1c59 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Neg.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Neg.java
@@ -103,7 +103,7 @@
         assert type == NumericType.DOUBLE;
         newConst = Double.doubleToLongBits(-sourceConst.getDoubleValue());
       }
-      return appView.abstractValueFactory().createSingleNumberValue(newConst);
+      return appView.abstractValueFactory().createSingleNumberValue(newConst, getOutType());
     }
     return AbstractValue.unknown();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Not.java b/src/main/java/com/android/tools/r8/ir/code/Not.java
index ea07979..483aa87 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Not.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Not.java
@@ -56,7 +56,7 @@
         assert this.type == NumericType.LONG;
         newConst = ~sourceConst.getLongValue();
       }
-      return appView.abstractValueFactory().createSingleNumberValue(newConst);
+      return appView.abstractValueFactory().createSingleNumberValue(newConst, getOutType());
     }
     return AbstractValue.unknown();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Or.java b/src/main/java/com/android/tools/r8/ir/code/Or.java
index 8765cb5..bca7903 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Or.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Or.java
@@ -108,7 +108,7 @@
       int result =
           foldIntegers(
               left.asSingleNumberValue().getIntValue(), right.asSingleNumberValue().getIntValue());
-      return appView.abstractValueFactory().createSingleNumberValue(result);
+      return appView.abstractValueFactory().createSingleNumberValue(result, getOutType());
     }
     if (left.hasDefinitelySetAndUnsetBitsInformation()
         && right.hasDefinitelySetAndUnsetBitsInformation()) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Shl.java b/src/main/java/com/android/tools/r8/ir/code/Shl.java
index 72f8fad..747daf6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Shl.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Shl.java
@@ -102,7 +102,7 @@
     }
     if (left.isSingleNumberValue()) {
       int result = foldIntegers(left.asSingleNumberValue().getIntValue(), rightConst);
-      return appView.abstractValueFactory().createSingleNumberValue(result);
+      return appView.abstractValueFactory().createSingleNumberValue(result, getOutType());
     }
     if (left.hasDefinitelySetAndUnsetBitsInformation() && rightConst > 0) {
       // Shift the known bits and add that we now know that the lowermost n bits are definitely
diff --git a/src/main/java/com/android/tools/r8/ir/code/Shr.java b/src/main/java/com/android/tools/r8/ir/code/Shr.java
index 74ec192..e18a846 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Shr.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Shr.java
@@ -102,7 +102,7 @@
     }
     if (left.isSingleNumberValue()) {
       int result = foldIntegers(left.asSingleNumberValue().getIntValue(), rightConst);
-      return appView.abstractValueFactory().createSingleNumberValue(result);
+      return appView.abstractValueFactory().createSingleNumberValue(result, getOutType());
     }
     if (left.hasDefinitelySetAndUnsetBitsInformation()) {
       return appView
diff --git a/src/main/java/com/android/tools/r8/ir/code/Ushr.java b/src/main/java/com/android/tools/r8/ir/code/Ushr.java
index f93eefc..104b90d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Ushr.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Ushr.java
@@ -108,7 +108,7 @@
     }
     if (left.isSingleNumberValue()) {
       int result = foldIntegers(left.asSingleNumberValue().getIntValue(), rightConst);
-      return appView.abstractValueFactory().createSingleNumberValue(result);
+      return appView.abstractValueFactory().createSingleNumberValue(result, getOutType());
     }
     if (left.hasDefinitelySetAndUnsetBitsInformation() && rightConst > 0) {
       // Shift the known bits information and add that we now know that the uppermost n bits are
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 c0253ac..003b119 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
@@ -867,7 +867,7 @@
     }
 
     if (getType().nullability().isDefinitelyNull()) {
-      return appView.abstractValueFactory().createNullValue();
+      return appView.abstractValueFactory().createNullValue(getType());
     }
 
     Value root = getAliasedValue();
diff --git a/src/main/java/com/android/tools/r8/ir/code/Xor.java b/src/main/java/com/android/tools/r8/ir/code/Xor.java
index a675feb..5116990 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Xor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Xor.java
@@ -102,7 +102,7 @@
       int result =
           foldIntegers(
               left.asSingleNumberValue().getIntValue(), right.asSingleNumberValue().getIntValue());
-      return appView.abstractValueFactory().createSingleNumberValue(result);
+      return appView.abstractValueFactory().createSingleNumberValue(result, getOutType());
     }
     if (left.hasDefinitelySetAndUnsetBitsInformation()
         && right.hasDefinitelySetAndUnsetBitsInformation()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ExtraConstantIntParameter.java b/src/main/java/com/android/tools/r8/ir/conversion/ExtraConstantIntParameter.java
index a725f36..1812e1c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/ExtraConstantIntParameter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ExtraConstantIntParameter.java
@@ -32,7 +32,12 @@
 
   @Override
   public SingleNumberValue getValue(AppView<?> appView) {
-    return appView.abstractValueFactory().createSingleNumberValue(value);
+    return appView.abstractValueFactory().createSingleNumberValue(value, TypeElement.getInt());
+  }
+
+  @Override
+  public boolean isUnused() {
+    return false;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ExtraParameter.java b/src/main/java/com/android/tools/r8/ir/conversion/ExtraParameter.java
index 44452f6..e7fcc11 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/ExtraParameter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ExtraParameter.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
+import com.android.tools.r8.ir.analysis.value.SingleConstValue;
 
 public abstract class ExtraParameter {
 
@@ -16,7 +16,9 @@
 
   public abstract TypeElement getTypeElement(AppView<?> appView, DexType argType);
 
-  public abstract SingleNumberValue getValue(AppView<?> appView);
+  public abstract SingleConstValue getValue(AppView<?> appView);
+
+  public abstract boolean isUnused();
 
   @Override
   public abstract boolean equals(Object obj);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ExtraUnusedIntParameter.java b/src/main/java/com/android/tools/r8/ir/conversion/ExtraUnusedIntParameter.java
new file mode 100644
index 0000000..dc1d09c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ExtraUnusedIntParameter.java
@@ -0,0 +1,44 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.conversion;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
+
+public class ExtraUnusedIntParameter extends ExtraParameter {
+
+  @Override
+  public DexType getType(DexItemFactory dexItemFactory) {
+    return dexItemFactory.intType;
+  }
+
+  @Override
+  public TypeElement getTypeElement(AppView<?> appView, DexType argType) {
+    return TypeElement.getInt();
+  }
+
+  @Override
+  public SingleNumberValue getValue(AppView<?> appView) {
+    return appView.abstractValueFactory().createZeroValue();
+  }
+
+  @Override
+  public boolean isUnused() {
+    return true;
+  }
+
+  @Override
+  @SuppressWarnings("EqualsGetClass")
+  public boolean equals(Object obj) {
+    return obj != null && getClass() == obj.getClass();
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ExtraUnusedNullParameter.java b/src/main/java/com/android/tools/r8/ir/conversion/ExtraUnusedNullParameter.java
index 41f3237..2ef4bcc 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/ExtraUnusedNullParameter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ExtraUnusedNullParameter.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
+import com.android.tools.r8.ir.analysis.value.SingleNullValue;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -20,6 +20,7 @@
   private final DexType type;
 
   public ExtraUnusedNullParameter(DexType type) {
+    assert type.isReferenceType();
     this.type = type;
   }
 
@@ -53,8 +54,13 @@
   }
 
   @Override
-  public SingleNumberValue getValue(AppView<?> appView) {
-    return appView.abstractValueFactory().createNullValue();
+  public SingleNullValue getValue(AppView<?> appView) {
+    return appView.abstractValueFactory().createNullValue(type);
+  }
+
+  @Override
+  public boolean isUnused() {
+    return true;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 63a52e5..a3ca7c5 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -601,7 +601,7 @@
       int newArgumentIndex =
           argumentsInfo.getNewArgumentIndex(argumentIndex, numberOfRemovedArguments);
       DexType extraArgumentType = method.getArgumentType(newArgumentIndex);
-      if (extraParameter instanceof ExtraUnusedNullParameter) {
+      if (extraParameter.isUnused()) {
         addExtraUnusedArgument(extraArgumentType);
       } else {
         addNonThisArgument(register, extraParameter.getTypeElement(appView, extraArgumentType));
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 cd1dbbf..391deb2 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
@@ -64,7 +64,7 @@
 import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
+import com.android.tools.r8.ir.analysis.value.SingleConstValue;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.Assume;
@@ -501,7 +501,7 @@
                   newOutValue = makeOutValue(invoke, code, graphLens, codeLens);
                 }
 
-                Map<SingleNumberValue, Map<DexType, Value>> parameterMap = new IdentityHashMap<>();
+                Map<SingleConstValue, Map<DexType, Value>> parameterMap = new IdentityHashMap<>();
 
                 int extraArgumentIndex =
                     numberOfArguments - prototypeChanges.getExtraParameters().size();
@@ -512,19 +512,19 @@
                       actualTarget.getArgumentType(
                           newExtraArgumentIndex, actualInvokeType.isStatic());
 
-                  SingleNumberValue numberValue = parameter.getValue(appView);
+                  SingleConstValue singleConstValue = parameter.getValue(appView);
 
                   // Try to find an existing constant instruction, otherwise generate a new one.
                   InstructionListIterator finalIterator = iterator;
                   Value value =
                       parameterMap
-                          .computeIfAbsent(numberValue, ignore -> new IdentityHashMap<>())
+                          .computeIfAbsent(singleConstValue, ignore -> new IdentityHashMap<>())
                           .computeIfAbsent(
                               extraArgumentType,
                               ignore -> {
                                 finalIterator.previous();
                                 Instruction instruction =
-                                    numberValue.createMaterializingInstruction(
+                                    singleConstValue.createMaterializingInstruction(
                                         appView,
                                         code,
                                         TypeAndLocalInfoSupplier.create(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 7327760..e5a0728 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -143,10 +143,14 @@
     }
   }
 
-  default void unsetOptimizationInfoForThrowNullMethod(ProgramMethod method) {
-    unsetOptimizationInfoForAbstractMethod(method);
-    methodNeverReturnsNormally(method);
-    setUnusedArguments(
-        method, BitSetUtils.createFilled(true, method.getDefinition().getNumberOfArguments()));
+  default void unsetOptimizationInfoForThrowNullMethod(AppView<?> appView, ProgramMethod method) {
+    if (!appView.hasClassHierarchy() || appView.getKeepInfo(method).isPinned(appView.options())) {
+      assert method.getOptimizationInfo().isDefault();
+    } else {
+      unsetOptimizationInfoForAbstractMethod(method);
+      methodNeverReturnsNormally(method);
+      setUnusedArguments(
+          method, BitSetUtils.createFilled(true, method.getDefinition().getNumberOfArguments()));
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
index cc12b6e..148e7df 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.ir.conversion.passes.FilledNewArrayRewriter;
 import com.android.tools.r8.ir.optimize.ConstantCanonicalizer;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover;
+import com.android.tools.r8.ir.optimize.info.MethodResolutionOptimizationInfoAnalysis;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.lightir.LirCode;
 import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
@@ -165,6 +166,9 @@
 
     outliner.rewriteWithLens();
 
+    MethodResolutionOptimizationInfoAnalysis.run(
+        appView, executorService, postMethodProcessorBuilder);
+
     {
       timing.begin("IR conversion phase 2");
       MethodProcessorEventConsumer eventConsumer =
@@ -197,6 +201,8 @@
       timing.end();
     }
 
+    appView.clearMethodResolutionOptimizationInfoCollection();
+
     enumUnboxer.unsetRewriter();
 
     // All the code that should be impacted by the lenses inserted between phase 1 and phase 2
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java
index f8f6235..b9b34ad 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java
@@ -6,9 +6,11 @@
 
 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.AppView;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers;
@@ -270,18 +272,22 @@
           }
         }
 
-        if (!instruction.isInvokeMethod()) {
+        if (!appView.hasClassHierarchy() || !instruction.isInvokeMethod()) {
           continue;
         }
 
+        AppView<? extends AppInfoWithClassHierarchy> appViewWithClassHierarchy =
+            appView.withClassHierarchy();
         InvokeMethod invoke = instruction.asInvokeMethod();
-        DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
-        if (singleTarget == null) {
+        SingleResolutionResult<?> resolutionResult =
+            invoke.resolveMethod(appViewWithClassHierarchy).asSingleResolution();
+        if (resolutionResult == null) {
           continue;
         }
 
+        DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
         MethodOptimizationInfo optimizationInfo =
-            singleTarget.getDefinition().getOptimizationInfo();
+            resolutionResult.getOptimizationInfo(appViewWithClassHierarchy, invoke, singleTarget);
 
         // If the invoke instruction is a null check, we can remove it.
         boolean isNullCheck = false;
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 8e23208..82314cf 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
@@ -1088,6 +1088,16 @@
     }
 
     private void initializeAndroidQMethodProviders(DexItemFactory factory) {
+      // BigDecimal BigDecimal.stripTrailingZeros()
+      DexType bigDecimal = factory.createType("Ljava/math/BigDecimal;");
+      addProvider(
+          new StatifyingMethodWithForwardingGenerator(
+              factory.createMethod(
+                  bigDecimal, factory.createProto(bigDecimal), "stripTrailingZeros"),
+              BackportedMethods::BigDecimalMethods_stripTrailingZeros,
+              "stripTrailingZeros",
+              bigDecimal));
+
       // void android.drm.DrmManagerClient.close()
       addProvider(
           new InvokeRewriter(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index 7d65007..6c73cde 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -35,6 +35,7 @@
 import com.android.tools.r8.cf.code.CfReturn;
 import com.android.tools.r8.cf.code.CfReturnVoid;
 import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStaticFieldRead;
 import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.cf.code.CfTryCatch;
@@ -78,6 +79,8 @@
     factory.createSynthesizedType("Ljava/lang/reflect/Constructor;");
     factory.createSynthesizedType("Ljava/lang/reflect/InvocationTargetException;");
     factory.createSynthesizedType("Ljava/lang/reflect/Method;");
+    factory.createSynthesizedType("Ljava/math/BigDecimal;");
+    factory.createSynthesizedType("Ljava/math/BigInteger;");
     factory.createSynthesizedType("Ljava/util/AbstractMap$SimpleImmutableEntry;");
     factory.createSynthesizedType("Ljava/util/ArrayList;");
     factory.createSynthesizedType("Ljava/util/Arrays;");
@@ -425,6 +428,69 @@
         ImmutableList.of());
   }
 
+  public static CfCode BigDecimalMethods_stripTrailingZeros(
+      DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        4,
+        1,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Ljava/math/BigDecimal;"),
+                    factory.createProto(factory.intType),
+                    factory.createString("signum")),
+                false),
+            new CfIf(IfType.NE, ValueType.INT, label2),
+            label1,
+            new CfNew(factory.createType("Ljava/math/BigDecimal;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Ljava/math/BigInteger;"),
+                    factory.createType("Ljava/math/BigInteger;"),
+                    factory.createString("ZERO"))),
+            new CfConstNumber(0, ValueType.INT),
+            new CfInvoke(
+                183,
+                factory.createMethod(
+                    factory.createType("Ljava/math/BigDecimal;"),
+                    factory.createProto(
+                        factory.voidType,
+                        factory.createType("Ljava/math/BigInteger;"),
+                        factory.intType),
+                    factory.createString("<init>")),
+                false),
+            new CfReturn(ValueType.OBJECT),
+            label2,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Ljava/math/BigDecimal;"))
+                    })),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Ljava/math/BigDecimal;"),
+                    factory.createProto(factory.createType("Ljava/math/BigDecimal;")),
+                    factory.createString("stripTrailingZeros")),
+                false),
+            new CfReturn(ValueType.OBJECT),
+            label3),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
   public static CfCode BooleanMethods_compare(DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibrarySpecification.java
index de42de1..e0af2eb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibrarySpecification.java
@@ -26,6 +26,8 @@
 
   boolean isLibraryCompilation();
 
+  String getIdentifier();
+
   String getJsonSource();
 
   String getSynthesizedLibraryClassesPackagePrefix();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java
index 325cdb0..fcd5d0a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java
@@ -69,6 +69,7 @@
     return rewritingFlags;
   }
 
+  @Override
   public String getIdentifier() {
     return topLevelFlags.getIdentifier();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java
index 3507445..419c71e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java
@@ -73,6 +73,7 @@
     return topLevelFlags.getSynthesizedLibraryClassesPackagePrefix();
   }
 
+  @Override
   public String getIdentifier() {
     return topLevelFlags.getIdentifier();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java
index f1092d4..f263c2e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java
@@ -4,26 +4,18 @@
 
 package com.android.tools.r8.ir.desugar.desugaredlibrary.lint;
 
+import com.android.tools.r8.ClassFileResourceProvider;
+import com.android.tools.r8.ProgramResourceProvider;
 import com.android.tools.r8.StringResource;
-import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
-import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.ImmutableList;
-import java.io.IOException;
-import java.nio.file.Files;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.Collection;
-import java.util.concurrent.ExecutorService;
 
 public abstract class AbstractGenerateFiles {
 
@@ -38,86 +30,36 @@
           .setAllowReadingEmptyArtProfileProvidersMultipleTimesForTesting(true)
           .getOptions();
 
-  final MachineDesugaredLibrarySpecification desugaredLibrarySpecification;
-  final Path desugaredLibrarySpecificationPath;
-  final Collection<Path> desugaredLibraryImplementation;
+  final DesugaredLibrarySpecification desugaredLibrarySpecification;
+  final StringResource desugaredLibrarySpecificationResource;
+  final Collection<ProgramResourceProvider> desugaredLibraryImplementation;
   final Path output;
-  final Path androidJar;
+  final Collection<ClassFileResourceProvider> androidJar;
 
   AbstractGenerateFiles(
-      String desugarConfigurationPath,
-      String desugarImplementationPath,
-      String output,
-      String androidJarPath)
-      throws Exception {
-    this(
-        Paths.get(desugarConfigurationPath),
-        ImmutableList.of(Paths.get(desugarImplementationPath)),
-        Paths.get(output),
-        Paths.get(androidJarPath));
-  }
-
-  AbstractGenerateFiles(
-      Path desugarConfigurationPath,
-      Collection<Path> desugarImplementationPath,
+      StringResource desugaredLibrarySpecificationResource,
+      Collection<ProgramResourceProvider> desugarImplementation,
       Path output,
-      Path androidJar)
-      throws Exception {
+      Collection<ClassFileResourceProvider> androidJar) {
     assert androidJar != null;
-    this.desugaredLibrarySpecificationPath = desugarConfigurationPath;
-    DesugaredLibrarySpecification specification =
-        readDesugaredLibraryConfiguration(desugarConfigurationPath);
+    this.desugaredLibrarySpecificationResource = desugaredLibrarySpecificationResource;
     this.androidJar = androidJar;
-    DexApplication app = createApp(androidJar, options);
-    this.desugaredLibrarySpecification = specification.toMachineSpecification(app, Timing.empty());
-    this.desugaredLibraryImplementation = desugarImplementationPath;
+    this.desugaredLibrarySpecification = readDesugaredLibrarySpecification();
+    this.desugaredLibraryImplementation = desugarImplementation;
     this.output = output;
   }
 
-  private DesugaredLibrarySpecification readDesugaredLibraryConfiguration(
-      Path desugarConfigurationPath) {
+  private DesugaredLibrarySpecification readDesugaredLibrarySpecification() {
+    if (desugaredLibrarySpecificationResource == null) {
+      return MachineDesugaredLibrarySpecification.empty();
+    }
     return DesugaredLibrarySpecificationParser.parseDesugaredLibrarySpecification(
-        StringResource.fromFile(desugarConfigurationPath),
+        desugaredLibrarySpecificationResource,
         factory,
         reporter,
         false,
         AndroidApiLevel.B.getLevel());
   }
 
-  private static DexApplication createApp(Path androidJar, InternalOptions options)
-      throws IOException {
-    AndroidApp.Builder builder = AndroidApp.builder();
-    AndroidApp inputApp = builder.addLibraryFiles(androidJar).build();
-    ApplicationReader applicationReader = new ApplicationReader(inputApp, options, Timing.empty());
-    ExecutorService executorService = ThreadUtils.getExecutorService(options);
-    assert !options.ignoreJavaLibraryOverride;
-    options.ignoreJavaLibraryOverride = true;
-    DexApplication app = applicationReader.read(executorService);
-    options.ignoreJavaLibraryOverride = false;
-    return app;
-  }
-
   abstract AndroidApiLevel run() throws Exception;
-
-  // TODO(b/289365156): Move this out.
-  private static String getFallBackAndroidJarPath(AndroidApiLevel apiLevel) {
-    String jar =
-        apiLevel == AndroidApiLevel.MASTER
-            ? "third_party/android_jar/lib-master/android.jar"
-            : String.format("third_party/android_jar/lib-v%d/android.jar", apiLevel.getLevel());
-    Path jarPath = Paths.get(jar);
-    if (!Files.exists(jarPath)) {
-      throw new RuntimeException(
-          "Generate files tools should pass a valid recent android.jar as parameter if used outside"
-              + " of the r8 repository. Missing file: "
-              + jarPath);
-    }
-    return jar;
-  }
-
-  static String getAndroidJarPath(String[] args, int fullLength) {
-    return args.length == fullLength
-        ? args[fullLength - 1]
-        : getFallBackAndroidJarPath(MAX_TESTED_ANDROID_API_LEVEL);
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/DesugaredMethodsList.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/DesugaredMethodsList.java
index 0a1a614..c2261d4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/DesugaredMethodsList.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/DesugaredMethodsList.java
@@ -6,37 +6,68 @@
 
 import static java.lang.Integer.parseInt;
 
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ArchiveProgramResourceProvider;
+import com.android.tools.r8.ClassFileResourceProvider;
 import com.android.tools.r8.Keep;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.Version;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
-import java.nio.file.Path;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.Collection;
+import java.util.List;
 
 @Keep
 public class DesugaredMethodsList extends GenerateDesugaredLibraryLintFiles {
 
   private final AndroidApiLevel minApi;
 
-  private DesugaredMethodsList(
+  private final StringConsumer outputConsumer;
+
+  DesugaredMethodsList(
       int minApi,
-      String desugarConfigurationPath,
-      String desugarImplementationPath,
-      String ouputFile,
-      String androidJarPath)
-      throws Exception {
-    super(desugarConfigurationPath, desugarImplementationPath, ouputFile, androidJarPath);
+      StringResource desugarConfiguration,
+      Collection<ProgramResourceProvider> desugarImplementation,
+      StringConsumer outputConsumer,
+      Collection<ClassFileResourceProvider> androidJar) {
+    super(desugarConfiguration, desugarImplementation, null, androidJar);
     this.minApi = AndroidApiLevel.getAndroidApiLevel(minApi);
+    this.outputConsumer = outputConsumer;
+  }
+
+  public static void run(DesugaredMethodsListCommand command) throws IOException {
+    if (command.isHelp()) {
+      System.out.println(DesugaredMethodsListCommand.getUsageMessage());
+      return;
+    }
+    if (command.isVersion()) {
+      System.out.println("DesugaredMethodsList " + Version.getVersionString());
+      return;
+    }
+    new DesugaredMethodsList(
+            command.getMinApi(),
+            command.getDesugarLibrarySpecification(),
+            command.getDesugarLibraryImplementation(),
+            command.getOutputConsumer(),
+            command.getLibrary())
+        .run();
   }
 
   @Override
-  public AndroidApiLevel run() throws Exception {
+  public AndroidApiLevel run() throws IOException {
     AndroidApiLevel compilationLevel =
         desugaredLibrarySpecification.getRequiredCompilationApiLevel();
     SupportedClasses supportedMethods =
         new SupportedClassesGenerator(options, androidJar, minApi, true)
-            .run(desugaredLibraryImplementation, desugaredLibrarySpecificationPath);
+            .run(desugaredLibraryImplementation, desugaredLibrarySpecificationResource);
     System.out.println(
         "Generating lint files for "
-            + desugaredLibrarySpecification.getIdentifier()
+            + getDebugIdentifier()
             + " (compile API "
             + compilationLevel
             + ")");
@@ -45,24 +76,47 @@
   }
 
   @Override
-  Path lintFile(
-      AndroidApiLevel compilationApiLevel, AndroidApiLevel minApiLevel, String extension) {
-    return output;
+  void writeOutput(
+      AndroidApiLevel compilationApiLevel,
+      AndroidApiLevel minApiLevel,
+      List<String> desugaredApisSignatures) {
+    for (String desugaredApisSignature : desugaredApisSignatures) {
+      outputConsumer.accept(desugaredApisSignature, options.reporter);
+    }
+    outputConsumer.finished(options.reporter);
+  }
+
+  private static StringResource getSpecificationArg(String arg) {
+    return arg == null ? null : StringResource.fromFile(Paths.get(arg));
+  }
+
+  private static Collection<ProgramResourceProvider> getImplementationArg(String arg) {
+    if (arg == null) {
+      return ImmutableList.of();
+    }
+    return ImmutableList.of(ArchiveProgramResourceProvider.fromArchive(Paths.get(arg)));
   }
 
   public static void main(String[] args) throws Exception {
     if (args.length == 4 || args.length == 5) {
-      new DesugaredMethodsList(
-              parseInt(args[0]), args[1], args[2], args[3], getAndroidJarPath(args, 5))
-          .run();
+      DesugaredMethodsListCommand.Builder builder =
+          DesugaredMethodsListCommand.builder()
+              .setMinApi(parseInt(args[0]))
+              .setDesugarLibrarySpecification(getSpecificationArg(args[1]))
+              .setOutputPath(Paths.get(args[3]));
+      for (ProgramResourceProvider programResourceProvider : getImplementationArg(args[2])) {
+        builder.addDesugarLibraryImplementation(programResourceProvider);
+      }
+      builder.addLibrary(new ArchiveClassFileProvider(Paths.get(args[4])));
+      DesugaredMethodsList.run(builder.build());
       return;
     }
     throw new RuntimeException(
         StringUtils.joinLines(
             "Invalid invocation.",
             "Usage: DesugaredMethodList <min-api> <desugar configuration> "
-                + "<desugar implementation> <output file> [<android jar path for Android "
+                + "<desugar implementation> <output file> <android jar path for Android "
                 + MAX_TESTED_ANDROID_API_LEVEL
-                + " or higher>]"));
+                + " or higher>"));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/DesugaredMethodsListCommand.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/DesugaredMethodsListCommand.java
new file mode 100644
index 0000000..924a0e6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/DesugaredMethodsListCommand.java
@@ -0,0 +1,253 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.desugaredlibrary.lint;
+
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ArchiveProgramResourceProvider;
+import com.android.tools.r8.ClassFileResourceProvider;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.ParseFlagInfo;
+import com.android.tools.r8.ParseFlagInfoImpl;
+import com.android.tools.r8.ParseFlagPrinter;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@Keep
+public class DesugaredMethodsListCommand {
+
+  private final boolean help;
+  private final boolean version;
+  private final int minApi;
+  private final StringResource desugarLibrarySpecification;
+  private final Collection<ProgramResourceProvider> desugarLibraryImplementation;
+  private final StringConsumer outputConsumer;
+  private final Collection<ClassFileResourceProvider> library;
+
+  DesugaredMethodsListCommand(
+      int minApi,
+      StringResource desugarLibrarySpecification,
+      Collection<ProgramResourceProvider> desugarLibraryImplementation,
+      StringConsumer outputConsumer,
+      Collection<ClassFileResourceProvider> library) {
+    this.help = false;
+    this.version = false;
+    this.minApi = minApi;
+    this.desugarLibrarySpecification = desugarLibrarySpecification;
+    this.desugarLibraryImplementation = desugarLibraryImplementation;
+    this.outputConsumer = outputConsumer;
+    this.library = library;
+  }
+
+  DesugaredMethodsListCommand(boolean help, boolean version) {
+    this.help = help;
+    this.version = version;
+    this.minApi = -1;
+    this.desugarLibrarySpecification = null;
+    this.desugarLibraryImplementation = null;
+    this.outputConsumer = null;
+    this.library = null;
+  }
+
+  public int getMinApi() {
+    return minApi;
+  }
+
+  public StringResource getDesugarLibrarySpecification() {
+    return desugarLibrarySpecification;
+  }
+
+  public Collection<ProgramResourceProvider> getDesugarLibraryImplementation() {
+    return desugarLibraryImplementation;
+  }
+
+  public StringConsumer getOutputConsumer() {
+    return outputConsumer;
+  }
+
+  public Collection<ClassFileResourceProvider> getLibrary() {
+    return library;
+  }
+
+  public boolean isHelp() {
+    return help;
+  }
+
+  public boolean isVersion() {
+    return version;
+  }
+
+  public static String getUsageMessage() {
+    StringBuilder builder = new StringBuilder();
+    StringUtils.appendLines(builder, "Usage: desugaredmethods [options] where  options are:");
+    new ParseFlagPrinter()
+        .addFlags(ImmutableList.copyOf(DesugaredMethodsListCommandParser.getFlags()))
+        .appendLinesToBuilder(builder);
+    return builder.toString();
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Keep
+  public static class Builder {
+
+    private int minApi = AndroidApiLevel.B.getLevel();
+    private StringResource desugarLibrarySpecification = null;
+    private Collection<ProgramResourceProvider> desugarLibraryImplementation = new ArrayList<>();
+    private StringConsumer outputConsumer;
+    private Collection<ClassFileResourceProvider> library = new ArrayList<>();
+
+    private boolean help = false;
+    private boolean version = false;
+
+    public Builder setMinApi(int minApi) {
+      this.minApi = minApi;
+      return this;
+    }
+
+    public Builder setDesugarLibrarySpecification(StringResource desugarLibrarySpecification) {
+      this.desugarLibrarySpecification = desugarLibrarySpecification;
+      return this;
+    }
+
+    public Builder setOutputConsumer(StringConsumer outputConsumer) {
+      this.outputConsumer = outputConsumer;
+      return this;
+    }
+
+    public Builder setOutputPath(Path outputPath) {
+      this.outputConsumer =
+          new StringConsumer.FileConsumer(outputPath) {
+            @Override
+            public void accept(String string, DiagnosticsHandler handler) {
+              super.accept(string, handler);
+              super.accept(System.lineSeparator(), handler);
+            }
+          };
+      return this;
+    }
+
+    public Builder addDesugarLibraryImplementation(
+        ProgramResourceProvider programResourceProvider) {
+      desugarLibraryImplementation.add(programResourceProvider);
+      return this;
+    }
+
+    public Builder addLibrary(ClassFileResourceProvider classFileResourceProvider) {
+      library.add(classFileResourceProvider);
+      return this;
+    }
+
+    public Builder setHelp() {
+      this.help = true;
+      return this;
+    }
+
+    public Builder setVersion() {
+      this.version = true;
+      return this;
+    }
+
+    public DesugaredMethodsListCommand build() {
+      // The min-api level defaults to 1, it's always present.
+      // If desugarLibraryImplementation is empty, this generates only the backported method list.
+
+      if (help || version) {
+        return new DesugaredMethodsListCommand(help, version);
+      }
+
+      if (desugarLibrarySpecification != null && library.isEmpty()) {
+        throw new CompilationError("With desugared library configuration a library is required");
+      }
+
+      if (!desugarLibraryImplementation.isEmpty() && desugarLibrarySpecification == null) {
+        throw new CompilationError(
+            "desugarLibrarySpecification is required when desugared library implementation is"
+                + " present.");
+      }
+
+      if (outputConsumer == null) {
+        outputConsumer =
+            new StringConsumer() {
+              @Override
+              public void accept(String string, DiagnosticsHandler handler) {
+                System.out.println(string);
+              }
+
+              @Override
+              public void finished(DiagnosticsHandler handler) {}
+            };
+      }
+      return new DesugaredMethodsListCommand(
+          minApi,
+          desugarLibrarySpecification,
+          desugarLibraryImplementation,
+          outputConsumer,
+          library);
+    }
+  }
+
+  public static class DesugaredMethodsListCommandParser {
+
+    static List<ParseFlagInfo> getFlags() {
+      return ImmutableList.<ParseFlagInfo>builder()
+          .add(ParseFlagInfoImpl.getOutput())
+          .add(ParseFlagInfoImpl.getLib())
+          .add(ParseFlagInfoImpl.getMinApi())
+          .add(ParseFlagInfoImpl.getVersion("DesugaredMethods"))
+          .add(ParseFlagInfoImpl.getHelp())
+          .add(ParseFlagInfoImpl.getDesugaredLib())
+          .add(
+              ParseFlagInfoImpl.flag1(
+                  "--desugared-lib-jar", "<file>", "Specify desugared library jar."))
+          .build();
+    }
+
+    public DesugaredMethodsListCommand parse(String[] args, Origin origin) throws IOException {
+      DesugaredMethodsListCommand.Builder builder = DesugaredMethodsListCommand.builder();
+      for (int i = 0; i < args.length; i += 2) {
+        String arg = args[i].trim();
+        if (arg.length() == 0) {
+          continue;
+        } else if (arg.equals("--help")) {
+          builder.setHelp();
+          continue;
+        } else if (arg.equals("--version")) {
+          builder.setVersion();
+          continue;
+        }
+        String argValue = args[i + 1].trim();
+        if (arg.equals("--min-api")) {
+          builder.setMinApi(Integer.parseInt(argValue));
+        } else if (arg.equals("--desugared-lib")) {
+          builder.setDesugarLibrarySpecification(StringResource.fromFile(Paths.get(argValue)));
+        } else if (arg.equals("--desugared-lib-jar")) {
+          builder.addDesugarLibraryImplementation(
+              ArchiveProgramResourceProvider.fromArchive(Paths.get(argValue)));
+        } else if (arg.equals("--output")) {
+          builder.setOutputPath(Paths.get(argValue));
+        } else if (arg.equals("--lib")) {
+          builder.addLibrary(new ArchiveClassFileProvider(Paths.get(argValue)));
+        }
+      }
+
+      return builder.build();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateDesugaredLibraryLintFiles.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateDesugaredLibraryLintFiles.java
index 16e9be8..49d979b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateDesugaredLibraryLintFiles.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateDesugaredLibraryLintFiles.java
@@ -4,13 +4,20 @@
 
 package com.android.tools.r8.ir.desugar.desugaredlibrary.lint;
 
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ArchiveProgramResourceProvider;
+import com.android.tools.r8.ClassFileResourceProvider;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.StringResource;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.MethodAnnotation;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
 import java.io.File;
+import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -18,36 +25,18 @@
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.List;
-import java.util.Set;
 
 public class GenerateDesugaredLibraryLintFiles extends AbstractGenerateFiles {
 
   // Only recent versions of studio support the format with fields.
   private static final boolean FORMAT_WITH_FIELD = true;
 
-  public static GenerateDesugaredLibraryLintFiles createForTesting(
-      Path specification, Set<Path> implementation, Path outputDirectory, Path androidJar)
-      throws Exception {
-    return new GenerateDesugaredLibraryLintFiles(
-        specification, implementation, outputDirectory, androidJar);
-  }
-
   public GenerateDesugaredLibraryLintFiles(
-      String desugarConfigurationPath,
-      String desugarImplementationPath,
-      String outputDirectory,
-      String androidJarPath)
-      throws Exception {
-    super(desugarConfigurationPath, desugarImplementationPath, outputDirectory, androidJarPath);
-  }
-
-  GenerateDesugaredLibraryLintFiles(
-      Path desugarConfigurationPath,
-      Collection<Path> desugarImplementationPath,
-      Path outputDirectory,
-      Path androidJar)
-      throws Exception {
-    super(desugarConfigurationPath, desugarImplementationPath, outputDirectory, androidJar);
+      StringResource desugarConfiguration,
+      Collection<ProgramResourceProvider> desugarImplementation,
+      Path output,
+      Collection<ClassFileResourceProvider> androidJar) {
+    super(desugarConfiguration, desugarImplementation, output, androidJar);
   }
 
   private String lintBaseFileName(
@@ -55,8 +44,9 @@
     return "desugared_apis_" + compilationApiLevel.getLevel() + "_" + minApiLevel.getLevel();
   }
 
-  Path lintFile(AndroidApiLevel compilationApiLevel, AndroidApiLevel minApiLevel, String extension)
-      throws Exception {
+  private Path lintFile(
+      AndroidApiLevel compilationApiLevel, AndroidApiLevel minApiLevel, String extension)
+      throws IOException {
     Path directory = output.resolve("compile_api_level_" + compilationApiLevel.getLevel());
     Files.createDirectories(directory);
     return Paths.get(
@@ -70,7 +60,7 @@
       AndroidApiLevel compilationApiLevel,
       AndroidApiLevel minApiLevel,
       SupportedClasses supportedClasses)
-      throws Exception {
+      throws IOException {
     // Build a plain text file with the desugared APIs.
     List<String> desugaredApisSignatures = new ArrayList<>();
 
@@ -118,6 +108,14 @@
 
     // Write a plain text file with the desugared APIs.
     desugaredApisSignatures.sort(Comparator.naturalOrder());
+    writeOutput(compilationApiLevel, minApiLevel, desugaredApisSignatures);
+  }
+
+  void writeOutput(
+      AndroidApiLevel compilationApiLevel,
+      AndroidApiLevel minApiLevel,
+      List<String> desugaredApisSignatures)
+      throws IOException {
     FileUtils.writeTextFile(
         lintFile(compilationApiLevel, minApiLevel, ".txt"), desugaredApisSignatures);
   }
@@ -149,16 +147,22 @@
     writeLintFiles(compilationApiLevel, minApiLevel, supportedMethods);
   }
 
+  String getDebugIdentifier() {
+    return desugaredLibrarySpecification.getIdentifier() == null
+        ? "backported methods only"
+        : desugaredLibrarySpecification.getIdentifier();
+  }
+
   @Override
   public AndroidApiLevel run() throws Exception {
     AndroidApiLevel compilationLevel =
         desugaredLibrarySpecification.getRequiredCompilationApiLevel();
     SupportedClasses supportedMethods =
         new SupportedClassesGenerator(options, androidJar)
-            .run(desugaredLibraryImplementation, desugaredLibrarySpecificationPath);
+            .run(desugaredLibraryImplementation, desugaredLibrarySpecificationResource);
     System.out.println(
         "Generating lint files for "
-            + desugaredLibrarySpecification.getIdentifier()
+            + getDebugIdentifier()
             + " (compile API "
             + compilationLevel
             + ")");
@@ -169,8 +173,12 @@
   }
 
   public static void main(String[] args) throws Exception {
-    if (args.length == 3 || args.length == 4) {
-      new GenerateDesugaredLibraryLintFiles(args[0], args[1], args[2], getAndroidJarPath(args, 4))
+    if (args.length == 4) {
+      new GenerateDesugaredLibraryLintFiles(
+              StringResource.fromFile(Paths.get(args[0])),
+              ImmutableList.of(ArchiveProgramResourceProvider.fromArchive(Paths.get(args[1]))),
+              Paths.get(args[2]),
+              ImmutableList.of(new ArchiveClassFileProvider(Paths.get(args[3]))))
           .run();
       return;
     }
@@ -178,8 +186,8 @@
         StringUtils.joinLines(
             "Invalid invocation.",
             "Usage: GenerateDesugaredLibraryLintFiles <desugar configuration> "
-                + "<desugar implementation> <output directory> [<android jar path for Android "
+                + "<desugar implementation> <output directory> <android jar path for Android "
                 + MAX_TESTED_ANDROID_API_LEVEL
-                + " or higher>]"));
+                + " or higher>"));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java
index 5889b77..4bc0821 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java
@@ -4,6 +4,11 @@
 
 package com.android.tools.r8.ir.desugar.desugaredlibrary.lint;
 
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ArchiveProgramResourceProvider;
+import com.android.tools.r8.ClassFileResourceProvider;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.StringResource;
 import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -19,9 +24,13 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
 import java.io.PrintStream;
 import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
@@ -37,12 +46,11 @@
   private static final String SUP_4 = "<sup>4</sup>";
 
   public GenerateHtmlDoc(
-      String desugarConfigurationPath,
-      String desugarImplementationPath,
-      String outputDirectory,
-      String androidJarPath)
-      throws Exception {
-    super(desugarConfigurationPath, desugarImplementationPath, outputDirectory, androidJarPath);
+      StringResource desugarSpecification,
+      Collection<ProgramResourceProvider> desugarImplementation,
+      Path outputDirectory,
+      Collection<ClassFileResourceProvider> androidJar) {
+    super(desugarSpecification, desugarImplementation, outputDirectory, androidJar);
   }
 
   private static class StringBuilderWithIndent {
@@ -558,7 +566,7 @@
 
     SupportedClasses supportedClasses =
         new SupportedClassesGenerator(options, androidJar)
-            .run(desugaredLibraryImplementation, desugaredLibrarySpecificationPath);
+            .run(desugaredLibraryImplementation, desugaredLibrarySpecificationResource);
 
     // Full classes added.
     supportedClasses.forEachClass(supportedClass -> generateClassHTML(ps, supportedClass));
@@ -567,8 +575,13 @@
 
   public static void main(String[] args) throws Exception {
     if (args[0].equals("--generate-api-docs")) {
-      if (args.length == 4 || args.length == 5) {
-        new GenerateHtmlDoc(args[1], args[2], args[3], getAndroidJarPath(args, 5)).run();
+      if (args.length == 5) {
+        new GenerateHtmlDoc(
+                StringResource.fromFile(Paths.get(args[1])),
+                ImmutableList.of(ArchiveProgramResourceProvider.fromArchive(Paths.get(args[2]))),
+                Paths.get(args[3]),
+                ImmutableList.of(new ArchiveClassFileProvider(Paths.get(args[4]))))
+            .run();
         return;
       }
     }
@@ -576,8 +589,8 @@
         StringUtils.joinLines(
             "Invalid invocation.",
             "Usage: GenerateHtmlDoc --generate-api-docs <desugar configuration> "
-                + "<desugar implementation> <output directory> [<android jar path for Android "
+                + "<desugar implementation> <output directory> <android jar path for Android "
                 + MAX_TESTED_ANDROID_API_LEVEL
-                + " or higher>]"));
+                + " or higher>"));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java
index 12970fc..db8847b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java
@@ -282,6 +282,10 @@
       this.extraMethods = extraMethods;
     }
 
+    public boolean hasOnlyExtraMethods() {
+      return supportedClassBuilders.isEmpty();
+    }
+
     SupportedClasses build() {
       Map<DexType, SupportedClass> map = new IdentityHashMap<>();
       supportedClassBuilders.forEach(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java
index f79e61a..595092b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java
@@ -6,6 +6,8 @@
 
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.lint.AbstractGenerateFiles.MAX_TESTED_ANDROID_API_LEVEL;
 
+import com.android.tools.r8.ClassFileResourceProvider;
+import com.android.tools.r8.ProgramResourceProvider;
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.dex.ApplicationReader;
@@ -44,7 +46,6 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.io.IOException;
-import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Comparator;
@@ -60,15 +61,17 @@
   private final SupportedClasses.Builder builder = SupportedClasses.builder();
   private final boolean addBackports;
 
-  public SupportedClassesGenerator(InternalOptions options, Path androidJar) throws IOException {
-    this.options = options;
-    this.appForMax = createAppForMax(androidJar);
-    this.minApi = AndroidApiLevel.B;
-    this.addBackports = false;
+  public SupportedClassesGenerator(
+      InternalOptions options, Collection<ClassFileResourceProvider> androidJar)
+      throws IOException {
+    this(options, androidJar, AndroidApiLevel.B, false);
   }
 
   public SupportedClassesGenerator(
-      InternalOptions options, Path androidJar, AndroidApiLevel minApi, boolean addBackports)
+      InternalOptions options,
+      Collection<ClassFileResourceProvider> androidJar,
+      AndroidApiLevel minApi,
+      boolean addBackports)
       throws IOException {
     this.options = options;
     this.appForMax = createAppForMax(androidJar);
@@ -76,7 +79,9 @@
     this.addBackports = addBackports;
   }
 
-  public SupportedClasses run(Collection<Path> desugaredLibraryImplementation, Path specification)
+  public SupportedClasses run(
+      Collection<ProgramResourceProvider> desugaredLibraryImplementation,
+      StringResource specification)
       throws IOException {
     // First analyze everything which is supported when desugaring for api 1.
     collectSupportedMembersInMinApi(desugaredLibraryImplementation, specification);
@@ -128,7 +133,10 @@
     return fullySupported;
   }
 
-  private void annotatePartialDesugaringMembers(Path specification) throws IOException {
+  private void annotatePartialDesugaringMembers(StringResource specification) throws IOException {
+    if (builder.hasOnlyExtraMethods()) {
+      return;
+    }
     // The first difference should be at 18 so we're safe starting at J and not B.
     for (int api = AndroidApiLevel.J.getLevel();
         api <= MAX_TESTED_ANDROID_API_LEVEL.getLevel();
@@ -252,7 +260,9 @@
   }
 
   private void collectSupportedMembersInMinApi(
-      Collection<Path> desugaredLibraryImplementation, Path specification) throws IOException {
+      Collection<ProgramResourceProvider> desugaredLibraryImplementation,
+      StringResource specification)
+      throws IOException {
 
     MachineDesugaredLibrarySpecification machineSpecification =
         getMachineSpecification(minApi, specification);
@@ -261,8 +271,11 @@
     options.resetDesugaredLibrarySpecificationForTesting();
     options.setDesugaredLibrarySpecification(machineSpecification);
 
-    AndroidApp implementation =
-        AndroidApp.builder().addProgramFiles(desugaredLibraryImplementation).build();
+    AndroidApp.Builder appBuilder = AndroidApp.builder();
+    for (ProgramResourceProvider programResource : desugaredLibraryImplementation) {
+      appBuilder.addProgramResourceProvider(programResource);
+    }
+    AndroidApp implementation = appBuilder.build();
     DirectMappedDexApplication implementationApplication =
         new ApplicationReader(implementation, options, Timing.empty()).read().toDirect();
 
@@ -305,7 +318,6 @@
             }
             builder.addSupportedMethod(clazz, method);
           }
-          addBackports(clazz, backports);
           for (DexEncodedField field : clazz.fields()) {
             if (!field.isPublic() && !field.isProtected()) {
               continue;
@@ -313,12 +325,13 @@
             builder.addSupportedField(clazz, field);
           }
         }
+        addBackports(clazz, backports);
       }
     }
 
     // All retargeted methods are supported.
     machineSpecification.forEachRetargetMethod(
-        method -> registerMethod(method, implementationApplication));
+        method -> registerMethod(method, implementationApplication, backports));
 
     machineSpecification
         .getStaticFieldRetarget()
@@ -353,7 +366,8 @@
     }
   }
 
-  private void registerMethod(DexMethod method, DexApplication implementationApplication) {
+  private void registerMethod(
+      DexMethod method, DexApplication implementationApplication, List<DexMethod> backports) {
     DexClass dexClass = implementationApplication.definitionFor(method.getHolderType());
     if (dexClass != null) {
       DexEncodedMethod dexEncodedMethod = dexClass.lookupMethod(method);
@@ -368,6 +382,7 @@
     if (dexEncodedMethod != null) {
       builder.addSupportedMethod(dexClass, dexEncodedMethod);
       builder.annotateClass(dexClass.getType(), ClassAnnotation.getAdditionnalMembersOnClass());
+      backports.remove(method);
     }
   }
 
@@ -425,20 +440,23 @@
   }
 
   private MachineDesugaredLibrarySpecification getMachineSpecification(
-      AndroidApiLevel api, Path specification) throws IOException {
+      AndroidApiLevel api, StringResource specification) throws IOException {
+    if (specification == null) {
+      return MachineDesugaredLibrarySpecification.empty();
+    }
     DesugaredLibrarySpecification librarySpecification =
         DesugaredLibrarySpecificationParser.parseDesugaredLibrarySpecification(
-            StringResource.fromFile(specification),
-            options.itemFactory,
-            options.reporter,
-            false,
-            api.getLevel());
+            specification, options.itemFactory, options.reporter, false, api.getLevel());
     return librarySpecification.toMachineSpecification(appForMax, Timing.empty());
   }
 
-  private DirectMappedDexApplication createAppForMax(Path androidJar) throws IOException {
+  private DirectMappedDexApplication createAppForMax(
+      Collection<ClassFileResourceProvider> androidJar) throws IOException {
     AndroidApp.Builder builder = AndroidApp.builder();
-    AndroidApp inputApp = builder.addLibraryFiles(androidJar).build();
+    for (ClassFileResourceProvider libraryResource : androidJar) {
+      builder.addLibraryResourceProvider(libraryResource);
+    }
+    AndroidApp inputApp = builder.build();
     ApplicationReader applicationReader = new ApplicationReader(inputApp, options, Timing.empty());
     ExecutorService executorService = ThreadUtils.getExecutorService(options);
     assert !options.ignoreJavaLibraryOverride;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
index 985eb29..51253df 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
@@ -89,6 +89,7 @@
     return topLevelFlags.getSynthesizedLibraryClassesPackagePrefix();
   }
 
+  @Override
   public String getIdentifier() {
     return topLevelFlags.getIdentifier();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
index 21abec1..f151273 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
@@ -268,10 +268,12 @@
     // Case (3), parameters that are not null after the invocation.
     BitSet nonNullParamOnNormalExits = optimizationInfo.getNonNullParamOnNormalExits();
     if (nonNullParamOnNormalExits != null) {
-      int start = invoke.isInvokeMethodWithReceiver() ? 1 : 0;
-      for (int i = start; i < invoke.arguments().size(); i++) {
-        if (nonNullParamOnNormalExits.get(i)) {
-          Value argument = invoke.getArgument(i);
+      for (int argumentIndex = 0; argumentIndex < invoke.arguments().size(); argumentIndex++) {
+        boolean isArgumentNonNullOnNormalExits =
+            (invoke.isInvokeMethodWithReceiver() && argumentIndex == 0)
+                || nonNullParamOnNormalExits.get(argumentIndex);
+        if (isArgumentNonNullOnNormalExits) {
+          Value argument = invoke.getArgument(argumentIndex);
           if (assumedValuesBuilder.isMaybeNullAndNotNullType(argument)
               && isNullableReferenceTypeWithOtherNonDebugUsers(argument, invoke)) {
             assumedValuesBuilder.addNonNullValueWithUnknownDominance(invoke, argument);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java
index f01fd1c..d0f24dd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
@@ -87,7 +88,7 @@
     assert isUnboxedEnum(enumType);
     EnumData enumData = get(enumType);
     return isSuperUnboxedEnum(enumType)
-        ? enumData.superEnumTypeSingleValue(factory, enumType)
+        ? enumData.superEnumTypeSingleValue(factory)
         : enumData.subEnumTypeSingleValue(factory, enumType);
   }
 
@@ -220,15 +221,15 @@
       return valuesSize;
     }
 
-    public SingleNumberValue superEnumTypeSingleValue(AbstractValueFactory factory, DexType type) {
+    public SingleNumberValue superEnumTypeSingleValue(AbstractValueFactory factory) {
       // If there is a single live enum instance, then return the unboxed value for this one.
       if (hasValues()) {
         if (valuesSize == 1) {
-          return factory.createSingleNumberValue(ordinalToUnboxedInt(0));
+          return factory.createSingleNumberValue(ordinalToUnboxedInt(0), TypeElement.getInt());
         }
       } else if (unboxedValues.size() == 1) {
         Integer next = unboxedValues.values().iterator().next();
-        return factory.createSingleNumberValue(ordinalToUnboxedInt(next));
+        return factory.createSingleNumberValue(ordinalToUnboxedInt(next), TypeElement.getInt());
       }
       return null;
     }
@@ -238,7 +239,8 @@
       assert valuesTypes.values().stream().filter(t -> t == type).count() <= 1;
       for (Entry<DexType> entry : valuesTypes.int2ReferenceEntrySet()) {
         if (entry.getValue() == type) {
-          return factory.createSingleNumberValue(ordinalToUnboxedInt(entry.getIntKey()));
+          return factory.createSingleNumberValue(
+              ordinalToUnboxedInt(entry.getIntKey()), TypeElement.getInt());
         }
       }
       return null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index a2639bf..20a8df6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -798,7 +798,7 @@
           @Override
           public void fixup(DexEncodedField field, MutableFieldOptimizationInfo optimizationInfo) {
             optimizationInfo
-                .fixupAbstractValue(appView, graphLens, codeLens)
+                .fixupAbstractValue(appView, field, graphLens, codeLens)
                 .fixupClassTypeReferences(appView, graphLens);
           }
 
@@ -820,7 +820,7 @@
                       }
                     })
                 .fixupClassTypeReferences(appView, graphLens)
-                .fixupAbstractReturnValue(appView, graphLens, codeLens)
+                .fixupAbstractReturnValue(appView, method, graphLens, codeLens)
                 .fixupInstanceInitializerInfo(
                     appView, graphLens, codeLens, treeFixerResult.getPrunedItems());
 
@@ -1087,7 +1087,9 @@
             enumClass, new MissingInstanceFieldValueForEnumInstanceReason(instanceField, ordinal));
         return EnumInstanceFieldUnknownData.getInstance();
       }
-      if (!(fieldValue.isSingleNumberValue() || fieldValue.isSingleStringValue())) {
+      if (!(fieldValue.isNull()
+          || fieldValue.isSingleNumberValue()
+          || fieldValue.isSingleStringValue())) {
         reportFailure(
             enumClass,
             new UnsupportedInstanceFieldValueForEnumInstanceReason(ordinal, instanceField));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
index 8a5f087..95e4ea6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
 import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.proto.RewrittenTypeInfo;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
@@ -212,7 +213,7 @@
       SingleFieldValue singleFieldValue = singleValue.asSingleFieldValue();
       if (unboxedEnums.hasUnboxedValueFor(singleFieldValue.getField())) {
         return abstractValueFactory.createSingleNumberValue(
-            unboxedEnums.getUnboxedValue(singleFieldValue.getField()));
+            unboxedEnums.getUnboxedValue(singleFieldValue.getField()), TypeElement.getInt());
       }
     }
     return singleValue;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index 7449cee..ac0bc7b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -34,7 +34,7 @@
 import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
+import com.android.tools.r8.ir.analysis.value.SingleConstValue;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.IRCode;
@@ -405,9 +405,9 @@
               }
               for (ExtraParameter extraParameter :
                   lookupResult.getPrototypeChanges().getExtraParameters()) {
-                SingleNumberValue singleNumberValue = extraParameter.getValue(appView);
+                SingleConstValue singleConstValue = extraParameter.getValue(appView);
                 Instruction materializingInstruction =
-                    singleNumberValue.createMaterializingInstruction(
+                    singleConstValue.createMaterializingInstruction(
                         appView,
                         code,
                         TypeAndLocalInfoSupplier.create(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
index 451acb2..01b4a7d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
@@ -161,8 +161,8 @@
         if (staticType.isArrayType()) {
           Nullability nullability = concreteParameterState.asArrayParameter().getNullability();
           if (nullability.isDefinitelyNull()) {
-              newCallSiteInfo.constants.put(
-                  argumentIndex, appView.abstractValueFactory().createNullValue());
+            newCallSiteInfo.constants.put(
+                argumentIndex, appView.abstractValueFactory().createNullValue(staticType));
             isTop = false;
           } else if (nullability.isDefinitelyNotNull()) {
             newCallSiteInfo.dynamicTypes.put(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/LibraryOptimizationInfoInitializerFeedback.java b/src/main/java/com/android/tools/r8/ir/optimize/info/LibraryOptimizationInfoInitializerFeedback.java
index ace2aa6..90136f4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/LibraryOptimizationInfoInitializerFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/LibraryOptimizationInfoInitializerFeedback.java
@@ -4,9 +4,6 @@
 
 package com.android.tools.r8.ir.optimize.info;
 
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-
 public class LibraryOptimizationInfoInitializerFeedback extends OptimizationFeedbackSimple {
 
   private static LibraryOptimizationInfoInitializerFeedback INSTANCE =
@@ -17,9 +14,4 @@
   public static LibraryOptimizationInfoInitializerFeedback getInstance() {
     return INSTANCE;
   }
-
-  public void recordLibraryFieldHasAbstractValue(
-      DexEncodedField field, AbstractValue abstractValue) {
-    field.getMutableOptimizationInfo().setAbstractValue(abstractValue);
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index 6a2bc86..fadb852 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -32,6 +32,10 @@
     return false;
   }
 
+  public boolean isMethodResolutionOptimizationInfo() {
+    return false;
+  }
+
   public abstract boolean cannotBeKept();
 
   public abstract boolean classInitializerMayBePostponed();
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 3c06996..73e2edd 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
@@ -847,7 +847,9 @@
 
     if (dynamicReturnType.isNullType()) {
       feedback.methodReturnsAbstractValue(
-          method.getDefinition(), appView, appView.abstractValueFactory().createNullValue());
+          method.getDefinition(),
+          appView,
+          appView.abstractValueFactory().createNullValue(method.getReturnType()));
       feedback.setDynamicReturnType(method, appView, dynamicReturnType);
       return;
     }
@@ -1015,7 +1017,7 @@
     }
     List<Value> arguments = code.collectArguments();
     BitSet paramsCheckedForNull = new BitSet();
-    for (int index = 0; index < arguments.size(); index++) {
+    for (int index = method.getFirstNonReceiverArgumentIndex(); index < arguments.size(); index++) {
       Value argument = arguments.get(index);
       // This handles cases where the parameter is checked via Kotlin Intrinsics:
       //
@@ -1055,7 +1057,9 @@
     if (nonNullParamOrThrow != null) {
       facts.or(nonNullParamOrThrow);
     }
-    for (int index = 0; index < arguments.size(); index++) {
+    for (int index = code.context().getDefinition().getFirstNonReceiverArgumentIndex();
+        index < arguments.size();
+        index++) {
       if (facts.get(index)) {
         continue;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfo.java
new file mode 100644
index 0000000..2139f0f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfo.java
@@ -0,0 +1,326 @@
+// Copyright (c) 2023, 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.info;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
+import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
+import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OptionalBool;
+import java.util.BitSet;
+import java.util.Set;
+import java.util.function.Consumer;
+
+public class MethodResolutionOptimizationInfo extends MethodOptimizationInfo {
+
+  private final AbstractValue abstractReturnValue;
+  private final DynamicType dynamicReturnType;
+  private final boolean mayHaveSideEffects;
+  private final boolean neverReturnsNormally;
+  private final BitSet nonNullParamOnNormalExits;
+  private final BitSet nonNullParamOrThrow;
+  private final int returnedArgument;
+  private final boolean returnValueOnlyDependsOnArguments;
+
+  MethodResolutionOptimizationInfo(
+      AbstractValue abstractReturnValue,
+      DynamicType dynamicReturnType,
+      boolean mayHaveSideEffects,
+      boolean neverReturnsNormally,
+      BitSet nonNullParamOnNormalExits,
+      BitSet nonNullParamOrThrow,
+      int returnedArgument,
+      boolean returnValueOnlyDependsOnArguments) {
+    this.abstractReturnValue = abstractReturnValue;
+    this.dynamicReturnType = dynamicReturnType;
+    this.mayHaveSideEffects = mayHaveSideEffects;
+    this.neverReturnsNormally = neverReturnsNormally;
+    this.nonNullParamOnNormalExits = nonNullParamOnNormalExits;
+    this.nonNullParamOrThrow = nonNullParamOrThrow;
+    this.returnedArgument = returnedArgument;
+    this.returnValueOnlyDependsOnArguments = returnValueOnlyDependsOnArguments;
+  }
+
+  static Builder builder() {
+    return new Builder();
+  }
+
+  static MethodOptimizationInfo create(MethodOptimizationInfo optimizationInfo) {
+    return builder()
+        .setAbstractReturnValue(optimizationInfo.getAbstractReturnValue())
+        .setDynamicReturnType(optimizationInfo.getDynamicType())
+        .setMayHaveSideEffects(optimizationInfo.mayHaveSideEffects())
+        .setNeverReturnsNormally(optimizationInfo.neverReturnsNormally())
+        .setNonNullParamOnNormalExits(optimizationInfo.getNonNullParamOnNormalExits())
+        .setNonNullParamOrThrow(optimizationInfo.getNonNullParamOrThrow())
+        .applyIf(
+            optimizationInfo.returnsArgument(),
+            builder -> builder.setReturnedArgument(optimizationInfo.getReturnedArgument()))
+        .setReturnValueOnlyDependsOnArguments(optimizationInfo.returnValueOnlyDependsOnArguments())
+        .build();
+  }
+
+  @Override
+  public boolean isMethodResolutionOptimizationInfo() {
+    return true;
+  }
+
+  @Override
+  public AbstractValue getAbstractReturnValue() {
+    return abstractReturnValue;
+  }
+
+  @Override
+  public DynamicType getDynamicType() {
+    return dynamicReturnType;
+  }
+
+  @Override
+  public boolean mayHaveSideEffects() {
+    return mayHaveSideEffects;
+  }
+
+  @Override
+  public boolean mayHaveSideEffects(InvokeMethod invoke, InternalOptions options) {
+    return mayHaveSideEffects;
+  }
+
+  @Override
+  public boolean neverReturnsNormally() {
+    return neverReturnsNormally;
+  }
+
+  @Override
+  public BitSet getNonNullParamOnNormalExits() {
+    return nonNullParamOnNormalExits;
+  }
+
+  @Override
+  public BitSet getNonNullParamOrThrow() {
+    return nonNullParamOrThrow;
+  }
+
+  @Override
+  public boolean returnsArgument() {
+    return returnedArgument >= 0;
+  }
+
+  @Override
+  public int getReturnedArgument() {
+    return returnedArgument;
+  }
+
+  @Override
+  public boolean returnValueOnlyDependsOnArguments() {
+    return returnValueOnlyDependsOnArguments;
+  }
+
+  @Override
+  public boolean hasBeenInlinedIntoSingleCallSite() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public MutableMethodOptimizationInfo toMutableOptimizationInfo() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public boolean cannotBeKept() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public boolean classInitializerMayBePostponed() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public CallSiteOptimizationInfo getArgumentInfos() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public ClassInlinerMethodConstraint getClassInlinerMethodConstraint() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public EnumUnboxerMethodClassification getEnumUnboxerMethodClassification() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public int getMaxRemovedAndroidLogLevel() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public BridgeInfo getBridgeInfo() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public Set<DexType> getInitializedClassesOnNormalExit() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public InstanceInitializerInfo getContextInsensitiveInstanceInitializerInfo() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public InstanceInitializerInfo getInstanceInitializerInfo(InvokeDirect invoke) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public boolean isConvertCheckNotNull() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public boolean isInitializerEnablingJavaVmAssertions() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public SimpleInliningConstraint getNopInliningConstraint(InternalOptions options) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public SimpleInliningConstraint getSimpleInliningConstraint() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public boolean hasParametersWithBitwiseOperations() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public BitSet getParametersWithBitwiseOperations() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public BitSet getUnusedArguments() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public boolean isMultiCallerMethod() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public OptionalBool isReturnValueUsed() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public boolean forceInline() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public boolean returnValueHasBeenPropagated() {
+    throw new Unreachable();
+  }
+
+  static class Builder {
+
+    private AbstractValue abstractReturnValue = AbstractValue.unknown();
+    private DynamicType dynamicReturnType = DynamicType.unknown();
+    private boolean mayHaveSideEffects = true;
+    private boolean neverReturnsNormally = false;
+    private BitSet nonNullParamOnNormalExits = null;
+    private BitSet nonNullParamOrThrow = null;
+    private int returnedArgument = -1;
+    private boolean returnValueOnlyDependsOnArguments = false;
+
+    Builder applyIf(boolean condition, Consumer<Builder> fn) {
+      if (condition) {
+        fn.accept(this);
+      }
+      return this;
+    }
+
+    boolean isEffectivelyDefault() {
+      return dynamicReturnType.isUnknown()
+          && abstractReturnValue.isUnknown()
+          && returnedArgument < 0
+          && nonNullParamOnNormalExits == null
+          && nonNullParamOrThrow == null
+          && mayHaveSideEffects
+          && !neverReturnsNormally
+          && !returnValueOnlyDependsOnArguments;
+    }
+
+    Builder setAbstractReturnValue(AbstractValue abstractReturnValue) {
+      this.abstractReturnValue = abstractReturnValue;
+      return this;
+    }
+
+    Builder setDynamicReturnType(DynamicType dynamicReturnType) {
+      this.dynamicReturnType = dynamicReturnType;
+      return this;
+    }
+
+    Builder setMayHaveSideEffects(boolean mayHaveSideEffects) {
+      this.mayHaveSideEffects = mayHaveSideEffects;
+      return this;
+    }
+
+    Builder setNeverReturnsNormally(boolean neverReturnsNormally) {
+      this.neverReturnsNormally = neverReturnsNormally;
+      return this;
+    }
+
+    Builder setNonNullParamOnNormalExits(BitSet nonNullParamOnNormalExits) {
+      this.nonNullParamOnNormalExits = nonNullParamOnNormalExits;
+      return this;
+    }
+
+    Builder setNonNullParamOrThrow(BitSet nonNullParamOrThrow) {
+      this.nonNullParamOrThrow = nonNullParamOrThrow;
+      return this;
+    }
+
+    Builder setReturnedArgument(int returnedArgument) {
+      this.returnedArgument = returnedArgument;
+      return this;
+    }
+
+    Builder setReturnValueOnlyDependsOnArguments(boolean returnValueOnlyDependsOnArguments) {
+      this.returnValueOnlyDependsOnArguments = returnValueOnlyDependsOnArguments;
+      return this;
+    }
+
+    MethodOptimizationInfo build() {
+      if (isEffectivelyDefault()) {
+        return DefaultMethodOptimizationInfo.getInstance();
+      }
+      return new MethodResolutionOptimizationInfo(
+          abstractReturnValue,
+          dynamicReturnType,
+          mayHaveSideEffects,
+          neverReturnsNormally,
+          nonNullParamOnNormalExits,
+          nonNullParamOrThrow,
+          returnedArgument,
+          returnValueOnlyDependsOnArguments);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoAnalysis.java
new file mode 100644
index 0000000..e1b6f85
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoAnalysis.java
@@ -0,0 +1,409 @@
+// Copyright (c) 2023, 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.info;
+
+import static com.google.common.base.Predicates.not;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethodSignature;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.conversion.PostMethodProcessor;
+import com.android.tools.r8.optimize.argumentpropagation.utils.DepthFirstTopDownClassHierarchyTraversal;
+import com.android.tools.r8.optimize.argumentpropagation.utils.ProgramClassesBidirectedGraph;
+import com.android.tools.r8.optimize.argumentpropagation.utils.WideningUtils;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepMethodInfo;
+import com.android.tools.r8.utils.BitSetUtils;
+import com.android.tools.r8.utils.MapUtils;
+import com.android.tools.r8.utils.NumberUtils;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.collections.DexMethodSignatureMap;
+import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
+import java.util.BitSet;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public class MethodResolutionOptimizationInfoAnalysis {
+
+  public static void run(
+      AppView<AppInfoWithLiveness> appView,
+      ExecutorService executorService,
+      PostMethodProcessor.Builder postMethodProcessorBuilder)
+      throws ExecutionException {
+    ImmediateProgramSubtypingInfo immediateSubtypingInfo =
+        ImmediateProgramSubtypingInfo.create(appView);
+    List<Set<DexProgramClass>> stronglyConnectedComponents =
+        new ProgramClassesBidirectedGraph(appView, immediateSubtypingInfo)
+            .computeStronglyConnectedComponents();
+    MethodResolutionOptimizationInfoCollection.Builder builder =
+        MethodResolutionOptimizationInfoCollection.builder();
+    ThreadUtils.processItems(
+        stronglyConnectedComponents,
+        stronglyConnectedComponent ->
+            new Traversal(appView, builder, immediateSubtypingInfo).run(stronglyConnectedComponent),
+        executorService);
+    MethodResolutionOptimizationInfoCollection methodResolutionOptimizationInfoCollection =
+        builder.build();
+    appView.setMethodResolutionOptimizationInfoCollection(
+        methodResolutionOptimizationInfoCollection);
+    if (!methodResolutionOptimizationInfoCollection.isEmpty()) {
+      new MethodResolutionOptimizationInfoReprocessingEnqueuer(appView)
+          .enqueueMethodForReprocessing(executorService, postMethodProcessorBuilder);
+    }
+  }
+
+  private static class Traversal extends DepthFirstTopDownClassHierarchyTraversal {
+
+    private final MethodResolutionOptimizationInfoCollection.Builder builder;
+    private final Map<DexProgramClass, TraversalState> states = new IdentityHashMap<>();
+
+    private Traversal(
+        AppView<AppInfoWithLiveness> appView,
+        MethodResolutionOptimizationInfoCollection.Builder builder,
+        ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
+      super(appView, immediateSubtypingInfo);
+      this.builder = builder;
+    }
+
+    /**
+     * Build up a state that describes which virtual methods need to have their optimization info
+     * pushed upwards in the subsequent upwards traversal (instead of naively pushing optimization
+     * info upwards for *all* virtual methods).
+     *
+     * <p>Example: In the below piece of code, we want to push the optimization for C.m() up to
+     * class B, but we do not want to push the optimization info for B.m() and C.m() up to class A,
+     * since class A does not declare the method m().
+     *
+     * <pre>
+     * class A {}
+     * class B extends A {
+     *   void m() { ... }
+     * }
+     * class C extends B {
+     *   void m() { ... }
+     * }
+     * </pre>
+     */
+    @Override
+    public void visit(DexProgramClass clazz) {
+      DownwardsTraversalState state = new DownwardsTraversalState(DexMethodSignatureSet.create());
+      immediateSubtypingInfo.forEachImmediateProgramSuperClass(
+          clazz,
+          superClass -> {
+            DownwardsTraversalState superState =
+                states
+                    .getOrDefault(superClass, DownwardsTraversalState.empty())
+                    .asDownwardsTraversalState();
+            state.add(superState);
+            for (DexEncodedMethod method : superClass.virtualMethods()) {
+              // No need to request optimization info for the (non-existing) overrides of final
+              // methods.
+              if (method.isFinal()) {
+                continue;
+              }
+              // If the method is not abstract and does not have any optimization info, there is no
+              // need to request the optimization info for overrides in subclasses, since the join
+              // of the optimization info becomes unknown anyway.
+              if (method.isAbstract() || !method.getOptimizationInfo().isDefault()) {
+                state.virtualMethodsInSuperClasses.add(method);
+              }
+            }
+          });
+      if (!state.isEmpty()) {
+        states.put(clazz, state);
+      }
+    }
+
+    @Override
+    public void prune(DexProgramClass clazz) {
+      // Extract the downwards traversal state that describes what info needs to be pushed upwards.
+      DownwardsTraversalState state =
+          MapUtils.removeOrDefault(states, clazz, DownwardsTraversalState.empty())
+              .asDownwardsTraversalState();
+
+      // Join the optimization info for all methods in all immediate subclasses.
+      UpwardsTraversalState newState = new UpwardsTraversalState(DexMethodSignatureMap.create());
+      forEachSubClass(
+          clazz,
+          subClass -> {
+            UpwardsTraversalState subClassState =
+                states
+                    .getOrDefault(subClass, UpwardsTraversalState.empty())
+                    .asUpwardsTraversalState();
+            newState.join(appView, subClassState);
+
+            // If the current class is an interface and the current subclass is not, then we need
+            // special handling to account for the fact that invoke-interface instructions may
+            // dispatch to virtual "sibling" methods that are declared on a class that is not a
+            // subtype of the interface declaring the interface method.
+            if (clazz.isInterface() && !subClass.isInterface()) {
+              handleInvokeInterfaceToSiblingMethod(clazz, subClass, state, newState);
+            }
+          });
+      ObjectAllocationInfoCollection objectAllocationInfoCollection =
+          appView.appInfo().getObjectAllocationInfoCollection();
+      if (objectAllocationInfoCollection.isImmediateInterfaceOfInstantiatedLambda(clazz)) {
+        for (DexEncodedMethod method : clazz.virtualMethods()) {
+          newState.joinMethodOptimizationInfo(
+              appView, method.getSignature(), DefaultMethodOptimizationInfo.getInstance());
+        }
+      } else {
+        for (DexEncodedMethod method : clazz.virtualMethods()) {
+          KeepMethodInfo keepInfo = appView.getKeepInfo().getMethodInfo(method, clazz);
+          if (!keepInfo.isShrinkingAllowed(appView.options())) {
+            // Method is kept and could be overridden outside app (e.g., in tests). Verify we don't
+            // have any optimization info recorded for non-abstract methods.
+            assert method.isAbstract() || method.getOptimizationInfo().isDefault();
+            newState.joinMethodOptimizationInfo(
+                appView, method.getSignature(), DefaultMethodOptimizationInfo.getInstance());
+          } else if (!method.isAbstract()) {
+            newState.joinMethodOptimizationInfo(
+                appView, method.getSignature(), method.getOptimizationInfo());
+          }
+        }
+      }
+
+      // Record the computed optimization info for all non-final virtual methods on the current
+      // class.
+      if (!clazz.isFinal()) {
+        for (DexEncodedMethod method : clazz.virtualMethods(not(DexEncodedMethod::isFinal))) {
+          MethodOptimizationInfo info = newState.getMethodOptimizationInfo(method);
+          assert info.isDefault() || info.isMethodResolutionOptimizationInfo();
+          builder.add(method.getReference(), info);
+        }
+      }
+
+      // Prune out everything that does not need to be pushed upwards and store the resulting state.
+      newState
+          .infos
+          .keySet()
+          .removeIf(method -> !state.virtualMethodsInSuperClasses.contains(method));
+      if (!newState.isEmpty()) {
+        states.put(clazz, newState);
+      }
+    }
+
+    private void handleInvokeInterfaceToSiblingMethod(
+        DexProgramClass iface,
+        DexProgramClass subClass,
+        DownwardsTraversalState state,
+        UpwardsTraversalState newState) {
+      assert iface.isInterface();
+      assert !subClass.isInterface();
+
+      DexMethodSignatureSet interfaceMethodsInClassOrAbove =
+          DexMethodSignatureSet.create(state.virtualMethodsInSuperClasses);
+      interfaceMethodsInClassOrAbove.addAllMethods(iface.virtualMethods());
+
+      for (DexMethodSignature method : interfaceMethodsInClassOrAbove) {
+        MethodResolutionResult resolutionResult =
+            appView.appInfo().resolveMethodOnClass(subClass, method);
+        if (resolutionResult.isFailedResolution()) {
+          assert resolutionResult.asFailedResolution().hasMethodsCausingError();
+          continue;
+        }
+
+        if (resolutionResult.isMultiMethodResolutionResult()) {
+          // Conservatively drop the current optimization info.
+          newState.joinMethodOptimizationInfo(
+              appView, method, DefaultMethodOptimizationInfo.getInstance());
+          continue;
+        }
+
+        assert resolutionResult.isSingleResolution();
+        DexClassAndMethod resolvedMethod = resolutionResult.getResolutionPair();
+        if (!resolvedMethod.getHolder().isInterface() && resolvedMethod.getHolder() != subClass) {
+          newState.joinMethodOptimizationInfo(
+              appView, method, resolvedMethod.getOptimizationInfo());
+        }
+      }
+    }
+  }
+
+  private abstract static class TraversalState {
+
+    DownwardsTraversalState asDownwardsTraversalState() {
+      return null;
+    }
+
+    UpwardsTraversalState asUpwardsTraversalState() {
+      return null;
+    }
+  }
+
+  // State that is used for each class during the downwards traversal over the class hierarchy.
+  private static class DownwardsTraversalState extends TraversalState {
+
+    private static final DownwardsTraversalState EMPTY =
+        new DownwardsTraversalState(DexMethodSignatureSet.empty());
+
+    // The set of virtual methods in the super classes of the current class. For each method in this
+    // set we want the subsequent upwards traversal to include the optimization info for any
+    // overrides.
+    DexMethodSignatureSet virtualMethodsInSuperClasses;
+
+    DownwardsTraversalState(DexMethodSignatureSet virtualMethodsInSuperClasses) {
+      this.virtualMethodsInSuperClasses = virtualMethodsInSuperClasses;
+    }
+
+    static DownwardsTraversalState empty() {
+      return EMPTY;
+    }
+
+    void add(DownwardsTraversalState state) {
+      virtualMethodsInSuperClasses.addAll(state.virtualMethodsInSuperClasses);
+    }
+
+    boolean isEmpty() {
+      return virtualMethodsInSuperClasses.isEmpty();
+    }
+
+    @Override
+    DownwardsTraversalState asDownwardsTraversalState() {
+      return this;
+    }
+  }
+
+  private static class UpwardsTraversalState extends TraversalState {
+
+    private static final UpwardsTraversalState EMPTY =
+        new UpwardsTraversalState(DexMethodSignatureMap.empty());
+
+    private final DexMethodSignatureMap<MethodOptimizationInfo> infos;
+
+    UpwardsTraversalState(DexMethodSignatureMap<MethodOptimizationInfo> infos) {
+      this.infos = infos;
+    }
+
+    static UpwardsTraversalState empty() {
+      return EMPTY;
+    }
+
+    void join(AppView<AppInfoWithLiveness> appView, UpwardsTraversalState state) {
+      state.infos.forEach((method, info) -> joinMethodOptimizationInfo(appView, method, info));
+    }
+
+    void joinMethodOptimizationInfo(
+        AppView<AppInfoWithLiveness> appView,
+        DexMethodSignature method,
+        MethodOptimizationInfo info) {
+      infos.compute(
+          method,
+          (m, existingInfo) -> existingInfo == null ? info : join(appView, m, existingInfo, info));
+    }
+
+    static MethodOptimizationInfo join(
+        AppView<AppInfoWithLiveness> appView,
+        DexMethodSignature method,
+        MethodOptimizationInfo info,
+        MethodOptimizationInfo otherInfo) {
+      if (info.isDefault() || otherInfo.isDefault()) {
+        return DefaultMethodOptimizationInfo.getInstance();
+      }
+
+      // Join dynamic return type.
+      DynamicType dynamicReturnType;
+      if (method.getReturnType().isVoidType()) {
+        dynamicReturnType = DynamicType.unknown();
+      } else {
+        dynamicReturnType = info.getDynamicType().join(appView, otherInfo.getDynamicType());
+        if (method.getReturnType().isClassType()) {
+          dynamicReturnType =
+              WideningUtils.widenDynamicNonReceiverType(
+                  appView, dynamicReturnType, method.getReturnType());
+        } else if (method.getReturnType().isArrayType()) {
+          // TODO: also widen array types.
+        } else {
+          assert method.getReturnType().isPrimitiveType();
+          assert dynamicReturnType.isUnknown();
+        }
+      }
+
+      // Join abstract return value.
+      AbstractValue abstractReturnValue;
+      if (method.getReturnType().isVoidType()) {
+        abstractReturnValue = AbstractValue.unknown();
+      } else {
+        TypeElement staticReturnType = method.getReturnType().toTypeElement(appView);
+        abstractReturnValue =
+            appView
+                .getAbstractValueConstantPropagationJoiner()
+                .join(
+                    info.getAbstractReturnValue(),
+                    otherInfo.getAbstractReturnValue(),
+                    staticReturnType);
+      }
+
+      // Join returned argument index.
+      int returnedArgument =
+          info.returnsArgument() && otherInfo.returnsArgument()
+              ? NumberUtils.getIfEqualsOrDefault(
+                  info.getReturnedArgument(), otherInfo.getReturnedArgument(), -1)
+              : -1;
+
+      // Join non-null-param-on-normal-exits information.
+      BitSet nonNullParamOnNormalExits =
+          BitSetUtils.intersectNullableBitSets(
+              info.getNonNullParamOnNormalExits(), otherInfo.getNonNullParamOnNormalExits());
+      assert nonNullParamOnNormalExits == null || !nonNullParamOnNormalExits.isEmpty();
+
+      // Join non-null-param-or-throw information.
+      BitSet nonNullParamOrThrow =
+          BitSetUtils.intersectNullableBitSets(
+              info.getNonNullParamOrThrow(), otherInfo.getNonNullParamOrThrow());
+      assert nonNullParamOrThrow == null || !nonNullParamOrThrow.isEmpty();
+
+      // Join flags.
+      boolean mayNotHaveSideEffects = !info.mayHaveSideEffects() && !otherInfo.mayHaveSideEffects();
+      boolean neverReturnsNormally =
+          info.neverReturnsNormally() && otherInfo.neverReturnsNormally();
+      boolean returnValueOnlyDependsOnArguments =
+          info.returnValueOnlyDependsOnArguments() && otherInfo.returnValueOnlyDependsOnArguments();
+
+      return MethodResolutionOptimizationInfo.builder()
+          .setAbstractReturnValue(abstractReturnValue)
+          .setDynamicReturnType(dynamicReturnType)
+          .setMayHaveSideEffects(!mayNotHaveSideEffects)
+          .setNeverReturnsNormally(neverReturnsNormally)
+          .setNonNullParamOnNormalExits(nonNullParamOnNormalExits)
+          .setNonNullParamOrThrow(nonNullParamOrThrow)
+          .setReturnedArgument(returnedArgument)
+          .setReturnValueOnlyDependsOnArguments(returnValueOnlyDependsOnArguments)
+          .build();
+    }
+
+    MethodOptimizationInfo getMethodOptimizationInfo(DexEncodedMethod method) {
+      MethodOptimizationInfo optimizationInfo =
+          infos.getOrDefault(method, DefaultMethodOptimizationInfo.getInstance());
+      if (optimizationInfo.isMutableOptimizationInfo()) {
+        return MethodResolutionOptimizationInfo.create(
+            optimizationInfo.asMutableMethodOptimizationInfo());
+      }
+      assert optimizationInfo.isDefault() || optimizationInfo.isMethodResolutionOptimizationInfo();
+      return optimizationInfo;
+    }
+
+    boolean isEmpty() {
+      return infos.isEmpty();
+    }
+
+    @Override
+    UpwardsTraversalState asUpwardsTraversalState() {
+      return this;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoCollection.java
index 6c2c13b..3ecca10 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoCollection.java
@@ -6,9 +6,11 @@
 
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexMethod;
 import java.util.Collections;
+import java.util.IdentityHashMap;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Collection of optimization info for virtual methods with dynamic dispatch.
@@ -23,12 +25,16 @@
   private static final MethodResolutionOptimizationInfoCollection EMPTY =
       new MethodResolutionOptimizationInfoCollection(Collections.emptyMap());
 
-  private final Map<DexReference, MethodOptimizationInfo> backing;
+  private final Map<DexMethod, MethodOptimizationInfo> backing;
 
-  MethodResolutionOptimizationInfoCollection(Map<DexReference, MethodOptimizationInfo> backing) {
+  MethodResolutionOptimizationInfoCollection(Map<DexMethod, MethodOptimizationInfo> backing) {
     this.backing = backing;
   }
 
+  public static Builder builder() {
+    return new Builder();
+  }
+
   public static MethodResolutionOptimizationInfoCollection empty() {
     return EMPTY;
   }
@@ -40,4 +46,24 @@
     }
     return backing.getOrDefault(method.getReference(), defaultValue);
   }
+
+  public boolean isEmpty() {
+    return backing.isEmpty();
+  }
+
+  public static class Builder {
+
+    private final Map<DexMethod, MethodOptimizationInfo> backing = new ConcurrentHashMap<>();
+
+    void add(DexMethod method, MethodOptimizationInfo optimizationInfo) {
+      assert !backing.containsKey(method);
+      if (!optimizationInfo.isDefault()) {
+        backing.put(method, optimizationInfo);
+      }
+    }
+
+    MethodResolutionOptimizationInfoCollection build() {
+      return new MethodResolutionOptimizationInfoCollection(new IdentityHashMap<>(backing));
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoReprocessingEnqueuer.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoReprocessingEnqueuer.java
new file mode 100644
index 0000000..9ad09b2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoReprocessingEnqueuer.java
@@ -0,0 +1,164 @@
+// Copyright (c) 2023, 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.info;
+
+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.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistryWithResult;
+import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.ir.conversion.PostMethodProcessor;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ThreadUtils;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * Finds the methods in the program that should be reprocessed due to non-trivial method resolution
+ * optimization info.
+ */
+class MethodResolutionOptimizationInfoReprocessingEnqueuer {
+
+  private final AppView<AppInfoWithLiveness> appView;
+
+  MethodResolutionOptimizationInfoReprocessingEnqueuer(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+  }
+
+  public void enqueueMethodForReprocessing(
+      ExecutorService executorService, PostMethodProcessor.Builder postMethodProcessorBuilder)
+      throws ExecutionException {
+    // Bring the methods to reprocess set up-to-date with the current graph lens.
+    postMethodProcessorBuilder.rewrittenWithLens(appView);
+    enqueueAffectedCallers(postMethodProcessorBuilder, executorService);
+  }
+
+  private void enqueueAffectedCallers(
+      PostMethodProcessor.Builder postMethodProcessorBuilder, ExecutorService executorService)
+      throws ExecutionException {
+    GraphLens currentGraphLens = appView.graphLens();
+    Collection<List<ProgramMethod>> methodsToReprocess =
+        ThreadUtils.processItemsWithResults(
+            appView.appInfo().classes(),
+            clazz -> {
+              List<ProgramMethod> methodsToReprocessInClass = new ArrayList<>();
+              clazz.forEachProgramMethodMatching(
+                  DexEncodedMethod::hasCode,
+                  method -> {
+                    if (!postMethodProcessorBuilder.contains(method, currentGraphLens)
+                        && !appView.appInfo().isNeverReprocessMethod(method)) {
+                      AffectedMethodUseRegistry registry =
+                          new AffectedMethodUseRegistry(appView, method);
+                      if (method.registerCodeReferencesWithResult(registry)) {
+                        methodsToReprocessInClass.add(method);
+                      }
+                    }
+                  });
+              return methodsToReprocessInClass;
+            },
+            executorService);
+    methodsToReprocess.forEach(
+        methodsToReprocessInClass ->
+            postMethodProcessorBuilder.addAll(methodsToReprocessInClass, currentGraphLens));
+  }
+
+  static class AffectedMethodUseRegistry extends UseRegistryWithResult<Boolean, ProgramMethod> {
+
+    private final AppView<AppInfoWithLiveness> appViewWithLiveness;
+
+    AffectedMethodUseRegistry(
+        AppView<AppInfoWithLiveness> appViewWithLiveness, ProgramMethod context) {
+      super(appViewWithLiveness, context, false);
+      this.appViewWithLiveness = appViewWithLiveness;
+    }
+
+    private void markAffected() {
+      setResult(Boolean.TRUE);
+    }
+
+    @Override
+    public void registerInvokeDirect(DexMethod method) {
+      DexMethod rewrittenMethod =
+          appViewWithLiveness.graphLens().lookupInvokeDirect(method, getContext()).getReference();
+      registerInvokeMethod(rewrittenMethod);
+    }
+
+    @Override
+    public void registerInvokeInterface(DexMethod method) {
+      DexMethod rewrittenMethod =
+          appViewWithLiveness
+              .graphLens()
+              .lookupInvokeInterface(method, getContext())
+              .getReference();
+      registerInvokeMethod(rewrittenMethod);
+    }
+
+    @Override
+    public void registerInvokeStatic(DexMethod method) {
+      DexMethod rewrittenMethod =
+          appViewWithLiveness.graphLens().lookupInvokeStatic(method, getContext()).getReference();
+      registerInvokeMethod(rewrittenMethod);
+    }
+
+    @Override
+    public void registerInvokeSuper(DexMethod method) {
+      DexMethod rewrittenMethod =
+          appViewWithLiveness.graphLens().lookupInvokeSuper(method, getContext()).getReference();
+      registerInvokeMethod(rewrittenMethod);
+    }
+
+    @Override
+    public void registerInvokeVirtual(DexMethod method) {
+      DexMethod rewrittenMethod =
+          appViewWithLiveness.graphLens().lookupInvokeVirtual(method, getContext()).getReference();
+      registerInvokeMethod(rewrittenMethod);
+    }
+
+    private void registerInvokeMethod(DexMethod method) {
+      SingleResolutionResult<?> resolutionResult =
+          appViewWithLiveness
+              .appInfo()
+              .unsafeResolveMethodDueToDexFormatLegacy(method)
+              .asSingleResolution();
+      if (resolutionResult == null) {
+        return;
+      }
+
+      MethodResolutionOptimizationInfoCollection methodResolutionOptimizationInfoCollection =
+          appViewWithLiveness.getMethodResolutionOptimizationInfoCollection();
+      MethodOptimizationInfo optimizationInfo =
+          methodResolutionOptimizationInfoCollection.get(
+              resolutionResult.getResolvedMethod(), resolutionResult.getResolvedHolder());
+      if (!optimizationInfo.isDefault()) {
+        markAffected();
+      }
+    }
+
+    @Override
+    public void registerInstanceFieldRead(DexField field) {}
+
+    @Override
+    public void registerInstanceFieldWrite(DexField field) {}
+
+    @Override
+    public void registerStaticFieldRead(DexField field) {}
+
+    @Override
+    public void registerStaticFieldWrite(DexField field) {}
+
+    @Override
+    public void registerInitClass(DexType type) {}
+
+    @Override
+    public void registerTypeReference(DexType type) {}
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
index 406aa24..4dc44df 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
@@ -7,6 +7,7 @@
 import static java.util.Collections.emptySet;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.ir.analysis.type.DynamicType;
@@ -59,15 +60,26 @@
     return abstractValue;
   }
 
-  void setAbstractValue(AbstractValue abstractValue) {
+  MutableFieldOptimizationInfo setAbstractValue(
+      AbstractValue abstractValue, DexEncodedField field) {
+    assert !abstractValue.isNull() || field.getType().isReferenceType();
+    assert !abstractValue.isSingleNumberValue() || field.getType().isPrimitiveType();
+    return setAbstractValue(abstractValue);
+  }
+
+  private MutableFieldOptimizationInfo setAbstractValue(AbstractValue abstractValue) {
     assert getAbstractValue().isUnknown() || abstractValue.isNonTrivial();
     this.abstractValue = abstractValue;
+    return this;
   }
 
   public MutableFieldOptimizationInfo fixupAbstractValue(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
-    setAbstractValue(abstractValue.rewrittenWithLens(appView, lens, codeLens));
-    return this;
+      AppView<AppInfoWithLiveness> appView,
+      DexEncodedField field,
+      GraphLens lens,
+      GraphLens codeLens) {
+    return setAbstractValue(
+        abstractValue.rewrittenWithLens(appView, field.getType(), lens, codeLens), field);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
index 2f29795..167bbdc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -13,7 +13,9 @@
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.ir.analysis.inlining.NeverSimpleInliningConstraint;
 import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
@@ -138,7 +140,7 @@
     initializedClassesOnNormalExit = template.initializedClassesOnNormalExit;
     returnedArgument = template.returnedArgument;
     abstractReturnValue = template.abstractReturnValue;
-    dynamicType = template.dynamicType;
+    setDynamicType(template.dynamicType);
     inlining = template.inlining;
     simpleInliningConstraint = template.simpleInliningConstraint;
     bridgeInfo = template.bridgeInfo;
@@ -174,13 +176,38 @@
 
   public MutableMethodOptimizationInfo fixupClassTypeReferences(
       AppView<AppInfoWithLiveness> appView, GraphLens lens, Set<DexType> prunedTypes) {
-    dynamicType = dynamicType.rewrittenWithLens(appView, lens, prunedTypes);
-    return this;
+    DynamicType rewrittenDynamicType = dynamicType.rewrittenWithLens(appView, lens, prunedTypes);
+    if (rewrittenDynamicType.hasDynamicUpperBoundType()) {
+      DynamicTypeWithUpperBound rewrittenDynamicTypeWithUpperBound =
+          rewrittenDynamicType.asDynamicTypeWithUpperBound();
+      if (rewrittenDynamicTypeWithUpperBound.getDynamicUpperBoundType().isPrimitiveType()) {
+        // Do not store primitive dynamic types.
+        assert verifyDynamicTypeIsUnboxedEnum(appView, dynamicType);
+        return unsetDynamicType();
+      }
+    }
+    return setDynamicType(rewrittenDynamicType);
+  }
+
+  private static boolean verifyDynamicTypeIsUnboxedEnum(
+      AppView<?> appView, DynamicType dynamicType) {
+    assert dynamicType.isDynamicTypeWithUpperBound();
+    DynamicTypeWithUpperBound dynamicTypeWithUpperBound = dynamicType.asDynamicTypeWithUpperBound();
+    TypeElement dynamicUpperBoundType = dynamicTypeWithUpperBound.getDynamicUpperBoundType();
+    assert dynamicUpperBoundType.isClassType();
+    ClassTypeElement dynamicUpperBoundClassType = dynamicUpperBoundType.asClassType();
+    assert appView.hasUnboxedEnums();
+    assert appView.unboxedEnums().isUnboxedEnum(dynamicUpperBoundClassType.getClassType());
+    return true;
   }
 
   public MutableMethodOptimizationInfo fixupAbstractReturnValue(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
-    abstractReturnValue = abstractReturnValue.rewrittenWithLens(appView, lens, codeLens);
+      AppView<AppInfoWithLiveness> appView,
+      DexEncodedMethod method,
+      GraphLens lens,
+      GraphLens codeLens) {
+    abstractReturnValue =
+        abstractReturnValue.rewrittenWithLens(appView, method.getReturnType(), lens, codeLens);
     return this;
   }
 
@@ -632,7 +659,13 @@
     clearFlag(NEVER_RETURNS_NORMALLY_FLAG);
   }
 
-  void markReturnsAbstractValue(AbstractValue value) {
+  void setAbstractReturnValue(AbstractValue value, DexEncodedMethod method) {
+    assert !value.isNull() || method.getReturnType().isReferenceType();
+    assert !value.isSingleNumberValue() || method.getReturnType().isPrimitiveType();
+    setAbstractReturnValue(value);
+  }
+
+  private void setAbstractReturnValue(AbstractValue value) {
     assert !abstractReturnValue.isSingleValue() || abstractReturnValue.equals(value)
         : "return single value changed from " + abstractReturnValue + " to " + value;
     abstractReturnValue = value;
@@ -643,6 +676,11 @@
   }
 
   void setDynamicType(AppView<?> appView, DynamicType newDynamicType, DexEncodedMethod method) {
+    setDynamicType(appView, newDynamicType, method.getReturnType().toTypeElement(appView));
+  }
+
+  public void setDynamicType(
+      AppView<?> appView, DynamicType newDynamicType, TypeElement staticReturnType) {
     assert newDynamicType != null;
     // We may get more precise type information if the method is reprocessed (e.g., due to
     // optimization info collected from all call sites), and hence the
@@ -651,14 +689,20 @@
     // Nullability could be less precise, though. For example, suppose a value is known to be
     // non-null after a safe invocation, hence recorded with the non-null variant. If that call is
     // inlined and the method is reprocessed, such non-null assumption cannot be made again.
-    assert verifyDynamicType(appView, newDynamicType, method);
-    dynamicType = newDynamicType;
+    assert verifyDynamicType(appView, newDynamicType, staticReturnType);
+    setDynamicType(newDynamicType);
+  }
+
+  private MutableMethodOptimizationInfo setDynamicType(DynamicType dynamicType) {
+    assert !dynamicType.hasDynamicUpperBoundType()
+        || !dynamicType.asDynamicTypeWithUpperBound().getDynamicUpperBoundType().isPrimitiveType();
+    this.dynamicType = dynamicType;
+    return this;
   }
 
   private boolean verifyDynamicType(
-      AppView<?> appView, DynamicType newDynamicType, DexEncodedMethod method) {
+      AppView<?> appView, DynamicType newDynamicType, TypeElement staticReturnType) {
     if (appView.enableWholeProgramOptimizations()) {
-      TypeElement staticReturnType = method.getReturnType().toTypeElement(appView);
       TypeElement previousDynamicUpperBoundType =
           dynamicType.getDynamicUpperBoundType(staticReturnType);
       TypeElement newDynamicUpperBoundType =
@@ -673,8 +717,8 @@
     return true;
   }
 
-  void unsetDynamicType() {
-    dynamicType = DynamicType.unknown();
+  MutableMethodOptimizationInfo unsetDynamicType() {
+    return setDynamicType(DynamicType.unknown());
   }
 
   // TODO(b/140214568): Should be package-private.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index bbb1cec..5999efb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -151,7 +151,7 @@
         .get(field.getReference())
         .hasReflectiveAccess();
     if (appView.appInfo().mayPropagateValueFor(appView, field.getReference())) {
-      getFieldOptimizationInfoForUpdating(field).setAbstractValue(abstractValue);
+      getFieldOptimizationInfoForUpdating(field).setAbstractValue(abstractValue, field);
     }
   }
 
@@ -188,7 +188,7 @@
   public synchronized void methodReturnsAbstractValue(
       DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue value) {
     if (appView.appInfo().mayPropagateValueFor(appView, method.getReference())) {
-      getMethodOptimizationInfoForUpdating(method).markReturnsAbstractValue(value);
+      getMethodOptimizationInfoForUpdating(method).setAbstractReturnValue(value, method);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index c49c8be..a440900 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -65,7 +65,7 @@
   public void recordFieldHasAbstractValue(
       DexEncodedField field, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue) {
     if (appView.appInfo().mayPropagateValueFor(appView, field.getReference())) {
-      field.getMutableOptimizationInfo().setAbstractValue(abstractValue);
+      field.getMutableOptimizationInfo().setAbstractValue(abstractValue, field);
     }
   }
 
@@ -111,7 +111,7 @@
   @Override
   public void methodReturnsAbstractValue(
       DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue value) {
-    method.getMutableOptimizationInfo().markReturnsAbstractValue(value);
+    method.getMutableOptimizationInfo().setAbstractReturnValue(value, method);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java
index d737cda..2683ff3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.info.field;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.graph.proto.ArgumentInfo;
 import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
@@ -62,7 +63,7 @@
 
   @Override
   public InstanceFieldInitializationInfo rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
+      AppView<AppInfoWithLiveness> appView, DexType newType, GraphLens lens, GraphLens codeLens) {
     // We don't have the context here to determine what should happen. It is the responsibility of
     // optimizations that change the proto of instance initializers to update the argument
     // initialization info.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java
index 856dfc0..003eef8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.info.field;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
@@ -51,5 +52,5 @@
       ArgumentInfoCollection argumentInfoCollection);
 
   InstanceFieldInitializationInfo rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens);
+      AppView<AppInfoWithLiveness> appView, DexType newType, GraphLens lens, GraphLens codeLens);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
index b095afc..0fecfb1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.info.field;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -55,7 +56,7 @@
 
   @Override
   public InstanceFieldInitializationInfo rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
+      AppView<AppInfoWithLiveness> appView, DexType newType, GraphLens lens, GraphLens codeLens) {
     EnumDataMap enumDataMap = appView.unboxedEnums();
     if (dynamicLowerBoundType != null
         && enumDataMap.isUnboxedEnum(dynamicLowerBoundType.getClassType())) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
index 88a7626..7fa9d04 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
@@ -85,7 +85,7 @@
         (field, info) ->
             builder.recordInitializationInfo(
                 lens.lookupField(field, codeLens),
-                info.rewrittenWithLens(appView, lens, codeLens)));
+                info.rewrittenWithLens(appView, field.getType(), lens, codeLens)));
     return builder.build();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java
index 356926c..e4faa42 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.info.field;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -37,7 +38,7 @@
 
   @Override
   public InstanceFieldInitializationInfo rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
+      AppView<AppInfoWithLiveness> appView, DexType newType, GraphLens lens, GraphLens codeLens) {
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java
index a4ea5c9..87e57d7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
-import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.ArrayGet;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlockIterator;
@@ -32,6 +31,7 @@
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo;
@@ -87,7 +87,7 @@
     Instruction replacement =
         appView
             .abstractValueFactory()
-            .createNullValue()
+            .createNullValue(memberType)
             .createMaterializingInstruction(appView, code, arrayGet);
     affectedValues.addAll(arrayGet.outValue().affectedValues());
     arrayGet.outValue().replaceUsers(replacement.outValue());
@@ -158,12 +158,12 @@
 
     AbstractValue abstractReturnValue;
     if (invokedMethod.getReturnType().isAlwaysNull(appView)) {
-      abstractReturnValue = appView.abstractValueFactory().createNullValue();
-    } else if (singleTarget != null) {
       abstractReturnValue =
-          singleTarget.getDefinition().getOptimizationInfo().getAbstractReturnValue();
+          appView.abstractValueFactory().createNullValue(invokedMethod.getReturnType());
     } else {
-      abstractReturnValue = UnknownValue.getInstance();
+      MethodOptimizationInfo optimizationInfo =
+          resolutionResult.getOptimizationInfo(appView, invoke, singleTarget);
+      abstractReturnValue = optimizationInfo.getAbstractReturnValue();
     }
 
     if (abstractReturnValue.isSingleValue()) {
@@ -275,7 +275,7 @@
 
     AbstractValue abstractValue;
     if (field.getType().isAlwaysNull(appView)) {
-      abstractValue = appView.abstractValueFactory().createSingleNumberValue(0);
+      abstractValue = appView.abstractValueFactory().createNullValue(field.getType());
     } else if (appView.appInfo().isFieldWrittenByFieldPutInstruction(target)) {
       abstractValue = definition.getOptimizationInfo().getAbstractValue();
       if (!definition.isStatic()) {
@@ -299,7 +299,7 @@
       assert verifyStaticFieldValueConsistentWithOptimizationInfo(appView, definition);
     } else {
       // This is guaranteed to read the default value of the field.
-      abstractValue = appView.abstractValueFactory().createSingleNumberValue(0);
+      abstractValue = appView.abstractValueFactory().createDefaultValue(field.getType());
     }
 
     if (abstractValue.isSingleValue()) {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
index f60fe64..ca847d1 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -54,20 +54,19 @@
       List<CfInstruction> instructions, AbstractValue value, DexType returnType) {
     // TODO(b/155368026): Support fields and const class fields.
     // Move this to something similar than SingleValue#createMaterializingInstruction
-    if (value.isSingleStringValue()) {
+    if (value.isNull()) {
+      assert returnType.isReferenceType();
+      instructions.add(new CfConstNull());
+    } else if (value.isSingleStringValue()) {
       assert returnType == appView.dexItemFactory().stringType;
       instructions.add(new CfConstString(value.asSingleStringValue().getDexString()));
     } else if (value.isSingleNumberValue()) {
-      if (returnType.isReferenceType()) {
-        assert value.isNull();
-        instructions.add(new CfConstNull());
-      } else {
-        instructions.add(
-            new CfConstNumber(
-                value.asSingleNumberValue().getValue(), ValueType.fromDexType(returnType)));
-      }
+      assert returnType.isPrimitiveType();
+      instructions.add(
+          new CfConstNumber(
+              value.asSingleNumberValue().getValue(), ValueType.fromDexType(returnType)));
     } else {
-      throw new Unreachable("Only Number and String fields in enums are supported.");
+      throw new Unreachable("Unsupported value: " + value);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationArgumentInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationArgumentInfo.java
index 5d67400..2c05e8f 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationArgumentInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationArgumentInfo.java
@@ -68,7 +68,8 @@
 
     private static KotlinAnnotationClassValueInfo create(KClassValue arg, DexItemFactory factory) {
       return new KotlinAnnotationClassValueInfo(
-          KotlinTypeReference.fromBinaryName(arg.getClassName(), factory, arg.getClassName()),
+          KotlinTypeReference.fromBinaryNameOrKotlinClassifier(
+              arg.getClassName(), factory, arg.getClassName()),
           arg.getArrayDimensionCount());
     }
 
@@ -98,7 +99,7 @@
 
     private static KotlinAnnotationEnumValueInfo create(EnumValue arg, DexItemFactory factory) {
       return new KotlinAnnotationEnumValueInfo(
-          KotlinTypeReference.fromBinaryName(
+          KotlinTypeReference.fromBinaryNameOrKotlinClassifier(
               arg.getEnumClassName(), factory, arg.getEnumClassName()),
           arg.getEnumEntryName());
     }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java
index 759ef69..0904502 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java
@@ -34,7 +34,7 @@
 
   static KotlinAnnotationInfo create(KmAnnotation annotation, DexItemFactory factory) {
     return new KotlinAnnotationInfo(
-        KotlinTypeReference.fromBinaryName(
+        KotlinTypeReference.fromBinaryNameOrKotlinClassifier(
             annotation.getClassName(), factory, annotation.getClassName()),
         KotlinAnnotationArgumentInfo.create(annotation.getArguments(), factory));
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
index 66c4f2e..8e60aa7 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
@@ -200,7 +200,7 @@
       KmClass kmClass, DexItemFactory factory) {
     String anonymousObjectOriginName = JvmExtensionsKt.getAnonymousObjectOriginName(kmClass);
     if (anonymousObjectOriginName != null) {
-      return KotlinTypeReference.fromBinaryName(
+      return KotlinTypeReference.fromBinaryNameOrKotlinClassifier(
           anonymousObjectOriginName, factory, anonymousObjectOriginName);
     }
     return null;
@@ -212,7 +212,8 @@
     for (String nestedClass : nestedClasses) {
       String binaryName =
           clazz.type.toBinaryName() + DescriptorUtils.INNER_CLASS_SEPARATOR + nestedClass;
-      nestedTypes.add(KotlinTypeReference.fromBinaryName(binaryName, factory, nestedClass));
+      nestedTypes.add(
+          KotlinTypeReference.fromBinaryNameOrKotlinClassifier(binaryName, factory, nestedClass));
     }
     return nestedTypes.build();
   }
@@ -224,7 +225,9 @@
       String binaryName =
           sealedSubClass.replace(
               DescriptorUtils.JAVA_PACKAGE_SEPARATOR, DescriptorUtils.INNER_CLASS_SEPARATOR);
-      sealedTypes.add(KotlinTypeReference.fromBinaryName(binaryName, factory, sealedSubClass));
+      sealedTypes.add(
+          KotlinTypeReference.fromBinaryNameOrKotlinClassifier(
+              binaryName, factory, sealedSubClass));
     }
     return sealedTypes.build();
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
index 1e387ff..dc949fc 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
@@ -110,7 +110,7 @@
       KmFunction kmFunction, DexItemFactory factory) {
     String lambdaClassOriginName = JvmExtensionsKt.getLambdaClassOriginName(kmFunction);
     if (lambdaClassOriginName != null) {
-      return KotlinTypeReference.fromBinaryName(
+      return KotlinTypeReference.fromBinaryNameOrKotlinClassifier(
           lambdaClassOriginName, factory, lambdaClassOriginName);
     }
     return null;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
index 9757ef4..0833a1c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
@@ -40,7 +40,9 @@
       DexItemFactory factory) {
     ImmutableList.Builder<KotlinTypeReference> builder = ImmutableList.builder();
     for (String partClassName : kmMultiFileClassFacade.getPartClassNames()) {
-      builder.add(KotlinTypeReference.fromBinaryName(partClassName, factory, partClassName));
+      builder.add(
+          KotlinTypeReference.fromBinaryNameOrKotlinClassifier(
+              partClassName, factory, partClassName));
     }
     return new KotlinMultiFileClassFacadeInfo(builder.build(), packageName, metadataVersion);
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java
index 553c174..700b508 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java
@@ -25,15 +25,22 @@
 
   private final DexType known;
   private final String originalName;
+  // A KotlinTypeReference may be either a Kotlin classifier or a binary name.
+  // We need to maintain the right type to avoid unnecessary difference between original and
+  // rewritten metadata.
+  private final boolean exportAsKotlinClassifier;
 
-  private KotlinTypeReference(String originalName, DexType known) {
+  private KotlinTypeReference(
+      String originalName, DexType known, boolean exportAsKotlinClassifier) {
     this.originalName = originalName;
     this.known = known;
+    this.exportAsKotlinClassifier = exportAsKotlinClassifier;
     assert known != null;
   }
 
   private KotlinTypeReference(String originalName) {
     this.known = null;
+    this.exportAsKotlinClassifier = false;
     this.originalName = originalName;
     assert originalName != null;
   }
@@ -46,24 +53,40 @@
     return originalName;
   }
 
-  static KotlinTypeReference fromBinaryName(
-      String binaryName, DexItemFactory factory, String originalName) {
-    if (DescriptorUtils.isValidBinaryName(binaryName)) {
+  static KotlinTypeReference fromBinaryNameOrKotlinClassifier(
+      String binaryNameOrKotlinClassifier, DexItemFactory factory, String originalName) {
+    // Kotlin classifiers are valid binary names.
+    if (DescriptorUtils.isValidBinaryName(binaryNameOrKotlinClassifier)) {
+      boolean interpretAsKotlinClassifier =
+          shouldBeInterpretedAsKotlinClassifier(binaryNameOrKotlinClassifier);
       return fromDescriptor(
-          DescriptorUtils.getDescriptorFromClassBinaryName(binaryName), factory, originalName);
+          interpretAsKotlinClassifier
+              ? DescriptorUtils.getDescriptorFromKotlinClassifier(binaryNameOrKotlinClassifier)
+              : DescriptorUtils.getDescriptorFromClassBinaryName(binaryNameOrKotlinClassifier),
+          factory,
+          originalName,
+          interpretAsKotlinClassifier);
     }
-    return new KotlinTypeReference(binaryName);
+    return new KotlinTypeReference(binaryNameOrKotlinClassifier);
+  }
+
+  private static boolean shouldBeInterpretedAsKotlinClassifier(
+      String binaryNameOrKotlinClassifier) {
+    return binaryNameOrKotlinClassifier.contains(".");
   }
 
   static KotlinTypeReference fromDescriptor(String descriptor, DexItemFactory factory) {
-    return fromDescriptor(descriptor, factory, descriptor);
+    return fromDescriptor(descriptor, factory, descriptor, false);
   }
 
   static KotlinTypeReference fromDescriptor(
-      String descriptor, DexItemFactory factory, String originalName) {
+      String descriptor,
+      DexItemFactory factory,
+      String originalName,
+      boolean exportAsKotlinClassifier) {
     if (DescriptorUtils.isDescriptor(descriptor)) {
       DexType type = factory.createType(descriptor);
-      return new KotlinTypeReference(originalName, type);
+      return new KotlinTypeReference(originalName, type, exportAsKotlinClassifier);
     }
     return new KotlinTypeReference(originalName);
   }
@@ -117,7 +140,11 @@
           if (descriptor == null || descriptor.equals(defaultValue)) {
             rewrittenConsumer.accept(descriptor);
           } else {
-            rewrittenConsumer.accept(DescriptorUtils.getBinaryNameFromDescriptor(descriptor));
+            String rewritten =
+                exportAsKotlinClassifier
+                    ? DescriptorUtils.descriptorToKotlinClassifier(descriptor)
+                    : DescriptorUtils.getBinaryNameFromDescriptor(descriptor);
+            rewrittenConsumer.accept(rewritten);
           }
         },
         appView,
diff --git a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
index a6f4321..877794a 100644
--- a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
+++ b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
@@ -99,7 +99,6 @@
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.conversion.ExtraParameter;
-import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.StringSwitchConverter;
 import com.android.tools.r8.lightir.LirBuilder.IntSwitchPayload;
@@ -328,7 +327,7 @@
       for (ExtraParameter extraParameter : protoChanges.getExtraParameters()) {
         int newArgumentIndex = argumentsInfo.getNewArgumentIndex(index, numberOfRemovedArguments);
         DexType extraArgumentType = method.getArgumentType(newArgumentIndex);
-        if (extraParameter instanceof ExtraUnusedNullParameter) {
+        if (extraParameter.isUnused()) {
           // Note that we do *not* increment the index here as that would shift the SSA value map.
           addUnusedArgument(extraArgumentType);
         } else {
diff --git a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
index ef75e94..4f1544e 100644
--- a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
@@ -384,7 +384,7 @@
             ? MethodSignatureEquivalence.get()
             : MethodJavaSignatureEquivalence.get();
     this.definitionEquivalence =
-        new Equivalence<DexEncodedMethod>() {
+        new Equivalence<>() {
           @Override
           protected boolean doEquivalent(DexEncodedMethod method, DexEncodedMethod other) {
             return equivalence.equivalent(method.getReference(), other.getReference());
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 64e6247..ccfeddc 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
+import static com.android.tools.r8.utils.MapUtils.unmodifiableForTesting;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -18,13 +21,19 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -234,10 +243,9 @@
                           namingStates
                               .getOrDefault(clazz.superType, rootNamingState)
                               .createChild(reservationState));
-              DexClass holder = appView.definitionFor(type);
-              if (holder != null && strategy.allowMemberRenaming(holder)) {
-                for (DexEncodedMethod method : holder.allMethodsSorted()) {
-                  assignNameToMethod(holder, method, namingState);
+              if (strategy.allowMemberRenaming(clazz)) {
+                for (DexEncodedMethod method : clazz.allMethodsSorted()) {
+                  assignNameToMethod(clazz, method, namingState);
                 }
               }
             });
@@ -312,15 +320,76 @@
         reservationStates.computeIfAbsent(frontier, ignore -> parent.createChild());
     DexClass holder = appView.definitionFor(type);
     if (holder != null) {
-      for (DexEncodedMethod method : shuffleMethods(holder.methods(), appView.options())) {
+      // When javac compiles code against a class-path it will assume that the target of a synthetic
+      // bridge has the same name as the bridge.
+      // <pre>
+      //   public class I { Result foo(); }
+      //   public class Impl {
+      //     ResultImpl foo() { ... }  <-- this needs to be named foo
+      //     Result {synthetic,bridge} foo() { invoke-virtual foo():()ResultImpl; }
+      //   }
+      // </pre>
+      // In general we should consider the bridge and target as a group and reserve/rename as one
+      // however that is difficult in the current setup of reservation states. We therefore allow
+      // them to differ unless there is a kept/reserved method. For one to observe this one would
+      // have to do byte-code rewriting against a mapping file to observe the issue. Doing that they
+      // may as well just adjust the keep rules to keep the targets of bridges.
+      // See b/290711987 for an actual issue regarding this.
+      Set<DexEncodedMethod> bridgeMethodCandidates = Sets.newIdentityHashSet();
+      Iterable<DexEncodedMethod> methods = shuffleMethods(holder.methods(), appView.options());
+      for (DexEncodedMethod method : methods) {
         DexString reservedName = strategy.getReservedName(method, holder);
         if (reservedName != null) {
           state.reserveName(reservedName, method);
+        } else if (appView.options().isGeneratingClassFiles() && method.isSyntheticBridgeMethod()) {
+          bridgeMethodCandidates.add(method);
+        }
+      }
+      Map<DexString, Set<Integer>> methodNamesToReserve =
+          computeBridgesThatAreReserved(holder, bridgeMethodCandidates);
+      if (!methodNamesToReserve.isEmpty()) {
+        for (DexEncodedMethod method : methods) {
+          if (methodNamesToReserve
+              .getOrDefault(method.getName(), Collections.emptySet())
+              .contains(method.getProto().getArity())) {
+            state.reserveName(method.getName(), method);
+          }
         }
       }
     }
   }
 
+  private Map<DexString, Set<Integer>> computeBridgesThatAreReserved(
+      DexClass holder, Set<DexEncodedMethod> methods) {
+    if (methods.isEmpty()) {
+      return Collections.emptyMap();
+    }
+    WorkList<DexClass> workList = WorkList.newIdentityWorkList(holder);
+    Map<DexString, Set<Integer>> reservedNamesWithArity = new HashMap<>();
+    while (workList.hasNext()) {
+      DexClass clazz = workList.next();
+      MethodReservationState<?> state = reservationStates.get(frontiers.get(clazz.getType()));
+      if (state != null) {
+        methods.forEach(
+            bridgeMethod -> {
+              if (state.isReserved(bridgeMethod.getName(), bridgeMethod.getReference())) {
+                reservedNamesWithArity
+                    .computeIfAbsent(bridgeMethod.getName(), ignoreArgument(HashSet::new))
+                    .add(bridgeMethod.getProto().getArity());
+              }
+            });
+      }
+      clazz.forEachImmediateSupertype(
+          superType -> {
+            DexClass superClass = appView.definitionFor(superType);
+            if (superClass != null) {
+              workList.addIfNotSeen(superClass);
+            }
+          });
+    }
+    return unmodifiableForTesting(reservedNamesWithArity);
+  }
+
   @SuppressWarnings("ReferenceEquality")
   private MethodNamingState<?> getOrAllocateMethodNamingStates(DexType type) {
     MethodNamingState<?> namingState = namingStates.get(type);
diff --git a/src/main/java/com/android/tools/r8/naming/MethodReservationState.java b/src/main/java/com/android/tools/r8/naming/MethodReservationState.java
index b963b3f..61aed28 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodReservationState.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodReservationState.java
@@ -98,8 +98,7 @@
         originalToReservedNames = new HashMap<>();
         reservedNames = new HashSet<>();
       }
-      final Wrapper<DexMethod> wrapped =
-          MethodSignatureEquivalence.get().wrap(method.getReference());
+      Wrapper<DexMethod> wrapped = MethodSignatureEquivalence.get().wrap(method.getReference());
       originalToReservedNames.computeIfAbsent(wrapped, ignore -> new HashSet<>()).add(name);
       reservedNames.add(name);
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
index 4f97afa..12a71ce 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
@@ -135,7 +135,7 @@
               @Override
               public void fixup(
                   DexEncodedField field, MutableFieldOptimizationInfo optimizationInfo) {
-                optimizationInfo.fixupAbstractValue(appView, graphLens, codeLens);
+                optimizationInfo.fixupAbstractValue(appView, field, graphLens, codeLens);
               }
 
               @Override
@@ -144,7 +144,7 @@
                 // Fixup the return value in case the method returns a field that had its signature
                 // changed.
                 optimizationInfo
-                    .fixupAbstractReturnValue(appView, graphLens, codeLens)
+                    .fixupAbstractReturnValue(appView, method, graphLens, codeLens)
                     .fixupInstanceInitializerInfo(appView, graphLens, codeLens, prunedItems);
 
                 // Rewrite the optimization info to account for method signature changes.
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
index 0991b0f..1a94bc3 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
@@ -285,7 +285,8 @@
                 return parameterState;
               }
               return new ConcreteClassTypeParameterState(
-                  appView.abstractValueFactory().createNullValue(), DynamicType.definitelyNull());
+                  appView.abstractValueFactory().createNullValue(argumentType),
+                  DynamicType.definitelyNull());
             },
             null);
     return narrowedParameterStates != null
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
index aa25c14..fbce71a 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
@@ -30,6 +30,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
+import com.android.tools.r8.ir.conversion.ExtraUnusedIntParameter;
 import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
@@ -457,7 +458,7 @@
         }
         AbstractValue returnValueForMethod =
             method.getReturnType().isAlwaysNull(appView)
-                ? appView.abstractValueFactory().createNullValue()
+                ? appView.abstractValueFactory().createNullValue(method.getReturnType())
                 : method.getOptimizationInfo().getAbstractReturnValue();
         if (!returnValueForMethod.isSingleValue()
             || !returnValueForMethod.asSingleValue().isMaterializableInAllContexts(appView)
@@ -863,7 +864,10 @@
         for (DexType extraArgumentType :
             ImmutableList.of(dexItemFactory.intType, dexItemFactory.objectType)) {
           RewrittenPrototypeDescription candidatePrototypeChanges =
-              prototypeChanges.withExtraParameters(new ExtraUnusedNullParameter(extraArgumentType));
+              prototypeChanges.withExtraParameters(
+                  extraArgumentType.isIntType()
+                      ? new ExtraUnusedIntParameter()
+                      : new ExtraUnusedNullParameter(extraArgumentType));
           rewrittenMethod = candidatePrototypeChanges.rewriteMethod(method, dexItemFactory);
           if (instanceInitializerSignatures.add(rewrittenMethod)) {
             return candidatePrototypeChanges;
@@ -1021,7 +1025,7 @@
     private SingleValue getReturnValue(ProgramMethod method) {
       AbstractValue returnValue;
       if (method.getReturnType().isAlwaysNull(appView)) {
-        returnValue = appView.abstractValueFactory().createNullValue();
+        returnValue = appView.abstractValueFactory().createNullValue(method.getReturnType());
       } else if (method.getDefinition().belongsToVirtualPool()
           && returnValuesForVirtualMethods.containsKey(method)) {
         assert method.getAccessFlags().isAbstract();
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeParameterState.java
index 9a9de09..801ada2 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeParameterState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeParameterState.java
@@ -50,7 +50,7 @@
   @Override
   public AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) {
     if (getNullability().isDefinitelyNull()) {
-      return appView.abstractValueFactory().createNullValue();
+      return appView.abstractValueFactory().createUncheckedNullValue();
     }
     return AbstractValue.unknown();
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java
index c0dab86..93683aa 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java
@@ -55,7 +55,7 @@
   public AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) {
     if (getDynamicType().getNullability().isDefinitelyNull()) {
       assert abstractValue.isNull() || abstractValue.isUnknown();
-      return appView.abstractValueFactory().createNullValue();
+      return appView.abstractValueFactory().createUncheckedNullValue();
     }
     return abstractValue;
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/DepthFirstTopDownClassHierarchyTraversal.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/DepthFirstTopDownClassHierarchyTraversal.java
index cc81166..a843a58 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/DepthFirstTopDownClassHierarchyTraversal.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/DepthFirstTopDownClassHierarchyTraversal.java
@@ -84,13 +84,12 @@
   }
 
   public boolean isRoot(DexProgramClass clazz) {
-    DexType superType = clazz.getSuperType();
-    if (superType == null) {
-      return true;
-    }
-    DexProgramClass superclass = asProgramClassOrNull(appView.definitionFor(superType));
-    if (superclass != null) {
-      return false;
+    if (clazz.hasSuperType()) {
+      DexType superType = clazz.getSuperType();
+      DexProgramClass superClass = asProgramClassOrNull(appView.definitionFor(superType));
+      if (superClass != null) {
+        return false;
+      }
     }
     for (DexType implementedType : clazz.getInterfaces()) {
       DexProgramClass implementedClass =
@@ -159,11 +158,11 @@
     immediateSubtypingInfo.getSubclasses(clazz).forEach(consumer);
   }
 
-  private void processClass(DexProgramClass interfaceDefinition) {
-    assert !isClassSeenButNotFinished(interfaceDefinition);
-    assert !isClassFinished(interfaceDefinition);
-    visit(interfaceDefinition);
-    markSeenButNotFinished(interfaceDefinition);
+  private void processClass(DexProgramClass clazz) {
+    assert !isClassSeenButNotFinished(clazz);
+    assert !isClassFinished(clazz);
+    visit(clazz);
+    markSeenButNotFinished(clazz);
   }
 
   protected boolean isClassUnseen(DexProgramClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java
index 9f7cbc7..726ab9d 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
@@ -132,7 +133,8 @@
     AbstractValueFactory abstractValueFactory = appView.abstractValueFactory();
     switch (type) {
       case BOOLEAN:
-        return abstractValueFactory.createSingleNumberValue(BooleanUtils.intValue(booleanValue));
+        return abstractValueFactory.createSingleNumberValue(
+            BooleanUtils.intValue(booleanValue), TypeElement.getBoolean());
 
       case FIELD:
         DexClass holder = appView.definitionFor(fieldHolder);
@@ -147,17 +149,17 @@
 
       case NULLABILITY:
         return nullability.isDefinitelyNull()
-            ? abstractValueFactory.createNullValue()
+            ? abstractValueFactory.createUncheckedNullValue()
             : AbstractValue.unknown();
 
       case VALUE_RANGE:
         if (valueType.isReferenceType()) {
-          return nullability.isDefinitelyNull()
-              ? abstractValueFactory.createNullValue()
-              : AbstractValue.unknown();
+          assert false;
+          return AbstractValue.unknown();
         }
         return longInterval.isSingleValue()
-            ? abstractValueFactory.createSingleNumberValue(longInterval.getSingleValue())
+            ? abstractValueFactory.createSingleNumberValue(
+                longInterval.getSingleValue(), TypeElement.getLong())
             : abstractValueFactory.createNumberFromIntervalValue(
                 longInterval.getMin(), longInterval.getMax());
 
diff --git a/src/main/java/com/android/tools/r8/utils/BitSetUtils.java b/src/main/java/com/android/tools/r8/utils/BitSetUtils.java
index 085a368..80e1790 100644
--- a/src/main/java/com/android/tools/r8/utils/BitSetUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/BitSetUtils.java
@@ -23,6 +23,16 @@
     return newBitSet;
   }
 
+  // A null bit set is interpreted as the empty bit set.
+  public static BitSet intersectNullableBitSets(BitSet bitSet, BitSet other) {
+    if (bitSet == null || other == null) {
+      return null;
+    }
+    BitSet result = (BitSet) bitSet.clone();
+    result.and(other);
+    return result.isEmpty() ? null : result;
+  }
+
   public static boolean verifyLessThanOrEqualTo(BitSet bitSet, BitSet other) {
     assert other.equals(or(bitSet, other));
     return true;
diff --git a/src/main/java/com/android/tools/r8/utils/MethodSignatureEquivalence.java b/src/main/java/com/android/tools/r8/utils/MethodSignatureEquivalence.java
index 6a8d2c9..01f4031 100644
--- a/src/main/java/com/android/tools/r8/utils/MethodSignatureEquivalence.java
+++ b/src/main/java/com/android/tools/r8/utils/MethodSignatureEquivalence.java
@@ -13,38 +13,21 @@
  */
 public class MethodSignatureEquivalence extends Equivalence<DexMethod> {
 
-  private static final MethodSignatureEquivalence INSTANCE = new MethodSignatureEquivalence(false);
+  private static final MethodSignatureEquivalence INSTANCE = new MethodSignatureEquivalence();
 
-  private static final MethodSignatureEquivalence INSTANCE_IGNORE_NAME =
-      new MethodSignatureEquivalence(true);
-
-  private final boolean ignoreName;
-
-  private MethodSignatureEquivalence(boolean ignoreName) {
-    this.ignoreName = ignoreName;
-  }
+  private MethodSignatureEquivalence() {}
 
   public static MethodSignatureEquivalence get() {
     return INSTANCE;
   }
 
-  public static MethodSignatureEquivalence getEquivalenceIgnoreName() {
-    return INSTANCE_IGNORE_NAME;
-  }
-
   @Override
   protected boolean doEquivalent(DexMethod a, DexMethod b) {
-    if (ignoreName) {
-      return a.proto.equals(b.proto);
-    }
     return a.name.equals(b.name) && a.proto.equals(b.proto);
   }
 
   @Override
   protected int doHash(DexMethod method) {
-    if (ignoreName) {
-      return method.proto.hashCode();
-    }
     return method.name.hashCode() * 31 + method.proto.hashCode();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/NumberUtils.java b/src/main/java/com/android/tools/r8/utils/NumberUtils.java
index ca5dc22..ac1ee2c 100644
--- a/src/main/java/com/android/tools/r8/utils/NumberUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/NumberUtils.java
@@ -8,6 +8,13 @@
 
 public class NumberUtils {
 
+  public static int getIfEqualsOrDefault(int value, int otherValue, int defaultValue) {
+    if (value == otherValue) {
+      return value;
+    }
+    return defaultValue;
+  }
+
   public static boolean is4Bit(long value) {
     return Constants.S4BIT_MIN <= value && value <= Constants.S4BIT_MAX;
   }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureMap.java b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureMap.java
index bbf5afb..39377e5 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureMap.java
@@ -80,6 +80,10 @@
     return backing.getOrDefault(key, defaultValue);
   }
 
+  public T getOrDefault(DexEncodedMethod method, T defaultValue) {
+    return getOrDefault(method.getSignature(), defaultValue);
+  }
+
   @Override
   public void forEach(BiConsumer<? super DexMethodSignature, ? super T> action) {
     backing.forEach(action);
@@ -170,6 +174,10 @@
     return backing.get(key);
   }
 
+  public T get(DexEncodedMethod method) {
+    return get(method.getSignature());
+  }
+
   public boolean containsKey(DexMethodSignature signature) {
     return backing.containsKey(signature);
   }
diff --git a/src/main/keep.txt b/src/main/keep.txt
index 89105b4..3e23b48 100644
--- a/src/main/keep.txt
+++ b/src/main/keep.txt
@@ -33,22 +33,6 @@
 # TODO(b/176783536): Avoid need to use -dontwarn.
 -include dontwarn.txt
 
-# TODO(b/185756596): Remove when no longer needed
--keep class com.android.tools.r8.jetbrains.kotlinx.metadata.jvm.KotlinClassMetadata$FileFacade {
-  com.android.tools.r8.jetbrains.kotlin.Lazy packageData$delegate;
-}
--keep class com.android.tools.r8.jetbrains.kotlinx.metadata.jvm.KotlinClassMetadata$Class {
-  com.android.tools.r8.jetbrains.kotlin.Lazy classData$delegate;
-}
--keep class com.android.tools.r8.jetbrains.kotlinx.metadata.jvm.KotlinClassMetadata$SyntheticClass {
-  com.android.tools.r8.jetbrains.kotlin.Lazy functionData$delegate;
-}
--keep class com.android.tools.r8.jetbrains.kotlinx.metadata.jvm.KotlinClassMetadata$MultiFileClassPart {
-  com.android.tools.r8.jetbrains.kotlin.Lazy packageData$delegate;
-}
--keep class com.android.tools.r8.jetbrains.kotlin.SafePublicationLazyImpl {
-  java.lang.Object getValue();
-}
 # Test in this class is using the class name to fing the original .java file.
 -keep class com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaringMethods
 
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 8cd7133..238594e 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -425,8 +425,8 @@
   public T enableAssumeNotNullAnnotations(String annotationPackageName) {
     return addInternalKeepRules(
         "-assumevalues class * {",
-        "  @" + annotationPackageName + ".AssumeNotNull *** * return 1;",
-        "  @" + annotationPackageName + ".AssumeNotNull *** *(...) return 1;",
+        "  @" + annotationPackageName + ".AssumeNotNull *** * return _NONNULL_;",
+        "  @" + annotationPackageName + ".AssumeNotNull *** *(...) return _NONNULL_;",
         "}");
   }
 
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index 276f815..f65ed07 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -8,7 +8,6 @@
 import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeFalse;
 
@@ -657,15 +656,15 @@
                 ImmutableList.copyOf(jars),
                 javaArgs.toArray(StringUtils.EMPTY_ARRAY));
         assertEquals("JVM run failed", javaResult.exitCode, 0);
-        assertTrue(
-            "JVM output does not match art output.\n\tjvm: "
-                + javaResult.stdout
-                + "\n\tart: "
-                + output,
-            output.replace("\r", "").equals(javaResult.stdout.replace("\r", "")));
+        assertEquals(
+            "JVM output does not match art output.",
+            javaResult.stdout.replace("\r", ""),
+            output.replace("\r", ""));
       }
     } catch (Throwable t) {
-      assertTrue("Test was not expected to fail. Failed with " + t.getMessage(), expectedToFail);
+      if (!expectedToFail) {
+        throw new AssertionError("Test was not expected to fail", t);
+      }
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index d902754..8cacb48 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -70,6 +70,7 @@
 import java.nio.file.Paths;
 import java.nio.file.SimpleFileVisitor;
 import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -82,6 +83,7 @@
 import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executors;
 import java.util.function.Consumer;
@@ -752,6 +754,61 @@
     }
   }
 
+  public static class CommandCacheStatistics {
+
+    public static CommandCacheStatistics INSTANCE = new CommandCacheStatistics();
+    private final Path cachePutCounter;
+    private final Path cacheMissCounter;
+    private final Path cacheHitCounter;
+
+    private CommandCacheStatistics() {
+      String commandCacheStatsDir = System.getProperty("command_cache_stats_dir");
+      if (commandCacheStatsDir != null) {
+        String processSpecificUUID = UUID.randomUUID().toString();
+        cachePutCounter = Paths.get(commandCacheStatsDir, processSpecificUUID + "CACHEPUT");
+        cacheMissCounter = Paths.get(commandCacheStatsDir, processSpecificUUID + "CACHEFAIL");
+        cacheHitCounter = Paths.get(commandCacheStatsDir, processSpecificUUID + "CACHEHIT");
+        try {
+          Files.createFile(cachePutCounter);
+          Files.createFile(cacheMissCounter);
+          Files.createFile(cacheHitCounter);
+        } catch (IOException e) {
+          throw new RuntimeException(e);
+        }
+      } else {
+        cachePutCounter = null;
+        cacheMissCounter = null;
+        cacheHitCounter = null;
+      }
+    }
+
+    private static void increaseCount(Path path) {
+      // Not enabled
+      if (path == null) {
+        return;
+      }
+      synchronized (path) {
+        try {
+          Files.write(path, "X".getBytes(StandardCharsets.UTF_8), StandardOpenOption.APPEND);
+        } catch (IOException e) {
+          throw new RuntimeException(e);
+        }
+      }
+    }
+
+    public void addCachePut() {
+      increaseCount(cachePutCounter);
+    }
+
+    public void addCacheHit() {
+      increaseCount(cacheHitCounter);
+    }
+
+    public void addCacheMiss() {
+      increaseCount(cacheMissCounter);
+    }
+  }
+
   public static class CommandResultCache {
     private static CommandResultCache INSTANCE =
         System.getProperty("command_cache_dir") != null
@@ -789,7 +846,7 @@
     }
 
     private Path getTempFile(Path path) {
-      return Paths.get(path.toString() + ".temp" + Thread.currentThread().getId());
+      return Paths.get(path.toString() + ".temp" + UUID.randomUUID());
     }
 
     private String getStringContent(Path path) {
@@ -815,6 +872,7 @@
         // but this should have no impact.
 
         Path outputFile = getOutputFile(cacheLookupKey);
+        CommandCacheStatistics.INSTANCE.addCacheHit();
         return new Pair(
             new ProcessResult(
                 exitCode,
@@ -822,23 +880,25 @@
                 getStringContent(getStderrFile(cacheLookupKey))),
             outputFile.toFile().exists() ? outputFile : null);
       }
+      CommandCacheStatistics.INSTANCE.addCacheMiss();
       return null;
     }
 
     public void putResult(ProcessResult result, CacheLookupKey cacheLookupKey, Path output) {
+      Path exitCodeFile = getExitCodeFile(cacheLookupKey);
+      Path exitCodeTempFile = getTempFile(exitCodeFile);
+      Path stdoutFile = getStdoutFile(cacheLookupKey);
+      Path stdoutTempFile = getTempFile(stdoutFile);
+      Path stderrFile = getStderrFile(cacheLookupKey);
+      Path stderrTempFile = getTempFile(stderrFile);
+      Path outputFile = getOutputFile(cacheLookupKey);
+      Path outputTempFile = getTempFile(outputFile);
+
       try {
         String exitCode = "" + result.exitCode;
         // We avoid race conditions of writing vs reading by first writing all 3 files to temp
         // files, then moving these to the result files, moving last the exitcode file (which is
         // what we use as cache present check)
-        Path exitCodeFile = getExitCodeFile(cacheLookupKey);
-        Path exitCodeTempFile = getTempFile(exitCodeFile);
-        Path stdoutFile = getStdoutFile(cacheLookupKey);
-        Path stdoutTempFile = getTempFile(stdoutFile);
-        Path stderrFile = getStderrFile(cacheLookupKey);
-        Path stderrTempFile = getTempFile(stderrFile);
-        Path outputfile = getOutputFile(cacheLookupKey);
-        Path outputTempFile = getTempFile(outputfile);
         Files.write(exitCodeTempFile, exitCode.getBytes(StandardCharsets.UTF_8));
         Files.write(stdoutTempFile, result.stdout.getBytes(StandardCharsets.UTF_8));
         Files.write(stderrTempFile, result.stderr.getBytes(StandardCharsets.UTF_8));
@@ -859,7 +919,7 @@
         if (output != null) {
           Files.move(
               outputTempFile,
-              outputfile,
+              outputFile,
               StandardCopyOption.ATOMIC_MOVE,
               StandardCopyOption.REPLACE_EXISTING);
         }
@@ -868,8 +928,15 @@
             exitCodeFile,
             StandardCopyOption.ATOMIC_MOVE,
             StandardCopyOption.REPLACE_EXISTING);
+        CommandCacheStatistics.INSTANCE.addCachePut();
       } catch (IOException e) {
-        throw new RuntimeException(e);
+        StringBuilder exceptionMessage = new StringBuilder();
+        exceptionMessage.append(
+            "Files.exists(exitCodeTempFile) = " + Files.exists(exitCodeTempFile));
+        exceptionMessage.append("Files.exists(stdoutTempFile) = " + Files.exists(stdoutTempFile));
+        exceptionMessage.append("Files.exists(stderrTempFile) = " + Files.exists(stderrTempFile));
+        exceptionMessage.append("Files.exists(outputTempFile) = " + Files.exists(outputTempFile));
+        throw new RuntimeException(exceptionMessage.toString(), e);
       }
     }
   }
@@ -1281,7 +1348,7 @@
   public static TemporaryFolder getTemporaryFolderForTest() {
     String tmpDir = System.getProperty("test_dir");
     if (tmpDir == null) {
-      return new TemporaryFolder(ToolHelper.isLinux() ? null : Paths.get("build", "tmp").toFile());
+      return new TemporaryFolder();
     } else {
       return new RetainedTemporaryFolder(new java.io.File(tmpDir));
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BigDecimalBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BigDecimalBackportTest.java
new file mode 100644
index 0000000..edb29f1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BigDecimalBackportTest.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2023, 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 com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.math.BigDecimal;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public final class BigDecimalBackportTest extends AbstractBackportTest {
+  @Parameters(name = "{0}")
+  public static Iterable<?> data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public BigDecimalBackportTest(TestParameters parameters) {
+    super(parameters, BigDecimal.class, Main.class);
+    registerTarget(AndroidApiLevel.Q, 3);
+    ignoreInvokes("valueOf");
+    ignoreInvokes("toString");
+  }
+
+  static final class Main extends MiniAssert {
+    public static void main(String[] args) {
+      testStripTrailingZeros();
+    }
+
+    private static void testStripTrailingZeros() {
+      assertEquals("0", BigDecimal.valueOf(0.0).stripTrailingZeros().toString());
+      assertEquals("1", BigDecimal.valueOf(1.0).stripTrailingZeros().toString());
+      assertEquals("-1", BigDecimal.valueOf(-1.0).stripTrailingZeros().toString());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
index 1a744bb..1071a87 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableSet;
+import java.math.BigDecimal;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
@@ -70,10 +71,17 @@
                 "compareAndSet", int.class, Object.class, Object.class));
     assert AtomicReference_compareAndSet.getReturnType().getTypeName().equals("boolean");
 
+    MethodReference BigDecimal_stripTrailingZeros =
+        Reference.methodFromMethod(BigDecimal.class.getDeclaredMethod("stripTrailingZeros"));
+    assert BigDecimal_stripTrailingZeros.getReturnType()
+        .getTypeName()
+        .equals("java.math.BigDecimal");
+
     return ImmutableSet.of(
         factory.createMethod(AtomicReferenceFieldUpdater_compareAndSet),
         factory.createMethod(AtomicReference_compareAndSet),
-        factory.createMethod(AtomicReferenceArray_compareAndSet));
+        factory.createMethod(AtomicReferenceArray_compareAndSet),
+        factory.createMethod(BigDecimal_stripTrailingZeros));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredMethodsListTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredMethodsListTest.java
index 451d62c..25494ca 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredMethodsListTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredMethodsListTest.java
@@ -11,17 +11,25 @@
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.DesugaredMethodsList;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.GenerateDesugaredLibraryLintFiles;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -52,24 +60,38 @@
     return lintContents.contains(type);
   }
 
-  private void checkFileContent(AndroidApiLevel minApiLevel, Path lintFile) throws Exception {
+  private void checkFileContent(
+      AndroidApiLevel minApiLevel,
+      Path lintFile,
+      boolean includeBackports,
+      boolean includeDesugaredLibrary)
+      throws Exception {
     // Just do some light probing in the generated lint files.
     lintContents = FileUtils.readAllLines(lintFile);
 
     // All methods supported on BiFunction with maintain prefix.
     assertEquals(
-        minApiLevel.isLessThan(AndroidApiLevel.N),
+        minApiLevel.isLessThan(AndroidApiLevel.N) && includeDesugaredLibrary,
         supportsAllMethodsOf("java/util/function/BiFunction"));
 
     assertEquals(
         minApiLevel.isLessThan(AndroidApiLevel.O)
-            && libraryDesugaringSpecification != JDK11_MINIMAL,
+            && libraryDesugaringSpecification != JDK11_MINIMAL
+            && includeDesugaredLibrary,
         supportsAllMethodsOf("java/time/MonthDay"));
 
+    assertEquals(
+        minApiLevel.isLessThan(AndroidApiLevel.O) && includeBackports,
+        lintContents.contains("java/lang/Byte#toUnsignedInt(B)I"));
+
     // File should be sorted.
     List<String> sorted = new ArrayList<>(lintContents);
     sorted.sort(Comparator.naturalOrder());
     assertEquals(lintContents, sorted);
+
+    // No duplicates.
+    Set<String> set = new HashSet<>(lintContents);
+    assertEquals(set.size(), lintContents.size());
   }
 
   @Test
@@ -80,6 +102,7 @@
             ? ToolHelper.DESUGARED_JDK_8_LIB_JAR
             : LibraryDesugaringSpecification.getTempLibraryJDK11Undesugar();
 
+    // TODO(b/b/302508099): Refactor users of parameters.getRuntime().asDex().getMinApiLevel().
     AndroidApiLevel minApi = parameters.getRuntime().asDex().getMinApiLevel();
     DesugaredMethodsList.main(
         new String[] {
@@ -89,6 +112,54 @@
           output.toString(),
           ToolHelper.getAndroidJar(AndroidApiLevel.U).toString()
         });
-    checkFileContent(minApi, output);
+    checkFileContent(minApi, output, true, true);
+  }
+
+  @Test
+  public void testDesugaredLibLint() throws Exception {
+    Assume.assumeTrue(
+        "Run for a single api because the test is independent of api",
+        parameters.getRuntime().asDex().getMinApiLevel().isEqualTo(AndroidApiLevel.T));
+    Path output = temp.newFolder("lint").toPath();
+    Path jdkLibJar =
+        libraryDesugaringSpecification == JDK8
+            ? ToolHelper.DESUGARED_JDK_8_LIB_JAR
+            : LibraryDesugaringSpecification.getTempLibraryJDK11Undesugar();
+
+    GenerateDesugaredLibraryLintFiles.main(
+        new String[] {
+          libraryDesugaringSpecification.getSpecification().toString(),
+          jdkLibJar.toString(),
+          output.toString(),
+          ToolHelper.getAndroidJar(AndroidApiLevel.U).toString()
+        });
+
+    InternalOptions options = new InternalOptions();
+    DesugaredLibrarySpecification spec =
+        DesugaredLibrarySpecificationParser.parseDesugaredLibrarySpecification(
+            StringResource.fromFile(libraryDesugaringSpecification.getSpecification()),
+            options.dexItemFactory(),
+            options.reporter,
+            true,
+            1);
+    int req = spec.getRequiredCompilationApiLevel().getLevel();
+    Path file = output.resolve("compile_api_level_" + req + "/desugared_apis_" + req + "_1.txt");
+    checkFileContent(AndroidApiLevel.B, file, false, true);
+  }
+
+  @Test
+  public void testBackportedMethodLint() throws Exception {
+    Path output = temp.newFile("backport.txt").toPath();
+    // TODO(b/b/302508099): Refactor users of parameters.getRuntime().asDex().getMinApiLevel().
+    AndroidApiLevel minApi = parameters.getRuntime().asDex().getMinApiLevel();
+    DesugaredMethodsList.main(
+        new String[] {
+          String.valueOf(minApi.getLevel()),
+          null,
+          null,
+          output.toString(),
+          ToolHelper.getAndroidJar(AndroidApiLevel.U).toString()
+        });
+    checkFileContent(minApi, output, true, false);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
index 2ccc573..87d5720 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
@@ -25,7 +25,6 @@
 import com.android.tools.r8.graph.GenericSignature.TypeSignature;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.GenerateDesugaredLibraryLintFiles;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.WrapperDescriptor;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -501,20 +500,6 @@
     return existing;
   }
 
-  private CodeInspector getDesugaredApiJar() throws Exception {
-    Path out = temp.newFolder().toPath();
-    GenerateDesugaredLibraryLintFiles desugaredApi =
-        GenerateDesugaredLibraryLintFiles.createForTesting(
-            libraryDesugaringSpecification.getSpecification(),
-            libraryDesugaringSpecification.getDesugarJdkLibs(),
-            out,
-            ToolHelper.getAndroidJar(AndroidApiLevel.U));
-    AndroidApiLevel compileApi = desugaredApi.run();
-    return new CodeInspector(
-        out.resolve("compile_api_level_" + compileApi.getLevel())
-            .resolve("desugared_apis_" + compileApi.getLevel() + "_" + minApi.getLevel() + ".jar"));
-  }
-
   private boolean addType(
       Consumer<ClassReference> additions,
       String type,
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
index ce9ae53..e07b779 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
@@ -13,6 +13,8 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ArchiveProgramResourceProvider;
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
@@ -249,18 +251,18 @@
               ? ToolHelper.DESUGARED_JDK_8_LIB_JAR
               : LibraryDesugaringSpecification.getTempLibraryJDK11Undesugar();
       new GenerateHtmlDoc(
-              spec.getSpecification().toString(),
-              jdkLibJar.toString(),
-              html.toString(),
-              ANDROID_JAR_34)
+              StringResource.fromFile(spec.getSpecification()),
+              ImmutableList.of(ArchiveProgramResourceProvider.fromArchive(jdkLibJar)),
+              html,
+              ImmutableList.of(new ArchiveClassFileProvider(Paths.get(ANDROID_JAR_34))))
           .run(spec + ".html");
       Path lint = top.resolve("lint_" + spec);
       Files.createDirectories(lint);
       new GenerateDesugaredLibraryLintFiles(
-              spec.getSpecification().toString(),
-              jdkLibJar.toString(),
-              lint.toString(),
-              ANDROID_JAR_34)
+              StringResource.fromFile(spec.getSpecification()),
+              ImmutableList.of(ArchiveProgramResourceProvider.fromArchive(jdkLibJar)),
+              lint,
+              ImmutableList.of(new ArchiveClassFileProvider(Paths.get(ANDROID_JAR_34))))
           .run();
     }
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java
index 18726787..74ac232 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java
@@ -11,6 +11,10 @@
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8AndAll3Jdk11;
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ArchiveProgramResourceProvider;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
@@ -133,9 +137,17 @@
     options
         .getArtProfileOptions()
         .setAllowReadingEmptyArtProfileProvidersMultipleTimesForTesting(true);
+    Set<ProgramResourceProvider> programResources =
+        librarySpecification.getDesugarJdkLibs().stream()
+            .map(ArchiveProgramResourceProvider::fromArchive)
+            .collect(Collectors.toSet());
     SupportedClasses supportedClasses =
-        new SupportedClassesGenerator(options, ToolHelper.getAndroidJar(AndroidApiLevel.U))
-            .run(librarySpecification.getDesugarJdkLibs(), librarySpecification.getSpecification());
+        new SupportedClassesGenerator(
+                options,
+                ImmutableList.of(
+                    new ArchiveClassFileProvider(ToolHelper.getAndroidJar(AndroidApiLevel.U))))
+            .run(
+                programResources, StringResource.fromFile(librarySpecification.getSpecification()));
 
     for (AndroidApiLevel api : getRelevantApiLevels()) {
 
diff --git a/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564Test.java b/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564Test.java
index c7d877e..ddd0285 100644
--- a/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564Test.java
@@ -27,10 +27,8 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    // TODO(b/218293990): Right now the JDK 20 tests are built with -target 17, as our Gradle
-    //  version does not know of -target 20.
     return getTestParameters()
-        .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK20)
         .withDexRuntimes()
         .withAllApiLevelsAlsoForCf()
         .build();
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ArgumentInfoCollectionTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ArgumentInfoCollectionTest.java
index faf304f..7cd720a 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ArgumentInfoCollectionTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ArgumentInfoCollectionTest.java
@@ -135,13 +135,13 @@
         1,
         RemovedArgumentInfo.builder()
             .setType(factory.intType)
-            .setSingleValue(abstractValueFactory.createNullValue())
+            .setSingleValue(abstractValueFactory.createZeroValue())
             .build());
     builder1.addArgumentInfo(
         3,
         RemovedArgumentInfo.builder()
             .setType(factory.intType)
-            .setSingleValue(abstractValueFactory.createNullValue())
+            .setSingleValue(abstractValueFactory.createZeroValue())
             .build());
     ArgumentInfoCollection arguments1 = builder1.setArgumentInfosSize(5).build();
 
diff --git a/src/test/java/com/android/tools/r8/internal/ChromeCompilationBase.java b/src/test/java/com/android/tools/r8/internal/ChromeCompilationBase.java
index b80b932..60dcfdd 100644
--- a/src/test/java/com/android/tools/r8/internal/ChromeCompilationBase.java
+++ b/src/test/java/com/android/tools/r8/internal/ChromeCompilationBase.java
@@ -7,6 +7,7 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.FileUtils;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
@@ -25,7 +26,8 @@
 
   public ChromeCompilationBase(int version, boolean minimal) {
     this.base =
-        "third_party/chrome/"
+        ToolHelper.THIRD_PARTY_DIR
+            + "chrome/"
             + (minimal ? "monochrome_public_minimal_apks/" : "")
             + "chrome_"
             + version
diff --git a/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java b/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java
index e220e0a..05d0d8b 100644
--- a/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java
+++ b/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java
@@ -42,7 +42,8 @@
   public static void setup() throws IOException {
     assumeTrue(ToolHelper.isLocalDevelopment());
     outDirectory = getStaticTemp().newFolder().toPath();
-    ZipUtils.unzip(Paths.get("third_party/opensource-apps/tivi/dump_app.zip"), outDirectory);
+    ZipUtils.unzip(
+        Paths.get(ToolHelper.THIRD_PARTY_DIR, "opensource-apps/tivi/dump_app.zip"), outDirectory);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/BigDecimalMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/BigDecimalMethods.java
new file mode 100644
index 0000000..8faac64
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/BigDecimalMethods.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.backports;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+public final class BigDecimalMethods {
+
+  // Make BigDecimal#stripTrailingZeros consistent (b/301570464).
+  public static BigDecimal stripTrailingZeros(BigDecimal biggie) {
+    // If biggie is ZERO, then platform appears to be inconsistent so return our own value.
+    if (biggie.signum() == 0) {
+      return new BigDecimal(BigInteger.ZERO, 0);
+    }
+    // If biggie is not zero, then platform appears to consistent so forward to the platform method.
+    return biggie.stripTrailingZeros();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index 3a7cb1e..d11c2f7 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -38,6 +38,7 @@
           AtomicReferenceArrayMethods.class,
           AtomicReferenceFieldUpdaterMethods.class,
           AtomicReferenceMethods.class,
+          BigDecimalMethods.class,
           BooleanMethods.class,
           ByteMethods.class,
           CharSequenceMethods.class,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithDynamicDispatchReturnTypePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithDynamicDispatchReturnTypePropagationTest.java
new file mode 100644
index 0000000..69a43e0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithDynamicDispatchReturnTypePropagationTest.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.callsites;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsentIf;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvokeInterfaceWithDynamicDispatchReturnTypePropagationTest extends TestBase {
+
+  @Parameter(0)
+  public boolean nonNullFromSiblingMethod;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, nonNullFromSiblingMethod: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepRules(
+            "-assumenosideeffects class " + A.class.getTypeName() + " {",
+            "  static boolean NON_NULL return " + nonNullFromSiblingMethod + ";",
+            "}")
+        .enableInliningAnnotations()
+        .enableNoUnusedInterfaceRemovalAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters)
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject mainClassSubject = inspector.clazz(Main.class);
+              assertThat(mainClassSubject, isPresent());
+              assertThat(
+                  mainClassSubject.uniqueMethodWithOriginalName("dead"),
+                  isAbsentIf(nonNullFromSiblingMethod));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithEmptyOutput();
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      I i = System.currentTimeMillis() > 0 ? new B() : new C();
+      Object o = i.m();
+      if (o == null) {
+        dead();
+      }
+    }
+
+    @NeverInline
+    static void dead() {
+      System.out.println("Dead!");
+    }
+  }
+
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface I {
+
+    Object m();
+  }
+
+  @NoVerticalClassMerging
+  static class A {
+
+    static boolean NON_NULL;
+
+    public Object m() {
+      if (NON_NULL) {
+        return new Object();
+      } else {
+        return System.currentTimeMillis() > 0 ? new Object() : null;
+      }
+    }
+  }
+
+  @NoVerticalClassMerging
+  static class B extends A implements I {}
+
+  static class C extends B {
+
+    @Override
+    public Object m() {
+      return new Object();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualToKeptEmptyMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualToKeptEmptyMethodTest.java
new file mode 100644
index 0000000..886238b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualToKeptEmptyMethodTest.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.callsites;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.R8TestCompileResult;
+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.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvokeVirtualToKeptEmptyMethodTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    R8TestCompileResult appCompileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(Main.class, I.class, A.class, Runner.class)
+            .addKeepMainRule(Main.class)
+            .addKeepClassAndMembersRules(I.class, Runner.class)
+            .setMinApi(parameters)
+            .compile()
+            .inspect(
+                inspector -> {
+                  ClassSubject iClassSubject = inspector.clazz(I.class);
+                  assertThat(iClassSubject, isPresent());
+
+                  MethodSubject fooMethodSubject =
+                      iClassSubject.uniqueMethodWithOriginalName("foo");
+                  assertThat(fooMethodSubject, isPresent());
+
+                  ClassSubject runnerClassSubject = inspector.clazz(Runner.class);
+                  assertThat(runnerClassSubject, isPresent());
+
+                  MethodSubject callFooMethodSubject =
+                      runnerClassSubject.uniqueMethodWithOriginalName("callFoo");
+                  assertThat(callFooMethodSubject, isPresent());
+                  assertThat(callFooMethodSubject, invokesMethod(fooMethodSubject));
+                });
+
+    appCompileResult.run(parameters.getRuntime(), Main.class).assertSuccessWithEmptyOutput();
+
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestMain.class, B.class)
+        .addClasspathClasses(I.class, Runner.class)
+        .addKeepAllClassesRule()
+        .setMinApi(parameters)
+        .compile()
+        .addRunClasspathFiles(appCompileResult.writeToZip())
+        .run(parameters.getRuntime(), TestMain.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      Runner.callFoo(new A());
+    }
+  }
+
+  static class TestMain {
+
+    public static void main(String[] args) {
+      Runner.callFoo(new B());
+    }
+  }
+
+  public static class Runner {
+
+    public static void callFoo(I i) {
+      i.foo();
+    }
+  }
+
+  // @Keep
+  public interface I {
+
+    void foo();
+  }
+
+  static class A implements I {
+
+    @Override
+    public void foo() {
+      // Intentionally empty.
+    }
+  }
+
+  static class B implements I {
+
+    @Override
+    public void foo() {
+      System.out.println("Hello, world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithDynamicDispatchReturnTypePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithDynamicDispatchReturnTypePropagationTest.java
new file mode 100644
index 0000000..18131ec
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithDynamicDispatchReturnTypePropagationTest.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.callsites;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvokeVirtualWithDynamicDispatchReturnTypePropagationTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters)
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject mainClassSubject = inspector.clazz(Main.class);
+              assertThat(mainClassSubject, isPresent());
+              assertThat(mainClassSubject.uniqueMethodWithOriginalName("dead"), isAbsent());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithEmptyOutput();
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      A aOrB = System.currentTimeMillis() > 0 ? new A() : new B();
+      Object o = aOrB.m();
+      if (o == null) {
+        dead();
+      }
+    }
+
+    @NeverInline
+    static void dead() {
+      System.out.println("Dead!");
+    }
+  }
+
+  @NoVerticalClassMerging
+  static class A {
+
+    Object m() {
+      return new Object();
+    }
+  }
+
+  static class B extends A {
+
+    @Override
+    Object m() {
+      return new Object();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/MethodResolutionOptimizationInfoWithRuntimeGeneratedLambdaTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/MethodResolutionOptimizationInfoWithRuntimeGeneratedLambdaTest.java
new file mode 100644
index 0000000..9f969fd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/MethodResolutionOptimizationInfoWithRuntimeGeneratedLambdaTest.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.callsites;
+
+import com.android.tools.r8.ReprocessMethod;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MethodResolutionOptimizationInfoWithRuntimeGeneratedLambdaTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .enableReprocessMethodAnnotations()
+        .setMinApi(parameters)
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  static class Main {
+
+    @ReprocessMethod
+    public static void main(String[] args) {
+      I a = new A();
+      I b = () -> ", world!";
+      boolean alwaysTrue = System.currentTimeMillis() > 0;
+      I aInDisguise = alwaysTrue ? a : b;
+      I bInDisguise = alwaysTrue ? b : a;
+      System.out.print(aInDisguise.m());
+      System.out.println(bInDisguise.m());
+    }
+  }
+
+  interface I {
+
+    String m();
+  }
+
+  static class A implements I {
+
+    @Override
+    public String m() {
+      return "Hello";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeNotNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeNotNullTest.java
index cb0e949..e592d03 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeNotNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeNotNullTest.java
@@ -47,10 +47,10 @@
         .addKeepMainRule(Main.class)
         .addKeepRules(
             "-" + flavor + " class " + Factory.class.getTypeName() + " {",
-            "  java.lang.Object create() return 1;",
+            "  java.lang.Object create() return _NONNULL_;",
             "}",
             "-" + flavor + " class " + Singleton.class.getTypeName() + " {",
-            "  java.lang.Object INSTANCE return 1;",
+            "  java.lang.Object INSTANCE return _NONNULL_;",
             "}")
         .enableInliningAnnotations()
         .setMinApi(parameters)
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinTypeReferenceTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinTypeReferenceTest.java
new file mode 100644
index 0000000..824d83b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinTypeReferenceTest.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexItemFactory;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KotlinTypeReferenceTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public KotlinTypeReferenceTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRef() {
+    // This is a unit test for b/299878903.
+    DexItemFactory factory = new DexItemFactory();
+    KotlinTypeReference bar =
+        KotlinTypeReference.fromBinaryNameOrKotlinClassifier(
+            "com/bar/Foo.Bar", factory, "com.bar.Foo.Bar");
+    Assert.assertEquals(bar.toString(), "Lcom/bar/Foo$Bar;");
+    KotlinTypeReference kotlinTypeReference =
+        KotlinTypeReference.fromBinaryNameOrKotlinClassifier(
+            "com/bar/Foo$Bar", factory, "com.bar.Foo.Bar");
+    Assert.assertEquals(kotlinTypeReference.toString(), "Lcom/bar/Foo$Bar;");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/bridge/Creator.java b/src/test/java/com/android/tools/r8/naming/bridge/Creator.java
index 69094e1..6e7217a 100644
--- a/src/test/java/com/android/tools/r8/naming/bridge/Creator.java
+++ b/src/test/java/com/android/tools/r8/naming/bridge/Creator.java
@@ -6,7 +6,7 @@
 
 public class Creator {
 
-  public static TesterImpl create() {
+  public static Tester create() {
     return new TesterImpl();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/bridge/RenameBridgeMethodJavacTest.java b/src/test/java/com/android/tools/r8/naming/bridge/RenameBridgeMethodJavacTest.java
index 94fd3f7..749ef09 100644
--- a/src/test/java/com/android/tools/r8/naming/bridge/RenameBridgeMethodJavacTest.java
+++ b/src/test/java/com/android/tools/r8/naming/bridge/RenameBridgeMethodJavacTest.java
@@ -41,11 +41,12 @@
                 ToolHelper.getSourceFileForTestClass(Tester.class),
                 ToolHelper.getSourceFileForTestClass(TesterImpl.class))
             .compile();
-    ProcessResult processResult = compileAgainstLibrary(library);
+    Path output = temp.newFolder().toPath();
+    ProcessResult processResult = compileAgainstLibrary(library, output);
     assertEquals(0, processResult.exitCode);
     testForJvm(parameters)
         .addRunClasspathFiles(library)
-        .addRunClasspathFiles(library.getParent())
+        .addRunClasspathFiles(output)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("Hello World!");
   }
@@ -61,18 +62,22 @@
                 Creator.class, Result.class, ResultImpl.class, Tester.class)
             .addKeepClassAndMembersRulesWithAllowObfuscation(TesterImpl.class)
             .compile();
-    Path libraryZip = libraryCompileResult.writeToZip();
-    ProcessResult processResult = compileAgainstLibrary(libraryZip);
-    // TODO(b/290711987): The minifier renames the target of a bridge preventing additional
-    //  compilations to succeed.
-    assertEquals(1, processResult.exitCode);
+    Path library = libraryCompileResult.writeToZip();
+    Path output = temp.newFolder().toPath();
+    ProcessResult processResult = compileAgainstLibrary(library, output);
+    assertEquals(0, processResult.exitCode);
+    testForJvm(parameters)
+        .addProgramClasses(Main.class)
+        .addRunClasspathFiles(library)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World!");
   }
 
-  private ProcessResult compileAgainstLibrary(Path classPath) throws Exception {
+  private ProcessResult compileAgainstLibrary(Path classPath, Path outputPath) throws Exception {
     return javac(parameters.asCfRuntime())
         .addSourceFiles(ToolHelper.getSourceFileForTestClass(Main.class))
         .addClasspathFiles(classPath)
-        .setOutputPath(classPath.getParent())
+        .setOutputPath(outputPath)
         .compileRaw();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
index 7ddf873..d2cc308 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
@@ -156,10 +156,7 @@
                           15,
                           FILENAME_INLINE_INSTANCE),
                       LinePosition.create(mainSubject.asFoundMethodSubject(), 2, 7, mainFileName));
-              // TODO(b/202811699): Retracing fails to find the right file for the inline frame.
-              if (false) {
-                checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
-              }
+              checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
             });
   }
 
diff --git a/tools/archive.py b/tools/archive.py
index ce558e9..7beaede 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -85,8 +85,9 @@
 
 def GetVersionDestination(storage_prefix, version_or_path, is_main,
                           new_gradle=False):
+  assert new_gradle
   archive_dir = 'raw/main' if is_main else 'raw'
-  bucket = ARCHIVE_BUCKET if not new_gradle else "r8-test-results"
+  bucket = ARCHIVE_BUCKET
   return '%s%s/%s/%s' % (storage_prefix, bucket,
                          archive_dir, version_or_path)
 
@@ -115,12 +116,10 @@
 
 def Main():
   (options, args) = ParseOptions()
-  Run(options, False)
-  # Clean to ensure that we actually generate all of the artifacts
-  shutil.rmtree(utils.BUILD)
   Run(options, True)
 
 def Run(options, new_gradle):
+  assert new_gradle
   if not utils.is_bot() and not options.dry_run:
     raise Exception('You are not a bot, don\'t archive builds. '
       + 'Use --dry-run to test locally')
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 6474441..60f50ac 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -621,7 +621,7 @@
     print("=" * 80)
     print(" RETRACED OUTPUT")
     print("=" * 80)
-    retrace.run(get_map_file(version, temp), stacktrace, no_r8lib=False)
+    retrace.run(get_map_file(version, temp), stacktrace, None, no_r8lib=False)
     return True
   except Exception as e2:
     print("Failed to retrace for version: %s" % version)
diff --git a/tools/create_maven_release.py b/tools/create_maven_release.py
index 137f584..f93213a 100755
--- a/tools/create_maven_release.py
+++ b/tools/create_maven_release.py
@@ -4,19 +4,21 @@
 # BSD-style license that can be found in the LICENSE file.
 
 import argparse
-import gradle
 import hashlib
 import jdk
-import json
-from os import makedirs
-from os.path import join, basename
-from shutil import copyfile, make_archive, move, rmtree
 import subprocess
 import sys
-from string import Template
-import tempfile
-import utils
 import zipfile
+from os import makedirs
+from os.path import basename
+from os.path import join
+from shutil import copyfile
+from shutil import make_archive
+from shutil import move
+from string import Template
+
+import gradle
+import utils
 
 LICENSETEMPLATE = Template(
 """
@@ -313,7 +315,8 @@
         'com.android.tools.r8.ir.desugar.desugaredlibrary.lint.GenerateDesugaredLibraryLintFiles',
         configuration,
         implementation,
-        lint_dir]
+        lint_dir,
+        utils.get_android_jar(34)]
     utils.PrintCmd(cmd)
     subprocess.check_call(cmd)
 
diff --git a/tools/golem_build.py b/tools/golem_build.py
index 4f668d6..1cbf39f 100755
--- a/tools/golem_build.py
+++ b/tools/golem_build.py
@@ -17,11 +17,17 @@
   return [ item.lower() for item in items ]
 
 def Main():
+  # TODO(b/302999071): Move java based benchmarking to new gradle
   targets = set()
-  targets.update(lower(run_benchmark.GOLEM_BUILD_TARGETS))
-  targets.update(lower(run_on_app_dump.GOLEM_BUILD_TARGETS))
+  targets.update(lower(run_benchmark.GOLEM_BUILD_TARGETS_OLD))
   cmd = GRADLE_ARGS + [target for target in targets]
   gradle.RunGradle(cmd)
 
+  targets = set()
+  targets.update(lower(run_benchmark.GOLEM_BUILD_TARGETS_NEW))
+  targets.update(lower(run_on_app_dump.GOLEM_BUILD_TARGETS))
+  cmd = GRADLE_ARGS + [target for target in targets]
+  gradle.RunGradle(cmd, new_gradle=True)
+
 if __name__ == '__main__':
   sys.exit(Main())
diff --git a/tools/run_benchmark.py b/tools/run_benchmark.py
index 41da451..c066678 100755
--- a/tools/run_benchmark.py
+++ b/tools/run_benchmark.py
@@ -16,11 +16,13 @@
 NONLIB_BUILD_TARGET = 'R8WithRelocatedDeps'
 NONLIB_TEST_BUILD_TARGETS = [utils.R8_TESTS_TARGET, utils.R8_TESTS_DEPS_TARGET]
 
-R8LIB_BUILD_TARGET = utils.R8LIB
+# TODO(b/300387869): Cleanup targets
+R8LIB_BUILD_TARGET = ':test:r8LibWithRelocatedDeps'
 R8LIB_TEST_BUILD_TARGETS = [utils.R8LIB_TESTS_TARGET, utils.R8LIB_TESTS_DEPS_TARGET]
 
 # The r8lib target is always the golem target.
-GOLEM_BUILD_TARGETS = [R8LIB_BUILD_TARGET] + R8LIB_TEST_BUILD_TARGETS
+GOLEM_BUILD_TARGETS_OLD = R8LIB_TEST_BUILD_TARGETS
+GOLEM_BUILD_TARGETS_NEW = [R8LIB_BUILD_TARGET]
 
 def get_golem_resource_path(benchmark):
   return os.path.join('benchmarks', benchmark)
@@ -87,7 +89,7 @@
     testjars = [utils.R8_TESTS_DEPS_JAR, utils.R8_TESTS_JAR]
   else:
     testBuildTargets = R8LIB_TEST_BUILD_TARGETS
-    buildTargets = GOLEM_BUILD_TARGETS
+    buildTargets = GOLEM_BUILD_TARGETS_OLD + GOLEM_BUILD_TARGETS_NEW
     r8jar = utils.R8LIB_JAR
     testjars = [utils.R8LIB_TESTS_DEPS_JAR, utils.R8LIB_TESTS_JAR]
 
diff --git a/tools/run_on_app_dump.py b/tools/run_on_app_dump.py
index 2b07526..8602ea9 100755
--- a/tools/run_on_app_dump.py
+++ b/tools/run_on_app_dump.py
@@ -23,7 +23,9 @@
 import update_prebuilds_in_android
 import utils
 
-GOLEM_BUILD_TARGETS = ['R8Lib', 'R8Retrace']
+# TODO(b/300387869): Cleanup targets
+GOLEM_BUILD_TARGETS = [':test:r8LibWithRelocatedDeps',
+                       ':test:retraceWithRelocatedDeps']
 SHRINKERS = ['r8', 'r8-full', 'r8-nolib', 'r8-nolib-full']
 
 class AttrDict(dict):
@@ -689,7 +691,7 @@
     'dump': dump_for_app(app_dir, app),
     'r8_jar': get_r8_jar(options, temp_dir, shrinker),
     'r8_flags': options.r8_flags,
-    'ea': not options.disable_assertions,
+    'disable_assertions': options.disable_assertions,
     'version': options.version,
     'compiler': 'r8full' if is_full_r8(shrinker) else 'r8',
     'debug_agent': options.debug_agent,
@@ -755,7 +757,7 @@
   args = AttrDict({
     'dump': dump_test_for_app(app_dir, app),
     'r8_jar': get_r8_jar(options, temp_dir, shrinker),
-    'ea': not options.disable_assertions,
+    'disable_assertions': options.disable_assertions,
     'version': options.version,
     'compiler': 'r8full' if is_full_r8(shrinker) else 'r8',
     'debug_agent': options.debug_agent,
@@ -1136,7 +1138,7 @@
   print_indented('%s.fromRevision = 9700;' % options, indentation);
   print_indented('%s.mainFile = "tools/run_on_app_dump.py "' % options,
                  indentation)
-  print_indented('"--golem --quiet --shrinker %s --app %s "'
+  print_indented('"--golem --disable-assertions --quiet --shrinker %s --app %s "'
                    % (shrinker, app.name),
                  indentation + 4)
   print_indented('"--minify %s --optimize %s --shrink %s";'
diff --git a/tools/test.py b/tools/test.py
index 0dbc827..0437701 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -118,6 +118,9 @@
   result.add_argument('--command-cache-dir', '--command_cache_dir',
       help='Cache command invocations to this directory, speeds up test runs',
       default=os.environ.get('R8_COMMAND_CACHE_DIR'))
+  result.add_argument('--command-cache-stats', '--command_cache_stats',
+      help='Collect and print statistics about the command cache.',
+      default=False, action='store_true')
   result.add_argument('--java-home', '--java_home',
       help='Use a custom java version to run tests.')
   result.add_argument('--java-max-memory-size', '--java_max_memory_size',
@@ -204,7 +207,7 @@
                     default=False, action='store_true')
   result.add_argument('--new-gradle',
                     help='Specify to run in the new gradle setup',
-                    default=False, action='store_true')
+                    default=True, action='store_true')
   return result.parse_known_args()
 
 def has_failures(classes_file):
@@ -243,6 +246,8 @@
   if utils.is_bot():
     gradle.RunGradle(['--no-daemon', 'clean'], new_gradle=options.new_gradle)
     print('Running with python ' + str(sys.version_info))
+    # Always print stats on bots if command cache is enabled
+    options.command_cache_stats = options.command_cache_dir is not None
 
   desugar_jdk_json_dir = None
   if options.desugared_library_configuration:
@@ -324,6 +329,15 @@
     gradle_args.append('-Pcommand_cache_dir=' + options.command_cache_dir)
     if not os.path.exists(options.command_cache_dir):
       os.makedirs(options.command_cache_dir)
+    if options.command_cache_stats:
+      stats_dir = os.path.join(options.command_cache_dir, 'stats')
+      gradle_args.append('-Pcommand_cache_stats_dir=' + stats_dir)
+      if not os.path.exists(stats_dir):
+        os.makedirs(stats_dir)
+      # Clean out old stats files
+      for (_, _, file_names) in os.walk(stats_dir):
+        for f in file_names:
+          os.remove(os.path.join(stats_dir, f))
   if options.java_home:
     gradle_args.append('-Dorg.gradle.java.home=' + options.java_home)
   if options.java_max_memory_size:
@@ -490,8 +504,7 @@
         utils.upload_file_to_cloud_storage(archive,
                                            'gs://r8-test-results/golden-files/' + archive)
 
-    if return_code != 0:
-      return archive_and_return(return_code, options)
+    return archive_and_return(return_code, options)
 
   return 0
 
@@ -499,6 +512,23 @@
   if return_code != 0:
     if options.archive_failures:
       archive_failures(options)
+  if options.command_cache_stats:
+    stats_dir = os.path.join(options.command_cache_dir, 'stats')
+    cache_hit = 0
+    cache_miss = 0
+    cache_put = 0
+    for (_, _, file_names) in os.walk(stats_dir):
+      for f in file_names:
+        if f.endswith('CACHEHIT'):
+          cache_hit += os.stat(os.path.join(stats_dir, f)).st_size
+        if f.endswith('CACHEMISS'):
+          cache_miss += os.stat(os.path.join(stats_dir, f)).st_size
+        if f.endswith('CACHEPUT'):
+          cache_put += os.stat(os.path.join(stats_dir, f)).st_size
+    print('Command cache stats')
+    print('  Cache hits: ' + str(cache_hit))
+    print('  Cache miss: ' + str(cache_miss))
+    print('  Cache puts: ' + str(cache_put))
   return return_code
 
 def print_jstacks():