Merge commit 'f748d174fc9b0a9606f70fc9a6350e81ebff0857' into dev-release

Change-Id: I7f7cec9c24ccb267feb20c39a73fe12327025341
diff --git a/doc/keepanno-guide.md b/doc/keepanno-guide.md
index 9970ae4..75d6b24 100644
--- a/doc/keepanno-guide.md
+++ b/doc/keepanno-guide.md
@@ -16,8 +16,20 @@
 [R8 component](https://issuetracker.google.com/issues?q=status:open%20componentid:326788).
 
 
+## Table of contents
 
-## Introduction
+- [Introduction](#introduction)
+- [Build configuration](#build-configuration)
+- [Annotating code using reflection](#using-reflection)
+- [Annotating code used by reflection (or via JNI)](#used-by-reflection)
+- [Annotating APIs](#apis)
+- [Migrating rules to annotations](#migrating-rules)
+- [My use case is not covered!](#other-uses)
+- [Troubleshooting](#troubleshooting)
+
+
+
+## Introduction<a id="introduction"></a>
 
 When using a Java/Kotlin shrinker such as R8 or Proguard, developers must inform
 the shrinker about parts of the program that are used either externally from the
@@ -35,7 +47,7 @@
 hopefully more clear and direct meaning.
 
 
-## Build configuration
+## Build configuration<a id="build-configuration"></a>
 
 To use the keep annotations your build must include the library of
 annotations. It is currently built as part of each R8 build and if used with R8,
@@ -61,7 +73,7 @@
   # ... the rest of your R8 compilation command here ...
 ```
 
-### Annotating code using reflection
+## Annotating code using reflection<a id="using-reflection"></a>
 
 The keep annotation library defines a family of annotations depending on your
 use case. You should generally prefer [@UsesReflection](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/UsesReflection.html) where applicable.
@@ -96,18 +108,68 @@
 
 
 
+## Annotating code used by reflection (or via JNI)<a id="used-by-reflection"></a>
 
-### Annotating code used by reflection (or via JNI)
+TODO
+
+
+## Annotating APIs<a id="apis"></a>
+
+TODO
+
+
+## Migrating rules to annotations<a id="migrating-rules"></a>
+
+There is no automatic migration of keep rules. Keep annotations often invert the
+direction and rules have no indication of where the reflection is taking
+place or why. Thus, migrating existing keep rules requires user involvement.
+Keep rules also have a tendency to be very general, matching a large
+number of classes and members. Often the rules are much too broad and are
+keeping more than needed which will have a negative impact on the shrinkers
+ability to reduce size.
+
+First step in converting a rule is to determine the purpose of the rule. Is it
+API surface or is it reflection? Note that a very general rule may be covering
+several use cases and even a mix of both reflection and API usage.
+
+When migrating it is preferable to use [@UsesReflection](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/UsesReflection.html) instead of
+[@UsedByReflection](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/UsedByReflection.html). For very general rules it might not be easy or worth it to
+migrate without completely reevaluating the rule. If one still wants to replace
+it by annotations, the general [@KeepEdge](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepEdge.html) can be used to define a context
+independent keep annotation.
+
+For example, to keep all main methods in the program one could use:
+
+
+```
+@KeepEdge(
+    consequences = {
+      @KeepTarget(
+          kind = KeepItemKind.CLASS_AND_MEMBERS,
+          methodName = "main",
+          methodReturnType = "void",
+          methodParameters = {"java.lang.String[]"},
+          methodAccess = {MethodAccessFlags.PUBLIC, MethodAccessFlags.STATIC})
+    })
+public class SomeClass {
+  // ...
+}
+```
 
 
 
-### Annotating APIs
+## My use case is not covered!<a id="other-uses"></a>
+
+The annotation library is in active development and not all use cases are
+described here or supported. Reach out to the R8 team by
+[filing a new issue in our tracker](https://issuetracker.google.com/issues/new?component=326788).
+Describe your use case and we will look at how best to support it.
 
 
-### Migrating rules to annotations
+## Troubleshooting<a id="troubleshooting"></a>
 
-
-### My use case is not covered!
-
-
-### Troubleshooting
+If an annotation is not working as expected it may be helpful to inspect the
+rules that have been extracted for the annotation. This can be done by
+inspecting the configuration output of the shrinker. For R8 you can use the
+command line argument `--pg-conf-output <path>` to emit the full configuration
+used by R8.
diff --git a/doc/keepanno-guide.template.md b/doc/keepanno-guide.template.md
index 6e0a8df..cfa98e2 100644
--- a/doc/keepanno-guide.template.md
+++ b/doc/keepanno-guide.template.md
@@ -13,8 +13,10 @@
 [R8 component](https://issuetracker.google.com/issues?q=status:open%20componentid:326788).
 
 
+[[[TOC]]]
 
-## Introduction
+
+## [Introduction](introduction)
 
 When using a Java/Kotlin shrinker such as R8 or Proguard, developers must inform
 the shrinker about parts of the program that are used either externally from the
@@ -32,7 +34,7 @@
 hopefully more clear and direct meaning.
 
 
-## Build configuration
+## [Build configuration](build-configuration)
 
 To use the keep annotations your build must include the library of
 annotations. It is currently built as part of each R8 build and if used with R8,
@@ -58,7 +60,7 @@
   # ... the rest of your R8 compilation command here ...
 ```
 
-### Annotating code using reflection
+## [Annotating code using reflection](using-reflection)
 
 The keep annotation library defines a family of annotations depending on your
 use case. You should generally prefer `@UsesReflection` where applicable.
@@ -68,18 +70,53 @@
 [[[INCLUDE CODE:UsesReflectionOnVirtualMethod]]]
 
 
+## [Annotating code used by reflection (or via JNI)](used-by-reflection)
 
-### Annotating code used by reflection (or via JNI)
+TODO
 
 
+## [Annotating APIs](apis)
 
-### Annotating APIs
+TODO
 
 
-### Migrating rules to annotations
+## [Migrating rules to annotations](migrating-rules)
+
+There is no automatic migration of keep rules. Keep annotations often invert the
+direction and rules have no indication of where the reflection is taking
+place or why. Thus, migrating existing keep rules requires user involvement.
+Keep rules also have a tendency to be very general, matching a large
+number of classes and members. Often the rules are much too broad and are
+keeping more than needed which will have a negative impact on the shrinkers
+ability to reduce size.
+
+First step in converting a rule is to determine the purpose of the rule. Is it
+API surface or is it reflection? Note that a very general rule may be covering
+several use cases and even a mix of both reflection and API usage.
+
+When migrating it is preferable to use `@UsesReflection` instead of
+`@UsedByReflection`. For very general rules it might not be easy or worth it to
+migrate without completely reevaluating the rule. If one still wants to replace
+it by annotations, the general `@KeepEdge` can be used to define a context
+independent keep annotation.
+
+[[[INCLUDE DOC:KeepMainMethods]]]
+
+[[[INCLUDE CODE:KeepMainMethods]]]
 
 
-### My use case is not covered!
+## [My use case is not covered!](other-uses)
+
+The annotation library is in active development and not all use cases are
+described here or supported. Reach out to the R8 team by
+[filing a new issue in our tracker](https://issuetracker.google.com/issues/new?component=326788).
+Describe your use case and we will look at how best to support it.
 
 
-### Troubleshooting
+## [Troubleshooting](troubleshooting)
+
+If an annotation is not working as expected it may be helpful to inspect the
+rules that have been extracted for the annotation. This can be done by
+inspecting the configuration output of the shrinker. For R8 you can use the
+command line argument `--pg-conf-output <path>` to emit the full configuration
+used by R8.
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 09a8ceb..07b3ef5 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -131,7 +131,8 @@
     private GraphConsumer keptGraphConsumer = null;
     private GraphConsumer mainDexKeptGraphConsumer = null;
     private InputDependencyGraphConsumer inputDependencyGraphConsumer = null;
-    private final List<FeatureSplit> featureSplits = new ArrayList<>();
+    private final FeatureSplitConfiguration.Builder featureSplitConfigurationBuilder =
+        FeatureSplitConfiguration.builder();
     private String synthesizedClassPrefix = "";
     private boolean enableMissingLibraryApiModeling = false;
     private boolean enableExperimentalKeepAnnotations =
@@ -354,6 +355,11 @@
       return self();
     }
 
+    /** Get the consumer for receiving the proguard configuration information if set. */
+    public StringConsumer getProguardConfigurationConsumer() {
+      return proguardConfigurationConsumer;
+    }
+
     /**
      * Set a consumer for receiving kept-graph events.
      */
@@ -441,7 +447,7 @@
     public Builder addFeatureSplit(
         Function<FeatureSplit.Builder, FeatureSplit> featureSplitGenerator) {
       FeatureSplit featureSplit = featureSplitGenerator.apply(FeatureSplit.builder(getReporter()));
-      featureSplits.add(featureSplit);
+      featureSplitConfigurationBuilder.addFeatureSplit(featureSplit);
       for (ProgramResourceProvider programResourceProvider : featureSplit
           .getProgramResourceProviders()) {
         // Data resources are handled separately and passed directly to the feature split consumer.
@@ -462,6 +468,18 @@
     }
 
     /**
+     * Used to specify if the application is using isolated splits, i.e., if split APKs installed
+     * for this application are loaded into their own Context objects.
+     *
+     * <p>See also <a href="https://developer.android.com/reference/android/R.attr#isolatedSplits">
+     * R.attr#isolatedSplits</a>.
+     */
+    public Builder setEnableExperimentalIsolatedSplits(boolean enableIsolatedSplits) {
+      featureSplitConfigurationBuilder.setEnableIsolatedSplits(enableIsolatedSplits);
+      return this;
+    }
+
+    /**
      * Enable experimental/pre-release support for modeling missing library APIs.
      *
      * <p>This allows enabling the feature while it is still default disabled by the compiler. Once
@@ -575,7 +593,7 @@
                   + " and above");
         }
       }
-      for (FeatureSplit featureSplit : featureSplits) {
+      for (FeatureSplit featureSplit : featureSplitConfigurationBuilder.getFeatureSplits()) {
         verifyResourceSplitOrProgramSplit(featureSplit);
         if (getProgramConsumer() != null && !(getProgramConsumer() instanceof DexIndexedConsumer)) {
           reporter.error("R8 does not support class file output when using feature splits");
@@ -662,9 +680,6 @@
               ? DesugarState.OFF
               : getDesugaringState();
 
-      FeatureSplitConfiguration featureSplitConfiguration =
-          !featureSplits.isEmpty() ? new FeatureSplitConfiguration(featureSplits) : null;
-
       R8Command command =
           new R8Command(
               getAppBuilder().build(),
@@ -693,7 +708,7 @@
               getDexClassChecksumFilter(),
               desugaredLibraryKeepRuleConsumer,
               desugaredLibrarySpecification,
-              featureSplitConfiguration,
+              featureSplitConfigurationBuilder.build(),
               getAssertionsConfiguration(),
               getOutputInspections(),
               synthesizedClassPrefix,
@@ -1289,6 +1304,7 @@
         .setMinification(getEnableMinification())
         .setForceProguardCompatibility(forceProguardCompatibility)
         .setFeatureSplitConfiguration(featureSplitConfiguration)
+        .setAndroidResourceProvider(androidResourceProvider)
         .setProguardConfiguration(proguardConfiguration)
         .setMainDexKeepRules(mainDexKeepRules)
         .setDesugaredLibraryConfiguration(desugaredLibrarySpecification)
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
index 460a7d7..b407f34 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
@@ -165,6 +165,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
index 0c25941..068d034 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
@@ -34,6 +34,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
index cd21707..a97e1ea 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -67,6 +67,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
index aee2e6f..0cad424 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
@@ -75,6 +75,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
index f450da4..091aa57 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -90,11 +90,12 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
       MethodVisitor visitor) {
-    DexType rewrittenType = graphLens.lookupType(type);
+    DexType rewrittenType = graphLens.lookupType(type, codeLens);
     visitor.visitTypeInsn(Opcodes.CHECKCAST, namingLens.lookupInternalName(rewrittenType));
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
index 5b35a3e..6ca820e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
@@ -110,6 +110,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
index bc226cb..151be2a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
@@ -103,11 +103,12 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
       MethodVisitor visitor) {
-    visitor.visitLdcInsn(Type.getObjectType(getInternalName(graphLens, namingLens)));
+    visitor.visitLdcInsn(Type.getObjectType(getInternalName(graphLens, codeLens, namingLens)));
   }
 
   @Override
@@ -126,8 +127,8 @@
     return true;
   }
 
-  private String getInternalName(GraphLens graphLens, NamingLens namingLens) {
-    DexType rewrittenType = graphLens.lookupType(type);
+  private String getInternalName(GraphLens graphLens, GraphLens codeLens, NamingLens namingLens) {
+    DexType rewrittenType = graphLens.lookupType(type, codeLens);
     switch (rewrittenType.toShorty()) {
       case '[':
       case 'L':
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
index 9f3f073..d47a830 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
@@ -155,6 +155,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
@@ -172,15 +173,15 @@
     ConstantDynamic constantDynamic =
         new ConstantDynamic(
             reference.getName().toString(),
-            getConstantTypeDescriptor(graphLens, namingLens, dexItemFactory),
+            getConstantTypeDescriptor(graphLens, codeLens, namingLens, dexItemFactory),
             rewrittenHandle.toAsmHandle(namingLens),
             bsmArgs);
     visitor.visitLdcInsn(constantDynamic);
   }
 
   private String getConstantTypeDescriptor(
-      GraphLens graphLens, NamingLens namingLens, DexItemFactory factory) {
-    DexType rewrittenType = graphLens.lookupType(reference.getType());
+      GraphLens graphLens, GraphLens codeLens, NamingLens namingLens, DexItemFactory factory) {
+    DexType rewrittenType = graphLens.lookupType(reference.getType(), codeLens);
     DexType renamedType = namingLens.lookupType(rewrittenType, factory);
     return renamedType.toDescriptorString();
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
index e02b1fe..9c59d83 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
@@ -63,6 +63,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
index 7e8d519..b9f5d88 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
@@ -63,6 +63,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
index 75eb939..5e6485a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
@@ -34,6 +34,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
index 21dbd68..991ed9d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
@@ -93,6 +93,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
index 9756641..d60203a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
@@ -73,6 +73,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
index 9bf20e3..b350e1f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
@@ -80,6 +80,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index 9045c59..9d91db7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -102,12 +102,13 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
       MethodVisitor visitor) {
-    DexField rewrittenField = graphLens.lookupField(field);
-    DexField rewrittenDeclaringField = graphLens.lookupField(declaringField);
+    DexField rewrittenField = graphLens.lookupField(field, codeLens);
+    DexField rewrittenDeclaringField = graphLens.lookupField(declaringField, codeLens);
     String owner = namingLens.lookupInternalName(rewrittenField.holder);
     String name = namingLens.lookupName(rewrittenDeclaringField).toString();
     String desc = namingLens.lookupDescriptor(rewrittenField.type).toString();
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
index 6813ed1..c8308b4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -182,14 +182,15 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
       MethodVisitor visitor) {
     int stackCount = computeStackCount();
-    Object[] stackTypes = computeStackTypes(stackCount, graphLens, namingLens);
+    Object[] stackTypes = computeStackTypes(stackCount, graphLens, codeLens, namingLens);
     int localsCount = computeLocalsCount();
-    Object[] localsTypes = computeLocalsTypes(localsCount, graphLens, namingLens);
+    Object[] localsTypes = computeLocalsTypes(localsCount, graphLens, codeLens, namingLens);
     visitor.visitFrame(F_NEW, localsCount, localsTypes, stackCount, stackTypes);
   }
 
@@ -210,7 +211,8 @@
     return size;
   }
 
-  private Object[] computeStackTypes(int stackCount, GraphLens graphLens, NamingLens namingLens) {
+  private Object[] computeStackTypes(
+      int stackCount, GraphLens graphLens, GraphLens codeLens, NamingLens namingLens) {
     assert stackCount == stack.size();
     if (stackCount == 0) {
       return null;
@@ -218,7 +220,7 @@
     Object[] stackTypes = new Object[stackCount];
     int index = 0;
     for (PreciseFrameType frameType : stack) {
-      stackTypes[index++] = frameType.getTypeOpcode(graphLens, namingLens);
+      stackTypes[index++] = frameType.getTypeOpcode(graphLens, codeLens, namingLens);
     }
     return stackTypes;
   }
@@ -240,7 +242,8 @@
     return localsCount;
   }
 
-  private Object[] computeLocalsTypes(int localsCount, GraphLens graphLens, NamingLens namingLens) {
+  private Object[] computeLocalsTypes(
+      int localsCount, GraphLens graphLens, GraphLens codeLens, NamingLens namingLens) {
     if (localsCount == 0) {
       return null;
     }
@@ -250,7 +253,7 @@
     for (int i = 0; i <= maxRegister; i++) {
       FrameType type = locals.get(i);
       localsTypes[localIndex++] =
-          type == null ? Opcodes.TOP : type.getTypeOpcode(graphLens, namingLens);
+          type == null ? Opcodes.TOP : type.getTypeOpcode(graphLens, codeLens, namingLens);
       if (type != null && type.isWide()) {
         i++;
       }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
index 48fe920..29dd22a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
@@ -85,6 +85,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIf.java b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
index 25dfd77..0ac4689 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
@@ -92,6 +92,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
index ee9ece8..24fc552 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
@@ -92,6 +92,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
index 063f778..7a4ba0a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
@@ -65,6 +65,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
index 440f799f..21608ae 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
@@ -74,13 +74,14 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
       MethodVisitor visitor) {
     // We intentionally apply the graph lens first, and then the init class lens, using the fact
     // that the init class lens maps classes in the final program to fields in the final program.
-    DexType rewrittenClass = graphLens.lookupType(clazz);
+    DexType rewrittenClass = graphLens.lookupType(clazz, codeLens);
     DexField clinitField = initClassLens.getInitClassField(rewrittenClass);
     String owner = namingLens.lookupInternalName(clinitField.holder);
     String name = namingLens.lookupName(clinitField).toString();
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
index 9a0308e..ec34784 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
@@ -89,11 +89,12 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
       MethodVisitor visitor) {
-    DexType rewrittenType = graphLens.lookupType(getType());
+    DexType rewrittenType = graphLens.lookupType(getType(), codeLens);
     visitor.visitTypeInsn(Opcodes.INSTANCEOF, namingLens.lookupInternalName(rewrittenType));
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index b13fbe8..ff95325 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -41,6 +41,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 8a87dd0..bcbe536 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -106,6 +106,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
@@ -124,7 +125,7 @@
           itf);
     } else {
       MethodLookupResult lookup =
-          graphLens.lookupMethod(method, context.getReference(), invokeType);
+          graphLens.lookupMethod(method, context.getReference(), invokeType, codeLens);
       InvokeType rewrittenType = lookup.getType();
       DexMethod rewrittenMethod = lookup.getReference();
       String owner = namingLens.lookupInternalName(rewrittenMethod.holder);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index c174214..79ba7f9 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -79,6 +79,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java b/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
index 6abd7ae..7ab3052 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
@@ -60,6 +60,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
index 0cb0665..29701e6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
@@ -73,6 +73,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
index 0479271..a0b7fdc 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
@@ -89,6 +89,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
index a173410..14f30b1 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
@@ -139,6 +139,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
index edfb91a..d7ef8ee 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
@@ -61,6 +61,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
index 344e65b..85f09af 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
@@ -91,11 +91,12 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
       MethodVisitor visitor) {
-    DexType rewrittenType = graphLens.lookupType(getType());
+    DexType rewrittenType = graphLens.lookupType(getType(), codeLens);
     visitor.visitMultiANewArrayInsn(namingLens.lookupInternalName(rewrittenType), dimensions);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
index d861c19..61216c1 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
@@ -62,6 +62,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNew.java b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
index e6a51f8..9e0af9c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNew.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
@@ -107,11 +107,12 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
       MethodVisitor visitor) {
-    DexType rewrittenType = graphLens.lookupType(getType());
+    DexType rewrittenType = graphLens.lookupType(getType(), codeLens);
     visitor.visitTypeInsn(Opcodes.NEW, namingLens.lookupInternalName(rewrittenType));
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
index 5c2a657..943252d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
@@ -101,7 +101,10 @@
   }
 
   private String getElementInternalName(
-      DexItemFactory dexItemFactory, GraphLens graphLens, NamingLens namingLens) {
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      GraphLens codeLens,
+      NamingLens namingLens) {
     assert !type.isPrimitiveArrayType();
     StringBuilder renamedElementDescriptor = new StringBuilder();
     // Intentionally starting from 1 to get the element descriptor.
@@ -110,7 +113,7 @@
       renamedElementDescriptor.append("[");
     }
     DexType baseType = getType().toBaseType(dexItemFactory);
-    DexType rewrittenBaseType = graphLens.lookupType(baseType);
+    DexType rewrittenBaseType = graphLens.lookupType(baseType, codeLens);
     renamedElementDescriptor.append(
         namingLens.lookupDescriptor(rewrittenBaseType).toSourceString());
     return DescriptorUtils.descriptorToInternalName(renamedElementDescriptor.toString());
@@ -122,6 +125,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
@@ -130,7 +134,8 @@
       visitor.visitIntInsn(Opcodes.NEWARRAY, getPrimitiveTypeCode());
     } else {
       visitor.visitTypeInsn(
-          Opcodes.ANEWARRAY, getElementInternalName(dexItemFactory, graphLens, namingLens));
+          Opcodes.ANEWARRAY,
+          getElementInternalName(dexItemFactory, graphLens, codeLens, namingLens));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java b/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java
index 5864372..11efcd1 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java
@@ -83,6 +83,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNop.java b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
index 139c2a7..fd5d76c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
@@ -49,6 +49,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
index 856cc36..32a1d4f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
@@ -72,6 +72,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
index 6a442c3..9e4bae3 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
@@ -63,6 +63,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java b/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java
index 98c1e78..c34d876 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java
@@ -48,6 +48,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
index bddd05e..725acb1 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
@@ -94,6 +94,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
index 0a96d17..0197fc2 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
@@ -59,6 +59,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
index e3b70f6..7679a02 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
@@ -107,6 +107,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStore.java b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
index 279014d..f59bfb6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
@@ -87,6 +87,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
index f54eaa1..f190ae4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
@@ -125,6 +125,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
index 4bdf343..f59f162 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
@@ -70,6 +70,7 @@
       ProgramMethod context,
       DexItemFactory dexItemFactory,
       GraphLens graphLens,
+      GraphLens codeLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/BooleanFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/BooleanFrameType.java
index b0ec955..50e26e6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/BooleanFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/BooleanFrameType.java
@@ -27,7 +27,7 @@
   }
 
   @Override
-  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+  public Object getTypeOpcode(GraphLens graphLens, GraphLens codeLens, NamingLens namingLens) {
     throw new Unreachable("Unexpected value type: " + this);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/ByteFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/ByteFrameType.java
index e6aa4da..5fcbec8 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/ByteFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/ByteFrameType.java
@@ -27,7 +27,7 @@
   }
 
   @Override
-  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+  public Object getTypeOpcode(GraphLens graphLens, GraphLens codeLens, NamingLens namingLens) {
     throw new Unreachable("Unexpected value type: " + this);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/CharFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/CharFrameType.java
index 8dc437d..81c1084 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/CharFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/CharFrameType.java
@@ -27,7 +27,7 @@
   }
 
   @Override
-  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+  public Object getTypeOpcode(GraphLens graphLens, GraphLens codeLens, NamingLens namingLens) {
     throw new Unreachable("Unexpected value type: " + this);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/DoubleFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/DoubleFrameType.java
index 7ad8dc2..2c14eb0 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/DoubleFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/DoubleFrameType.java
@@ -62,7 +62,7 @@
   }
 
   @Override
-  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+  public Object getTypeOpcode(GraphLens graphLens, GraphLens codeLens, NamingLens namingLens) {
     return Opcodes.DOUBLE;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/DoubleHighFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/DoubleHighFrameType.java
index 655fa52..056eaf3 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/DoubleHighFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/DoubleHighFrameType.java
@@ -52,7 +52,7 @@
   }
 
   @Override
-  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+  public Object getTypeOpcode(GraphLens graphLens, GraphLens codeLens, NamingLens namingLens) {
     throw new Unreachable();
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/FloatFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/FloatFrameType.java
index 9cc991a..8597b0e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/FloatFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/FloatFrameType.java
@@ -27,7 +27,7 @@
   }
 
   @Override
-  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+  public Object getTypeOpcode(GraphLens graphLens, GraphLens codeLens, NamingLens namingLens) {
     return Opcodes.FLOAT;
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/FrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/FrameType.java
index 9db9a68..ba7cf84 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/FrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/FrameType.java
@@ -176,7 +176,7 @@
 
   DexType getObjectType(DexItemFactory dexItemFactory, DexType context);
 
-  Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens);
+  Object getTypeOpcode(GraphLens graphLens, GraphLens codeLens, NamingLens namingLens);
 
   CfLabel getUninitializedLabel();
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/InitializedNonNullReferenceFrameTypeWithInterfaces.java b/src/main/java/com/android/tools/r8/cf/code/frame/InitializedNonNullReferenceFrameTypeWithInterfaces.java
index c92d003..16dceeb 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/InitializedNonNullReferenceFrameTypeWithInterfaces.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/InitializedNonNullReferenceFrameTypeWithInterfaces.java
@@ -55,7 +55,7 @@
   }
 
   @Override
-  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+  public Object getTypeOpcode(GraphLens graphLens, GraphLens codeLens, NamingLens namingLens) {
     throw new Unreachable(
         "Unexpected InitializedNonNullReferenceFrameTypeWithInterfaces in writer");
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/InitializedNonNullReferenceFrameTypeWithoutInterfaces.java b/src/main/java/com/android/tools/r8/cf/code/frame/InitializedNonNullReferenceFrameTypeWithoutInterfaces.java
index a7475f8..7d09140 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/InitializedNonNullReferenceFrameTypeWithoutInterfaces.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/InitializedNonNullReferenceFrameTypeWithoutInterfaces.java
@@ -54,8 +54,8 @@
 
   @Override
   @SuppressWarnings("ReferenceEquality")
-  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
-    DexType rewrittenType = graphLens.lookupType(type);
+  public Object getTypeOpcode(GraphLens graphLens, GraphLens codeLens, NamingLens namingLens) {
+    DexType rewrittenType = graphLens.lookupType(type, codeLens);
     assert rewrittenType != DexItemFactory.nullValueType;
     switch (rewrittenType.toShorty()) {
       case 'L':
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/IntFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/IntFrameType.java
index 0f802b4..ad66a51 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/IntFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/IntFrameType.java
@@ -27,7 +27,7 @@
   }
 
   @Override
-  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+  public Object getTypeOpcode(GraphLens graphLens, GraphLens codeLens, NamingLens namingLens) {
     return Opcodes.INTEGER;
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/LongFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/LongFrameType.java
index a73ab16..cda7263 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/LongFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/LongFrameType.java
@@ -62,7 +62,7 @@
   }
 
   @Override
-  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+  public Object getTypeOpcode(GraphLens graphLens, GraphLens codeLens, NamingLens namingLens) {
     return Opcodes.LONG;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/LongHighFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/LongHighFrameType.java
index f2c5c9a..291cae7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/LongHighFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/LongHighFrameType.java
@@ -52,7 +52,7 @@
   }
 
   @Override
-  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+  public Object getTypeOpcode(GraphLens graphLens, GraphLens codeLens, NamingLens namingLens) {
     throw new Unreachable();
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/NullFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/NullFrameType.java
index be3fa37..007bfd7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/NullFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/NullFrameType.java
@@ -78,7 +78,7 @@
   }
 
   @Override
-  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+  public Object getTypeOpcode(GraphLens graphLens, GraphLens codeLens, NamingLens namingLens) {
     return Opcodes.NULL;
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/OneWord.java b/src/main/java/com/android/tools/r8/cf/code/frame/OneWord.java
index 3153ef8..47e37f5 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/OneWord.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/OneWord.java
@@ -33,7 +33,7 @@
   }
 
   @Override
-  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+  public Object getTypeOpcode(GraphLens graphLens, GraphLens codeLens, NamingLens namingLens) {
     return Opcodes.TOP;
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/ShortFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/ShortFrameType.java
index 82f0279..46d0316 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/ShortFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/ShortFrameType.java
@@ -27,7 +27,7 @@
   }
 
   @Override
-  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+  public Object getTypeOpcode(GraphLens graphLens, GraphLens codeLens, NamingLens namingLens) {
     throw new Unreachable("Unexpected value type: " + this);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/TwoWord.java b/src/main/java/com/android/tools/r8/cf/code/frame/TwoWord.java
index 95cab6f..2b5a049 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/TwoWord.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/TwoWord.java
@@ -41,7 +41,7 @@
   }
 
   @Override
-  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+  public Object getTypeOpcode(GraphLens graphLens, GraphLens codeLens, NamingLens namingLens) {
     throw new Unreachable("Should only be used for verification");
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedNew.java b/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedNew.java
index eadec8a..9159f99 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedNew.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedNew.java
@@ -30,7 +30,7 @@
   }
 
   @Override
-  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+  public Object getTypeOpcode(GraphLens graphLens, GraphLens codeLens, NamingLens namingLens) {
     return label.getLabel();
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedThis.java b/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedThis.java
index a1654ab..ff15b98 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedThis.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedThis.java
@@ -24,7 +24,7 @@
   }
 
   @Override
-  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+  public Object getTypeOpcode(GraphLens graphLens, GraphLens codeLens, NamingLens namingLens) {
     return Opcodes.UNINITIALIZED_THIS;
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/DebugBytecodeWriter.java b/src/main/java/com/android/tools/r8/dex/DebugBytecodeWriter.java
index e314417..0b4b15c 100644
--- a/src/main/java/com/android/tools/r8/dex/DebugBytecodeWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/DebugBytecodeWriter.java
@@ -16,19 +16,24 @@
 
   private final ObjectToOffsetMapping mapping;
   private final GraphLens graphLens;
+  private final GraphLens codeLens;
   private final DexDebugInfoForWriting info;
   private ByteBuffer buffer;
 
   public DebugBytecodeWriter(
-      DexDebugInfoForWriting info, ObjectToOffsetMapping mapping, GraphLens graphLens) {
+      DexDebugInfoForWriting info,
+      ObjectToOffsetMapping mapping,
+      GraphLens graphLens,
+      GraphLens codeLens) {
     this.info = info;
     this.mapping = mapping;
     this.graphLens = graphLens;
+    this.codeLens = codeLens;
     this.buffer = ByteBuffer.allocate(info.estimatedWriteSize());
   }
 
   public byte[] generate() {
-    info.write(this, mapping, graphLens);
+    info.write(this, mapping, graphLens, codeLens);
     return Arrays.copyOf(buffer.array(), buffer.position());
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 0744b77..3aa1c44 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -235,10 +235,10 @@
       layout.setDebugInfosOffset(dest.align(1));
       Set<DexDebugInfoForWriting> seen = new HashSet<>(mixedSectionOffsets.getDebugInfos().size());
       for (ProgramMethod method : codes) {
-        DexDebugInfoForWriting info =
-            method.getDefinition().getCode().asDexWritableCode().getDebugInfoForWriting();
+        DexWritableCode code = method.getDefinition().getCode().asDexWritableCode();
+        DexDebugInfoForWriting info = code.getDebugInfoForWriting();
         if (info != null && seen.add(info)) {
-          writeDebugItem(info);
+          writeDebugItem(code, info);
         }
       }
     }
@@ -485,6 +485,7 @@
   }
 
   private int sizeOfCodeItem(DexWritableCode code) {
+    GraphLens codeLens = code.getCodeLens(appView);
     int result = 16;
     int insnSize = code.codeSizeInBytes();
     result += insnSize * 2;
@@ -497,7 +498,7 @@
         result += LebUtils
             .sizeAsSleb128(hasCatchAll ? -handler.pairs.length : handler.pairs.length);
         for (TypeAddrPair pair : handler.pairs) {
-          result += sizeAsUleb128(mapping.getOffsetFor(pair.getType(graphLens)));
+          result += sizeAsUleb128(mapping.getOffsetFor(pair.getType(graphLens, codeLens)));
           result += sizeAsUleb128(pair.addr);
         }
         if (hasCatchAll) {
@@ -562,12 +563,14 @@
         mixedSectionOffsets.getOffsetFor(mixedSectionOffsets.getStaticFieldValuesForClass(clazz)));
   }
 
-  private void writeDebugItem(DexDebugInfoForWriting debugInfo) {
+  private void writeDebugItem(DexWritableCode code, DexDebugInfoForWriting debugInfo) {
+    GraphLens codeLens = code.getCodeLens(appView);
     mixedSectionOffsets.setOffsetFor(debugInfo, dest.position());
-    dest.putBytes(new DebugBytecodeWriter(debugInfo, mapping, graphLens).generate());
+    dest.putBytes(new DebugBytecodeWriter(debugInfo, mapping, graphLens, codeLens).generate());
   }
 
   private int writeCodeItem(ProgramMethod method, DexWritableCode code) {
+    GraphLens codeLens = code.getCodeLens(appView);
     int codeOffset = dest.align(4);
     mixedSectionOffsets.setOffsetFor(method.getDefinition(), codeOffset);
     // Fixed size header information.
@@ -600,9 +603,9 @@
         boolean hasCatchAll = handler.catchAllAddr != TryHandler.NO_HANDLER;
         dest.putSleb128(hasCatchAll ? -handler.pairs.length : handler.pairs.length);
         for (TypeAddrPair pair : handler.pairs) {
-          dest.putUleb128(mapping.getOffsetFor(pair.getType(graphLens)));
+          dest.putUleb128(mapping.getOffsetFor(pair.getType(graphLens, codeLens)));
           dest.putUleb128(pair.addr);
-          desugaredLibraryCodeToKeep.recordClass(pair.getType(graphLens));
+          desugaredLibraryCodeToKeep.recordClass(pair.getType(graphLens, codeLens));
         }
         if (hasCatchAll) {
           dest.putUleb128(handler.catchAllAddr);
diff --git a/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java b/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java
index b98df1c..1a2f918 100644
--- a/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java
+++ b/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.graph.DexWritableCode;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.profile.startup.profile.StartupProfile;
 import com.android.tools.r8.profile.startup.profile.StartupProfileClassRule;
@@ -124,7 +125,8 @@
       methodReference.collectIndexedItems(appView, indexedItemCollection);
       if (indexedItemCollection.addCode(method)) {
         DexWritableCode code = method.getDefinition().getCode().asDexWritableCode();
-        code.collectIndexedItems(appView, indexedItemCollection, method, rewriter);
+        GraphLens codeLens = code.getCodeLens(appView);
+        code.collectIndexedItems(appView, codeLens, indexedItemCollection, method, rewriter);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexCheckCast.java b/src/main/java/com/android/tools/r8/dex/code/DexCheckCast.java
index 224f94a..c0012cb 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexCheckCast.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexCheckCast.java
@@ -62,10 +62,11 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
-    DexType rewritten = appView.graphLens().lookupType(getType());
+    DexType rewritten = appView.graphLens().lookupType(getType(), codeLens);
     rewritten.collectIndexedItems(appView, indexedItems);
   }
 
@@ -77,7 +78,7 @@
       GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
-    DexType rewritten = graphLens.lookupType(getType());
+    DexType rewritten = graphLens.lookupType(getType(), codeLens);
     writeFirst(AA, dest);
     write16BitReference(rewritten, dest, mapping);
   }
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexConstClass.java b/src/main/java/com/android/tools/r8/dex/code/DexConstClass.java
index b4a593e..0013063 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexConstClass.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexConstClass.java
@@ -62,10 +62,11 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
-    DexType rewritten = appView.graphLens().lookupType(getType());
+    DexType rewritten = appView.graphLens().lookupType(getType(), codeLens);
     rewritten.collectIndexedItems(appView, indexedItems);
   }
 
@@ -77,7 +78,7 @@
       GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
-    DexType rewritten = graphLens.lookupType(getType());
+    DexType rewritten = graphLens.lookupType(getType(), codeLens);
     writeFirst(AA, dest);
     write16BitReference(rewritten, dest, mapping);
   }
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexConstMethodHandle.java b/src/main/java/com/android/tools/r8/dex/code/DexConstMethodHandle.java
index 7aa77db..f9ee91b 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexConstMethodHandle.java
@@ -95,6 +95,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexConstMethodType.java b/src/main/java/com/android/tools/r8/dex/code/DexConstMethodType.java
index 485b3da..bef8628 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexConstMethodType.java
@@ -91,6 +91,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexConstString.java b/src/main/java/com/android/tools/r8/dex/code/DexConstString.java
index b95b247..7ed956d 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexConstString.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexConstString.java
@@ -43,6 +43,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFilledNewArray.java b/src/main/java/com/android/tools/r8/dex/code/DexFilledNewArray.java
index 72fac38..d3815e8 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFilledNewArray.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFilledNewArray.java
@@ -46,10 +46,11 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
-    DexType rewritten = appView.graphLens().lookupType(getType());
+    DexType rewritten = appView.graphLens().lookupType(getType(), codeLens);
     rewritten.collectIndexedItems(appView, indexedItems);
   }
 
@@ -75,7 +76,7 @@
       GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
-    DexType rewritten = graphLens.lookupType(getType());
+    DexType rewritten = graphLens.lookupType(getType(), codeLens);
     writeFirst(A, G, dest);
     write16BitReference(rewritten, dest, mapping);
     write16BitValue(combineBytes(makeByte(F, E), makeByte(D, C)), dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFilledNewArrayRange.java b/src/main/java/com/android/tools/r8/dex/code/DexFilledNewArrayRange.java
index 436ec57..d26b547 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFilledNewArrayRange.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFilledNewArrayRange.java
@@ -46,10 +46,11 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
-    DexType rewritten = appView.graphLens().lookupType(getType());
+    DexType rewritten = appView.graphLens().lookupType(getType(), codeLens);
     rewritten.collectIndexedItems(appView, indexedItems);
   }
 
@@ -75,7 +76,7 @@
       GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
-    DexType rewritten = graphLens.lookupType(getType());
+    DexType rewritten = graphLens.lookupType(getType(), codeLens);
     writeFirst(AA, dest);
     write16BitReference(rewritten, dest, mapping);
     write16BitValue(CCCC, dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat10t.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat10t.java
index eaee763..1f1567c 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat10t.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat10t.java
@@ -69,6 +69,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat10x.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat10x.java
index 3071bfa..bdfca29 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat10x.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat10x.java
@@ -46,6 +46,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat11n.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat11n.java
index 5796308..82c165e 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat11n.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat11n.java
@@ -79,6 +79,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat11x.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat11x.java
index ef59bfb..cedd4a9 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat11x.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat11x.java
@@ -69,6 +69,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat12x.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat12x.java
index 05a6b22..12d53d2 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat12x.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat12x.java
@@ -77,6 +77,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat20t.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat20t.java
index d398e0d..d4d90c5 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat20t.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat20t.java
@@ -69,6 +69,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat21h.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat21h.java
index 5dfec30..144d491 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat21h.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat21h.java
@@ -68,6 +68,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat21s.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat21s.java
index 8ccce8c..254efee 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat21s.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat21s.java
@@ -80,6 +80,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat21t.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat21t.java
index 55eaac6..696b5bc 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat21t.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat21t.java
@@ -98,6 +98,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat22b.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat22b.java
index d3bb2df..6334bc5 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat22b.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat22b.java
@@ -85,6 +85,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat22s.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat22s.java
index fc22957..f01a75b 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat22s.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat22s.java
@@ -85,6 +85,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat22t.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat22t.java
index 00b22c1..bec44e3 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat22t.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat22t.java
@@ -102,6 +102,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat22x.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat22x.java
index 0dedb95..1077ce0 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat22x.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat22x.java
@@ -79,6 +79,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat23x.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat23x.java
index bcadc94..fab119f 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat23x.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat23x.java
@@ -83,6 +83,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat30t.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat30t.java
index 18c9137..a65da4f 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat30t.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat30t.java
@@ -68,6 +68,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat31c.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat31c.java
index 695779e..9895905 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat31c.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat31c.java
@@ -77,6 +77,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat31i.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat31i.java
index d2eed2d..d0a2932 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat31i.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat31i.java
@@ -73,6 +73,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat31t.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat31t.java
index 2e7de21..28b16eb 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat31t.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat31t.java
@@ -88,6 +88,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat32x.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat32x.java
index dd6f6bf..fd8ae9d 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat32x.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat32x.java
@@ -81,6 +81,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat45cc.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat45cc.java
index a8935b6..6506871 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat45cc.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat45cc.java
@@ -102,13 +102,14 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
     MethodLookupResult lookup =
         appView
             .graphLens()
-            .lookupMethod(getMethod(), context.getReference(), InvokeType.POLYMORPHIC);
+            .lookupMethod(getMethod(), context.getReference(), InvokeType.POLYMORPHIC, codeLens);
     assert lookup.getType() == InvokeType.POLYMORPHIC;
     lookup.getReference().collectIndexedItems(appView, indexedItems);
 
@@ -130,7 +131,7 @@
     assert rewriter.dexItemFactory().polymorphicMethods.isPolymorphicInvoke(getMethod());
     assert getMethod()
         == graphLens
-            .lookupMethod(getMethod(), context.getReference(), InvokeType.POLYMORPHIC)
+            .lookupMethod(getMethod(), context.getReference(), InvokeType.POLYMORPHIC, codeLens)
             .getReference();
     DexProto rewrittenProto = rewriter.rewriteProto(getProto());
     writeFirst(A, G, dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat4rcc.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat4rcc.java
index fcf77ed..0418db4 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat4rcc.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat4rcc.java
@@ -66,7 +66,7 @@
     assert rewriter.dexItemFactory().polymorphicMethods.isPolymorphicInvoke(getMethod());
     assert getMethod()
         == graphLens
-            .lookupMethod(getMethod(), context.getReference(), InvokeType.POLYMORPHIC)
+            .lookupMethod(getMethod(), context.getReference(), InvokeType.POLYMORPHIC, codeLens)
             .getReference();
     DexProto rewrittenProto = rewriter.rewriteProto(getProto());
     writeFirst(AA, dest);
@@ -116,13 +116,14 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
     MethodLookupResult lookup =
         appView
             .graphLens()
-            .lookupMethod(getMethod(), context.getReference(), InvokeType.POLYMORPHIC);
+            .lookupMethod(getMethod(), context.getReference(), InvokeType.POLYMORPHIC, codeLens);
     assert lookup.getType() == InvokeType.POLYMORPHIC;
     lookup.getReference().collectIndexedItems(appView, indexedItems);
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat51l.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat51l.java
index a135f5b..bf29e4b 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat51l.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat51l.java
@@ -73,6 +73,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexIgetOrIput.java b/src/main/java/com/android/tools/r8/dex/code/DexIgetOrIput.java
index 8edfdf6..26330f6 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexIgetOrIput.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexIgetOrIput.java
@@ -25,10 +25,11 @@
   @Override
   public final void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
-    DexField rewritten = appView.graphLens().lookupField(getField());
+    DexField rewritten = appView.graphLens().lookupField(getField(), codeLens);
     rewritten.collectIndexedItems(appView, indexedItems);
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexInitClass.java b/src/main/java/com/android/tools/r8/dex/code/DexInitClass.java
index 76a3289..47a4392 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexInitClass.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexInitClass.java
@@ -49,12 +49,13 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
     // We intentionally apply the graph lens first, and then the init class lens, using the fact
     // that the init class lens maps classes in the final program to fields in the final program.
-    DexType rewrittenClass = appView.graphLens().lookupType(clazz);
+    DexType rewrittenClass = appView.graphLens().lookupType(clazz, codeLens);
     DexField clinitField = appView.initClassLens().getInitClassField(rewrittenClass);
     clinitField.collectIndexedItems(appView, indexedItems);
   }
@@ -123,7 +124,7 @@
       LensCodeRewriterUtils rewriter) {
     // We intentionally apply the graph lens first, and then the init class lens, using the fact
     // that the init class lens maps classes in the final program to fields in the final program.
-    DexType rewrittenClass = graphLens.lookupType(clazz);
+    DexType rewrittenClass = graphLens.lookupType(clazz, codeLens);
     DexField clinitField = mapping.getClinitField(rewrittenClass);
     writeFirst(dest, buffer, getOpcode(clinitField));
     write16BitReference(clinitField, buffer, mapping);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexInstanceOf.java b/src/main/java/com/android/tools/r8/dex/code/DexInstanceOf.java
index a85b4a3..386a942 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexInstanceOf.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexInstanceOf.java
@@ -57,10 +57,11 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
-    DexType rewritten = appView.graphLens().lookupType(getType());
+    DexType rewritten = appView.graphLens().lookupType(getType(), codeLens);
     rewritten.collectIndexedItems(appView, indexedItems);
   }
 
@@ -91,7 +92,7 @@
       GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
-    DexType lookup = graphLens.lookupType(getType());
+    DexType lookup = graphLens.lookupType(getType(), codeLens);
     writeFirst(B, A, dest);
     write16BitReference(lookup, dest, mapping);
   }
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexInstruction.java b/src/main/java/com/android/tools/r8/dex/code/DexInstruction.java
index 12fc104..545528a 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexInstruction.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexInstruction.java
@@ -407,6 +407,7 @@
 
   public abstract void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexInvokeCustom.java b/src/main/java/com/android/tools/r8/dex/code/DexInvokeCustom.java
index 21dd606..27f54c4 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexInvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexInvokeCustom.java
@@ -47,6 +47,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexInvokeCustomRange.java b/src/main/java/com/android/tools/r8/dex/code/DexInvokeCustomRange.java
index 3bc4796..93096bd 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexInvokeCustomRange.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexInvokeCustomRange.java
@@ -47,6 +47,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexInvokeMethod.java b/src/main/java/com/android/tools/r8/dex/code/DexInvokeMethod.java
index 763b823..2a31f06 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexInvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexInvokeMethod.java
@@ -27,13 +27,14 @@
   @Override
   public final void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
     DexMethod rewritten =
         appView
             .graphLens()
-            .lookupMethod(getMethod(), context.getReference(), getInvokeType())
+            .lookupMethod(getMethod(), context.getReference(), getInvokeType(), codeLens)
             .getReference();
     rewritten.collectIndexedItems(appView, indexedItems);
   }
@@ -54,7 +55,7 @@
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     MethodLookupResult lookup =
-        graphLens.lookupMethod(getMethod(), context.getReference(), getInvokeType());
+        graphLens.lookupMethod(getMethod(), context.getReference(), getInvokeType(), codeLens);
     writeFirst(A, G, dest, lookup.getType().getDexOpcode());
     write16BitReference(lookup.getReference(), dest, mapping);
     write16BitValue(combineBytes(makeByte(F, E), makeByte(D, C)), dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexInvokeMethodRange.java b/src/main/java/com/android/tools/r8/dex/code/DexInvokeMethodRange.java
index 8809f06..9f53ea6 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexInvokeMethodRange.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexInvokeMethodRange.java
@@ -27,13 +27,14 @@
   @Override
   public final void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
     DexMethod rewritten =
         appView
             .graphLens()
-            .lookupMethod(getMethod(), context.getReference(), getInvokeType())
+            .lookupMethod(getMethod(), context.getReference(), getInvokeType(), codeLens)
             .getReference();
     rewritten.collectIndexedItems(appView, indexedItems);
   }
@@ -54,7 +55,7 @@
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     MethodLookupResult lookup =
-        graphLens.lookupMethod(getMethod(), context.getReference(), getInvokeType());
+        graphLens.lookupMethod(getMethod(), context.getReference(), getInvokeType(), codeLens);
     writeFirst(AA, dest, lookup.getType().getDexOpcodeRange());
     write16BitReference(lookup.getReference(), dest, mapping);
     write16BitValue(CCCC, dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/dex/code/DexItemBasedConstString.java
index d2adc25..88f47f8 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexItemBasedConstString.java
@@ -42,6 +42,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexNewArray.java b/src/main/java/com/android/tools/r8/dex/code/DexNewArray.java
index cad1490..e24cd52 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexNewArray.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexNewArray.java
@@ -47,10 +47,11 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
-    DexType rewritten = appView.graphLens().lookupType(getType());
+    DexType rewritten = appView.graphLens().lookupType(getType(), codeLens);
     rewritten.collectIndexedItems(appView, indexedItems);
   }
 
@@ -81,7 +82,7 @@
       GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
-    DexType lookup = graphLens.lookupType(getType());
+    DexType lookup = graphLens.lookupType(getType(), codeLens);
     writeFirst(B, A, dest);
     write16BitReference(lookup, dest, mapping);
   }
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexNewInstance.java b/src/main/java/com/android/tools/r8/dex/code/DexNewInstance.java
index 20067e5..f3885c6 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexNewInstance.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexNewInstance.java
@@ -53,10 +53,11 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
-    DexType rewritten = appView.graphLens().lookupType(getType());
+    DexType rewritten = appView.graphLens().lookupType(getType(), codeLens);
     rewritten.collectIndexedItems(appView, indexedItems);
   }
 
@@ -68,7 +69,7 @@
       GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
-    DexType rewritten = graphLens.lookupType(getType());
+    DexType rewritten = graphLens.lookupType(getType(), codeLens);
     writeFirst(AA, dest);
     write16BitReference(rewritten, dest, mapping);
   }
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexNewUnboxedEnumInstance.java b/src/main/java/com/android/tools/r8/dex/code/DexNewUnboxedEnumInstance.java
index 0ef15d8..0e02d63 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexNewUnboxedEnumInstance.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexNewUnboxedEnumInstance.java
@@ -53,6 +53,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexRecordFieldValues.java b/src/main/java/com/android/tools/r8/dex/code/DexRecordFieldValues.java
index f7e4684..7fc116b 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexRecordFieldValues.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexRecordFieldValues.java
@@ -40,6 +40,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexSgetOrSput.java b/src/main/java/com/android/tools/r8/dex/code/DexSgetOrSput.java
index 3fe34b47..9e0b6be 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexSgetOrSput.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexSgetOrSput.java
@@ -26,10 +26,11 @@
   @Override
   public final void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
-    DexField rewritten = appView.graphLens().lookupField(getField());
+    DexField rewritten = appView.graphLens().lookupField(getField(), codeLens);
     rewritten.collectIndexedItems(appView, indexedItems);
   }
 
diff --git a/src/main/java/com/android/tools/r8/dump/DumpOptions.java b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
index 0bca3bd..ac46112 100644
--- a/src/main/java/com/android/tools/r8/dump/DumpOptions.java
+++ b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.dump;
 
+import com.android.tools.r8.AndroidResourceProvider;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.dex.Marker.Backend;
 import com.android.tools.r8.dex.Marker.Tool;
@@ -75,7 +76,7 @@
   private final Collection<StartupProfileProvider> startupProfileProviders;
   private final boolean enableMissingLibraryApiModeling;
   private final boolean isAndroidPlatformBuild;
-
+  private final AndroidResourceProvider androidResourceProvider;
   private final Map<String, String> systemProperties;
 
   // TraceReferences only.
@@ -107,7 +108,8 @@
       boolean isAndroidPlatformBuild,
       Map<String, String> systemProperties,
       boolean dumpInputToFile,
-      String traceReferencesConsumer) {
+      String traceReferencesConsumer,
+      AndroidResourceProvider androidResourceProvider) {
     this.backend = backend;
     this.tool = tool;
     this.compilationMode = compilationMode;
@@ -131,6 +133,7 @@
     this.systemProperties = systemProperties;
     this.dumpInputToFile = dumpInputToFile;
     this.traceReferencesConsumer = traceReferencesConsumer;
+    this.androidResourceProvider = androidResourceProvider;
   }
 
   public String getBuildPropertiesFileContent() {
@@ -333,6 +336,14 @@
     return new Builder().setTool(tool);
   }
 
+  public boolean hasAndroidResourcesProvider() {
+    return androidResourceProvider != null;
+  }
+
+  public AndroidResourceProvider getAndroidResourceProvider() {
+    return androidResourceProvider;
+  }
+
   public static class Builder {
     // Initialize backend to DEX for backwards compatibility.
     private Backend backend = Backend.DEX;
@@ -354,6 +365,7 @@
     private List<ProguardConfigurationRule> mainDexKeepRules;
     private Collection<ArtProfileProvider> artProfileProviders;
     private Collection<StartupProfileProvider> startupProfileProviders;
+    private AndroidResourceProvider androidResourceProvider;
 
     private boolean enableMissingLibraryApiModeling = false;
     private boolean isAndroidPlatformBuild = false;
@@ -530,7 +542,17 @@
           isAndroidPlatformBuild,
           systemProperties,
           dumpInputToFile,
-          traceReferencesConsumer);
+          traceReferencesConsumer,
+          androidResourceProvider);
+    }
+
+    public AndroidResourceProvider getAndroidResourceProvider() {
+      return androidResourceProvider;
+    }
+
+    public Builder setAndroidResourceProvider(AndroidResourceProvider androidResourceProvider) {
+      this.androidResourceProvider = androidResourceProvider;
+      return this;
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java b/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
index c357177..af85bd2 100644
--- a/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
+++ b/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
@@ -16,9 +16,15 @@
 public class FeatureSplitConfiguration {
 
   private final List<FeatureSplit> featureSplits;
+  private final boolean isolatedSplits;
 
-  public FeatureSplitConfiguration(List<FeatureSplit> featureSplits) {
+  public FeatureSplitConfiguration(List<FeatureSplit> featureSplits, boolean isolatedSplits) {
     this.featureSplits = featureSplits;
+    this.isolatedSplits = isolatedSplits;
+  }
+
+  public static Builder builder() {
+    return new Builder();
   }
 
   public static class DataResourceProvidersAndConsumer {
@@ -67,4 +73,34 @@
   public List<FeatureSplit> getFeatureSplits() {
     return featureSplits;
   }
+
+  public boolean isIsolatedSplitsEnabled() {
+    return isolatedSplits;
+  }
+
+  public static class Builder {
+
+    private List<FeatureSplit> featureSplits = new ArrayList<>();
+    private boolean isolatedSplits;
+
+    public Builder addFeatureSplit(FeatureSplit featureSplit) {
+      featureSplits.add(featureSplit);
+      return this;
+    }
+
+    public List<FeatureSplit> getFeatureSplits() {
+      return featureSplits;
+    }
+
+    public Builder setEnableIsolatedSplits(boolean isolatedSplits) {
+      this.isolatedSplits = isolatedSplits;
+      return this;
+    }
+
+    public FeatureSplitConfiguration build() {
+      return featureSplits.isEmpty()
+          ? null
+          : new FeatureSplitConfiguration(featureSplits, isolatedSplits);
+    }
+  }
 }
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 f806710..d93fae1 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -751,7 +751,12 @@
     return mainDexRootSet;
   }
 
+  public boolean hasKeepInfo() {
+    return keepInfo != null;
+  }
+
   public KeepInfoCollection getKeepInfo() {
+    assert hasKeepInfo();
     return keepInfo;
   }
 
@@ -768,6 +773,10 @@
     return getKeepInfo().getClassInfo(clazz);
   }
 
+  public KeepClassInfo getKeepInfoOrDefault(DexProgramClass clazz, KeepClassInfo defaultValue) {
+    return hasKeepInfo() ? getKeepInfo().getClassInfo(clazz) : defaultValue;
+  }
+
   public KeepFieldInfo getKeepInfo(ProgramField field) {
     return getKeepInfo().getFieldInfo(field);
   }
@@ -1043,7 +1052,6 @@
     //  MemberRebindingIdentityLens.
     GraphLens newMemberRebindingLens =
         computeNewMemberRebindingLens(appView, appliedLens, firstUnappliedLens, timing);
-
     firstUnappliedLens.withAlternativeParentLens(
         newMemberRebindingLens,
         () -> {
@@ -1178,7 +1186,8 @@
               new ThreadTask() {
                 @Override
                 public void run(Timing threadTiming) {
-                  appView.resourceAnalysisResult.rewrittenWithLens(lens, threadTiming);
+                  appView.resourceAnalysisResult.rewrittenWithLens(
+                      lens, appliedLensInModifiedLens, threadTiming);
                 }
 
                 @Override
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index fe5b2c4..e730dee 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -424,6 +424,7 @@
       LensCodeRewriterUtils rewriter,
       MethodVisitor visitor) {
     GraphLens graphLens = appView.graphLens();
+    GraphLens codeLens = getCodeLens(appView);
     assert getOrComputeStackMapStatus(method, appView).isValidOrNotPresent()
         : "Could not validate stack map frames";
     DexItemFactory dexItemFactory = appView.dexItemFactory();
@@ -433,7 +434,15 @@
     if (shouldAddParameterNames(method.getDefinition(), appView)) {
       parameterLabel = new CfLabel();
       parameterLabel.write(
-          appView, method, dexItemFactory, graphLens, initClassLens, namingLens, rewriter, visitor);
+          appView,
+          method,
+          dexItemFactory,
+          graphLens,
+          codeLens,
+          initClassLens,
+          namingLens,
+          rewriter,
+          visitor);
     }
     boolean discardFrames =
         classFileVersion.isLessThan(CfVersion.V1_6)
@@ -453,7 +462,15 @@
         continue;
       }
       instruction.write(
-          appView, method, dexItemFactory, graphLens, initClassLens, namingLens, rewriter, visitor);
+          appView,
+          method,
+          dexItemFactory,
+          graphLens,
+          codeLens,
+          initClassLens,
+          namingLens,
+          rewriter,
+          visitor);
     }
     visitor.visitEnd();
     visitor.visitMaxs(maxStack, maxLocals);
@@ -462,7 +479,7 @@
       Label end = tryCatch.end.getLabel();
       for (int i = 0; i < tryCatch.guards.size(); i++) {
         DexType guard = tryCatch.guards.get(i);
-        DexType rewrittenGuard = graphLens.lookupType(guard);
+        DexType rewrittenGuard = graphLens.lookupType(guard, codeLens);
         Label target = tryCatch.targets.get(i).getLabel();
         visitor.visitTryCatchBlock(
             start,
@@ -480,6 +497,7 @@
         writeLocalVariableEntry(
             visitor,
             graphLens,
+            codeLens,
             namingLens,
             entry.getValue(),
             parameterLabel,
@@ -489,7 +507,14 @@
     } else {
       for (LocalVariableInfo local : localVariables) {
         writeLocalVariableEntry(
-            visitor, graphLens, namingLens, local.local, local.start, local.end, local.index);
+            visitor,
+            graphLens,
+            codeLens,
+            namingLens,
+            local.local,
+            local.start,
+            local.end,
+            local.index);
       }
     }
   }
@@ -497,12 +522,13 @@
   private void writeLocalVariableEntry(
       MethodVisitor visitor,
       GraphLens graphLens,
+      GraphLens codeLens,
       NamingLens namingLens,
       DebugLocalInfo info,
       CfLabel start,
       CfLabel end,
       int index) {
-    DexType rewrittenType = graphLens.lookupType(info.type);
+    DexType rewrittenType = graphLens.lookupType(info.type, codeLens);
     visitor.visitLocalVariable(
         info.name.toString(),
         namingLens.lookupDescriptor(rewrittenType).toString(),
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
index 2974981..665a472 100644
--- a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
@@ -189,12 +189,13 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
     DexMethod parentConstructor = getParentConstructor(context, rewriter.dexItemFactory());
     MethodLookupResult lookupResult =
-        appView.graphLens().lookupInvokeDirect(parentConstructor, context);
+        appView.graphLens().lookupInvokeDirect(parentConstructor, context, codeLens);
     lookupResult.getReference().collectIndexedItems(appView, indexedItems);
   }
 
@@ -398,8 +399,7 @@
       LensCodeRewriterUtils lensCodeRewriter,
       ObjectToOffsetMapping mapping) {
     DexMethod parentConstructor = getParentConstructor(context, mapping.dexItemFactory());
-    MethodLookupResult lookupResult = graphLens.lookupInvokeDirect(parentConstructor, context);
-    new DexInvokeDirect(1, lookupResult.getReference(), 0, 0, 0, 0, 0)
+    new DexInvokeDirect(1, parentConstructor, 0, 0, 0, 0, 0)
         .write(shortBuffer, context, graphLens, codeLens, mapping, lensCodeRewriter);
     new DexReturnVoid().write(shortBuffer, context, graphLens, codeLens, mapping, lensCodeRewriter);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 2a80c0b..e61ec53 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -339,10 +339,6 @@
     return methodCollection.allMethodsSorted();
   }
 
-  public void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods) {
-    methodCollection.virtualizeMethods(privateInstanceMethods);
-  }
-
   /**
    * For all annotations on the class and all annotations on its methods and fields apply the
    * specified consumer.
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 00febce..95d19ee 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -749,13 +749,14 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
     highestSortingString = null;
     for (DexInstruction insn : instructions) {
       assert !insn.isDexItemBasedConstString();
-      insn.collectIndexedItems(appView, indexedItems, context, rewriter);
+      insn.collectIndexedItems(appView, codeLens, indexedItems, context, rewriter);
       if (insn.isConstString()) {
         updateHighestSortingString(insn.asConstString().getString());
       } else if (insn.isConstStringJumbo()) {
@@ -763,10 +764,10 @@
       }
     }
     if (debugInfo != null) {
-      getDebugInfoForWriting().collectIndexedItems(appView, indexedItems);
+      getDebugInfoForWriting().collectIndexedItems(appView, codeLens, indexedItems);
     }
     for (TryHandler handler : handlers) {
-      handler.collectIndexedItems(appView, indexedItems);
+      handler.collectIndexedItems(appView, codeLens, indexedItems);
     }
   }
 
@@ -996,9 +997,10 @@
       return Equatable.equalsImpl(this, other);
     }
 
-    public void collectIndexedItems(AppView<?> appView, IndexedItemCollection indexedItems) {
+    public void collectIndexedItems(
+        AppView<?> appView, GraphLens codeLens, IndexedItemCollection indexedItems) {
       for (TypeAddrPair pair : pairs) {
-        pair.collectIndexedItems(appView, indexedItems);
+        pair.collectIndexedItems(appView, codeLens, indexedItems);
       }
     }
 
@@ -1056,12 +1058,13 @@
         return type;
       }
 
-      public DexType getType(GraphLens lens) {
-        return lens.lookupType(type);
+      public DexType getType(GraphLens lens, GraphLens codeLens) {
+        return lens.lookupType(type, codeLens);
       }
 
-      public void collectIndexedItems(AppView<?> appView, IndexedItemCollection indexedItems) {
-        DexType rewritten = getType(appView.graphLens());
+      public void collectIndexedItems(
+          AppView<?> appView, GraphLens codeLens, IndexedItemCollection indexedItems) {
+        DexType rewritten = getType(appView.graphLens(), codeLens);
         rewritten.collectIndexedItems(appView, indexedItems);
       }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
index ac495b4..5b4d625 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -24,7 +24,8 @@
 
   public static final DexDebugEvent[] EMPTY_ARRAY = {};
 
-  public void collectIndexedItems(AppView<?> appView, IndexedItemCollection collection) {
+  public void collectIndexedItems(
+      AppView<?> appView, GraphLens codeLens, IndexedItemCollection collection) {
     // Empty by default.
   }
 
@@ -74,9 +75,12 @@
   }
 
   public final void writeOn(
-      DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
+      DebugBytecodeWriter writer,
+      ObjectToOffsetMapping mapping,
+      GraphLens graphLens,
+      GraphLens codeLens) {
     assert isWritableEvent();
-    internalWriteOn(writer, mapping, graphLens);
+    internalWriteOn(writer, mapping, graphLens, codeLens);
   }
 
   boolean isWritableEvent() {
@@ -84,7 +88,10 @@
   }
 
   void internalWriteOn(
-      DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
+      DebugBytecodeWriter writer,
+      ObjectToOffsetMapping mapping,
+      GraphLens graphLens,
+      GraphLens codeLens) {
     throw new Unreachable();
   }
 
@@ -125,7 +132,10 @@
 
     @Override
     public void internalWriteOn(
-        DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
+        DebugBytecodeWriter writer,
+        ObjectToOffsetMapping mapping,
+        GraphLens graphLens,
+        GraphLens codeLens) {
       writer.putByte(Constants.DBG_ADVANCE_PC);
       writer.putUleb128(delta);
     }
@@ -180,7 +190,10 @@
 
     @Override
     public void internalWriteOn(
-        DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
+        DebugBytecodeWriter writer,
+        ObjectToOffsetMapping mapping,
+        GraphLens graphLens,
+        GraphLens codeLens) {
       writer.putByte(Constants.DBG_SET_PROLOGUE_END);
     }
 
@@ -230,7 +243,10 @@
 
     @Override
     public void internalWriteOn(
-        DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
+        DebugBytecodeWriter writer,
+        ObjectToOffsetMapping mapping,
+        GraphLens graphLens,
+        GraphLens codeLens) {
       writer.putByte(Constants.DBG_SET_EPILOGUE_BEGIN);
     }
 
@@ -291,7 +307,10 @@
 
     @Override
     public void internalWriteOn(
-        DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
+        DebugBytecodeWriter writer,
+        ObjectToOffsetMapping mapping,
+        GraphLens graphLens,
+        GraphLens codeLens) {
       writer.putByte(Constants.DBG_ADVANCE_LINE);
       writer.putSleb128(delta);
     }
@@ -364,25 +383,29 @@
 
     @Override
     public void internalWriteOn(
-        DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
+        DebugBytecodeWriter writer,
+        ObjectToOffsetMapping mapping,
+        GraphLens graphLens,
+        GraphLens codeLens) {
       writer.putByte(signature == null
           ? Constants.DBG_START_LOCAL
           : Constants.DBG_START_LOCAL_EXTENDED);
       writer.putUleb128(registerNum);
       writer.putString(name);
-      writer.putType(graphLens.lookupType(type));
+      writer.putType(graphLens.lookupType(type, codeLens));
       if (signature != null) {
         writer.putString(signature);
       }
     }
 
     @Override
-    public void collectIndexedItems(AppView<?> appView, IndexedItemCollection collection) {
+    public void collectIndexedItems(
+        AppView<?> appView, GraphLens codeLens, IndexedItemCollection collection) {
       if (name != null) {
         name.collectIndexedItems(collection);
       }
       if (type != null) {
-        DexType rewritten = appView.graphLens().lookupType(type);
+        DexType rewritten = appView.graphLens().lookupType(type, codeLens);
         rewritten.collectIndexedItems(appView, collection);
       }
       if (signature != null) {
@@ -440,7 +463,10 @@
 
     @Override
     public void internalWriteOn(
-        DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
+        DebugBytecodeWriter writer,
+        ObjectToOffsetMapping mapping,
+        GraphLens graphLens,
+        GraphLens codeLens) {
       writer.putByte(Constants.DBG_END_LOCAL);
       writer.putUleb128(registerNum);
     }
@@ -492,7 +518,10 @@
 
     @Override
     public void internalWriteOn(
-        DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
+        DebugBytecodeWriter writer,
+        ObjectToOffsetMapping mapping,
+        GraphLens graphLens,
+        GraphLens codeLens) {
       writer.putByte(Constants.DBG_RESTART_LOCAL);
       writer.putUleb128(registerNum);
     }
@@ -551,7 +580,8 @@
     }
 
     @Override
-    public void collectIndexedItems(AppView<?> appView, IndexedItemCollection collection) {
+    public void collectIndexedItems(
+        AppView<?> appView, GraphLens codeLens, IndexedItemCollection collection) {
       fileName.collectIndexedItems(collection);
     }
 
@@ -682,7 +712,10 @@
 
     @Override
     public void internalWriteOn(
-        DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
+        DebugBytecodeWriter writer,
+        ObjectToOffsetMapping mapping,
+        GraphLens graphLens,
+        GraphLens codeLens) {
       writer.putByte(value);
     }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
index 3707fdf..24ee6d2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
@@ -152,7 +152,8 @@
     }
 
     @Override
-    public void collectIndexedItems(AppView<?> appView, IndexedItemCollection indexedItems) {
+    public void collectIndexedItems(
+        AppView<?> appView, GraphLens codeLens, IndexedItemCollection indexedItems) {
       // No indexed items to collect.
     }
 
@@ -172,15 +173,21 @@
 
     @Override
     public void write(
-        DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
+        DebugBytecodeWriter writer,
+        ObjectToOffsetMapping mapping,
+        GraphLens graphLens,
+        GraphLens codeLens) {
       writer.putUleb128(START_LINE);
       writer.putUleb128(parameterCount);
       for (int i = 0; i < parameterCount; i++) {
         writer.putString(null);
       }
-      mapping.dexItemFactory().zeroChangeDefaultEvent.writeOn(writer, mapping, graphLens);
+      mapping.dexItemFactory().zeroChangeDefaultEvent.writeOn(writer, mapping, graphLens, codeLens);
       for (int i = 0; i < maxPc; i++) {
-        mapping.dexItemFactory().oneChangeDefaultEvent.writeOn(writer, mapping, graphLens);
+        mapping
+            .dexItemFactory()
+            .oneChangeDefaultEvent
+            .writeOn(writer, mapping, graphLens, codeLens);
       }
       writer.putByte(Constants.DBG_END_SEQUENCE);
     }
@@ -263,14 +270,15 @@
       return visitor.visit(this, other.asEventBasedInfo(), EventBasedDebugInfo::specify);
     }
 
-    public void collectIndexedItems(AppView<?> appView, IndexedItemCollection indexedItems) {
+    public void collectIndexedItems(
+        AppView<?> appView, GraphLens codeLens, IndexedItemCollection indexedItems) {
       for (DexString parameter : parameters) {
         if (parameter != null) {
           parameter.collectIndexedItems(indexedItems);
         }
       }
       for (DexDebugEvent event : events) {
-        event.collectIndexedItems(appView, indexedItems);
+        event.collectIndexedItems(appView, codeLens, indexedItems);
       }
     }
 
@@ -363,8 +371,9 @@
     }
 
     @Override
-    public void collectIndexedItems(AppView<?> appView, IndexedItemCollection indexedItems) {
-      super.collectIndexedItems(appView, indexedItems);
+    public void collectIndexedItems(
+        AppView<?> appView, GraphLens codeLens, IndexedItemCollection indexedItems) {
+      super.collectIndexedItems(appView, codeLens, indexedItems);
     }
 
     @Override
@@ -385,14 +394,17 @@
 
     @Override
     public void write(
-        DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
+        DebugBytecodeWriter writer,
+        ObjectToOffsetMapping mapping,
+        GraphLens graphLens,
+        GraphLens codeLens) {
       writer.putUleb128(startLine);
       writer.putUleb128(parameters.length);
       for (DexString name : parameters) {
         writer.putString(name);
       }
       for (DexDebugEvent event : events) {
-        event.writeOn(writer, mapping, graphLens);
+        event.writeOn(writer, mapping, graphLens, codeLens);
       }
       writer.putByte(Constants.DBG_END_SEQUENCE);
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfoForWriting.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfoForWriting.java
index 67ab2aa..a01f2fb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugInfoForWriting.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfoForWriting.java
@@ -14,9 +14,14 @@
 
   void collectMixedSectionItems(MixedSectionCollection collection);
 
-  void collectIndexedItems(AppView<?> appView, IndexedItemCollection indexedItems);
+  void collectIndexedItems(
+      AppView<?> appView, GraphLens codeLens, IndexedItemCollection indexedItems);
 
   int estimatedWriteSize();
 
-  void write(DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens);
+  void write(
+      DebugBytecodeWriter writer,
+      ObjectToOffsetMapping mapping,
+      GraphLens graphLens,
+      GraphLens codeLens);
 }
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 730fb6c..40bdbe2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -761,8 +761,7 @@
 
   @Override
   public String toString() {
-    checkIfObsolete();
-    return "Encoded method " + getReference();
+    return toSourceString();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexWritableCode.java b/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
index afe2afb..7cc7830 100644
--- a/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
@@ -56,6 +56,7 @@
 
   void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter);
diff --git a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
index 9afb69a..8e04491 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
@@ -208,33 +208,6 @@
   }
 
   @Override
-  void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods) {
-    int vLen = virtualMethods.length;
-    int dLen = directMethods.length;
-    int mLen = privateInstanceMethods.size();
-    assert mLen <= dLen;
-
-    DexEncodedMethod[] newDirectMethods = new DexEncodedMethod[dLen - mLen];
-    int index = 0;
-    for (int i = 0; i < dLen; i++) {
-      DexEncodedMethod encodedMethod = directMethods[i];
-      if (!privateInstanceMethods.contains(encodedMethod)) {
-        newDirectMethods[index++] = encodedMethod;
-      }
-    }
-    assert index == dLen - mLen;
-    setDirectMethods(newDirectMethods);
-
-    DexEncodedMethod[] newVirtualMethods = new DexEncodedMethod[vLen + mLen];
-    System.arraycopy(virtualMethods, 0, newVirtualMethods, 0, vLen);
-    index = vLen;
-    for (DexEncodedMethod encodedMethod : privateInstanceMethods) {
-      newVirtualMethods[index++] = encodedMethod;
-    }
-    setVirtualMethods(newVirtualMethods);
-  }
-
-  @Override
   DexEncodedMethod getDirectMethod(DexMethod method) {
     for (DexEncodedMethod directMethod : directMethods) {
       if (method.match(directMethod)) {
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
index e14da8e..cc69b8e 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -367,11 +367,6 @@
     backing.setVirtualMethods(methods);
   }
 
-  public void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods) {
-    resetVirtualMethodCaches();
-    backing.virtualizeMethods(privateInstanceMethods);
-  }
-
   public boolean hasAnnotations() {
     return traverse(
             method ->
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
index b270516..18a72a3 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
@@ -135,7 +135,4 @@
 
   abstract DexEncodedMethod replaceDirectMethodWithVirtualMethod(
       DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement);
-
-  abstract void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods);
-
 }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollectionConcurrencyChecked.java b/src/main/java/com/android/tools/r8/graph/MethodCollectionConcurrencyChecked.java
index 0889c63..78cfebe8 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollectionConcurrencyChecked.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollectionConcurrencyChecked.java
@@ -323,13 +323,6 @@
   }
 
   @Override
-  public void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods) {
-    assert assertWriteEntry();
-    super.virtualizeMethods(privateInstanceMethods);
-    assert assertWriteExit();
-  }
-
-  @Override
   public void useSortedBacking() {
     assert assertWriteEntry();
     super.useSortedBacking();
diff --git a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
index 84f114f..fa553d9 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
@@ -365,21 +365,6 @@
   }
 
   @Override
-  void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods) {
-    // This is a no-op as the virtualizer has modified the encoded method bits.
-    assert verifyVirtualizedMethods(privateInstanceMethods);
-  }
-
-  @SuppressWarnings("ReferenceEquality")
-  private boolean verifyVirtualizedMethods(Set<DexEncodedMethod> methods) {
-    for (DexEncodedMethod method : methods) {
-      assert belongsToVirtualPool(method);
-      assert methodMap.get(method.getSignature()) == method;
-    }
-    return true;
-  }
-
-  @Override
   MethodMapBacking map(Function<DexEncodedMethod, DexEncodedMethod> fn) {
     MethodMapBacking newBacking = new MethodMapBacking(isSorted, createMap(methodMap.size()));
     methodMap.forEach((ignore, method) -> newBacking.addMethod(fn.apply(method)));
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 53e4ddc..9b4fa96 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -72,7 +72,8 @@
     getReference().collectIndexedItems(appView, indexedItems);
     if (definition.hasCode()) {
       Code code = definition.getCode();
-      code.asDexWritableCode().collectIndexedItems(appView, indexedItems, this, rewriter);
+      GraphLens codeLens = code.getCodeLens(appView);
+      code.asDexWritableCode().collectIndexedItems(appView, codeLens, indexedItems, this, rewriter);
     }
     definition.annotations().collectIndexedItems(appView, indexedItems);
     definition.parameterAnnotationsList.collectIndexedItems(appView, indexedItems);
diff --git a/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java b/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
index dc2bbdc..7e359ec 100644
--- a/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
+++ b/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
@@ -79,9 +79,11 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
+    assert exceptionType.isIdenticalTo(appView.graphLens().lookupType(exceptionType, codeLens));
     rewriter
         .dexItemFactory()
         .createInstanceInitializer(exceptionType)
diff --git a/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java b/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
index ddfbba1..48fe289 100644
--- a/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
+++ b/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
@@ -100,6 +100,7 @@
   @Override
   public void collectIndexedItems(
       AppView<?> appView,
+      GraphLens codeLens,
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
index ed61adb..950bff6 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
@@ -210,16 +210,15 @@
       return resourceShrinkerState.getR8ResourceShrinkerModel();
     }
 
-    @SuppressWarnings("ReferenceEquality")
-    public void rewrittenWithLens(GraphLens lens, Timing timing) {
+    public void rewrittenWithLens(GraphLens lens, GraphLens appliedLens, Timing timing) {
       Map<DexType, DexType> changed = new IdentityHashMap<>();
       for (DexType dexType : rClassFieldToValueStoreMap.keySet()) {
-        DexType rewritten = lens.lookupClassType(dexType);
-        if (rewritten != dexType) {
+        DexType rewritten = lens.lookupClassType(dexType, appliedLens);
+        if (rewritten.isNotIdenticalTo(dexType)) {
           changed.put(dexType, rewritten);
         }
       }
-      if (changed.size() > 0) {
+      if (!changed.isEmpty()) {
         Map<DexType, RClassFieldToValueStore> rewrittenMap = new IdentityHashMap<>();
         rClassFieldToValueStoreMap.forEach(
             (type, map) -> {
diff --git a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
index 64f8399..eff9aa9 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
@@ -414,6 +414,10 @@
     return null;
   }
 
+  public boolean isNumberUnboxerLens() {
+    return false;
+  }
+
   public boolean isHorizontalClassMergerGraphLens() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java
index d906b4c..63aa73a 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.utils.collections.EmptyBidirectionalOneToOneMap;
 import java.util.Map;
 import java.util.function.Function;
-import java.util.stream.Collectors;
 
 /**
  * GraphLens implementation with a parent lens where the mapping of types, methods and fields can be
@@ -291,24 +290,6 @@
 
   @Override
   public String toString() {
-    StringBuilder builder = new StringBuilder();
-    typeMap.forEach(
-        (from, to) ->
-            builder
-                .append(from.getTypeName())
-                .append(" -> ")
-                .append(to.getTypeName())
-                .append(System.lineSeparator()));
-    fieldMap.forEachManyToOneMapping(
-        (fromSet, to) -> {
-          builder.append(
-              fromSet.stream()
-                  .map(DexField::toSourceString)
-                  .collect(Collectors.joining("," + System.lineSeparator())));
-          builder.append(" -> ");
-          builder.append(to.toSourceString()).append(System.lineSeparator());
-        });
-    builder.append(getPrevious().toString());
-    return builder.toString();
+    return getClass().getTypeName();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLensWithCustomLensCodeRewriter.java b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLensWithCustomLensCodeRewriter.java
index 84599f0..e64f101 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLensWithCustomLensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLensWithCustomLensCodeRewriter.java
@@ -21,8 +21,10 @@
       AppView<?> appView,
       BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap,
       BidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> methodMap,
-      BidirectionalManyToManyRepresentativeMap<DexType, DexType> typeMap) {
+      BidirectionalManyToManyRepresentativeMap<DexType, DexType> typeMap,
+      CustomLensCodeRewriter customLensCodeRewriter) {
     super(appView, fieldMap, methodMap, typeMap);
+    this.customLensCodeRewriter = customLensCodeRewriter;
   }
 
   public NestedGraphLensWithCustomLensCodeRewriter(
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 23df11c..3f628b9 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
@@ -33,7 +33,7 @@
 
   private static final RewrittenPrototypeDescription NONE = new RewrittenPrototypeDescription();
 
-  private final List<ExtraParameter> extraParameters;
+  private final List<? extends ExtraParameter> extraParameters;
   private final ArgumentInfoCollection argumentInfoCollection;
   private final RewrittenTypeInfo rewrittenReturnInfo;
 
@@ -44,7 +44,7 @@
   }
 
   private RewrittenPrototypeDescription(
-      List<ExtraParameter> extraParameters,
+      List<? extends ExtraParameter> extraParameters,
       RewrittenTypeInfo rewrittenReturnInfo,
       ArgumentInfoCollection argumentsInfo) {
     assert argumentsInfo != null;
@@ -55,7 +55,7 @@
   }
 
   public static RewrittenPrototypeDescription create(
-      List<ExtraParameter> extraParameters,
+      List<? extends ExtraParameter> extraParameters,
       RewrittenTypeInfo rewrittenReturnInfo,
       ArgumentInfoCollection argumentsInfo) {
     return extraParameters.isEmpty() && rewrittenReturnInfo == null && argumentsInfo.isEmpty()
@@ -116,7 +116,7 @@
     return !extraParameters.isEmpty();
   }
 
-  public List<ExtraParameter> getExtraParameters() {
+  public List<? extends ExtraParameter> getExtraParameters() {
     return extraParameters;
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
index e0f2b3b..8062a16 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
@@ -14,7 +14,6 @@
 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.shaking.KeepInfoCollection;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
@@ -178,11 +177,10 @@
     }
     Iterator<DexProgramClass> candidateIterator = candidates.iterator();
     DexProgramClass target = IterableUtils.first(candidates);
-    KeepInfoCollection keepInfo = appView.getKeepInfo();
     while (candidateIterator.hasNext()) {
       DexProgramClass current = candidateIterator.next();
-      if (keepInfo != null
-          && keepInfo.getClassInfo(current).isMinificationAllowed(appView.options())) {
+      if (appView.hasKeepInfo()
+          && appView.getKeepInfo(current).isMinificationAllowed(appView.options())) {
         target = current;
         break;
       }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java
index cb443c4..eb6fe55 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java
@@ -19,7 +19,7 @@
 
   @Override
   public boolean canMerge(DexProgramClass clazz) {
-    return !appView.appInfo().isNoHorizontalClassMergingOfType(clazz);
+    return appView.getKeepInfo(clazz).isHorizontalClassMergingAllowed(appView.options());
   }
 
   @Override
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 712fa0d..e8e4799 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
@@ -54,6 +54,7 @@
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 
@@ -80,6 +81,9 @@
   private final Map<DexProgramClass, ProgramFieldMap<AbstractValue>>
       abstractFinalInstanceFieldValues = new ConcurrentHashMap<>();
 
+  private final Set<DexProgramClass> classesWithPrunedInstanceInitializers =
+      ConcurrentHashMap.newKeySet();
+
   FieldAssignmentTracker(AppView<AppInfoWithLiveness> appView) {
     this.abstractValueFactory = appView.abstractValueFactory();
     this.appView = appView;
@@ -360,41 +364,49 @@
 
   private void recordAllInstanceFieldPutsProcessed(
       ProgramField field, OptimizationFeedbackDelayed feedback) {
-    if (appView.appInfo().isInstanceFieldWrittenOnlyInInstanceInitializers(field)) {
-      AbstractValue abstractValue = BottomValue.getInstance();
-      DexProgramClass clazz = field.getHolder();
-      for (DexEncodedMethod method : clazz.directMethods(DexEncodedMethod::isInstanceInitializer)) {
-        InstanceFieldInitializationInfo fieldInitializationInfo =
-            method
-                .getOptimizationInfo()
-                .getContextInsensitiveInstanceInitializerInfo()
-                .fieldInitializationInfos()
-                .get(field);
-        if (fieldInitializationInfo.isSingleValue()) {
-          abstractValue =
-              appView
-                  .getAbstractValueFieldJoiner()
-                  .join(abstractValue, fieldInitializationInfo.asSingleValue(), field);
-          if (abstractValue.isUnknown()) {
-            break;
-          }
-        } else if (fieldInitializationInfo.isTypeInitializationInfo()) {
-          // TODO(b/149732532): Not handled, for now.
-          abstractValue = UnknownValue.getInstance();
-          break;
-        } else {
-          assert fieldInitializationInfo.isArgumentInitializationInfo()
-              || fieldInitializationInfo.isUnknown();
-          abstractValue = UnknownValue.getInstance();
+    if (!appView.appInfo().isInstanceFieldWrittenOnlyInInstanceInitializers(field)) {
+      return;
+    }
+    DexProgramClass clazz = field.getHolder();
+    if (classesWithPrunedInstanceInitializers.contains(clazz)) {
+      // The current method is analyzing the instance-puts to the field in the instance initializers
+      // of the holder class. Due to single caller inlining of instance initializers some of the
+      // methods needed for the analysis may have been pruned from the app, in which case the
+      // analysis result will not be conservative.
+      return;
+    }
+    AbstractValue abstractValue = BottomValue.getInstance();
+    for (DexEncodedMethod method : clazz.directMethods(DexEncodedMethod::isInstanceInitializer)) {
+      InstanceFieldInitializationInfo fieldInitializationInfo =
+          method
+              .getOptimizationInfo()
+              .getContextInsensitiveInstanceInitializerInfo()
+              .fieldInitializationInfos()
+              .get(field);
+      if (fieldInitializationInfo.isSingleValue()) {
+        abstractValue =
+            appView
+                .getAbstractValueFieldJoiner()
+                .join(abstractValue, fieldInitializationInfo.asSingleValue(), field);
+        if (abstractValue.isUnknown()) {
           break;
         }
+      } else if (fieldInitializationInfo.isTypeInitializationInfo()) {
+        // TODO(b/149732532): Not handled, for now.
+        abstractValue = UnknownValue.getInstance();
+        break;
+      } else {
+        assert fieldInitializationInfo.isArgumentInitializationInfo()
+            || fieldInitializationInfo.isUnknown();
+        abstractValue = UnknownValue.getInstance();
+        break;
       }
+    }
 
-      assert !abstractValue.isBottom();
+    assert !abstractValue.isBottom();
 
-      if (!abstractValue.isUnknown()) {
-        feedback.recordFieldHasAbstractValue(field.getDefinition(), appView, abstractValue);
-      }
+    if (!abstractValue.isUnknown()) {
+      feedback.recordFieldHasAbstractValue(field.getDefinition(), appView, abstractValue);
     }
   }
 
@@ -421,6 +433,16 @@
         });
   }
 
+  public void onMethodPruned(ProgramMethod method) {
+    onMethodCodePruned(method);
+  }
+
+  public void onMethodCodePruned(ProgramMethod method) {
+    if (method.getDefinition().isInstanceInitializer()) {
+      classesWithPrunedInstanceInitializers.add(method.getHolder());
+    }
+  }
+
   public void waveDone(ProgramMethodSet wave, OptimizationFeedbackDelayed feedback) {
     // This relies on the instance initializer info in the method optimization feedback. It is
     // therefore important that the optimization info has been flushed in advance.
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index 0dcf961..5cbe158 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -315,17 +315,15 @@
       AppView<? extends AppInfoWithClassHierarchy> appView,
       SubtypingInfo subtypingInfo,
       PredicateSet<DexType> alwaysClassInline,
-      Set<DexType> neverMergeClassVertically,
-      Set<DexType> neverMergeClassHorizontally,
       Set<DexMethod> alwaysInline,
-      Set<DexMethod> bypassClinitforInlining) {
+      Set<DexMethod> bypassClinitforInlining,
+      DependentMinimumKeepInfoCollection dependentMinimumKeepInfo) {
     new RootSetExtension(
             appView,
             alwaysClassInline,
-            neverMergeClassVertically,
-            neverMergeClassHorizontally,
             alwaysInline,
-            bypassClinitforInlining)
+            bypassClinitforInlining,
+            dependentMinimumKeepInfo)
         .extend(subtypingInfo);
   }
 
@@ -444,26 +442,22 @@
     private final ProtoReferences references;
 
     private final PredicateSet<DexType> alwaysClassInline;
-    private final Set<DexType> neverMergeClassVertically;
-    private final Set<DexType> neverMergeClassHorizontally;
-
     private final Set<DexMethod> alwaysInline;
     private final Set<DexMethod> bypassClinitforInlining;
+    private final DependentMinimumKeepInfoCollection dependentMinimumKeepInfo;
 
     RootSetExtension(
         AppView<? extends AppInfoWithClassHierarchy> appView,
         PredicateSet<DexType> alwaysClassInline,
-        Set<DexType> neverMergeClassVertically,
-        Set<DexType> neverMergeClassHorizontally,
         Set<DexMethod> alwaysInline,
-        Set<DexMethod> bypassClinitforInlining) {
+        Set<DexMethod> bypassClinitforInlining,
+        DependentMinimumKeepInfoCollection dependentMinimumKeepInfo) {
       this.appView = appView;
       this.references = appView.protoShrinker().references;
       this.alwaysClassInline = alwaysClassInline;
-      this.neverMergeClassVertically = neverMergeClassVertically;
-      this.neverMergeClassHorizontally = neverMergeClassHorizontally;
       this.alwaysInline = alwaysInline;
       this.bypassClinitforInlining = bypassClinitforInlining;
+      this.dependentMinimumKeepInfo = dependentMinimumKeepInfo;
     }
 
     void extend(SubtypingInfo subtypingInfo) {
@@ -527,8 +521,11 @@
     }
 
     private void neverMergeClass(DexType type) {
-      neverMergeClassVertically.add(type);
-      neverMergeClassHorizontally.add(type);
+      dependentMinimumKeepInfo
+          .getOrCreateUnconditionalMinimumKeepInfoFor(type)
+          .asClassJoiner()
+          .disallowHorizontalClassMerging()
+          .disallowVerticalClassMerging();
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index 3588346..0f6fb1f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -87,6 +87,13 @@
     Instruction previous = previous();
     assert previous == instruction;
   }
+
+  default void addBeforeAndPositionAfterNewInstruction(Instruction instruction) {
+    previous();
+    add(instruction);
+    next();
+  }
+
   /** See {@link #replaceCurrentInstruction(Instruction, Set)}. */
   default void replaceCurrentInstruction(Instruction newInstruction) {
     replaceCurrentInstruction(newInstruction, null);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 2a10e4c..9a7b1e0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -247,9 +247,11 @@
       assert appView.rootSet() != null;
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
       assumeInserter = new AssumeInserter(appViewWithLiveness);
+      this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness);
+      this.inliner = new Inliner(appViewWithLiveness, this, lensCodeRewriter);
       this.classInliner =
           options.enableClassInlining && options.inlinerOptions().enableInlining
-              ? new ClassInliner()
+              ? new ClassInliner(appViewWithLiveness, inliner)
               : null;
       this.dynamicTypeOptimization = new DynamicTypeOptimization(appViewWithLiveness);
       this.fieldAccessAnalysis = new FieldAccessAnalysis(appViewWithLiveness);
@@ -259,8 +261,6 @@
               : null;
       this.enumUnboxer = EnumUnboxer.create(appViewWithLiveness);
       this.numberUnboxer = NumberUnboxer.create(appViewWithLiveness);
-      this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness);
-      this.inliner = new Inliner(appViewWithLiveness, this, lensCodeRewriter);
       this.outliner = Outliner.create(appViewWithLiveness);
       this.memberValuePropagation = new R8MemberValuePropagation(appViewWithLiveness);
       this.methodOptimizationInfoCollector =
@@ -766,13 +766,11 @@
       // lambda, it does not get collected by merger.
       assert options.inlinerOptions().enableInlining && inliner != null;
       classInliner.processMethodCode(
-          appView.withLiveness(),
           code.context(),
           code,
           feedback,
           methodProcessor,
           methodProcessingContext,
-          inliner,
           new LazyBox<>(
               () ->
                   inliner.createDefaultOracle(
@@ -1152,6 +1150,7 @@
     assert method.getHolder().lookupMethod(method.getReference()) == null;
     appView.withArgumentPropagator(argumentPropagator -> argumentPropagator.onMethodPruned(method));
     enumUnboxer.onMethodPruned(method);
+    fieldAccessAnalysis.fieldAssignmentTracker().onMethodPruned(method);
     numberUnboxer.onMethodPruned(method);
     outliner.onMethodPruned(method);
     if (inliner != null) {
@@ -1170,6 +1169,7 @@
     appView.withArgumentPropagator(
         argumentPropagator -> argumentPropagator.onMethodCodePruned(method));
     enumUnboxer.onMethodCodePruned(method);
+    fieldAccessAnalysis.fieldAssignmentTracker().onMethodCodePruned(method);
     numberUnboxer.onMethodCodePruned(method);
     outliner.onMethodCodePruned(method);
     if (inliner != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
index c48cb06..6880ed1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexItemBasedValueString;
@@ -50,6 +51,7 @@
 import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo;
 import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo.ClassNameMapping;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepClassInfo;
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.google.common.collect.Maps;
@@ -177,7 +179,7 @@
           Value value = put.value().getAliasedValue();
           if (unnecessaryStaticPuts.contains(put)) {
             if (fieldType == dexItemFactory.stringType) {
-              fieldsWithStaticValues.put(field, getDexStringValue(value, context.getHolderType()));
+              fieldsWithStaticValues.put(field, getDexStringValue(value, context.getHolder()));
             } else if (fieldType.isClassType() || fieldType.isArrayType()) {
               if (value.isZero()) {
                 fieldsWithStaticValues.put(field, DexValueNull.NULL);
@@ -308,7 +310,7 @@
     return new ClassInitializerDefaultsResult(fieldsWithStaticValues);
   }
 
-  private DexValue getDexStringValue(Value inValue, DexType holder) {
+  private DexValue getDexStringValue(Value inValue, DexProgramClass holder) {
     if (inValue.isPhi()) {
       return null;
     }
@@ -337,25 +339,21 @@
   }
 
   @SuppressWarnings("ReferenceEquality")
-  private DexValue getDexStringValueForInvoke(DexMethod invokedMethod, DexType holder) {
-    DexClass clazz = appView.definitionFor(holder);
-    if (clazz == null) {
-      assert false;
-      return null;
-    }
-
-    if (appView.enableWholeProgramOptimizations()
-        && appView.withLiveness().appInfo().isMinificationAllowed(clazz)) {
+  private DexValue getDexStringValueForInvoke(DexMethod invokedMethod, DexProgramClass holder) {
+    if (appView
+        .getKeepInfoOrDefault(holder, KeepClassInfo.top())
+        .isMinificationAllowed(appView.options())) {
       if (invokedMethod == dexItemFactory.classMethods.getName) {
-        return new DexItemBasedValueString(holder, ClassNameComputationInfo.getInstance(NAME));
+        return new DexItemBasedValueString(
+            holder.getType(), ClassNameComputationInfo.getInstance(NAME));
       }
       if (invokedMethod == dexItemFactory.classMethods.getCanonicalName) {
         return new DexItemBasedValueString(
-            holder, ClassNameComputationInfo.getInstance(CANONICAL_NAME));
+            holder.getType(), ClassNameComputationInfo.getInstance(CANONICAL_NAME));
       }
       if (invokedMethod == dexItemFactory.classMethods.getSimpleName) {
         return new DexItemBasedValueString(
-            holder, ClassNameComputationInfo.getInstance(SIMPLE_NAME));
+            holder.getType(), ClassNameComputationInfo.getInstance(SIMPLE_NAME));
       }
       if (invokedMethod == dexItemFactory.classMethods.getTypeName) {
         // TODO(b/119426668): desugar Type#getTypeName
@@ -375,7 +373,8 @@
       // TODO(b/119426668): desugar Type#getTypeName
     }
     if (mapping != null) {
-      return new DexValueString(mapping.map(holder.toDescriptorString(), clazz, dexItemFactory));
+      return new DexValueString(
+          mapping.map(holder.getType().toDescriptorString(), holder, dexItemFactory));
     }
     assert false;
     return null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 020390f..08ff606 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
@@ -42,9 +41,17 @@
     NOT_ELIGIBLE
   }
 
+  private final AppView<AppInfoWithLiveness> appView;
+  private final Inliner inliner;
+
   private final ConcurrentHashMap<DexClass, EligibilityStatus> knownClasses =
       new ConcurrentHashMap<>();
 
+  public ClassInliner(AppView<AppInfoWithLiveness> appView, Inliner inliner) {
+    this.appView = appView;
+    this.inliner = inliner;
+  }
+
   // Process method code and inline eligible class instantiations, in short:
   //
   // - collect all 'new-instance' and 'static-get' instructions (called roots below) in
@@ -122,16 +129,13 @@
   //     }
   //   }
   //
-  public final void processMethodCode(
-      AppView<AppInfoWithLiveness> appView,
+  public void processMethodCode(
       ProgramMethod method,
       IRCode code,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
       MethodProcessingContext methodProcessingContext,
-      Inliner inliner,
       LazyBox<InliningOracle> defaultOracle) {
-
     // Collect all the new-instance and static-get instructions in the code before inlining.
     List<Instruction> roots =
         Lists.newArrayList(code.instructions(insn -> insn.isNewInstance() || insn.isStaticGet()));
@@ -152,7 +156,6 @@
             new InlineCandidateProcessor(
                 appView,
                 inliner,
-                clazz -> isClassEligible(appView, clazz),
                 methodProcessor,
                 method,
                 root);
@@ -164,7 +167,7 @@
           rootsIterator.remove();
           continue;
         }
-        status = processor.isClassAndUsageEligible();
+        status = isClassEligible(processor.getEligibleClass());
         if (status != EligibilityStatus.ELIGIBLE) {
           // This root will never be inlined.
           rootsIterator.remove();
@@ -247,15 +250,15 @@
       new BranchSimplifier(appView)
           .run(code, methodProcessor, methodProcessingContext, Timing.empty());
       // If a method was inlined we may see more trivial computation/conversion of String.
-      new StringOptimizer(appView).run(code, Timing.empty());
+      new StringOptimizer(appView)
+          .run(code, methodProcessor, methodProcessingContext, Timing.empty());
     }
   }
 
-  private EligibilityStatus isClassEligible(
-      AppView<AppInfoWithLiveness> appView, DexProgramClass clazz) {
+  private EligibilityStatus isClassEligible(DexProgramClass clazz) {
     EligibilityStatus eligible = knownClasses.get(clazz);
     if (eligible == null) {
-      EligibilityStatus computed = computeClassEligible(appView, clazz);
+      EligibilityStatus computed = computeClassEligible(clazz);
       EligibilityStatus existing = knownClasses.putIfAbsent(clazz, computed);
       assert existing == null || existing == computed;
       eligible = existing == null ? computed : existing;
@@ -263,27 +266,22 @@
     return eligible;
   }
 
-  @SuppressWarnings("ReferenceEquality")
   // Class is eligible for this optimization. Eligibility implementation:
   //   - is not an abstract class or interface
   //   - does not declare finalizer
   //   - does not trigger any static initializers except for its own
-  private EligibilityStatus computeClassEligible(
-      AppView<AppInfoWithLiveness> appView, DexProgramClass clazz) {
+  private EligibilityStatus computeClassEligible(DexProgramClass clazz) {
     if (clazz == null
         || clazz.isAbstract()
         || clazz.isInterface()
-        || !appView.appInfo().isClassInliningAllowed(clazz)) {
+        || !appView.getKeepInfo(clazz).isClassInliningAllowed(appView.options())) {
       return EligibilityStatus.NOT_ELIGIBLE;
     }
 
     // Class must not define finalizer.
     DexItemFactory dexItemFactory = appView.dexItemFactory();
-    for (DexEncodedMethod method : clazz.virtualMethods()) {
-      if (method.getReference().name == dexItemFactory.finalizeMethodName
-          && method.getReference().proto == dexItemFactory.objectMembers.finalize.proto) {
-        return EligibilityStatus.NOT_ELIGIBLE;
-      }
+    if (clazz.lookupMethod(dexItemFactory.objectMembers.finalize) != null) {
+      return EligibilityStatus.NOT_ELIGIBLE;
     }
 
     // Check for static initializers in this class or any of interfaces it implements.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index afa1947..7935419 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -82,7 +82,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Function;
 
 final class InlineCandidateProcessor {
 
@@ -92,7 +91,6 @@
   private final AppView<AppInfoWithLiveness> appView;
   private final DexItemFactory dexItemFactory;
   private final Inliner inliner;
-  private final Function<DexProgramClass, EligibilityStatus> isClassEligible;
   private final MethodProcessor methodProcessor;
   private final ProgramMethod method;
   private final Instruction root;
@@ -115,14 +113,12 @@
   InlineCandidateProcessor(
       AppView<AppInfoWithLiveness> appView,
       Inliner inliner,
-      Function<DexProgramClass, EligibilityStatus> isClassEligible,
       MethodProcessor methodProcessor,
       ProgramMethod method,
       Instruction root) {
     this.appView = appView;
     this.dexItemFactory = appView.dexItemFactory();
     this.inliner = inliner;
-    this.isClassEligible = isClassEligible;
     this.method = method;
     this.root = root;
     this.methodProcessor = methodProcessor;
@@ -200,21 +196,6 @@
     return EligibilityStatus.ELIGIBLE;
   }
 
-  // Checks if the class is eligible and is properly used. Regarding general class
-  // eligibility rules see comment on computeClassEligible(...).
-  //
-  // In addition to class being eligible this method also checks:
-  //   -- for 'new-instance' root:
-  //      * class itself does not have static initializer
-  //   -- for 'static-get' root:
-  //      * class does not have instance fields
-  //      * class is final
-  //      * class has class initializer marked as TrivialClassInitializer, and
-  //        class initializer initializes the field we are reading here.
-  EligibilityStatus isClassAndUsageEligible() {
-    return isClassEligible.apply(eligibleClass);
-  }
-
   /**
    * Checks if the inlining candidate instance users are eligible, see comment on {@link
    * ClassInliner#processMethodCode}.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
index ab1c629..9b95fb8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
@@ -330,9 +330,7 @@
     for (SimpleStringFormatSpec.Part part : parsedSpec.parts) {
       if (part.isPlaceholder()) {
         Value paramValue = elementValues.get(part.placeholderIdx);
-        if (paramValue == null || paramValue.isAlwaysNull(appView)) {
-          // Save having to call isAlwaysNull() again.
-          elementValues.set(part.placeholderIdx, null);
+        if (isArrayElementAlwaysNull(paramValue)) {
           continue;
         }
         if (!isSupportedFormatType(part.formatChar, paramValue.getType())) {
@@ -388,7 +386,7 @@
         appendMethod = dexItemFactory.stringBuilderMethods.appendString;
       } else {
         paramValue = elementValues.get(part.placeholderIdx);
-        if (paramValue == null) {
+        if (isArrayElementAlwaysNull(paramValue)) {
           ConstString constString =
               ConstString.builder()
                   .setValue(dexItemFactory.createString(part.formatChar == 'b' ? "false" : "null"))
@@ -448,6 +446,10 @@
     return instructionIterator;
   }
 
+  private boolean isArrayElementAlwaysNull(Value value) {
+    return value == null || value.isAlwaysNull(appView);
+  }
+
   private void optimizeValueOf(
       IRCode code,
       InstructionListIterator instructionIterator,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxer.java
index dbfb37a..b360857 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxer.java
@@ -34,7 +34,8 @@
   public abstract void unboxNumbers(
       PostMethodProcessor.Builder postMethodProcessorBuilder,
       Timing timing,
-      ExecutorService executorService);
+      ExecutorService executorService)
+      throws ExecutionException;
 
   public abstract void onMethodPruned(ProgramMethod method);
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerBoxingStatusResolution.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerBoxingStatusResolution.java
index 2f0a128..4a9cbfd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerBoxingStatusResolution.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerBoxingStatusResolution.java
@@ -7,12 +7,12 @@
 import static com.android.tools.r8.ir.optimize.numberunboxer.NumberUnboxerBoxingStatusResolution.MethodBoxingStatusResult.BoxingStatusResult.NO_UNBOX;
 import static com.android.tools.r8.ir.optimize.numberunboxer.NumberUnboxerBoxingStatusResolution.MethodBoxingStatusResult.BoxingStatusResult.TO_PROCESS;
 import static com.android.tools.r8.ir.optimize.numberunboxer.NumberUnboxerBoxingStatusResolution.MethodBoxingStatusResult.BoxingStatusResult.UNBOX;
-import static com.android.tools.r8.utils.ListUtils.*;
 
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.optimize.numberunboxer.NumberUnboxerBoxingStatusResolution.MethodBoxingStatusResult.BoxingStatusResult;
 import com.android.tools.r8.ir.optimize.numberunboxer.TransitiveDependency.MethodArg;
 import com.android.tools.r8.ir.optimize.numberunboxer.TransitiveDependency.MethodRet;
+import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.WorkList;
 import java.util.Arrays;
@@ -72,6 +72,20 @@
     public BoxingStatusResult[] getArgs() {
       return args;
     }
+
+    public boolean isNoneUnboxable() {
+      return ret == NO_UNBOX && ArrayUtils.all(args, NO_UNBOX);
+    }
+
+    public boolean shouldUnboxArg(int i) {
+      assert args[i] != TO_PROCESS;
+      return args[i] == UNBOX;
+    }
+
+    public boolean shouldUnboxRet() {
+      assert ret != TO_PROCESS;
+      return ret == UNBOX;
+    }
   }
 
   void markNoneUnboxable(DexMethod method) {
@@ -135,9 +149,14 @@
       }
     }
     assert allProcessed();
+    clearNoneUnboxable();
     return boxingStatusResultMap;
   }
 
+  private void clearNoneUnboxable() {
+    boxingStatusResultMap.values().removeIf(MethodBoxingStatusResult::isNoneUnboxable);
+  }
+
   private boolean allProcessed() {
     boxingStatusResultMap.forEach(
         (k, v) -> {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerImpl.java
index cf8f2ec..1e623e1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerImpl.java
@@ -43,13 +43,13 @@
 
 public class NumberUnboxerImpl extends NumberUnboxer {
 
-  private AppView<AppInfoWithLiveness> appView;
-  private DexItemFactory factory;
-  private Set<DexType> boxedTypes;
+  private final AppView<AppInfoWithLiveness> appView;
+  private final DexItemFactory factory;
+  private final Set<DexType> boxedTypes;
 
   // Temporarily keep the information here, and not in the MethodOptimizationInfo as the
   // optimization is developed and unfinished.
-  private Map<DexMethod, MethodBoxingStatus> methodBoxingStatus = new ConcurrentHashMap<>();
+  private final Map<DexMethod, MethodBoxingStatus> methodBoxingStatus = new ConcurrentHashMap<>();
   private Map<DexMethod, DexMethod> virtualMethodsRepresentative;
 
   public NumberUnboxerImpl(AppView<AppInfoWithLiveness> appView) {
@@ -301,17 +301,28 @@
   public void unboxNumbers(
       PostMethodProcessor.Builder postMethodProcessorBuilder,
       Timing timing,
-      ExecutorService executorService) {
-
-    Map<DexMethod, MethodBoxingStatusResult> result =
+      ExecutorService executorService)
+      throws ExecutionException {
+    Map<DexMethod, MethodBoxingStatusResult> unboxingResult =
         new NumberUnboxerBoxingStatusResolution().resolve(methodBoxingStatus);
+    if (unboxingResult.isEmpty()) {
+      return;
+    }
 
-    // TODO(b/307872552): The result encodes for each method which return value and parameter of
-    //  each method should be unboxed. We need here to implement the treefixer using it, and set up
-    //  correctly the reprocessing with a code rewriter similar to the enum unboxing code rewriter.
-    //  We should implement the optimization, so far, we just print out the result.
+    NumberUnboxerLens numberUnboxerLens =
+        new NumberUnboxerTreeFixer(appView, unboxingResult).fixupTree(executorService, timing);
+    appView.rewriteWithLens(numberUnboxerLens, executorService, timing);
+    new NumberUnboxerMethodReprocessingEnqueuer(appView, numberUnboxerLens)
+        .enqueueMethodsForReprocessing(postMethodProcessorBuilder, executorService, timing);
+
+    if (appView.testing().printNumberUnboxed) {
+      printNumberUnboxed(unboxingResult);
+    }
+  }
+
+  private void printNumberUnboxed(Map<DexMethod, MethodBoxingStatusResult> unboxingResult) {
     StringBuilder stringBuilder = new StringBuilder();
-    result.forEach(
+    unboxingResult.forEach(
         (k, v) -> {
           if (v.getRet() == UNBOX) {
             stringBuilder
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerLens.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerLens.java
new file mode 100644
index 0000000..eeb1768
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerLens.java
@@ -0,0 +1,108 @@
+// 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.numberunboxer;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.lens.NestedGraphLensWithCustomLensCodeRewriter;
+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.conversion.ExtraUnusedNullParameter;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
+import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
+import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class NumberUnboxerLens extends NestedGraphLensWithCustomLensCodeRewriter {
+  private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod;
+
+  NumberUnboxerLens(
+      AppView<AppInfoWithLiveness> appView,
+      BidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> renamedSignatures,
+      Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod,
+      NumberUnboxerRewriter numberUnboxerRewriter) {
+    super(appView, EMPTY_FIELD_MAP, renamedSignatures, EMPTY_TYPE_MAP, numberUnboxerRewriter);
+    this.prototypeChangesPerMethod = prototypeChangesPerMethod;
+  }
+
+  @Override
+  protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
+      RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
+    RewrittenPrototypeDescription enumUnboxingPrototypeChanges =
+        prototypeChangesPerMethod.getOrDefault(method, RewrittenPrototypeDescription.none());
+    return prototypeChanges.combine(enumUnboxingPrototypeChanges);
+  }
+
+  @Override
+  public boolean isNumberUnboxerLens() {
+    return true;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+
+    private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod =
+        new IdentityHashMap<>();
+    private final MutableBidirectionalOneToOneMap<DexMethod, DexMethod> newMethodSignatures =
+        new BidirectionalOneToOneHashMap<>();
+
+    public RewrittenPrototypeDescription move(DexEncodedMethod fromEncoded, DexMethod to) {
+      DexMethod from = fromEncoded.getReference();
+      assert !from.isIdenticalTo(to);
+      List<ExtraUnusedNullParameter> extraUnusedNullParameters =
+          ExtraUnusedNullParameter.computeExtraUnusedNullParameters(from, to);
+      RewrittenPrototypeDescription prototypeChanges =
+          computePrototypeChanges(from, to, fromEncoded.isStatic(), extraUnusedNullParameters);
+      synchronized (this) {
+        newMethodSignatures.put(from, to);
+        prototypeChangesPerMethod.put(to, prototypeChanges);
+      }
+      return prototypeChanges;
+    }
+
+    private RewrittenPrototypeDescription computePrototypeChanges(
+        DexMethod from,
+        DexMethod to,
+        boolean staticMethod,
+        List<ExtraUnusedNullParameter> extraUnusedNullParameters) {
+      assert from.getArity() + extraUnusedNullParameters.size() == to.getArity();
+      ArgumentInfoCollection.Builder builder =
+          ArgumentInfoCollection.builder()
+              .setArgumentInfosSize(from.getNumberOfArguments(staticMethod));
+      for (int i = 0; i < from.getParameters().size(); i++) {
+        DexType fromType = from.getParameter(i);
+        DexType toType = to.getParameter(i);
+        if (!fromType.isIdenticalTo(toType)) {
+          builder.addArgumentInfo(
+              i, RewrittenTypeInfo.builder().setOldType(fromType).setNewType(toType).build());
+        }
+      }
+      RewrittenTypeInfo returnInfo =
+          from.getReturnType().isIdenticalTo(to.getReturnType())
+              ? null
+              : RewrittenTypeInfo.builder()
+                  .setOldType(from.getReturnType())
+                  .setNewType(to.getReturnType())
+                  .build();
+      return RewrittenPrototypeDescription.create(
+          extraUnusedNullParameters, returnInfo, builder.build());
+    }
+
+    public NumberUnboxerLens build(
+        AppView<AppInfoWithLiveness> appView, NumberUnboxerRewriter numberUnboxerRewriter) {
+      return new NumberUnboxerLens(
+          appView, newMethodSignatures, prototypeChangesPerMethod, numberUnboxerRewriter);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerMethodReprocessingEnqueuer.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerMethodReprocessingEnqueuer.java
new file mode 100644
index 0000000..a48a77f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerMethodReprocessingEnqueuer.java
@@ -0,0 +1,175 @@
+// 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.numberunboxer;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DefaultUseRegistryWithResult;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.InvokeType;
+import com.android.tools.r8.ir.conversion.PostMethodProcessor;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+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 number unboxing. */
+public class NumberUnboxerMethodReprocessingEnqueuer {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final NumberUnboxerLens numberUnboxerLens;
+
+  public NumberUnboxerMethodReprocessingEnqueuer(
+      AppView<AppInfoWithLiveness> appView, NumberUnboxerLens numberUnboxerLens) {
+    this.appView = appView;
+    this.numberUnboxerLens = numberUnboxerLens;
+  }
+
+  public void enqueueMethodsForReprocessing(
+      PostMethodProcessor.Builder postMethodProcessorBuilder,
+      ExecutorService executorService,
+      Timing timing)
+      throws ExecutionException {
+    timing.begin("Enqueue methods for reprocessing due to the number unboxer");
+    assert appView.graphLens() == numberUnboxerLens;
+    postMethodProcessorBuilder.rewrittenWithLens(appView);
+    enqueueAffectedCallees(postMethodProcessorBuilder);
+    enqueueAffectedCallers(postMethodProcessorBuilder, executorService);
+    timing.end();
+  }
+
+  boolean shouldReprocess(DexMethod reference) {
+    return reference.isNotIdenticalTo(numberUnboxerLens.getPreviousMethodSignature(reference));
+  }
+
+  private void enqueueAffectedCallees(PostMethodProcessor.Builder postMethodProcessorBuilder) {
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      clazz.forEachProgramMethodMatching(
+          DexEncodedMethod::hasCode,
+          method -> {
+            if (method.getDefinition().getCode().isSharedCodeObject()) {
+              return;
+            }
+            if (shouldReprocess(method.getReference())) {
+              assert !appView.appInfo().isNeverReprocessMethod(method);
+              postMethodProcessorBuilder.add(method, numberUnboxerLens);
+            }
+          });
+    }
+  }
+
+  // TODO(b/190154391): This could invalidate the @NeverReprocessMethod testing annotations (non
+  //  critical). If @NeverReprocessMethod is used, we would need to scan the application to mark
+  //  methods as unoptimizable prior to removing parameters from the application.
+  private void enqueueAffectedCallers(
+      PostMethodProcessor.Builder postMethodProcessorBuilder, ExecutorService executorService)
+      throws ExecutionException {
+    Collection<List<ProgramMethod>> methodsToReprocess =
+        ThreadUtils.processItemsWithResultsThatMatches(
+            appView.appInfo().classes(),
+            clazz -> {
+              List<ProgramMethod> methodsToReprocessInClass = new ArrayList<>();
+              clazz.forEachProgramMethodMatching(
+                  DexEncodedMethod::hasCode,
+                  method -> {
+                    AffectedMembersGraphLensUseRegistry registry =
+                        new AffectedMembersGraphLensUseRegistry(appView, method);
+                    if (method.registerCodeReferencesWithResult(registry)) {
+                      assert !method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite();
+                      methodsToReprocessInClass.add(method);
+                    }
+                  });
+              return methodsToReprocessInClass;
+            },
+            res -> !res.isEmpty(),
+            appView.options().getThreadingModule(),
+            executorService);
+    methodsToReprocess.forEach(
+        methodsToReprocessInClass ->
+            postMethodProcessorBuilder.addAll(methodsToReprocessInClass, numberUnboxerLens));
+  }
+
+  public class AffectedMembersGraphLensUseRegistry
+      extends DefaultUseRegistryWithResult<Boolean, ProgramMethod> {
+
+    public AffectedMembersGraphLensUseRegistry(
+        AppView<AppInfoWithLiveness> appViewWithLiveness, ProgramMethod context) {
+      super(appViewWithLiveness, context, false);
+    }
+
+    private void markAffected() {
+      setResult(Boolean.TRUE);
+    }
+
+    @Override
+    public void registerInvokeDirect(DexMethod method) {
+      registerInvokeMethod(method, InvokeType.DIRECT);
+    }
+
+    @Override
+    public void registerInvokeInterface(DexMethod method) {
+      registerInvokeMethod(method, InvokeType.INTERFACE);
+    }
+
+    @Override
+    public void registerInvokeStatic(DexMethod method) {
+      registerInvokeMethod(method, InvokeType.STATIC);
+    }
+
+    @Override
+    public void registerInvokeSuper(DexMethod method) {
+      registerInvokeMethod(method, InvokeType.SUPER);
+    }
+
+    @Override
+    public void registerInvokeVirtual(DexMethod method) {
+      registerInvokeMethod(method, InvokeType.VIRTUAL);
+    }
+
+    private void registerInvokeMethod(DexMethod method, InvokeType invokeType) {
+      // TODO(b/314117865): This is assuming that there are no non-rebound method references.
+      DexMethod reference =
+          numberUnboxerLens
+              .lookupMethod(
+                  method, getContext().getReference(), invokeType, numberUnboxerLens.getPrevious())
+              .getReference();
+      assert reference != null;
+      if (shouldReprocess(reference)) {
+        markAffected();
+      }
+    }
+
+    @Override
+    public void registerInstanceFieldRead(DexField field) {
+      registerFieldAccess(field);
+    }
+
+    @Override
+    public void registerInstanceFieldWrite(DexField field) {
+      registerFieldAccess(field);
+    }
+
+    @Override
+    public void registerStaticFieldRead(DexField field) {
+      registerFieldAccess(field);
+    }
+
+    @Override
+    public void registerStaticFieldWrite(DexField field) {
+      registerFieldAccess(field);
+    }
+
+    private void registerFieldAccess(DexField unused) {
+      // TODO(b/307872552): Deal with unboxed fields.
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerRewriter.java
new file mode 100644
index 0000000..83b1c96
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerRewriter.java
@@ -0,0 +1,189 @@
+// 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.numberunboxer;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.proto.ArgumentInfo;
+import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
+import com.android.tools.r8.graph.proto.RewrittenTypeInfo;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.optimize.CustomLensCodeRewriter;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+public class NumberUnboxerRewriter implements CustomLensCodeRewriter {
+
+  private final AppView<AppInfoWithLiveness> appView;
+
+  public NumberUnboxerRewriter(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+  }
+
+  @Override
+  public Set<Phi> rewriteCode(
+      IRCode code,
+      MethodProcessor methodProcessor,
+      RewrittenPrototypeDescription prototypeChanges,
+      NonIdentityGraphLens graphLens) {
+    assert graphLens.isNumberUnboxerLens();
+    Set<Phi> affectedPhis = Sets.newIdentityHashSet();
+    rewriteArgs(code, prototypeChanges, affectedPhis);
+    InstructionListIterator iterator = code.instructionListIterator();
+    while (iterator.hasNext()) {
+      Instruction next = iterator.next();
+      if (next.isInvokeMethod()) {
+        InvokeMethod invokeMethod = next.asInvokeMethod();
+        // TODO(b/314117865): This is assuming that there are no non-rebound method references.
+        DexMethod rewrittenMethod =
+            graphLens
+                .lookupMethod(
+                    invokeMethod.getInvokedMethod(),
+                    code.context().getReference(),
+                    invokeMethod.getType(),
+                    graphLens.getPrevious())
+                .getReference();
+        assert rewrittenMethod != null;
+        RewrittenPrototypeDescription rewrittenPrototypeDescription =
+            graphLens.lookupPrototypeChangesForMethodDefinition(
+                rewrittenMethod, graphLens.getPrevious());
+        if (!rewrittenPrototypeDescription.isEmpty()) {
+          unboxInvokeValues(
+              code, iterator, invokeMethod, rewrittenPrototypeDescription, affectedPhis);
+        }
+      } else if (next.isReturn() && next.asReturn().hasReturnValue()) {
+        unboxReturnIfNeeded(code, iterator, next.asReturn(), prototypeChanges);
+      }
+    }
+    return affectedPhis;
+  }
+
+  private void unboxInvokeValues(
+      IRCode code,
+      InstructionListIterator iterator,
+      InvokeMethod invokeMethod,
+      RewrittenPrototypeDescription prototypeChanges,
+      Set<Phi> affectedPhis) {
+    assert prototypeChanges.getArgumentInfoCollection().numberOfRemovedArguments() == 0;
+    for (int inValueIndex = 0; inValueIndex < invokeMethod.inValues().size(); inValueIndex++) {
+      ArgumentInfo argumentInfo =
+          prototypeChanges.getArgumentInfoCollection().getArgumentInfo(inValueIndex);
+      if (argumentInfo.isRewrittenTypeInfo()) {
+        Value invokeArg = invokeMethod.getArgument(inValueIndex);
+        InvokeVirtual unboxOperation =
+            computeUnboxInvokeIfNeeded(
+                code, invokeArg, argumentInfo.asRewrittenTypeInfo(), invokeMethod.getPosition());
+        if (unboxOperation != null) {
+          iterator.addBeforeAndPositionAfterNewInstruction(unboxOperation);
+          invokeMethod.replaceValue(inValueIndex, unboxOperation.outValue());
+        }
+      }
+    }
+    if (invokeMethod.hasOutValue()) {
+      InvokeStatic boxOperation =
+          computeBoxInvokeIfNeeded(
+              code,
+              invokeMethod.outValue(),
+              prototypeChanges.getRewrittenReturnInfo(),
+              invokeMethod.getPosition());
+      if (boxOperation != null) {
+        iterator.add(boxOperation);
+        affectedPhis.addAll(boxOperation.outValue().uniquePhiUsers());
+      }
+    }
+  }
+
+  private void unboxReturnIfNeeded(
+      IRCode code,
+      InstructionListIterator iterator,
+      Return ret,
+      RewrittenPrototypeDescription prototypeChanges) {
+    InvokeVirtual unbox =
+        computeUnboxInvokeIfNeeded(
+            code, ret.returnValue(), prototypeChanges.getRewrittenReturnInfo(), ret.getPosition());
+    if (unbox != null) {
+      iterator.addBeforeAndPositionAfterNewInstruction(unbox);
+      ret.replaceValue(ret.returnValue(), unbox.outValue());
+    }
+  }
+
+  private InvokeVirtual computeUnboxInvokeIfNeeded(
+      IRCode code, Value input, RewrittenTypeInfo rewrittenTypeInfo, Position pos) {
+    if (rewrittenTypeInfo == null) {
+      return null;
+    }
+    assert rewrittenTypeInfo.getOldType().isReferenceType();
+    assert rewrittenTypeInfo.getNewType().isPrimitiveType();
+    assert appView.dexItemFactory().primitiveToBoxed.containsValue(rewrittenTypeInfo.getOldType());
+    return InvokeVirtual.builder()
+        .setMethod(appView.dexItemFactory().getUnboxPrimitiveMethod(rewrittenTypeInfo.getNewType()))
+        .setFreshOutValue(
+            code.valueNumberGenerator, rewrittenTypeInfo.getNewType().toTypeElement(appView))
+        .setArguments(ImmutableList.of(input))
+        .setPosition(pos)
+        .build();
+  }
+
+  private InvokeStatic computeBoxInvokeIfNeeded(
+      IRCode code, Value output, RewrittenTypeInfo rewrittenTypeInfo, Position pos) {
+    if (rewrittenTypeInfo == null) {
+      return null;
+    }
+    assert rewrittenTypeInfo.getOldType().isReferenceType();
+    assert rewrittenTypeInfo.getNewType().isPrimitiveType();
+    assert appView.dexItemFactory().primitiveToBoxed.containsValue(rewrittenTypeInfo.getOldType());
+    Value outValue = code.createValue(rewrittenTypeInfo.getOldType().toTypeElement(appView));
+    output.replaceUsers(outValue);
+    return InvokeStatic.builder()
+        .setOutValue(outValue)
+        .setMethod(appView.dexItemFactory().getBoxPrimitiveMethod(rewrittenTypeInfo.getNewType()))
+        .setSingleArgument(output)
+        .setPosition(pos)
+        .build();
+  }
+
+  private void rewriteArgs(
+      IRCode code, RewrittenPrototypeDescription prototypeChanges, Set<Phi> affectedPhis) {
+    List<InvokeStatic> boxingOperations = new ArrayList<>();
+    InstructionListIterator iterator = code.entryBlock().listIterator(code);
+    Position pos = iterator.peekNext().getPosition();
+    assert prototypeChanges.getArgumentInfoCollection().numberOfRemovedArguments() == 0;
+    int originalNumberOfArguments = code.getNumberOfArguments();
+    for (int argumentIndex = 0; argumentIndex < originalNumberOfArguments; argumentIndex++) {
+      ArgumentInfo argumentInfo =
+          prototypeChanges.getArgumentInfoCollection().getArgumentInfo(argumentIndex);
+      Instruction next = iterator.next();
+      assert next.isArgument();
+      if (argumentInfo.isRewrittenTypeInfo()) {
+        InvokeStatic boxOperation =
+            computeBoxInvokeIfNeeded(
+                code, next.outValue(), argumentInfo.asRewrittenTypeInfo(), pos);
+        if (boxOperation != null) {
+          boxingOperations.add(boxOperation);
+        }
+      }
+    }
+    assert !iterator.peekNext().isArgument();
+    for (InvokeStatic boxingOp : boxingOperations) {
+      iterator.add(boxingOp);
+      affectedPhis.addAll(boxingOp.outValue().uniquePhiUsers());
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerTreeFixer.java
new file mode 100644
index 0000000..166cccd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerTreeFixer.java
@@ -0,0 +1,111 @@
+// 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.numberunboxer;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.fixup.ConcurrentMethodFixup;
+import com.android.tools.r8.graph.fixup.ConcurrentMethodFixup.ProgramClassFixer;
+import com.android.tools.r8.graph.fixup.MethodNamingUtility;
+import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
+import com.android.tools.r8.ir.optimize.numberunboxer.NumberUnboxerBoxingStatusResolution.MethodBoxingStatusResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.Timing;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public class NumberUnboxerTreeFixer implements ProgramClassFixer {
+
+  private final Map<DexMethod, MethodBoxingStatusResult> unboxingResult;
+  private final AppView<AppInfoWithLiveness> appView;
+
+  private final NumberUnboxerLens.Builder lensBuilder = NumberUnboxerLens.builder();
+
+  public NumberUnboxerTreeFixer(
+      AppView<AppInfoWithLiveness> appView,
+      Map<DexMethod, MethodBoxingStatusResult> unboxingResult) {
+    this.unboxingResult = unboxingResult;
+    this.appView = appView;
+  }
+
+  public NumberUnboxerLens fixupTree(ExecutorService executorService, Timing timing)
+      throws ExecutionException {
+    // Take strongly connected components, for each merge data from call-sites and methods and
+    // unbox.
+    new ConcurrentMethodFixup(appView, this)
+        .fixupClassesConcurrentlyByConnectedProgramComponents(timing, executorService);
+    return lensBuilder.build(appView, new NumberUnboxerRewriter(appView));
+  }
+
+  @Override
+  public void fixupProgramClass(DexProgramClass clazz, MethodNamingUtility utility) {
+    clazz.getMethodCollection().replaceMethods(m -> fixupEncodedMethod(m, utility));
+  }
+
+  @Override
+  public boolean shouldReserveAsIfPinned(ProgramMethod method) {
+    // We don't reprocess dependencies of unchanged methods so we have to maintain them
+    // with the same signature.
+    return !unboxingResult.containsKey(method.getReference());
+  }
+
+  private DexEncodedMethod fixupEncodedMethod(
+      DexEncodedMethod method, MethodNamingUtility utility) {
+    if (!unboxingResult.containsKey(method.getReference())) {
+      assert method
+          .getReference()
+          .isIdenticalTo(
+              utility.nextUniqueMethod(
+                  method, method.getProto(), appView.dexItemFactory().shortType));
+      return method;
+    }
+    MethodBoxingStatusResult methodBoxingStatus = unboxingResult.get(method.getReference());
+    assert !methodBoxingStatus.isNoneUnboxable();
+    DexProto newProto = fixupProto(method.getProto(), methodBoxingStatus);
+    DexMethod newMethod =
+        utility.nextUniqueMethod(method, newProto, appView.dexItemFactory().shortType);
+
+    RewrittenPrototypeDescription prototypeChanges = lensBuilder.move(method, newMethod);
+    return method.toTypeSubstitutedMethodAsInlining(
+        newMethod,
+        appView.dexItemFactory(),
+        builder ->
+            builder
+                .fixupOptimizationInfo(
+                    appView, prototypeChanges.createMethodOptimizationInfoFixer())
+                .setCompilationState(method.getCompilationState())
+                .setIsLibraryMethodOverrideIf(
+                    method.isNonPrivateVirtualMethod(), OptionalBool.FALSE));
+  }
+
+  private DexType fixupType(DexType type, boolean unbox) {
+    if (!unbox) {
+      return type;
+    }
+    DexType newType = appView.dexItemFactory().primitiveToBoxed.inverse().get(type);
+    assert newType != null;
+    return newType;
+  }
+
+  private DexProto fixupProto(DexProto proto, MethodBoxingStatusResult methodBoxingStatus) {
+    DexType[] argTypes = proto.getParameters().values;
+    DexType[] newArgTypes =
+        ArrayUtils.initialize(
+            new DexType[argTypes.length],
+            i -> fixupType(argTypes[i], methodBoxingStatus.shouldUnboxArg(i)));
+    DexType newReturnType = fixupType(proto.getReturnType(), methodBoxingStatus.shouldUnboxRet());
+    DexProto newProto = appView.dexItemFactory().createProto(newReturnType, newArgTypes);
+    assert newProto.isNotIdenticalTo(proto);
+    return newProto;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index f60a3fe..3eeaae6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -35,6 +35,7 @@
 import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
 import com.android.tools.r8.ir.optimize.AffectedValues;
 import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo;
+import com.android.tools.r8.shaking.KeepClassInfo;
 import java.io.UTFDataFormatException;
 import java.util.function.BiFunction;
 import java.util.function.Function;
@@ -308,8 +309,10 @@
         continue;
       }
       boolean mayBeRenamed =
-          appView.enableWholeProgramOptimizations()
-              && appView.withLiveness().appInfo().isMinificationAllowed(holder);
+          holder.isProgramClass()
+              && appView
+                  .getKeepInfoOrDefault(holder.asProgramClass(), KeepClassInfo.top())
+                  .isMinificationAllowed(options);
       // b/120138731: Filter out escaping uses. In such case, the result of this optimization will
       // be stored somewhere, which can lead to a regression if the corresponding class is in a deep
       // package hierarchy. For local cases, it is likely a one-time computation, but make sure the
diff --git a/src/main/java/com/android/tools/r8/lightir/LirCode.java b/src/main/java/com/android/tools/r8/lightir/LirCode.java
index 54a9c8c..056b7fa 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirCode.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirCode.java
@@ -373,6 +373,21 @@
         .withAssert(c -> c.metadataMap == null);
   }
 
+  protected LirCode(LirCode<EV> code) {
+    this(
+        code.irMetadata,
+        code.constants,
+        code.positionTable,
+        code.argumentCount,
+        code.instructions,
+        code.instructionCount,
+        code.tryCatchTable,
+        code.debugLocalInfoTable,
+        code.strategyInfo,
+        code.useDexEstimationStrategy,
+        code.metadataMap);
+  }
+
   /** Should be constructed using {@link LirBuilder}. */
   LirCode(
       IRMetadata irMetadata,
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index 71ff9e8..559d7f0 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -674,6 +674,11 @@
     }
     if (isInit) {
       expect('>');
+      // There are some files that include identifier text after <clinit>, such as
+      // <clinit>$redex$opt as seen in b/309080420.
+      while (IdentifierUtils.isDexIdentifierPart(peekCodePoint())) {
+        nextCodePoint();
+      }
     }
     if (IdentifierUtils.isDexIdentifierPart(peekCodePoint())) {
       throw new ParseException(
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 ea9d69a..0b947a3 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
@@ -131,7 +131,9 @@
       methodState = MethodState.unknown();
     }
 
-    methodState = getMethodStateAfterUninstantiatedParameterRemoval(method, methodState);
+    if (appView.getKeepInfo(method).isArgumentPropagationAllowed(options)) {
+      methodState = getMethodStateAfterUninstantiatedParameterRemoval(method, methodState);
+    }
 
     if (methodState.isUnknown()) {
       // Nothing is known about the arguments to this method.
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 794ef20..f130874 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -166,13 +166,6 @@
   private final Set<DexMethod> neverReprocess;
   /** All types that should be inlined if possible due to a configuration directive. */
   public final PredicateSet<DexType> alwaysClassInline;
-  /** All types that *must* never be inlined due to a configuration directive (testing only). */
-  private final Set<DexType> neverClassInline;
-
-  private final Set<DexType> noClassMerging;
-  private final Set<DexType> noHorizontalClassMerging;
-  private final Set<DexType> noVerticalClassMerging;
-
   /**
    * Set of lock candidates (i.e., types whose class reference may flow to a monitor instruction).
    */
@@ -232,10 +225,6 @@
       Set<DexMethod> reprocess,
       Set<DexMethod> neverReprocess,
       PredicateSet<DexType> alwaysClassInline,
-      Set<DexType> neverClassInline,
-      Set<DexType> noClassMerging,
-      Set<DexType> noVerticalClassMerging,
-      Set<DexType> noHorizontalClassMerging,
       Set<DexMember<?, ?>> neverPropagateValue,
       Object2BooleanMap<DexMember<?, ?>> identifierNameStrings,
       Set<DexType> prunedTypes,
@@ -265,10 +254,6 @@
     this.reprocess = reprocess;
     this.neverReprocess = neverReprocess;
     this.alwaysClassInline = alwaysClassInline;
-    this.neverClassInline = neverClassInline;
-    this.noClassMerging = noClassMerging;
-    this.noVerticalClassMerging = noVerticalClassMerging;
-    this.noHorizontalClassMerging = noHorizontalClassMerging;
     this.neverPropagateValue = neverPropagateValue;
     this.identifierNameStrings = identifierNameStrings;
     this.prunedTypes = prunedTypes;
@@ -306,10 +291,6 @@
         previous.reprocess,
         previous.neverReprocess,
         previous.alwaysClassInline,
-        previous.neverClassInline,
-        previous.noClassMerging,
-        previous.noVerticalClassMerging,
-        previous.noHorizontalClassMerging,
         previous.neverPropagateValue,
         previous.identifierNameStrings,
         previous.prunedTypes,
@@ -348,10 +329,6 @@
         pruneMethods(previous.reprocess, prunedItems, tasks),
         pruneMethods(previous.neverReprocess, prunedItems, tasks),
         previous.alwaysClassInline,
-        pruneClasses(previous.neverClassInline, prunedItems, tasks),
-        pruneClasses(previous.noClassMerging, prunedItems, tasks),
-        pruneClasses(previous.noVerticalClassMerging, prunedItems, tasks),
-        pruneClasses(previous.noHorizontalClassMerging, prunedItems, tasks),
         pruneMembers(previous.neverPropagateValue, prunedItems, tasks),
         pruneMapFromMembers(previous.identifierNameStrings, prunedItems, tasks),
         prunedItems.hasRemovedClasses()
@@ -536,10 +513,6 @@
         reprocess,
         neverReprocess,
         alwaysClassInline,
-        neverClassInline,
-        noClassMerging,
-        noVerticalClassMerging,
-        noHorizontalClassMerging,
         neverPropagateValue,
         identifierNameStrings,
         prunedTypes,
@@ -614,10 +587,6 @@
     this.reprocess = previous.reprocess;
     this.neverReprocess = previous.neverReprocess;
     this.alwaysClassInline = previous.alwaysClassInline;
-    this.neverClassInline = previous.neverClassInline;
-    this.noClassMerging = previous.noClassMerging;
-    this.noVerticalClassMerging = previous.noVerticalClassMerging;
-    this.noHorizontalClassMerging = previous.noHorizontalClassMerging;
     this.neverPropagateValue = previous.neverPropagateValue;
     this.identifierNameStrings = previous.identifierNameStrings;
     this.prunedTypes = previous.prunedTypes;
@@ -1043,39 +1012,31 @@
     return this;
   }
 
-  public boolean isClassInliningAllowed(DexProgramClass clazz) {
-    return !isPinned(clazz) && !neverClassInline.contains(clazz.getType());
-  }
-
+  @Deprecated
   public boolean isMinificationAllowed(DexProgramClass clazz) {
     return options().isMinificationEnabled()
         && keepInfo.getInfo(clazz).isMinificationAllowed(options());
   }
 
+  @Deprecated
   public boolean isMinificationAllowed(ProgramDefinition definition) {
     return options().isMinificationEnabled()
         && keepInfo.getInfo(definition).isMinificationAllowed(options());
   }
 
+  @Deprecated
   public boolean isMinificationAllowed(DexDefinition definition) {
     return options().isMinificationEnabled()
         && keepInfo.getInfo(definition, this).isMinificationAllowed(options());
   }
 
+  @Deprecated
   public boolean isMinificationAllowed(DexType reference) {
     return options().isMinificationEnabled()
         && keepInfo.getClassInfo(reference, this).isMinificationAllowed(options());
   }
 
-  public boolean isAccessModificationAllowed(ProgramDefinition definition) {
-    assert options().getProguardConfiguration().isAccessModificationAllowed();
-    return keepInfo.getInfo(definition).isAccessModificationAllowed(options());
-  }
-
   public boolean isRepackagingAllowed(DexProgramClass clazz, AppView<?> appView) {
-    if (!options().isRepackagingEnabled()) {
-      return false;
-    }
     if (!keepInfo.getInfo(clazz).isRepackagingAllowed(options())) {
       return false;
     }
@@ -1086,19 +1047,23 @@
     return applyMappingSeedMapper == null || !applyMappingSeedMapper.hasMapping(clazz.type);
   }
 
+  @Deprecated
   public boolean isPinnedWithDefinitionLookup(DexReference reference) {
     assert checkIfObsolete();
     return keepInfo.isPinnedWithDefinitionLookup(reference, options(), this);
   }
 
+  @Deprecated
   public boolean isPinned(DexDefinition definition) {
     return keepInfo.isPinned(definition, options(), this);
   }
 
+  @Deprecated
   public boolean isPinned(DexProgramClass clazz) {
     return keepInfo.isPinned(clazz, options());
   }
 
+  @Deprecated
   public boolean isPinned(Definition definition) {
     assert definition != null;
     return definition.isProgramDefinition()
@@ -1199,10 +1164,6 @@
         lens.rewriteReferences(reprocess),
         lens.rewriteReferences(neverReprocess),
         alwaysClassInline.rewriteItems(lens::lookupType),
-        lens.rewriteReferences(neverClassInline),
-        lens.rewriteReferences(noClassMerging),
-        lens.rewriteReferences(noVerticalClassMerging),
-        lens.rewriteReferences(noHorizontalClassMerging),
         lens.rewriteReferences(neverPropagateValue),
         lens.rewriteReferenceKeys(identifierNameStrings),
         // Don't rewrite pruned types - the removed types are identified by their original name.
@@ -1513,18 +1474,6 @@
     return SubtypingInfo.create(this);
   }
 
-  /** Predicate on types that *must* never be merged horizontally. */
-  public boolean isNoHorizontalClassMergingOfType(DexProgramClass clazz) {
-    return noClassMerging.contains(clazz.getType())
-        || noHorizontalClassMerging.contains(clazz.getType());
-  }
-
-  /** Predicate on types that *must* never be merged vertically. */
-  public boolean isNoVerticalClassMergingOfType(DexProgramClass clazz) {
-    return noClassMerging.contains(clazz.getType())
-        || noVerticalClassMerging.contains(clazz.getType());
-  }
-
   public boolean verifyNoIteratingOverPrunedClasses() {
     classes()
         .forEach(
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index d1a2dda..30d6e81 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -10,6 +10,7 @@
 import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor.ExcludeDexResources;
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentifier;
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
+import static com.android.tools.r8.shaking.KeepInfo.Joiner.asClassJoinerOrNull;
 import static com.android.tools.r8.shaking.KeepInfo.Joiner.asFieldJoinerOrNull;
 import static com.android.tools.r8.utils.CovariantReturnTypeUtils.modelLibraryMethodsWithCovariantReturnTypes;
 import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
@@ -739,6 +740,16 @@
     return keepInfo.getClassInfo(clazz);
   }
 
+  public boolean hasMinimumKeepInfoThatMatches(
+      DexProgramClass clazz, Predicate<KeepClassInfo.Joiner> predicate) {
+    MinimumKeepInfoCollection minimumKeepInfoCollection =
+        dependentMinimumKeepInfo.getUnconditionalMinimumKeepInfoOrDefault(
+            MinimumKeepInfoCollection.empty());
+    KeepClassInfo.Joiner minimumKeepInfo =
+        asClassJoinerOrNull(minimumKeepInfoCollection.getOrDefault(clazz.getReference(), null));
+    return minimumKeepInfo != null && predicate.test(minimumKeepInfo);
+  }
+
   public KeepFieldInfo getKeepInfo(ProgramField field) {
     return keepInfo.getFieldInfo(field);
   }
@@ -2225,8 +2236,8 @@
       return;
     }
 
-    if (!appView.options().enableUnusedInterfaceRemoval
-        || rootSet.noUnusedInterfaceRemoval.contains(type)
+    if (!options.enableUnusedInterfaceRemoval
+        || hasMinimumKeepInfoThatMatches(clazz, info -> !info.isUnusedInterfaceRemovalAllowed())
         || mode.isMainDexTracing()) {
       markTypeAsLive(clazz, implementer);
       return;
@@ -4309,10 +4320,6 @@
             amendWithCompanionMethods(rootSet.reprocess),
             amendWithCompanionMethods(rootSet.neverReprocess),
             rootSet.alwaysClassInline,
-            rootSet.neverClassInline,
-            noClassMerging,
-            rootSet.noVerticalClassMerging,
-            rootSet.noHorizontalClassMerging,
             rootSet.neverPropagateValue,
             joinIdentifierNameStrings(rootSet.identifierNameStrings, identifierNameStrings),
             emptySet(),
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
index c199247..bd4cc3c 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
@@ -15,11 +15,27 @@
 
   // Requires all aspects of a class to be kept.
   private static final KeepClassInfo TOP =
-      new Builder().makeTop().disallowRepackaging().disallowPermittedSubclassesRemoval().build();
+      new Builder()
+          .makeTop()
+          .disallowClassInlining()
+          .disallowHorizontalClassMerging()
+          .disallowPermittedSubclassesRemoval()
+          .disallowRepackaging()
+          .disallowUnusedInterfaceRemoval()
+          .disallowVerticalClassMerging()
+          .build();
 
   // Requires no aspects of a class to be kept.
   private static final KeepClassInfo BOTTOM =
-      new Builder().makeBottom().allowRepackaging().allowPermittedSubclassesRemoval().build();
+      new Builder()
+          .makeBottom()
+          .allowClassInlining()
+          .allowHorizontalClassMerging()
+          .allowPermittedSubclassesRemoval()
+          .allowRepackaging()
+          .allowUnusedInterfaceRemoval()
+          .allowVerticalClassMerging()
+          .build();
 
   public static KeepClassInfo top() {
     return TOP;
@@ -33,15 +49,23 @@
     return bottom().joiner();
   }
 
-  private final boolean allowRepackaging;
-  private final boolean checkEnumUnboxed;
+  private final boolean allowClassInlining;
+  private final boolean allowHorizontalClassMerging;
   private final boolean allowPermittedSubclassesRemoval;
+  private final boolean allowRepackaging;
+  private final boolean allowUnusedInterfaceRemoval;
+  private final boolean allowVerticalClassMerging;
+  private final boolean checkEnumUnboxed;
 
   private KeepClassInfo(Builder builder) {
     super(builder);
-    this.allowRepackaging = builder.isRepackagingAllowed();
-    this.checkEnumUnboxed = builder.isCheckEnumUnboxedEnabled();
+    this.allowClassInlining = builder.isClassInliningAllowed();
+    this.allowHorizontalClassMerging = builder.isHorizontalClassMergingAllowed();
     this.allowPermittedSubclassesRemoval = builder.isPermittedSubclassesRemovalAllowed();
+    this.allowRepackaging = builder.isRepackagingAllowed();
+    this.allowUnusedInterfaceRemoval = builder.isUnusedInterfaceRemovalAllowed();
+    this.allowVerticalClassMerging = builder.isVerticalClassMergingAllowed();
+    this.checkEnumUnboxed = builder.isCheckEnumUnboxedEnabled();
   }
 
   @Override
@@ -57,28 +81,24 @@
     return checkEnumUnboxed;
   }
 
-  public Joiner joiner() {
-    assert !isTop();
-    return new Joiner(this);
+  public boolean isClassInliningAllowed(GlobalKeepInfoConfiguration configuration) {
+    return isOptimizationAllowed(configuration)
+        && isShrinkingAllowed(configuration)
+        && internalIsClassInliningAllowed();
   }
 
-  public boolean isPermittedSubclassesRemovalAllowed(GlobalKeepInfoConfiguration configuration) {
-    return !configuration.isKeepPermittedSubclassesEnabled()
-        && internalIsPermittedSubclassesRemovalAllowed();
+  boolean internalIsClassInliningAllowed() {
+    return allowClassInlining;
   }
 
-  /**
-   * True if an item may be repackaged.
-   *
-   * <p>This method requires knowledge of the global configuration as that can override the concrete
-   * value on a given item.
-   */
-  public boolean isRepackagingAllowed(GlobalKeepInfoConfiguration configuration) {
-    return configuration.isRepackagingEnabled() && internalIsRepackagingAllowed();
+  public boolean isHorizontalClassMergingAllowed(GlobalKeepInfoConfiguration configuration) {
+    return isOptimizationAllowed(configuration)
+        && isShrinkingAllowed(configuration)
+        && internalIsHorizontalClassMergingAllowed();
   }
 
-  boolean internalIsRepackagingAllowed() {
-    return allowRepackaging;
+  boolean internalIsHorizontalClassMergingAllowed() {
+    return allowHorizontalClassMerging;
   }
 
   public boolean isKotlinMetadataRemovalAllowed(
@@ -101,10 +121,48 @@
         || !getClassInfo.apply(kotlinMetadataClass.asProgramClass()).isShrinkingAllowed(options);
   }
 
+  public boolean isPermittedSubclassesRemovalAllowed(GlobalKeepInfoConfiguration configuration) {
+    return !configuration.isKeepPermittedSubclassesEnabled()
+        && internalIsPermittedSubclassesRemovalAllowed();
+  }
+
   boolean internalIsPermittedSubclassesRemovalAllowed() {
     return allowPermittedSubclassesRemoval;
   }
 
+  /**
+   * True if an item may be repackaged.
+   *
+   * <p>This method requires knowledge of the global configuration as that can override the concrete
+   * value on a given item.
+   */
+  public boolean isRepackagingAllowed(GlobalKeepInfoConfiguration configuration) {
+    return configuration.isRepackagingEnabled() && internalIsRepackagingAllowed();
+  }
+
+  boolean internalIsRepackagingAllowed() {
+    return allowRepackaging;
+  }
+
+  boolean internalIsUnusedInterfaceRemovalAllowed() {
+    return allowUnusedInterfaceRemoval;
+  }
+
+  public boolean isVerticalClassMergingAllowed(GlobalKeepInfoConfiguration configuration) {
+    return isOptimizationAllowed(configuration)
+        && isShrinkingAllowed(configuration)
+        && internalIsVerticalClassMergingAllowed();
+  }
+
+  boolean internalIsVerticalClassMergingAllowed() {
+    return allowVerticalClassMerging;
+  }
+
+  public Joiner joiner() {
+    assert !isTop();
+    return new Joiner(this);
+  }
+
   @Override
   public boolean isTop() {
     return this.equals(top());
@@ -117,9 +175,13 @@
 
   public static class Builder extends KeepInfo.Builder<Builder, KeepClassInfo> {
 
-    private boolean allowRepackaging;
-    private boolean checkEnumUnboxed;
+    private boolean allowClassInlining;
+    private boolean allowHorizontalClassMerging;
     private boolean allowPermittedSubclassesRemoval;
+    private boolean allowRepackaging;
+    private boolean allowUnusedInterfaceRemoval;
+    private boolean allowVerticalClassMerging;
+    private boolean checkEnumUnboxed;
 
     private Builder() {
       super();
@@ -127,9 +189,13 @@
 
     private Builder(KeepClassInfo original) {
       super(original);
-      allowRepackaging = original.internalIsRepackagingAllowed();
-      checkEnumUnboxed = original.internalIsCheckEnumUnboxedEnabled();
+      allowClassInlining = original.internalIsClassInliningAllowed();
+      allowHorizontalClassMerging = original.internalIsHorizontalClassMergingAllowed();
       allowPermittedSubclassesRemoval = original.internalIsPermittedSubclassesRemovalAllowed();
+      allowRepackaging = original.internalIsRepackagingAllowed();
+      allowUnusedInterfaceRemoval = original.internalIsUnusedInterfaceRemovalAllowed();
+      allowVerticalClassMerging = original.internalIsVerticalClassMergingAllowed();
+      checkEnumUnboxed = original.internalIsCheckEnumUnboxedEnabled();
     }
 
     // Check enum unboxed.
@@ -143,6 +209,73 @@
       return self();
     }
 
+    public Builder setCheckEnumUnboxed() {
+      return setCheckEnumUnboxed(true);
+    }
+
+    public Builder unsetCheckEnumUnboxed() {
+      return setCheckEnumUnboxed(false);
+    }
+
+    // Class inlining.
+
+    public Builder allowClassInlining() {
+      return setAllowClassInlining(true);
+    }
+
+    public boolean isClassInliningAllowed() {
+      return allowClassInlining;
+    }
+
+    private Builder setAllowClassInlining(boolean allowClassInlining) {
+      this.allowClassInlining = allowClassInlining;
+      return self();
+    }
+
+    public Builder disallowClassInlining() {
+      return setAllowClassInlining(false);
+    }
+
+    // Horizontal class merging.
+
+    public Builder allowHorizontalClassMerging() {
+      return setAllowHorizontalClassMerging(true);
+    }
+
+    public boolean isHorizontalClassMergingAllowed() {
+      return allowHorizontalClassMerging;
+    }
+
+    private Builder setAllowHorizontalClassMerging(boolean allowHorizontalClassMerging) {
+      this.allowHorizontalClassMerging = allowHorizontalClassMerging;
+      return self();
+    }
+
+    public Builder disallowHorizontalClassMerging() {
+      return setAllowHorizontalClassMerging(false);
+    }
+
+    // Permitted subclasses removal.
+
+    public Builder allowPermittedSubclassesRemoval() {
+      return setAllowPermittedSubclassesRemoval(true);
+    }
+
+    public boolean isPermittedSubclassesRemovalAllowed() {
+      return allowPermittedSubclassesRemoval;
+    }
+
+    private Builder setAllowPermittedSubclassesRemoval(boolean allowPermittedSubclassesRemoval) {
+      this.allowPermittedSubclassesRemoval = allowPermittedSubclassesRemoval;
+      return self();
+    }
+
+    public Builder disallowPermittedSubclassesRemoval() {
+      return setAllowPermittedSubclassesRemoval(false);
+    }
+
+    // Repackaging.
+
     public boolean isRepackagingAllowed() {
       return allowRepackaging;
     }
@@ -160,29 +293,42 @@
       return setAllowRepackaging(false);
     }
 
-    public Builder setCheckEnumUnboxed() {
-      return setCheckEnumUnboxed(true);
+    // Unused interface removal.
+
+    public Builder allowUnusedInterfaceRemoval() {
+      return setAllowUnusedInterfaceRemoval(true);
     }
 
-    public Builder unsetCheckEnumUnboxed() {
-      return setCheckEnumUnboxed(false);
+    public boolean isUnusedInterfaceRemovalAllowed() {
+      return allowUnusedInterfaceRemoval;
     }
 
-    public Builder allowPermittedSubclassesRemoval() {
-      return setAllowPermittedSubclassesRemoval(true);
-    }
-
-    public boolean isPermittedSubclassesRemovalAllowed() {
-      return allowPermittedSubclassesRemoval;
-    }
-
-    private Builder setAllowPermittedSubclassesRemoval(boolean allowPermittedSubclassesRemoval) {
-      this.allowPermittedSubclassesRemoval = allowPermittedSubclassesRemoval;
+    private Builder setAllowUnusedInterfaceRemoval(boolean allowUnusedInterfaceRemoval) {
+      this.allowUnusedInterfaceRemoval = allowUnusedInterfaceRemoval;
       return self();
     }
 
-    public Builder disallowPermittedSubclassesRemoval() {
-      return setAllowPermittedSubclassesRemoval(false);
+    public Builder disallowUnusedInterfaceRemoval() {
+      return setAllowUnusedInterfaceRemoval(false);
+    }
+
+    // Vertical class merging.
+
+    public Builder allowVerticalClassMerging() {
+      return setAllowVerticalClassMerging(true);
+    }
+
+    public boolean isVerticalClassMergingAllowed() {
+      return allowVerticalClassMerging;
+    }
+
+    private Builder setAllowVerticalClassMerging(boolean allowVerticalClassMerging) {
+      this.allowVerticalClassMerging = allowVerticalClassMerging;
+      return self();
+    }
+
+    public Builder disallowVerticalClassMerging() {
+      return setAllowVerticalClassMerging(false);
     }
 
     @Override
@@ -208,10 +354,14 @@
     @Override
     boolean internalIsEqualTo(KeepClassInfo other) {
       return super.internalIsEqualTo(other)
-          && isRepackagingAllowed() == other.internalIsRepackagingAllowed()
           && isCheckEnumUnboxedEnabled() == other.internalIsCheckEnumUnboxedEnabled()
+          && isClassInliningAllowed() == other.internalIsClassInliningAllowed()
+          && isHorizontalClassMergingAllowed() == other.internalIsHorizontalClassMergingAllowed()
           && isPermittedSubclassesRemovalAllowed()
-              == other.internalIsPermittedSubclassesRemovalAllowed();
+              == other.internalIsPermittedSubclassesRemovalAllowed()
+          && isRepackagingAllowed() == other.internalIsRepackagingAllowed()
+          && isUnusedInterfaceRemovalAllowed() == other.internalIsUnusedInterfaceRemovalAllowed()
+          && isVerticalClassMergingAllowed() == other.internalIsVerticalClassMergingAllowed();
     }
 
     @Override
@@ -222,17 +372,25 @@
     @Override
     public Builder makeTop() {
       return super.makeTop()
-          .unsetCheckEnumUnboxed()
+          .disallowClassInlining()
+          .disallowHorizontalClassMerging()
+          .disallowPermittedSubclassesRemoval()
           .disallowRepackaging()
-          .disallowPermittedSubclassesRemoval();
+          .disallowUnusedInterfaceRemoval()
+          .disallowVerticalClassMerging()
+          .unsetCheckEnumUnboxed();
     }
 
     @Override
     public Builder makeBottom() {
       return super.makeBottom()
-          .unsetCheckEnumUnboxed()
+          .allowClassInlining()
+          .allowHorizontalClassMerging()
+          .allowPermittedSubclassesRemoval()
           .allowRepackaging()
-          .allowPermittedSubclassesRemoval();
+          .allowUnusedInterfaceRemoval()
+          .allowVerticalClassMerging()
+          .unsetCheckEnumUnboxed();
     }
   }
 
@@ -242,13 +400,13 @@
       super(info.builder());
     }
 
-    public Joiner setCheckEnumUnboxed() {
-      builder.setCheckEnumUnboxed();
+    public Joiner disallowClassInlining() {
+      builder.disallowClassInlining();
       return self();
     }
 
-    public Joiner disallowRepackaging() {
-      builder.disallowRepackaging();
+    public Joiner disallowHorizontalClassMerging() {
+      builder.disallowHorizontalClassMerging();
       return self();
     }
 
@@ -257,8 +415,27 @@
       return self();
     }
 
-    public Joiner allowPermittedSubclassesRemoval() {
-      builder.allowPermittedSubclassesRemoval();
+    public Joiner disallowRepackaging() {
+      builder.disallowRepackaging();
+      return self();
+    }
+
+    public Joiner disallowUnusedInterfaceRemoval() {
+      builder.disallowUnusedInterfaceRemoval();
+      return self();
+    }
+
+    public Joiner disallowVerticalClassMerging() {
+      builder.disallowVerticalClassMerging();
+      return self();
+    }
+
+    public boolean isUnusedInterfaceRemovalAllowed() {
+      return builder.isUnusedInterfaceRemovalAllowed();
+    }
+
+    public Joiner setCheckEnumUnboxed() {
+      builder.setCheckEnumUnboxed();
       return self();
     }
 
@@ -272,10 +449,20 @@
       // Should be extended to merge the fields of this class in case any are added.
       return super.merge(joiner)
           .applyIf(joiner.builder.isCheckEnumUnboxedEnabled(), Joiner::setCheckEnumUnboxed)
-          .applyIf(!joiner.builder.isRepackagingAllowed(), Joiner::disallowRepackaging)
+          .applyIf(!joiner.builder.isClassInliningAllowed(), Joiner::disallowClassInlining)
+          .applyIf(
+              !joiner.builder.isHorizontalClassMergingAllowed(),
+              Joiner::disallowHorizontalClassMerging)
           .applyIf(
               !joiner.builder.isPermittedSubclassesRemovalAllowed(),
-              Joiner::disallowPermittedSubclassesRemoval);
+              Joiner::disallowPermittedSubclassesRemoval)
+          .applyIf(!joiner.builder.isRepackagingAllowed(), Joiner::disallowRepackaging)
+          .applyIf(
+              !joiner.builder.isUnusedInterfaceRemovalAllowed(),
+              Joiner::disallowUnusedInterfaceRemoval)
+          .applyIf(
+              !joiner.builder.isVerticalClassMergingAllowed(),
+              Joiner::disallowVerticalClassMerging);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
index fc0ec2d..e09ed70 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -502,6 +502,10 @@
       return null;
     }
 
+    public static KeepClassInfo.Joiner asClassJoinerOrNull(Joiner<?, ?, ?> joiner) {
+      return joiner != null ? joiner.asClassJoiner() : null;
+    }
+
     public KeepFieldInfo.Joiner asFieldJoiner() {
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index 1fcb8e6..e478be4 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -124,10 +124,6 @@
     private final Set<DexMethod> reprocess = Sets.newIdentityHashSet();
     private final Set<DexMethod> neverReprocess = Sets.newIdentityHashSet();
     private final PredicateSet<DexType> alwaysClassInline = new PredicateSet<>();
-    private final Set<DexType> neverClassInline = Sets.newIdentityHashSet();
-    private final Set<DexType> noUnusedInterfaceRemoval = Sets.newIdentityHashSet();
-    private final Set<DexType> noVerticalClassMerging = Sets.newIdentityHashSet();
-    private final Set<DexType> noHorizontalClassMerging = Sets.newIdentityHashSet();
     private final Set<DexMember<?, ?>> neverPropagateValue = Sets.newIdentityHashSet();
     private final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule =
         new IdentityHashMap<>();
@@ -387,10 +383,9 @@
             appView,
             subtypingInfo,
             alwaysClassInline,
-            noVerticalClassMerging,
-            noHorizontalClassMerging,
             alwaysInline,
-            bypassClinitforInlining);
+            bypassClinitforInlining,
+            dependentMinimumKeepInfo);
       }
       return new RootSet(
           dependentMinimumKeepInfo,
@@ -402,10 +397,6 @@
           reprocess,
           neverReprocess,
           alwaysClassInline,
-          neverClassInline,
-          noUnusedInterfaceRemoval,
-          noVerticalClassMerging,
-          noHorizontalClassMerging,
           neverPropagateValue,
           mayHaveSideEffects,
           dependentKeepClassCompatRule,
@@ -473,8 +464,6 @@
     ConsequentRootSet buildConsequentRootSet() {
       return new ConsequentRootSet(
           neverInlineDueToSingleCaller,
-          neverClassInline,
-          noHorizontalClassMerging,
           dependentMinimumKeepInfo,
           dependentKeepClassCompatRule,
           Lists.newArrayList(delayedRootSetActionItems),
@@ -1221,10 +1210,13 @@
         }
         switch (classInlineRule.getType()) {
           case ALWAYS:
-            alwaysClassInline.addElement(item.asClass().getType());
+            alwaysClassInline.addElement(clazz.getType());
             break;
           case NEVER:
-            neverClassInline.add(item.asClass().getType());
+            dependentMinimumKeepInfo
+                .getOrCreateUnconditionalMinimumKeepInfoFor(clazz.getType())
+                .asClassJoiner()
+                .disallowClassInlining();
             break;
           default:
             throw new Unreachable();
@@ -1251,13 +1243,25 @@
             .disallowRedundantFieldLoadElimination();
         context.markAsUsed();
       } else if (context instanceof NoUnusedInterfaceRemovalRule) {
-        noUnusedInterfaceRemoval.add(item.asClass().type);
+        assert item.isProgramClass();
+        dependentMinimumKeepInfo
+            .getOrCreateUnconditionalMinimumKeepInfoFor(item.getReference())
+            .asClassJoiner()
+            .disallowUnusedInterfaceRemoval();
         context.markAsUsed();
       } else if (context instanceof NoVerticalClassMergingRule) {
-        noVerticalClassMerging.add(item.asClass().type);
+        assert item.isProgramClass();
+        dependentMinimumKeepInfo
+            .getOrCreateUnconditionalMinimumKeepInfoFor(item.getReference())
+            .asClassJoiner()
+            .disallowVerticalClassMerging();
         context.markAsUsed();
       } else if (context instanceof NoHorizontalClassMergingRule) {
-        noHorizontalClassMerging.add(item.asClass().type);
+        assert item.isProgramClass();
+        dependentMinimumKeepInfo
+            .getOrCreateUnconditionalMinimumKeepInfoFor(item.getReference())
+            .asClassJoiner()
+            .disallowHorizontalClassMerging();
         context.markAsUsed();
       } else if (context instanceof NoMethodStaticizingRule) {
         assert item.isProgramMethod();
@@ -1793,8 +1797,6 @@
   abstract static class RootSetBase {
 
     final Set<DexMethod> neverInlineDueToSingleCaller;
-    final Set<DexType> neverClassInline;
-    final Set<DexType> noHorizontalClassMerging;
     private final DependentMinimumKeepInfoCollection dependentMinimumKeepInfo;
     final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule;
     final List<DelayedRootSetActionItem> delayedRootSetActionItems;
@@ -1802,15 +1804,11 @@
 
     RootSetBase(
         Set<DexMethod> neverInlineDueToSingleCaller,
-        Set<DexType> neverClassInline,
-        Set<DexType> noHorizontalClassMerging,
         DependentMinimumKeepInfoCollection dependentMinimumKeepInfo,
         Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
         List<DelayedRootSetActionItem> delayedRootSetActionItems,
         ProgramMethodMap<ProgramMethod> pendingMethodMoveInverse) {
       this.neverInlineDueToSingleCaller = neverInlineDueToSingleCaller;
-      this.neverClassInline = neverClassInline;
-      this.noHorizontalClassMerging = noHorizontalClassMerging;
       this.dependentMinimumKeepInfo = dependentMinimumKeepInfo;
       this.dependentKeepClassCompatRule = dependentKeepClassCompatRule;
       this.delayedRootSetActionItems = delayedRootSetActionItems;
@@ -1835,8 +1833,6 @@
     public final Set<DexMethod> reprocess;
     public final Set<DexMethod> neverReprocess;
     public final PredicateSet<DexType> alwaysClassInline;
-    public final Set<DexType> noUnusedInterfaceRemoval;
-    public final Set<DexType> noVerticalClassMerging;
     public final Set<DexMember<?, ?>> neverPropagateValue;
     public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
     public final Set<DexMember<?, ?>> identifierNameStrings;
@@ -1852,10 +1848,6 @@
         Set<DexMethod> reprocess,
         Set<DexMethod> neverReprocess,
         PredicateSet<DexType> alwaysClassInline,
-        Set<DexType> neverClassInline,
-        Set<DexType> noUnusedInterfaceRemoval,
-        Set<DexType> noVerticalClassMerging,
-        Set<DexType> noHorizontalClassMerging,
         Set<DexMember<?, ?>> neverPropagateValue,
         Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
         Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
@@ -1865,8 +1857,6 @@
         ProgramMethodMap<ProgramMethod> pendingMethodMoveInverse) {
       super(
           neverInlineDueToSingleCaller,
-          neverClassInline,
-          noHorizontalClassMerging,
           dependentMinimumKeepInfo,
           dependentKeepClassCompatRule,
           delayedRootSetActionItems,
@@ -1878,8 +1868,6 @@
       this.reprocess = reprocess;
       this.neverReprocess = neverReprocess;
       this.alwaysClassInline = alwaysClassInline;
-      this.noUnusedInterfaceRemoval = noUnusedInterfaceRemoval;
-      this.noVerticalClassMerging = noVerticalClassMerging;
       this.neverPropagateValue = neverPropagateValue;
       this.mayHaveSideEffects = mayHaveSideEffects;
       this.identifierNameStrings = Collections.unmodifiableSet(identifierNameStrings);
@@ -1911,8 +1899,6 @@
 
     void addConsequentRootSet(ConsequentRootSet consequentRootSet) {
       neverInlineDueToSingleCaller.addAll(consequentRootSet.neverInlineDueToSingleCaller);
-      neverClassInline.addAll(consequentRootSet.neverClassInline);
-      noHorizontalClassMerging.addAll(consequentRootSet.noHorizontalClassMerging);
       consequentRootSet.dependentKeepClassCompatRule.forEach(
           (type, rules) ->
               dependentKeepClassCompatRule
@@ -1939,9 +1925,6 @@
       getDependentMinimumKeepInfo().pruneDeadItems(definitions, enqueuer);
       timing.end();
       timing.begin("Prune others");
-      pruneDeadReferences(noUnusedInterfaceRemoval, definitions, enqueuer);
-      pruneDeadReferences(noVerticalClassMerging, definitions, enqueuer);
-      pruneDeadReferences(noHorizontalClassMerging, definitions, enqueuer);
       pruneDeadReferences(alwaysInline, definitions, enqueuer);
       timing.end();
     }
@@ -1997,10 +1980,6 @@
                 reprocess,
                 neverReprocess,
                 alwaysClassInline,
-                neverClassInline,
-                noUnusedInterfaceRemoval,
-                noVerticalClassMerging,
-                noHorizontalClassMerging,
                 neverPropagateValue,
                 mayHaveSideEffects,
                 dependentKeepClassCompatRule,
@@ -2238,16 +2217,12 @@
 
     ConsequentRootSet(
         Set<DexMethod> neverInlineDueToSingleCaller,
-        Set<DexType> neverClassInline,
-        Set<DexType> noHorizontalClassMerging,
         DependentMinimumKeepInfoCollection dependentMinimumKeepInfo,
         Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
         List<DelayedRootSetActionItem> delayedRootSetActionItems,
         ProgramMethodMap<ProgramMethod> pendingMethodMoveInverse) {
       super(
           neverInlineDueToSingleCaller,
-          neverClassInline,
-          noHorizontalClassMerging,
           dependentMinimumKeepInfo,
           dependentKeepClassCompatRule,
           delayedRootSetActionItems,
@@ -2311,10 +2286,6 @@
           Collections.emptySet(),
           PredicateSet.empty(),
           Collections.emptySet(),
-          Collections.emptySet(),
-          Collections.emptySet(),
-          Collections.emptySet(),
-          Collections.emptySet(),
           emptyMap(),
           emptyMap(),
           Collections.emptySet(),
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index ac6c169..8bbf801 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -12,6 +12,8 @@
 import static com.android.tools.r8.utils.ZipUtils.writeToZipStream;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import com.android.tools.r8.AndroidResourceInput;
+import com.android.tools.r8.AndroidResourceProvider;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.ClassFileResourceProvider;
 import com.android.tools.r8.DataDirectoryResource;
@@ -99,18 +101,19 @@
   private static final String dumpMainDexListResourceFileName = "main-dex-list.txt";
   private static final String dumpMainDexRulesResourceFileName = "main-dex-rules.txt";
   private static final String dumpProgramFileName = "program.jar";
+  private static final String dumpInputResourcesFileName = "app-res.ap_";
   private static final String dumpClasspathFileName = "classpath.jar";
   private static final String dumpLibraryFileName = "library.jar";
   private static final String dumpConfigFileName = "proguard.config";
   private static final String dumpInputConfigFileName = "proguard_input.config";
 
   private static Map<FeatureSplit, String> dumpFeatureSplitFileNames(
-      FeatureSplitConfiguration featureSplitConfiguration) {
+      FeatureSplitConfiguration featureSplitConfiguration, String postfix) {
     Map<FeatureSplit, String> featureSplitFileNames = new IdentityHashMap<>();
     if (featureSplitConfiguration != null) {
       int i = 1;
       for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) {
-        featureSplitFileNames.put(featureSplit, "feature-" + i + ".jar");
+        featureSplitFileNames.put(featureSplit, "feature-" + i + postfix);
         i++;
       }
     }
@@ -550,6 +553,23 @@
       if (dumpOptions.hasStartupProfileProviders()) {
         dumpStartupProfileProviders(dumpOptions.getStartupProfileProviders(), options, out);
       }
+      if (dumpOptions.hasAndroidResourcesProvider()) {
+        dumpAndroidResourcesProvider(
+            dumpOptions.getAndroidResourceProvider(), out, dumpInputResourcesFileName);
+        if (dumpOptions.getFeatureSplitConfiguration() != null) {
+          Map<FeatureSplit, String> featureSplitNameMap =
+              dumpFeatureSplitFileNames(dumpOptions.getFeatureSplitConfiguration(), ".ap_");
+          for (FeatureSplit featureSplit :
+              dumpOptions.getFeatureSplitConfiguration().getFeatureSplits()) {
+            if (featureSplit.getAndroidResourceProvider() != null) {
+              dumpAndroidResourcesProvider(
+                  featureSplit.getAndroidResourceProvider(),
+                  out,
+                  featureSplitNameMap.get(featureSplit));
+            }
+          }
+        }
+      }
       nextDexIndex =
           dumpProgramResources(
               dumpProgramFileName,
@@ -627,6 +647,23 @@
     };
   }
 
+  private void dumpAndroidResourcesProvider(
+      AndroidResourceProvider androidResourceProvider, ZipOutputStream out, String name)
+      throws IOException, ResourceException {
+    try (ByteArrayOutputStream archiveByteStream = new ByteArrayOutputStream()) {
+      try (ZipOutputStream archiveOutputStream = new ZipOutputStream(archiveByteStream)) {
+        for (AndroidResourceInput androidResource : androidResourceProvider.getAndroidResources()) {
+          writeToZipStream(
+              archiveOutputStream,
+              androidResource.getPath().location(),
+              androidResource.getByteStream().readAllBytes(),
+              ZipEntry.DEFLATED);
+        }
+      }
+      writeToZipStream(out, name, archiveByteStream.toByteArray(), ZipEntry.DEFLATED);
+    }
+  }
+
   private int dumpProgramResources(
       String archiveName,
       FeatureSplitConfiguration featureSplitConfiguration,
@@ -635,7 +672,7 @@
       InternalOptions options)
       throws IOException, ResourceException {
     Map<FeatureSplit, String> featureSplitArchiveNames =
-        dumpFeatureSplitFileNames(featureSplitConfiguration);
+        dumpFeatureSplitFileNames(featureSplitConfiguration, ".jar");
     Map<FeatureSplit, ByteArrayOutputStream> featureSplitArchiveByteStreams =
         new IdentityHashMap<>();
     Map<FeatureSplit, ZipOutputStream> featureSplitArchiveOutputStreams = new IdentityHashMap<>();
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index ae6e72c..b71ad68 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -63,6 +63,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.analysis.ResourceAccessAnalysis;
 import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
 import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
 import com.android.tools.r8.horizontalclassmerging.Policy;
@@ -2173,8 +2174,8 @@
   public static class TestingOptions {
 
     public boolean enableNumberUnboxer = false;
+    public boolean printNumberUnboxed = false;
     public boolean roundtripThroughLir = false;
-
     public boolean canUseLir(AppView<?> appView) {
       return appView.enableWholeProgramOptimizations();
     }
@@ -2218,6 +2219,19 @@
                           BytecodeMetadataProvider.empty(),
                           LirStrategy.getDefaultStrategy().getEncodingStrategy(),
                           appView.options());
+                  // TODO(b/312890994): Setting a custom code lens is only needed until we convert
+                  //  code objects to LIR before we create the first code object with a custom code
+                  //  lens (horizontal class merging).
+                  GraphLens codeLens = method.getDefinition().getCode().getCodeLens(appView);
+                  if (codeLens != appView.codeLens()) {
+                    lirCode =
+                        new LirCode<>(lirCode) {
+                          @Override
+                          public GraphLens getCodeLens(AppView<?> appView) {
+                            return codeLens;
+                          }
+                        };
+                  }
                   method.setCode(lirCode, appView);
                 });
           },
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
index 6cb5e12..0ffde6c 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
@@ -214,9 +214,8 @@
     if (allocationInfo.isInstantiatedDirectly(sourceClass)
         || allocationInfo.isInterfaceWithUnknownSubtypeHierarchy(sourceClass)
         || allocationInfo.isImmediateInterfaceOfInstantiatedLambda(sourceClass)
-        || appView.getKeepInfo(sourceClass).isPinned(options)
-        || pinnedClasses.contains(sourceClass)
-        || appView.appInfo().isNoVerticalClassMergingOfType(sourceClass)) {
+        || !appView.getKeepInfo(sourceClass).isVerticalClassMergingAllowed(options)
+        || pinnedClasses.contains(sourceClass)) {
       return false;
     }
 
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index d1eb192..2183979 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -34,7 +34,6 @@
 import com.android.tools.r8.shaking.NoReturnTypeStrengtheningRule;
 import com.android.tools.r8.shaking.NoUnusedInterfaceRemovalRule;
 import com.android.tools.r8.shaking.NoVerticalClassMergingRule;
-import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.utils.AndroidApp;
@@ -147,25 +146,21 @@
     class Box {
 
       private List<ProguardConfigurationRule> syntheticProguardRules;
-      private ProguardConfiguration proguardConfiguration;
     }
     Box box = new Box();
     ToolHelper.addSyntheticProguardRulesConsumerForTesting(
         builder, rules -> box.syntheticProguardRules = rules);
     libraryDesugaringTestConfiguration.configure(builder);
     builder.setEnableExperimentalMissingLibraryApiModeling(enableMissingLibraryApiModeling);
-    ToolHelper.runAndBenchmarkR8WithoutResult(
-        builder,
-        optionsConsumer.andThen(
-            options -> box.proguardConfiguration = options.getProguardConfiguration()),
-        benchmarkResults);
+    StringBuilder pgConfOutput = wrapProguardConfigConsumer(builder);
+    ToolHelper.runAndBenchmarkR8WithoutResult(builder, optionsConsumer, benchmarkResults);
     R8TestCompileResult compileResult =
         new R8TestCompileResult(
             getState(),
             getOutputMode(),
             libraryDesugaringTestConfiguration,
             app.get(),
-            box.proguardConfiguration,
+            pgConfOutput.toString(),
             box.syntheticProguardRules,
             proguardMapBuilder.toString(),
             graphConsumer,
@@ -208,6 +203,20 @@
     return compileResult;
   }
 
+  private static StringBuilder wrapProguardConfigConsumer(Builder builder) {
+    StringBuilder pgConfOutput = new StringBuilder();
+    StringConsumer pgConfConsumer = builder.getProguardConfigurationConsumer();
+    builder.setProguardConfigurationConsumer(
+        new StringConsumer.ForwardingConsumer(pgConfConsumer) {
+          @Override
+          public void accept(String string, DiagnosticsHandler handler) {
+            super.accept(string, handler);
+            pgConfOutput.append(string);
+          }
+        });
+    return pgConfOutput;
+  }
+
   public Builder getBuilder() {
     return builder;
   }
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index 20b9f2d..25001ac 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.profile.art.model.ExternalArtProfile;
 import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
 import com.android.tools.r8.shaking.CollectingGraphConsumer;
-import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.FileUtils;
@@ -35,7 +34,7 @@
 
 public class R8TestCompileResult extends TestCompileResult<R8TestCompileResult, R8TestRunResult> {
 
-  private final ProguardConfiguration proguardConfiguration;
+  private final String proguardConfiguration;
   private final List<ProguardConfigurationRule> syntheticProguardRules;
   private final String proguardMap;
   private final CollectingGraphConsumer graphConsumer;
@@ -49,7 +48,7 @@
       OutputMode outputMode,
       LibraryDesugaringTestConfiguration libraryDesugaringTestConfiguration,
       AndroidApp app,
-      ProguardConfiguration proguardConfiguration,
+      String proguardConfiguration,
       List<ProguardConfigurationRule> syntheticProguardRules,
       String proguardMap,
       CollectingGraphConsumer graphConsumer,
@@ -182,12 +181,11 @@
     return new GraphInspector(graphConsumer, inspector());
   }
 
-  public ProguardConfiguration getProguardConfiguration() {
+  public String getProguardConfiguration() {
     return proguardConfiguration;
   }
 
-  public R8TestCompileResult inspectProguardConfiguration(
-      Consumer<ProguardConfiguration> consumer) {
+  public R8TestCompileResult inspectProguardConfiguration(Consumer<String> consumer) {
     consumer.accept(getProguardConfiguration());
     return self();
   }
diff --git a/src/test/java/com/android/tools/r8/androidresources/AndroidResourcesDumpTest.java b/src/test/java/com/android/tools/r8/androidresources/AndroidResourcesDumpTest.java
new file mode 100644
index 0000000..f4ad013
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/AndroidResourcesDumpTest.java
@@ -0,0 +1,118 @@
+// 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.androidresources;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder;
+import com.android.tools.r8.dump.CompilerDump;
+import com.android.tools.r8.utils.DumpInputFlags;
+import com.android.tools.r8.utils.ZipUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+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 AndroidResourcesDumpTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withDefaultDexRuntime().withMinimumApiLevel().build();
+  }
+
+  public static AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception {
+    return new AndroidTestResourceBuilder()
+        .withSimpleManifestAndAppNameString()
+        .addStringValue("foo", "the foobar string")
+        .addXmlWithStringReference("file.xml", "foo")
+        .build(temp);
+  }
+
+  public static AndroidTestResource getFeatureTestResources(TemporaryFolder temp)
+      throws IOException {
+    return new AndroidTestResourceBuilder()
+        .withSimpleManifestAndAppNameString()
+        .addStringValue("feature_foo", "the feature string")
+        .addXmlWithStringReference("feature.xml", "feature_foo")
+        .build(temp);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Path dump = temp.newFile("with_resources.zip").toPath();
+    AndroidTestResource testResources = getTestResources(temp);
+    TemporaryFolder featureSplitTemp = ToolHelper.getTemporaryFolderForTest();
+    featureSplitTemp.create();
+    AndroidTestResource featureTestResources = getFeatureTestResources(featureSplitTemp);
+    try {
+      testForR8(parameters.getBackend())
+          .addProgramClasses(Main.class)
+          .addKeepMainRule(Main.class)
+          .addAndroidResources(testResources)
+          .addFeatureSplitAndroidResources(featureTestResources, "thefeature")
+          .addOptionsModification(
+              options -> options.setDumpInputFlags(DumpInputFlags.dumpToFile(dump)))
+          .setMinApi(parameters)
+          .compile();
+      fail("Expected to fail compilation");
+    } catch (CompilationFailedException ignored) {
+    }
+    CompilerDump compilerDump = CompilerDump.fromArchive(dump, temp.newFolder().toPath());
+    validateResourceEquality(
+        testResources,
+        ImmutableList.of("res/xml/file.xml", "AndroidManifest.xml", "resources.pb"),
+        compilerDump.getAndroidResources());
+    validateResourceEquality(
+        featureTestResources,
+        ImmutableList.of("res/xml/feature.xml", "AndroidManifest.xml", "resources.pb"),
+        compilerDump.getAndroidResourcesForFeature(1));
+  }
+
+  private static void validateResourceEquality(
+      AndroidTestResource testResources, List<String> fileEntries, Path dumpInput)
+      throws IOException {
+    int resourceFileCount = 0;
+    try (ZipInputStream zipInputStream = new ZipInputStream(Files.newInputStream(dumpInput))) {
+      ZipEntry nextEntry = zipInputStream.getNextEntry();
+      Path resourceZip = testResources.getResourceZip();
+      while (nextEntry != null) {
+        resourceFileCount++;
+        String name = nextEntry.getName();
+        assertTrue(fileEntries.contains(name));
+        byte[] original = ZipUtils.readSingleEntry(resourceZip, name);
+        assertArrayEquals(original, ByteStreams.toByteArray(zipInputStream));
+        nextEntry = zipInputStream.getNextEntry();
+      }
+    }
+    assertEquals(resourceFileCount, fileEntries.size());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
index f5b7bfe..6d9774b 100644
--- a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
+++ b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
@@ -73,6 +73,7 @@
         new DebugBytecodeWriter(
             DexDebugInfo.convertToWritable(debugInfo),
             emptyObjectTObjectMapping(),
+            GraphLens.getIdentityLens(),
             GraphLens.getIdentityLens());
     Assert.assertEquals(3, writer.generate().length);
   }
diff --git a/src/main/java/com/android/tools/r8/dump/CompilerDump.java b/src/test/java/com/android/tools/r8/dump/CompilerDump.java
similarity index 88%
rename from src/main/java/com/android/tools/r8/dump/CompilerDump.java
rename to src/test/java/com/android/tools/r8/dump/CompilerDump.java
index 7febf31..46d5ec1 100644
--- a/src/main/java/com/android/tools/r8/dump/CompilerDump.java
+++ b/src/test/java/com/android/tools/r8/dump/CompilerDump.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// 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.dump;
@@ -49,6 +49,14 @@
     return Files.exists(directory.resolve("desugared-library.json"));
   }
 
+  public Path getAndroidResources() {
+    return directory.resolve("app-res.ap_");
+  }
+
+  public Path getAndroidResourcesForFeature(int feature) {
+    return directory.resolve("feature-" + feature + ".ap_");
+  }
+
   public Path getDesugaredLibraryFile() {
     return directory.resolve("desugared-library.json");
   }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingMappingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingMappingTest.java
new file mode 100644
index 0000000..0f84aa8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingMappingTest.java
@@ -0,0 +1,95 @@
+// 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.enumunboxing;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EnumUnboxingMappingTest extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public EnumUnboxingMappingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepRules(EnumKeepRules.STUDIO.getKeepRules())
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .allowDiagnosticMessages()
+        .setMinApi(parameters)
+        .compile()
+        .inspect(this::assertParameterTypes)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(
+            "1", "2",
+            "DebugInfoForThisPrint", "1",
+            "DebugInfoForThisPrint", "2");
+  }
+
+  private void assertParameterTypes(CodeInspector codeInspector) {
+    ClassSubject main = codeInspector.clazz(Main.class);
+
+    MethodSubject debugInfoMethod = main.uniqueMethodWithOriginalName("debugInfoAfterUnboxing");
+    MethodSubject noDebugInfoMethod = main.uniqueMethodWithOriginalName("noDebugInfoAfterUnboxing");
+
+    assertEquals("int", debugInfoMethod.getFinalSignature().asMethodSignature().parameters[0]);
+    assertEquals("int", noDebugInfoMethod.getFinalSignature().asMethodSignature().parameters[0]);
+
+    assertEquals(MyEnum.class.getName(), debugInfoMethod.getOriginalSignature().parameters[0]);
+    // TODO(b/314076309): The original parameter should be MyEnum.class but is int.
+    assertEquals("int", noDebugInfoMethod.getOriginalSignature().parameters[0]);
+  }
+
+  @NeverClassInline
+  enum MyEnum {
+    A,
+    B
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(noDebugInfoAfterUnboxing(MyEnum.A));
+      System.out.println(noDebugInfoAfterUnboxing(null));
+      System.out.println(debugInfoAfterUnboxing(MyEnum.A));
+      System.out.println(debugInfoAfterUnboxing(null));
+    }
+
+    @NeverInline
+    private static int noDebugInfoAfterUnboxing(MyEnum e) {
+      return (e == null ? 1 : 0) + 1;
+    }
+
+    @NeverInline
+    private static int debugInfoAfterUnboxing(MyEnum e) {
+      System.out.println("DebugInfoForThisPrint");
+      return (e == null ? 1 : 0) + 1;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldsOnlyWrittenInInitializersWithSingleCallerPruningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldsOnlyWrittenInInitializersWithSingleCallerPruningTest.java
new file mode 100644
index 0000000..250dd0b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldsOnlyWrittenInInitializersWithSingleCallerPruningTest.java
@@ -0,0 +1,94 @@
+// 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.membervaluepropagation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+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 InstanceFieldsOnlyWrittenInInitializersWithSingleCallerPruningTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    assumeTrue(parameters.canInitNewInstanceUsingSuperclassConstructor());
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        // Ensure that A.<init>(String) is single caller inlined and removed from the application
+        // before processing the last instance field write to A.f.
+        .addOptionsModification(
+            options ->
+                options.testing.waveModifier =
+                    waves -> {
+                      int numberOfWaves = waves.size();
+                      for (ProgramMethodSet wave : waves) {
+                        boolean changed =
+                            wave.removeIf(
+                                method -> {
+                                  if (method.getHolder().getTypeName().equals(A.class.getTypeName())
+                                      && method.getDefinition().isInstanceInitializer()
+                                      && method.getParameters().isEmpty()) {
+                                    waves.addLast(ProgramMethodSet.create(method));
+                                    return true;
+                                  }
+                                  return false;
+                                });
+                        if (changed) {
+                          break;
+                        }
+                      }
+                      assertEquals(numberOfWaves + 1, waves.size());
+                    })
+        .enableInliningAnnotations()
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.print(new A());
+      System.out.println(new A(", world!"));
+    }
+  }
+
+  static class A {
+
+    String f;
+
+    @NeverInline
+    A() {
+      f = "Hello";
+    }
+
+    A(String f) {
+      this.f = f;
+    }
+
+    @Override
+    public String toString() {
+      return f;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullParamInterfaceImpl.java b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullParamInterfaceImpl.java
index 4f1dc5f..94eb392 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullParamInterfaceImpl.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullParamInterfaceImpl.java
@@ -3,8 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize.nonnull;
 
+import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 
+@NeverClassInline
 public class NonNullParamInterfaceImpl implements NonNullParamInterface {
   @NeverInline
   public int sum(NotPinnedClass arg1, NotPinnedClass arg2) {
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionOnFieldTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionOnFieldTest.java
index 47613a0..414146d6 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionOnFieldTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionOnFieldTest.java
@@ -57,7 +57,7 @@
         .compile()
         .apply(
             c -> {
-              String rules = c.getProguardConfiguration().getParsedConfiguration();
+              String rules = c.getProguardConfiguration();
               assertThat(rules, containsString("context: " + descriptor(A.class) + "foo()V"));
               assertThat(rules, containsString("description: Keep the\\nstring-valued fields"));
             })
diff --git a/src/test/java/com/android/tools/r8/keepanno/doctests/MainMethodsDocumentationTest.java b/src/test/java/com/android/tools/r8/keepanno/doctests/MainMethodsDocumentationTest.java
new file mode 100644
index 0000000..fdbe498
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/doctests/MainMethodsDocumentationTest.java
@@ -0,0 +1,86 @@
+// 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.keepanno.doctests;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.annotations.KeepEdge;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.MethodAccessFlags;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MainMethodsDocumentationTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello I'm kept!");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+  }
+
+  public MainMethodsDocumentationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getInputClasses())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testWithRuleExtraction() throws Exception {
+    testForR8(parameters.getBackend())
+        .enableExperimentalKeepAnnotations()
+        .addProgramClasses(getInputClasses())
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  public List<Class<?>> getInputClasses() {
+    return ImmutableList.of(TestClass.class, SomeClass.class);
+  }
+
+  /* INCLUDE DOC: KeepMainMethods
+  For example, to keep all main methods in the program one could use:
+  INCLUDE END */
+
+  static
+  // INCLUDE CODE: KeepMainMethods
+  @KeepEdge(
+      consequences = {
+        @KeepTarget(
+            kind = KeepItemKind.CLASS_AND_MEMBERS,
+            methodName = "main",
+            methodReturnType = "void",
+            methodParameters = {"java.lang.String[]"},
+            methodAccess = {MethodAccessFlags.PUBLIC, MethodAccessFlags.STATIC})
+      })
+  public class SomeClass {
+    // ...
+  }
+
+  // INCLUDE END
+
+  static class TestClass {
+
+    public static void main(String[] args) throws Exception {
+      System.out.println("Hello I'm kept!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/utils/KeepAnnoMarkdownGenerator.java b/src/test/java/com/android/tools/r8/keepanno/utils/KeepAnnoMarkdownGenerator.java
index f4fd1a8..6fd05eb 100644
--- a/src/test/java/com/android/tools/r8/keepanno/utils/KeepAnnoMarkdownGenerator.java
+++ b/src/test/java/com/android/tools/r8/keepanno/utils/KeepAnnoMarkdownGenerator.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.keepanno.utils;
 
+import static com.android.tools.r8.keepanno.utils.KeepItemAnnotationGenerator.quote;
+
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.keepanno.annotations.KeepBinding;
 import com.android.tools.r8.keepanno.annotations.KeepCondition;
@@ -13,6 +15,7 @@
 import com.android.tools.r8.keepanno.annotations.UsedByNative;
 import com.android.tools.r8.keepanno.annotations.UsedByReflection;
 import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.keepanno.doctests.MainMethodsDocumentationTest;
 import com.android.tools.r8.keepanno.doctests.UsesReflectionDocumentationTest;
 import com.android.tools.r8.keepanno.utils.KeepItemAnnotationGenerator.Generator;
 import com.android.tools.r8.utils.FileUtils;
@@ -22,9 +25,15 @@
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 public class KeepAnnoMarkdownGenerator {
 
@@ -39,6 +48,8 @@
   private static String JAVADOC_URL =
       "https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/";
 
+  private static final String TOC_MARKER = "[[[TOC]]]";
+
   private static final String INCLUDE_MD_START = "[[[INCLUDE";
   private static final String INCLUDE_MD_DOC_START = "[[[INCLUDE DOC";
   private static final String INCLUDE_MD_CODE_START = "[[[INCLUDE CODE";
@@ -66,7 +77,8 @@
             UsedByReflection.class,
             UsedByNative.class,
             KeepForApi.class);
-    populateCodeAndDocReplacements(UsesReflectionDocumentationTest.class);
+    populateCodeAndDocReplacements(
+        UsesReflectionDocumentationTest.class, MainMethodsDocumentationTest.class);
   }
 
   private Map<String, String> getTypeLinkReplacements(Class<?>... classes) {
@@ -135,10 +147,17 @@
     println("[comment]: <> (Changes should be made in " + template + ")");
     println();
     List<String> readAllLines = FileUtils.readAllLines(template);
+    TableEntry root = new TableEntry(0, "root", "root", null);
+    readAllLines = collectTableOfContents(readAllLines, root);
+
     for (int i = 0; i < readAllLines.size(); i++) {
       String line = readAllLines.get(i);
       try {
-        processLine(line, generator);
+        if (line.trim().equals(TOC_MARKER)) {
+          printTableOfContents(root);
+        } else {
+          processLine(line, generator);
+        }
       } catch (Exception e) {
         System.err.println("Parse error on line " + (i + 1) + ":");
         System.err.println(line);
@@ -147,6 +166,70 @@
     }
   }
 
+  private void printTableOfContents(TableEntry root) {
+    println("## Table of contents");
+    println();
+    printTableSubEntries(root.subSections.values());
+    println();
+  }
+
+  private void printTableSubEntries(Collection<TableEntry> entries) {
+    for (TableEntry entry : entries) {
+      println("- " + entry.getHrefLink());
+      generator.withIndent(() -> printTableSubEntries(entry.subSections.values()));
+    }
+  }
+
+  private List<String> collectTableOfContents(List<String> lines, TableEntry root) {
+    Set<String> seen = new HashSet<>();
+    TableEntry current = root;
+    List<String> newLines = new ArrayList<>(lines.size());
+    Iterator<String> iterator = lines.iterator();
+    // Skip forward until the TOC insertion.
+    while (iterator.hasNext()) {
+      String line = iterator.next();
+      newLines.add(line);
+      if (line.trim().equals(TOC_MARKER)) {
+        break;
+      }
+    }
+    // Find TOC entries and replace the headings with links.
+    while (iterator.hasNext()) {
+      String line = iterator.next();
+      int headingDepth = 0;
+      for (int i = 0; i < line.length(); i++) {
+        char c = line.charAt(i);
+        if (c != '#') {
+          headingDepth = i;
+          break;
+        }
+      }
+      if (headingDepth == 0) {
+        newLines.add(line);
+        continue;
+      }
+      String headingPrefix = line.substring(0, headingDepth);
+      String headingContent = line.substring(headingDepth).trim();
+      int splitIndex = headingContent.indexOf("](");
+      if (splitIndex < 0 || !headingContent.startsWith("[") || !headingContent.endsWith(")")) {
+        throw new RuntimeException("Invalid heading format. Use [Heading Text](heading-id)");
+      }
+      String headingText = headingContent.substring(1, splitIndex);
+      String headingId = headingContent.substring(splitIndex + 2, headingContent.length() - 1);
+      if (!seen.add(headingId)) {
+        throw new RuntimeException("Duplicate heading id: " + headingText);
+      }
+      while (headingDepth <= current.depth) {
+        current = current.parent;
+      }
+      TableEntry entry = new TableEntry(headingDepth, headingText, headingId, current);
+      current.subSections.put(headingText, entry);
+      current = entry;
+      newLines.add(headingPrefix + " " + entry.getIdAnchor());
+    }
+    return newLines;
+  }
+
   private String replaceCodeAndDocMarkers(String line) {
     String originalLine = line;
     line = line.trim();
@@ -190,25 +273,46 @@
   private StringBuilder unindentLines(String replacement, StringBuilder builder) {
     int shortestSpacePrefix = Integer.MAX_VALUE;
     List<String> lines = StringUtils.split(replacement, '\n');
+    lines = trimEmptyLines(lines);
     for (String line : lines) {
       if (!line.isEmpty()) {
         shortestSpacePrefix = Math.min(shortestSpacePrefix, findFirstNonSpaceIndex(line));
       }
     }
-    if (shortestSpacePrefix > 0) {
-      for (String line : lines) {
-        if (!line.isEmpty()) {
-          builder.append(line.substring(shortestSpacePrefix));
-        }
-        builder.append('\n');
+    for (String line : lines) {
+      if (!line.isEmpty()) {
+        builder.append(line.substring(shortestSpacePrefix));
       }
-    } else {
-      builder.append(replacement);
       builder.append('\n');
     }
     return builder;
   }
 
+  private static List<String> trimEmptyLines(List<String> lines) {
+    int startLineIndex = 0;
+    int endLineIndex = lines.size() - 1;
+    while (true) {
+      String line = lines.get(startLineIndex);
+      if (line.trim().isEmpty()) {
+        startLineIndex++;
+      } else {
+        break;
+      }
+    }
+    while (true) {
+      String line = lines.get(endLineIndex);
+      if (line.trim().isEmpty()) {
+        --endLineIndex;
+      } else {
+        break;
+      }
+    }
+    if (startLineIndex != 0 || endLineIndex != lines.size() - 1) {
+      lines = lines.subList(startLineIndex, endLineIndex + 1);
+    }
+    return lines;
+  }
+
   private int findFirstNonSpaceIndex(String line) {
     for (int i = 0; i < line.length(); i++) {
       if (line.charAt(i) != ' ') {
@@ -244,4 +348,27 @@
     }
     generator.println(line);
   }
+
+  private static class TableEntry {
+    final int depth;
+    final String name;
+    final String id;
+    final TableEntry parent;
+    final Map<String, TableEntry> subSections = new LinkedHashMap<>();
+
+    public TableEntry(int depth, String name, String id, TableEntry parent) {
+      this.depth = depth;
+      this.name = name;
+      this.id = id;
+      this.parent = parent;
+    }
+
+    public String getHrefLink() {
+      return "[" + name + "](#" + id + ")";
+    }
+
+    public String getIdAnchor() {
+      return name + "<a id=" + quote(id) + "></a>";
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
index 005c344..d139b43 100644
--- a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
+++ b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
@@ -47,7 +47,7 @@
     Generator.run();
   }
 
-  private static String quote(String str) {
+  public static String quote(String str) {
     return "\"" + str + "\"";
   }
 
@@ -218,7 +218,7 @@
       this.writer = writer;
     }
 
-    private void withIndent(Runnable fn) {
+    public void withIndent(Runnable fn) {
       indent += 2;
       fn.run();
       indent -= 2;
diff --git a/src/test/java/com/android/tools/r8/naming/adaptclassstrings/AdaptClassStringContextClassTest.java b/src/test/java/com/android/tools/r8/naming/adaptclassstrings/AdaptClassStringContextClassTest.java
new file mode 100644
index 0000000..4add73d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/adaptclassstrings/AdaptClassStringContextClassTest.java
@@ -0,0 +1,104 @@
+// 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.naming.adaptclassstrings;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.ProguardVersion;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.io.IOException;
+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 AdaptClassStringContextClassTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public boolean isCompat;
+
+  @Parameter(2)
+  public ProguardVersion proguardVersion;
+
+  @Parameters(name = "{0}, isCompat: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDefaultCfRuntime().build(),
+        BooleanUtils.values(),
+        ProguardVersion.values());
+  }
+
+  @Test
+  public void testProguard() throws Exception {
+    assumeTrue(isCompat);
+    testForProguard(proguardVersion)
+        .addDontWarn(AdaptClassStringContextClassTest.class)
+        .apply(this::setUpTest)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(
+            "com.android.tools.r8.naming.adaptclassstrings.a",
+            // PG 5 replaces in both contexts despite the filter.
+            proguardVersion == ProguardVersion.V5_2_1
+                ? "com.android.tools.r8.naming.adaptclassstrings.a"
+                : typeName(Foo.class))
+        .inspect(inspector -> assertThat(inspector.clazz(Foo.class), isPresentAndRenamed()));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    assumeTrue(proguardVersion == ProguardVersion.getLatest());
+    (isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
+        .setMinApi(AndroidApiLevel.B)
+        .apply(this::setUpTest)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(
+            // TODO(b/313666380): R8 uses the class filter as which classes may adapt their names.
+            //   The actual meaning of the class filter is in which contexts to allow adaption.
+            typeName(Foo.class), typeName(Foo.class))
+        .inspect(inspector -> assertThat(inspector.clazz(Foo.class), isPresentAndRenamed()));
+  }
+
+  private void setUpTest(TestShrinkerBuilder<?, ?, ?, ?, ?> builder) throws IOException {
+    builder
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepClassRulesWithAllowObfuscation(Foo.class)
+        .addKeepRules("-adaptclassstrings " + typeName(StringContainer1.class));
+  }
+
+  public static class Foo {}
+
+  public static class StringContainer1 {
+    static String getString() {
+      return "com.android.tools.r8.naming.adaptclassstrings.AdaptClassStringContextClassTest$Foo";
+    }
+  }
+
+  public static class StringContainer2 {
+    static String getString() {
+      return "com.android.tools.r8.naming.adaptclassstrings.AdaptClassStringContextClassTest$Foo";
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(StringContainer1.getString());
+      System.out.println(StringContainer2.getString());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/adaptclassstrings/AdaptClassStringPrefixTest.java b/src/test/java/com/android/tools/r8/naming/adaptclassstrings/AdaptClassStringPrefixTest.java
new file mode 100644
index 0000000..ecc0209
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/adaptclassstrings/AdaptClassStringPrefixTest.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.naming.adaptclassstrings;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.ProguardVersion;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.io.IOException;
+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 AdaptClassStringPrefixTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public boolean isCompat;
+
+  @Parameter(2)
+  public ProguardVersion proguardVersion;
+
+  @Parameters(name = "{0}, isCompat: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDefaultCfRuntime().build(),
+        BooleanUtils.values(),
+        ProguardVersion.values());
+  }
+
+  @Test
+  public void testProguard() throws Exception {
+    assumeTrue(isCompat);
+    testForProguard(proguardVersion)
+        .addDontWarn(AdaptClassStringPrefixTest.class)
+        .apply(this::setUpTest)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("com.android.tools.r8.naming.adaptclassstrings.a")
+        .inspect(inspector -> assertThat(inspector.clazz(Foo.class), isPresentAndRenamed()));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    assumeTrue(proguardVersion == ProguardVersion.getLatest());
+    (isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
+        .setMinApi(AndroidApiLevel.B)
+        .apply(this::setUpTest)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("a.a")
+        .inspect(inspector -> assertThat(inspector.clazz(Foo.class), isPresentAndRenamed()));
+  }
+
+  private void setUpTest(TestShrinkerBuilder<?, ?, ?, ?, ?> builder) throws IOException {
+    builder
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepRules("-keep,allowobfuscation class " + typeName(Foo.class))
+        .addKeepRules("-adaptclassstrings com.android.tools.r8.naming.**");
+  }
+
+  public static class Foo {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(
+          "com.android.tools.r8.naming.adaptclassstrings.AdaptClassStringPrefixTest$Foo");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/numberunboxing/SimpleNumberUnboxingTest.java b/src/test/java/com/android/tools/r8/numberunboxing/SimpleNumberUnboxingTest.java
index 755096b..ceaf904 100644
--- a/src/test/java/com/android/tools/r8/numberunboxing/SimpleNumberUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/numberunboxing/SimpleNumberUnboxingTest.java
@@ -4,10 +4,17 @@
 
 package com.android.tools.r8.numberunboxing;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.util.Objects;
 import org.hamcrest.CoreMatchers;
 import org.junit.Test;
@@ -36,9 +43,11 @@
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
         .addOptionsModification(opt -> opt.testing.enableNumberUnboxer = true)
+        .addOptionsModification(opt -> opt.testing.printNumberUnboxed = true)
         .setMinApi(parameters)
         .allowDiagnosticWarningMessages()
         .compile()
+        .inspect(this::assertUnboxing)
         .assertWarningMessageThatMatches(
             CoreMatchers.containsString(
                 "Unboxing of arg 0 of void"
@@ -67,6 +76,33 @@
         .assertSuccessWithOutputLines("32", "33", "42", "43", "51", "52", "2");
   }
 
+  private void assertFirstParameterUnboxed(ClassSubject mainClass, String methodName) {
+    MethodSubject methodSubject = mainClass.uniqueMethodWithOriginalName(methodName);
+    assertThat(methodSubject, isPresent());
+    assertEquals("java.lang.Integer", methodSubject.getOriginalSignature().parameters[0]);
+    assertEquals("int", methodSubject.getFinalSignature().asMethodSignature().parameters[0]);
+  }
+
+  private void assertReturnUnboxed(ClassSubject mainClass, String methodName) {
+    MethodSubject methodSubject = mainClass.uniqueMethodWithOriginalName(methodName);
+    assertThat(methodSubject, isPresent());
+    assertEquals("java.lang.Integer", methodSubject.getOriginalSignature().type);
+    assertEquals("int", methodSubject.getFinalSignature().asMethodSignature().type);
+  }
+
+  private void assertUnboxing(CodeInspector codeInspector) {
+    ClassSubject mainClass = codeInspector.clazz(Main.class);
+    assertThat(mainClass, isPresent());
+
+    assertFirstParameterUnboxed(mainClass, "print");
+    assertFirstParameterUnboxed(mainClass, "forwardToPrint2");
+    assertFirstParameterUnboxed(mainClass, "directPrintUnbox");
+    assertFirstParameterUnboxed(mainClass, "forwardToPrint");
+
+    assertReturnUnboxed(mainClass, "get");
+    assertReturnUnboxed(mainClass, "forwardGet");
+  }
+
   static class Main {
 
     public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/retrace/InvalidMappingRangesB309080420Test.java b/src/test/java/com/android/tools/r8/retrace/InvalidMappingRangesB309080420Test.java
index 9e45fe5..09ca4e0 100644
--- a/src/test/java/com/android/tools/r8/retrace/InvalidMappingRangesB309080420Test.java
+++ b/src/test/java/com/android/tools/r8/retrace/InvalidMappingRangesB309080420Test.java
@@ -34,7 +34,9 @@
           "a.x -> a.x:",
           "    1:1:void a(com.example.Foo) -> a",
           "    11:2:void a() -> a", // Unexpected line range [11:2] - interpreting as [2:11]
-          "    12:21:void a(android.content.Intent) -> a");
+          "    12:21:void a(android.content.Intent) -> a",
+          // Allow identifier content to follow <init>/<clinit>.
+          "    22:41:void <clinit>$more$stuff() -> clinit$move$stuff");
 
   @Test
   public void test() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesR8SpecificTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesR8SpecificTest.java
index 1079641..ccb56de 100644
--- a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesR8SpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesR8SpecificTest.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
+import java.util.ArrayList;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -44,29 +45,17 @@
         ProviderType.values());
   }
 
-  private static final String EXPECTED_A =
-      StringUtils.lines(
-          "-keep class A1 {", "  <init>();", "}", "-keep class A2 {", "  <init>();", "}");
+  private static final String EXPECTED_A = StringUtils.lines("-keep class A1", "-keep class A2");
 
-  private static final String EXPECTED_B =
-      StringUtils.lines(
-          "-keep class B1 {", "  <init>();", "}", "-keep class B2 {", "  <init>();", "}");
+  private static final String EXPECTED_B = StringUtils.lines("-keep class B1", "-keep class B2");
 
-  private static final String EXPECTED_C =
-      StringUtils.lines(
-          "-keep class C1 {", "  <init>();", "}", "-keep class C2 {", "  <init>();", "}");
+  private static final String EXPECTED_C = StringUtils.lines("-keep class C1", "-keep class C2");
 
-  private static final String EXPECTED_D =
-      StringUtils.lines(
-          "-keep class D1 {", "  <init>();", "}", "-keep class D2 {", "  <init>();", "}");
+  private static final String EXPECTED_D = StringUtils.lines("-keep class D1", "-keep class D2");
 
-  private static final String EXPECTED_E =
-      StringUtils.lines(
-          "-keep class E1 {", "  <init>();", "}", "-keep class E2 {", "  <init>();", "}");
+  private static final String EXPECTED_E = StringUtils.lines("-keep class E1", "-keep class E2");
 
-  private static final String EXPECTED_X =
-      StringUtils.lines(
-          "-keep class X1 {", "  <init>();", "}", "-keep class X2 {", "  <init>();", "}");
+  private static final String EXPECTED_X = StringUtils.lines("-keep class X1", "-keep class X2");
 
   private Path buildLibrary() throws Exception {
     ZipBuilder jarBuilder =
@@ -144,7 +133,28 @@
         .allowUnusedProguardConfigurationRules()
         .compile()
         .inspectProguardConfiguration(
-            configuration -> assertEquals(expected, configuration.toString()));
+            configuration ->
+                assertEquals(
+                    expected,
+                    stripCommentsAndInJars(configuration, providerType == ProviderType.INJARS)));
+  }
+
+  private static String stripCommentsAndInJars(String configuration, boolean expectOneInJar) {
+    int expectedInJars = expectOneInJar ? 1 : 0;
+    int foundInJars = 0;
+    List<String> lines = StringUtils.splitLines(configuration);
+    List<String> filtered = new ArrayList<>(lines.size());
+    for (String line : lines) {
+      if (line.trim().startsWith("-injars ")) {
+        if (++foundInJars > expectedInJars) {
+          fail("Unexpected: " + line);
+        }
+      } else if (!line.trim().startsWith("#")) {
+        filtered.add(line);
+      }
+    }
+    assertEquals(expectedInJars, foundInJars);
+    return StringUtils.lines(filtered);
   }
 
   @Test
@@ -222,9 +232,7 @@
     }
     Path library = buildLibrary();
     testForR8(Backend.DEX)
-        .applyIf(
-            providerType == ProviderType.API,
-            b -> b.addProgramFiles(library).addProgramFiles(library))
+        .applyIf(providerType == ProviderType.API, b -> b.addProgramFiles(library))
         .applyIf(providerType == ProviderType.INJARS, b -> b.addKeepRules("-injars " + library))
         .setMinApi(AndroidApiLevel.B)
         .allowUnusedProguardConfigurationRules()
@@ -241,7 +249,7 @@
             configuration ->
                 assertEquals(
                     StringUtils.lines(EXPECTED_A.trim(), EXPECTED_B.trim(), EXPECTED_E.trim()),
-                    configuration.toString()));
+                    stripCommentsAndInJars(configuration, providerType == ProviderType.INJARS)));
   }
 
   @Test
@@ -257,7 +265,10 @@
         .allowUnusedProguardConfigurationRules()
         .compile()
         .inspectProguardConfiguration(
-            configuration -> assertEquals(EXPECTED_A, configuration.toString()));
+            configuration ->
+                assertEquals(
+                    EXPECTED_A,
+                    stripCommentsAndInJars(configuration, providerType == ProviderType.INJARS)));
   }
 
   @Test
@@ -271,7 +282,10 @@
         .allowUnusedProguardConfigurationRules()
         .compile()
         .inspectProguardConfiguration(
-            configuration -> assertEquals(EXPECTED_X, configuration.toString()));
+            configuration ->
+                assertEquals(
+                    EXPECTED_X,
+                    stripCommentsAndInJars(configuration, providerType == ProviderType.INJARS)));
   }
 
   @Test
@@ -284,7 +298,10 @@
         .allowUnusedProguardConfigurationRules()
         .compile()
         .inspectProguardConfiguration(
-            configuration -> assertEquals(EXPECTED_X, configuration.toString()));
+            configuration ->
+                assertEquals(
+                    EXPECTED_X,
+                    stripCommentsAndInJars(configuration, providerType == ProviderType.INJARS)));
   }
 
   @Test
@@ -308,7 +325,10 @@
           .setFakeCompilerVersion(SemanticVersion.create(1, 2, 3))
           .compile()
           .inspectProguardConfiguration(
-              configuration -> assertEquals("", configuration.toString()));
+              configuration ->
+                  assertEquals(
+                      "",
+                      stripCommentsAndInJars(configuration, providerType == ProviderType.INJARS)));
     }
   }
 
@@ -332,7 +352,10 @@
           .setMinApi(AndroidApiLevel.B)
           .compile()
           .inspectProguardConfiguration(
-              configuration -> assertEquals("", configuration.toString()));
+              configuration ->
+                  assertEquals(
+                      "",
+                      stripCommentsAndInJars(configuration, providerType == ProviderType.INJARS)));
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalKeepRulePrintingTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalKeepRulePrintingTest.java
index 9245a4d..925f70e 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalKeepRulePrintingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalKeepRulePrintingTest.java
@@ -39,7 +39,7 @@
         .compile()
         .apply(
             r -> {
-              assertThat(r.getProguardConfiguration().toString(), containsString("*** main(...);"));
+              assertThat(r.getProguardConfiguration(), containsString("*** main(...);"));
             });
   }