Merge commit 'fa022880caa3b073b638754aea63a435c0becac4' into dev-release

Change-Id: Id43bf4460065aaae02337fc999b994d306969be3
diff --git a/.gitignore b/.gitignore
index 1c8e875..2f6bfaa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -287,6 +287,8 @@
 third_party/r8-releases/2.0.74.tar.gz
 third_party/r8-releases/3.2.54
 third_party/r8-releases/3.2.54.tar.gz
+third_party/r8-releases/8.0.46
+third_party/r8-releases/8.0.46.tar.gz
 third_party/r8mappings
 third_party/r8mappings.tar.gz
 third_party/remapper
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
index 56562ac..fe9bd92 100644
--- a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
+++ b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
@@ -584,6 +584,10 @@
     "r8-v3-2-54",
     Paths.get("third_party", "r8-releases","3.2.54").toFile(),
     Paths.get("third_party", "r8-releases", "3.2.54.tar.gz.sha1").toFile())
+  val r8v8_0_46 = ThirdPartyDependency(
+    "r8-v8-0-46",
+    Paths.get("third_party", "r8-releases","8.0.46").toFile(),
+    Paths.get("third_party", "r8-releases", "8.0.46.tar.gz.sha1").toFile())
   val retraceBenchmark = ThirdPartyDependency(
     "retrace-benchmark",
     Paths.get("third_party", "retrace_benchmark").toFile(),
@@ -692,7 +696,12 @@
 }
 
 fun getThirdPartyProguards() : List<ThirdPartyDependency> {
-  return listOf("proguard5.2.1", "proguard6.0.1", "proguard-7.0.0", "proguard-7.3.2")
+  return listOf(
+    "proguard5.2.1",
+    "proguard6.0.1",
+    "proguard-7.0.0",
+    "proguard-7.3.2",
+    "proguard-7.4.1")
     .map { ThirdPartyDependency(
       it,
       Paths.get("third_party", "proguard", it).toFile(),
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java
index 91934e0..aac3246 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java
@@ -44,12 +44,14 @@
   /**
    * Specify the kind of this item pattern.
    *
-   * <p>Default kind is CLASS_AND_MEMBERS , meaning the annotated class and/or member is to be kept.
-   * When annotating a class this can be set to ONLY_CLASS to avoid patterns on any members. That
-   * can be useful when the API members are themselves explicitly annotated.
+   * <p>Default kind is {@link KeepItemKind#CLASS_AND_MEMBERS}, meaning the annotated class and/or
+   * member is to be kept. When annotating a class this can be set to {@link
+   * KeepItemKind#ONLY_CLASS} to avoid patterns on any members. That can be useful when the API
+   * members are themselves explicitly annotated.
    *
-   * <p>It is not possible to use ONLY_CLASS if annotating a member. Also, it is never valid to use
-   * kind ONLY_MEMBERS as the API surface must keep the class if any member is to be accessible.
+   * <p>It is not possible to use {@link KeepItemKind#ONLY_CLASS} if annotating a member. Also, it
+   * is never valid to use kind {@link KeepItemKind#ONLY_MEMBERS} as the API surface must keep the
+   * class if any member is to be accessible.
    *
    * @return The kind for this pattern.
    */
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
index 239dd0e..18b2784 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
@@ -172,16 +172,6 @@
     public static final String CLASS_OPEN_HIERARCHY = "CLASS_OPEN_HIERARCHY";
   }
 
-  public static final class Option {
-    public static final String DESCRIPTOR =
-        "Lcom/android/tools/r8/keepanno/annotations/KeepOption;";
-    public static final String SHRINKING = "SHRINKING";
-    public static final String OPTIMIZATION = "OPTIMIZATION";
-    public static final String OBFUSCATION = "OBFUSCATION";
-    public static final String ACCESS_MODIFICATION = "ACCESS_MODIFICATION";
-    public static final String ANNOTATION_REMOVAL = "ANNOTATION_REMOVAL";
-  }
-
   public static final class MemberAccess {
     public static final String DESCRIPTOR =
         "Lcom/android/tools/r8/keepanno/annotations/MemberAccessFlags;";
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
index 815f6ba..c5d43c5 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
@@ -49,16 +49,17 @@
 /** Extract the PG keep rules that over-approximate a keep edge. */
 public class KeepRuleExtractor {
 
-  private final KeepRuleExtractorOptions options;
+  private final KeepRuleExtractorOptions extractorOptions;
   private final Consumer<String> ruleConsumer;
 
   public KeepRuleExtractor(Consumer<String> ruleConsumer) {
     this(ruleConsumer, KeepRuleExtractorOptions.getR8Options());
   }
 
-  public KeepRuleExtractor(Consumer<String> ruleConsumer, KeepRuleExtractorOptions options) {
+  public KeepRuleExtractor(
+      Consumer<String> ruleConsumer, KeepRuleExtractorOptions extractorOptions) {
     this.ruleConsumer = ruleConsumer;
-    this.options = options;
+    this.extractorOptions = extractorOptions;
   }
 
   public void extract(KeepDeclaration declaration) {
@@ -66,7 +67,7 @@
     PgRule.groupByKinds(rules);
     StringBuilder builder = new StringBuilder();
     for (PgRule rule : rules) {
-      rule.printRule(builder, options);
+      rule.printRule(builder, extractorOptions);
       builder.append("\n");
     }
     ruleConsumer.accept(builder.toString());
@@ -80,7 +81,7 @@
   }
 
   private List<PgRule> generateCheckRules(KeepCheck check) {
-    if (!options.hasCheckDiscardSupport()) {
+    if (!extractorOptions.hasCheckDiscardSupport()) {
       return Collections.emptyList();
     }
     KeepItemPattern itemPattern = check.getItemPattern();
@@ -114,7 +115,8 @@
             KeepOptions.keepAll(),
             memberPatterns,
             targetMembers,
-            TargetKeepKind.CHECK_DISCARD));
+            TargetKeepKind.CHECK_DISCARD,
+            extractorOptions));
     // If the check declaration is to ensure full removal we generate a soft-pin rule to disallow
     // moving/inlining the items.
     if (isRemovedPattern) {
@@ -130,7 +132,8 @@
                 allowShrinking,
                 Collections.singletonMap(memberSymbol, KeepMemberPattern.allMembers()),
                 Collections.singletonList(memberSymbol),
-                TargetKeepKind.CLASS_OR_MEMBERS));
+                TargetKeepKind.CLASS_OR_MEMBERS,
+                extractorOptions));
       } else {
         // A check removal on members just soft-pins the members.
         rules.add(
@@ -141,7 +144,8 @@
                 memberPatterns,
                 Collections.emptyList(),
                 targetMembers,
-                TargetKeepKind.JUST_MEMBERS));
+                TargetKeepKind.JUST_MEMBERS,
+                extractorOptions));
       }
     }
     return rules;
@@ -255,7 +259,7 @@
   }
 
   @SuppressWarnings("UnnecessaryParentheses")
-  private static List<PgRule> doSplit(KeepEdge edge) {
+  private List<PgRule> doSplit(KeepEdge edge) {
     List<PgRule> rules = new ArrayList<>();
     // Collection for all attribute constraints required for this edge.
     Set<KeepAttribute> allAttributeConstraints = new HashSet<>();
@@ -287,7 +291,8 @@
 
     // Generate at most one `-keepattributes` rule for the edge if needed.
     if (!allAttributeConstraints.isEmpty()) {
-      rules.add(new PgKeepAttributeRule(edge.getMetaInfo(), allAttributeConstraints));
+      rules.add(
+          new PgKeepAttributeRule(edge.getMetaInfo(), allAttributeConstraints, extractorOptions));
     }
 
     bindingUsers.forEach(
@@ -400,7 +405,7 @@
             callback.accept(newTargetHolder, memberPatterns, targetMembers, finalKeepKind));
   }
 
-  private static void createUnconditionalRules(
+  private void createUnconditionalRules(
       List<PgRule> rules,
       Holder holder,
       KeepEdgeMetaInfo metaInfo,
@@ -423,7 +428,8 @@
                     memberPatterns,
                     Collections.emptyList(),
                     targetMembers,
-                    targetKeepKind));
+                    targetKeepKind,
+                    extractorOptions));
           } else {
             rules.add(
                 new PgUnconditionalRule(
@@ -432,12 +438,13 @@
                     options,
                     memberPatterns,
                     targetMembers,
-                    targetKeepKind));
+                    targetKeepKind,
+                    extractorOptions));
           }
         });
   }
 
-  private static void createConditionalRules(
+  private void createConditionalRules(
       List<PgRule> rules,
       KeepEdgeMetaInfo metaInfo,
       Holder conditionHolder,
@@ -472,10 +479,11 @@
                     memberPatterns,
                     conditionMembers,
                     targetMembers,
-                    targetKeepKind)));
+                    targetKeepKind,
+                    extractorOptions)));
   }
 
-  private static void createDependentRules(
+  private void createDependentRules(
       List<PgRule> rules,
       Holder initialHolder,
       KeepEdgeMetaInfo metaInfo,
@@ -510,7 +518,8 @@
                       copyWithMethod,
                       conditionMembers,
                       Collections.singletonList(targetMember),
-                      targetKeepKind));
+                      targetKeepKind,
+                      extractorOptions));
               HashMap<KeepBindingSymbol, KeepMemberPattern> copyWithField =
                   new HashMap<>(memberPatterns);
               copyWithField.put(targetMember, copyFieldFromMember(memberPattern));
@@ -522,7 +531,8 @@
                       copyWithField,
                       conditionMembers,
                       Collections.singletonList(targetMember),
-                      targetKeepKind));
+                      targetKeepKind,
+                      extractorOptions));
             } else {
               nonAllMemberTargets.add(targetMember);
             }
@@ -538,7 +548,8 @@
                   memberPatterns,
                   conditionMembers,
                   nonAllMemberTargets,
-                  targetKeepKind));
+                  targetKeepKind,
+                  extractorOptions));
         });
   }
 
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractorOptions.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractorOptions.java
index c30b430..f155852 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractorOptions.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractorOptions.java
@@ -6,12 +6,86 @@
 
 import com.android.tools.r8.keepanno.ast.KeepOptions.KeepOption;
 
-public class KeepRuleExtractorOptions {
+public abstract class KeepRuleExtractorOptions {
 
   private static final KeepRuleExtractorOptions PG_OPTIONS =
-      new KeepRuleExtractorOptions(false, false);
+      new KeepRuleExtractorOptions() {
+        @Override
+        public boolean hasCheckDiscardSupport() {
+          return false;
+        }
+
+        @Override
+        public boolean hasAllowAccessModificationOptionSupport() {
+          return false;
+        }
+
+        @Override
+        public boolean hasAllowAnnotationRemovalOptionSupport() {
+          return false;
+        }
+
+        @Override
+        public boolean hasFieldTypeBackReference() {
+          return false;
+        }
+
+        @Override
+        public boolean hasMethodReturnTypeBackReference() {
+          return false;
+        }
+
+        @Override
+        public boolean hasMethodParameterTypeBackReference() {
+          return false;
+        }
+
+        @Override
+        public boolean hasMethodParameterListBackReference() {
+          return false;
+        }
+      };
+
   private static final KeepRuleExtractorOptions R8_OPTIONS =
-      new KeepRuleExtractorOptions(true, true);
+      new KeepRuleExtractorOptions() {
+        @Override
+        public boolean hasCheckDiscardSupport() {
+          return true;
+        }
+
+        @Override
+        public boolean hasAllowAccessModificationOptionSupport() {
+          return true;
+        }
+
+        @Override
+        public boolean hasAllowAnnotationRemovalOptionSupport() {
+          // Allow annotation removal is currently a testing only option.
+          return false;
+        }
+
+        @Override
+        public boolean hasFieldTypeBackReference() {
+          return true;
+        }
+
+        @Override
+        public boolean hasMethodReturnTypeBackReference() {
+          return true;
+        }
+
+        @Override
+        public boolean hasMethodParameterTypeBackReference() {
+          return true;
+        }
+
+        @Override
+        public boolean hasMethodParameterListBackReference() {
+          // TODO(b/265892343): R8 does not support backrefs for `(...)`.
+          //  When resolving this the options need to be split in legacy R8 and current R8.
+          return false;
+        }
+      };
 
   public static KeepRuleExtractorOptions getPgOptions() {
     return PG_OPTIONS;
@@ -21,27 +95,21 @@
     return R8_OPTIONS;
   }
 
-  private final boolean allowCheckDiscard;
-  private final boolean allowAccessModificationOption;
-  private final boolean allowAnnotationRemovalOption = false;
+  private KeepRuleExtractorOptions() {}
 
-  private KeepRuleExtractorOptions(
-      boolean allowCheckDiscard, boolean allowAccessModificationOption) {
-    this.allowCheckDiscard = allowCheckDiscard;
-    this.allowAccessModificationOption = allowAccessModificationOption;
-  }
+  public abstract boolean hasCheckDiscardSupport();
 
-  public boolean hasCheckDiscardSupport() {
-    return allowCheckDiscard;
-  }
+  public abstract boolean hasAllowAccessModificationOptionSupport();
 
-  private boolean hasAllowAccessModificationOptionSupport() {
-    return allowAccessModificationOption;
-  }
+  public abstract boolean hasAllowAnnotationRemovalOptionSupport();
 
-  private boolean hasAllowAnnotationRemovalOptionSupport() {
-    return allowAnnotationRemovalOption;
-  }
+  public abstract boolean hasFieldTypeBackReference();
+
+  public abstract boolean hasMethodReturnTypeBackReference();
+
+  public abstract boolean hasMethodParameterTypeBackReference();
+
+  public abstract boolean hasMethodParameterListBackReference();
 
   public boolean isKeepOptionSupported(KeepOption keepOption) {
     switch (keepOption) {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/PgRule.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/PgRule.java
index 75ea20c..328b6ca 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/PgRule.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/PgRule.java
@@ -94,16 +94,23 @@
 
   private final KeepEdgeMetaInfo metaInfo;
   private final KeepOptions options;
+  private final KeepRuleExtractorOptions extractorOptions;
 
-  private PgRule(KeepEdgeMetaInfo metaInfo, KeepOptions options) {
+  private PgRule(
+      KeepEdgeMetaInfo metaInfo, KeepOptions options, KeepRuleExtractorOptions extractorOptions) {
     this.metaInfo = metaInfo;
     this.options = options;
+    this.extractorOptions = extractorOptions;
   }
 
   public KeepEdgeMetaInfo getMetaInfo() {
     return metaInfo;
   }
 
+  public KeepRuleExtractorOptions getExtractorOptions() {
+    return extractorOptions;
+  }
+
   // Helper to print the class-name pattern in a class-item.
   public static BiConsumer<StringBuilder, KeepQualifiedClassNamePattern> classNamePrinter(
       KeepQualifiedClassNamePattern classNamePattern) {
@@ -203,8 +210,9 @@
         KeepOptions options,
         Map<KeepBindingSymbol, KeepMemberPattern> memberPatterns,
         List<KeepBindingSymbol> targetMembers,
-        TargetKeepKind targetKeepKind) {
-      super(metaInfo, options);
+        TargetKeepKind targetKeepKind,
+        KeepRuleExtractorOptions extractorOptions) {
+      super(metaInfo, options, extractorOptions);
       assert !targetKeepKind.equals(TargetKeepKind.JUST_MEMBERS);
       this.holderNamePattern = holder.getNamePattern();
       this.holderPattern = holder.getClassItemPattern();
@@ -234,7 +242,8 @@
     @Override
     void printTargetMember(StringBuilder builder, KeepBindingSymbol memberReference) {
       KeepMemberPattern memberPattern = memberPatterns.get(memberReference);
-      printMemberClause(memberPattern, RulePrinter.withoutBackReferences(builder));
+      printMemberClause(
+          memberPattern, RulePrinter.withoutBackReferences(builder), getExtractorOptions());
     }
   }
 
@@ -242,8 +251,11 @@
 
     private final Set<KeepAttribute> attributes;
 
-    public PgKeepAttributeRule(KeepEdgeMetaInfo metaInfo, Set<KeepAttribute> attributes) {
-      super(metaInfo, null);
+    public PgKeepAttributeRule(
+        KeepEdgeMetaInfo metaInfo,
+        Set<KeepAttribute> attributes,
+        KeepRuleExtractorOptions extractorOptions) {
+      super(metaInfo, null, extractorOptions);
       assert !attributes.isEmpty();
       this.attributes = attributes;
     }
@@ -308,8 +320,9 @@
         Map<KeepBindingSymbol, KeepMemberPattern> memberPatterns,
         List<KeepBindingSymbol> memberConditions,
         List<KeepBindingSymbol> memberTargets,
-        TargetKeepKind keepKind) {
-      super(metaInfo, options);
+        TargetKeepKind keepKind,
+        KeepRuleExtractorOptions extractorOptions) {
+      super(metaInfo, options, extractorOptions);
       this.classCondition = classCondition.getClassItemPattern();
       this.classTarget = classTarget.getClassItemPattern();
       this.memberPatterns = memberPatterns;
@@ -336,7 +349,8 @@
     @Override
     void printConditionMember(StringBuilder builder, KeepBindingSymbol member) {
       KeepMemberPattern memberPattern = memberPatterns.get(member);
-      printMemberClause(memberPattern, RulePrinter.withoutBackReferences(builder));
+      printMemberClause(
+          memberPattern, RulePrinter.withoutBackReferences(builder), getExtractorOptions());
     }
 
     @Override
@@ -360,7 +374,8 @@
     @Override
     void printTargetMember(StringBuilder builder, KeepBindingSymbol member) {
       KeepMemberPattern memberPattern = memberPatterns.get(member);
-      printMemberClause(memberPattern, RulePrinter.withoutBackReferences(builder));
+      printMemberClause(
+          memberPattern, RulePrinter.withoutBackReferences(builder), getExtractorOptions());
     }
 
     private void printClassName(StringBuilder builder, KeepQualifiedClassNamePattern clazzName) {
@@ -402,8 +417,9 @@
         Map<KeepBindingSymbol, KeepMemberPattern> memberPatterns,
         List<KeepBindingSymbol> memberConditions,
         List<KeepBindingSymbol> memberTargets,
-        TargetKeepKind keepKind) {
-      super(metaInfo, options);
+        TargetKeepKind keepKind,
+        KeepRuleExtractorOptions extractorOptions) {
+      super(metaInfo, options, extractorOptions);
       this.holderNamePattern = holder.getNamePattern();
       this.holderPattern = holder.getClassItemPattern();
       this.memberPatterns = memberPatterns;
@@ -458,7 +474,7 @@
       KeepMemberPattern memberPattern = memberPatterns.get(member);
       BackReferencePrinter printer =
           RulePrinter.withBackReferences(builder, this::getNextBackReferenceNumber);
-      printMemberClause(memberPattern, printer);
+      printMemberClause(memberPattern, printer, getExtractorOptions());
       membersBackReferencePatterns.put(member, printer.getBackReference());
     }
 
@@ -492,7 +508,8 @@
         }
       }
       KeepMemberPattern memberPattern = memberPatterns.get(member);
-      printMemberClause(memberPattern, RulePrinter.withoutBackReferences(builder));
+      printMemberClause(
+          memberPattern, RulePrinter.withoutBackReferences(builder), getExtractorOptions());
     }
   }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrinter.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrinter.java
index 518eeba..78513c6 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrinter.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrinter.java
@@ -22,6 +22,10 @@
     this.builder = builder;
   }
 
+  public RulePrinter allowBackReferencesIf(boolean isBackReferenceSupported) {
+    return this;
+  }
+
   public RulePrinter append(String str) {
     assert !str.contains("*");
     assert !str.contains("(...)");
@@ -102,7 +106,51 @@
 
     @Override
     public RulePrinter appendAnyParameters() {
-      // TODO(b/265892343): R8 does not yet support back reference to `...`.
+      return addBackRef("(...)");
+    }
+
+    @Override
+    public RulePrinter allowBackReferencesIf(boolean isBackReferenceSupported) {
+      return isBackReferenceSupported ? this : new SkipBackreferencePrinter(this);
+    }
+  }
+
+  private static class SkipBackreferencePrinter extends RulePrinter {
+    final BackReferencePrinter printer;
+
+    private SkipBackreferencePrinter(BackReferencePrinter printer) {
+      super(((RulePrinter) printer).builder);
+      this.printer = printer;
+    }
+
+    @Override
+    public RulePrinter appendWithoutBackReferenceAssert(String str) {
+      printer.appendWithoutBackReferenceAssert(str);
+      return this;
+    }
+
+    @Override
+    public RulePrinter appendStar() {
+      return appendWithoutBackReferenceAssert("*");
+    }
+
+    @Override
+    public RulePrinter appendDoubleStar() {
+      return appendWithoutBackReferenceAssert("**");
+    }
+
+    @Override
+    public RulePrinter appendTripleStar() {
+      return appendWithoutBackReferenceAssert("***");
+    }
+
+    @Override
+    public RulePrinter appendPercent() {
+      return appendWithoutBackReferenceAssert("%");
+    }
+
+    @Override
+    public RulePrinter appendAnyParameters() {
       return appendWithoutBackReferenceAssert("(...)");
     }
   }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
index 636dbfc..07c7c9b 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
@@ -115,7 +115,8 @@
     return builder;
   }
 
-  public static RulePrinter printMemberClause(KeepMemberPattern member, RulePrinter printer) {
+  public static RulePrinter printMemberClause(
+      KeepMemberPattern member, RulePrinter printer, KeepRuleExtractorOptions options) {
     if (member.isAllMembers()) {
       // Note: the rule language does not allow backref to a full member. A rule matching all
       // members via a binding must be split in two up front: one for methods and one for fields.
@@ -127,10 +128,10 @@
       printer.append(" ");
     }
     if (member.isMethod()) {
-      return printMethod(member.asMethod(), printer);
+      return printMethod(member.asMethod(), printer, options);
     }
     if (member.isField()) {
-      return printField(member.asField(), printer);
+      return printField(member.asField(), printer, options);
     }
     // The pattern is a restricted member pattern, e.g., it must apply to fields and methods
     // without any specifics not common to both. For now that is annotated-by and access patterns.
@@ -139,27 +140,37 @@
     return printer.appendWithoutBackReferenceAssert("*").append(";");
   }
 
-  private static RulePrinter printField(KeepFieldPattern fieldPattern, RulePrinter printer) {
+  private static RulePrinter printField(
+      KeepFieldPattern fieldPattern, RulePrinter printer, KeepRuleExtractorOptions options) {
     printFieldAccess(printer, fieldPattern.getAccessPattern());
-    printType(printer, fieldPattern.getTypePattern().asType());
+    printType(
+        printer.allowBackReferencesIf(options.hasFieldTypeBackReference()),
+        fieldPattern.getTypePattern().asType());
     printer.append(" ");
     printFieldName(printer, fieldPattern.getNamePattern());
     return printer.append(";");
   }
 
-  private static RulePrinter printMethod(KeepMethodPattern methodPattern, RulePrinter printer) {
+  private static RulePrinter printMethod(
+      KeepMethodPattern methodPattern, RulePrinter printer, KeepRuleExtractorOptions options) {
     printMethodAccess(printer, methodPattern.getAccessPattern());
-    printReturnType(printer, methodPattern.getReturnTypePattern());
+    printReturnType(
+        printer.allowBackReferencesIf(options.hasMethodReturnTypeBackReference()),
+        methodPattern.getReturnTypePattern());
     printer.append(" ");
     printMethodName(printer, methodPattern.getNamePattern());
-    printParameters(printer, methodPattern.getParametersPattern());
+    printParameters(printer, methodPattern.getParametersPattern(), options);
     return printer.append(";");
   }
 
   private static RulePrinter printParameters(
-      RulePrinter builder, KeepMethodParametersPattern parametersPattern) {
+      RulePrinter builder,
+      KeepMethodParametersPattern parametersPattern,
+      KeepRuleExtractorOptions options) {
     if (parametersPattern.isAny()) {
-      return builder.appendAnyParameters();
+      return builder
+          .allowBackReferencesIf(options.hasMethodParameterListBackReference())
+          .appendAnyParameters();
     }
     builder.append("(");
     List<KeepTypePattern> patterns = parametersPattern.asList();
@@ -167,7 +178,9 @@
       if (i > 0) {
         builder.append(", ");
       }
-      printType(builder, patterns.get(i));
+      printType(
+          builder.allowBackReferencesIf(options.hasMethodParameterTypeBackReference()),
+          patterns.get(i));
     }
     return builder.append(")");
   }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 24f0c28..f9694e5 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -728,39 +728,44 @@
       // already have been leveraged.
       OptimizationInfoRemover.run(appView, executorService);
 
-      // Perform repackaging.
-      if (appView.hasLiveness()) {
-        if (options.isRepackagingEnabled()) {
-          new Repackaging(appView.withLiveness()).run(executorService, timing);
+      GenericSignatureContextBuilder genericContextBuilderBeforeFinalMerging = null;
+      if (appView.hasCfByteCodePassThroughMethods()) {
+        LirConverter.finalizeLirToOutputFormat(appView, timing, executorService);
+      } else {
+        // Perform repackaging.
+        if (appView.hasLiveness()) {
+          if (options.isRepackagingEnabled()) {
+            new Repackaging(appView.withLiveness()).run(executorService, timing);
+          }
+          assert Repackaging.verifyIdentityRepackaging(appView.withLiveness(), executorService);
         }
-        assert Repackaging.verifyIdentityRepackaging(appView.withLiveness(), executorService);
+
+        // Rewrite LIR with lens to allow building IR from LIR in class mergers.
+        LirConverter.rewriteLirWithLens(appView, timing, executorService);
+        appView.clearCodeRewritings(executorService, timing);
+
+        if (appView.hasLiveness()) {
+          VerticalClassMerger.createForFinalClassMerging(appView.withLiveness())
+              .runIfNecessary(executorService, timing);
+        }
+
+        // TODO(b/225838009): Move further down.
+        LirConverter.finalizeLirToOutputFormat(appView, timing, executorService);
+        assert appView.dexItemFactory().verifyNoCachedTypeElements();
+
+        genericContextBuilderBeforeFinalMerging = GenericSignatureContextBuilder.create(appView);
+
+        // Run horizontal class merging. This runs even if shrinking is disabled to ensure
+        // synthetics are always merged.
+        HorizontalClassMerger.createForFinalClassMerging(appView)
+            .runIfNecessary(
+                executorService,
+                timing,
+                finalRuntimeTypeCheckInfoBuilder != null
+                    ? finalRuntimeTypeCheckInfoBuilder.build(appView.graphLens())
+                    : null);
       }
 
-      // Rewrite LIR with lens to allow building IR from LIR in class mergers.
-      LirConverter.rewriteLirWithLens(appView, timing, executorService);
-
-      if (appView.hasLiveness()) {
-        VerticalClassMerger.createForFinalClassMerging(appView.withLiveness())
-            .runIfNecessary(executorService, timing);
-      }
-
-      // TODO(b/225838009): Move further down.
-      LirConverter.finalizeLirToOutputFormat(appView, timing, executorService);
-      assert appView.dexItemFactory().verifyNoCachedTypeElements();
-
-      GenericSignatureContextBuilder genericContextBuilderBeforeFinalMerging =
-          GenericSignatureContextBuilder.create(appView);
-
-      // Run horizontal class merging. This runs even if shrinking is disabled to ensure synthetics
-      // are always merged.
-      HorizontalClassMerger.createForFinalClassMerging(appView)
-          .runIfNecessary(
-              executorService,
-              timing,
-              finalRuntimeTypeCheckInfoBuilder != null
-                  ? finalRuntimeTypeCheckInfoBuilder.build(appView.graphLens())
-                  : null);
-
       // Perform minification.
       if (options.getProguardConfiguration().hasApplyMappingFile()) {
         timing.begin("apply-mapping");
@@ -823,8 +828,10 @@
       new KotlinMetadataRewriter(appView).runForR8(executorService);
       timing.end();
 
-      new GenericSignatureRewriter(appView, genericContextBuilderBeforeFinalMerging)
-          .run(appView.appInfo().classes(), executorService);
+      if (genericContextBuilderBeforeFinalMerging != null) {
+        new GenericSignatureRewriter(appView, genericContextBuilderBeforeFinalMerging)
+            .run(appView.appInfo().classes(), executorService);
+      }
 
       assert appView.checkForTesting(
               () ->
@@ -921,7 +928,9 @@
       LegacyResourceShrinker shrinker = resourceShrinkerBuilder.build();
       ShrinkerResult shrinkerResult;
       if (options.resourceShrinkerConfiguration.isOptimizedShrinking()) {
-        shrinkerResult = shrinker.shrinkModel(appView.getResourceAnalysisResult().getModel(), true);
+        shrinkerResult =
+            shrinker.shrinkModel(
+                appView.getResourceShrinkerState().getR8ResourceShrinkerModel(), true);
       } else {
         shrinkerResult = shrinker.run();
       }
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 b407f34..e9c6f6b 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
@@ -6,7 +6,6 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -18,8 +17,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -211,12 +208,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forBinop();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState state, AppView<?> appView, CfAnalysisConfig config) {
     // ..., value1, value2 →
     // ..., result
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 068d034..fd9586f 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
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -16,8 +15,6 @@
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -82,12 +79,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forArrayLength();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., arrayref →
     // ..., length
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 a97e1ea..a0662ad 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
@@ -7,7 +7,6 @@
 import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.lens.GraphLens;
@@ -19,8 +18,6 @@
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -97,12 +94,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forArrayGet();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., arrayref, index →
     // ..., value
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 0cad424..f7cb0c5 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
@@ -6,7 +6,6 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.lens.GraphLens;
@@ -17,8 +16,6 @@
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -97,12 +94,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forArrayPut();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., arrayref, index, value →
     // ...
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 091aa57..9e23bd0 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
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -19,8 +18,6 @@
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -133,12 +130,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forCheckCast(type, context);
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., objectref →
     // ..., objectref
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 6ca820e..a18c05a 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
@@ -6,7 +6,6 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -20,8 +19,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -131,12 +128,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forBinop();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., value1, value2 →
     // ..., result
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConditionalJumpInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfConditionalJumpInstruction.java
index def3dd4..9d4f58d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConditionalJumpInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConditionalJumpInstruction.java
@@ -4,12 +4,8 @@
 
 package com.android.tools.r8.cf.code;
 
-import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IfType;
 import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 
 public abstract class CfConditionalJumpInstruction extends CfJumpInstruction {
 
@@ -29,12 +25,6 @@
   }
 
   @Override
-  public final ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forJumpInstruction();
-  }
-
-  @Override
   public final boolean isConditionalJump() {
     return true;
   }
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 151be2a..0d0838e 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
@@ -6,7 +6,6 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -19,8 +18,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -150,12 +147,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forConstClass(type, context);
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ... →
     // ..., value
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 d47a830..c16eae4 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
@@ -8,9 +8,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unimplemented;
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -28,8 +26,6 @@
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicReference;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -217,12 +213,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    throw new Unreachable();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ... →
     // ..., value
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 9c59d83..25cb808 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
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -19,8 +18,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -104,12 +101,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forConstMethodHandle();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ... →
     // ..., value
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 b9f5d88..2f0fb30 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
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -18,8 +17,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -102,12 +99,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forConstMethodType();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ... →
     // ..., value
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 5e6485a..09d9f3c 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
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -16,8 +15,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -74,12 +71,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forConstInstruction();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ... →
     // ..., value
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 991ed9d..9246c77 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
@@ -6,7 +6,6 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -17,8 +16,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -231,12 +228,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forConstInstruction();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ... →
     // ..., value
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 d60203a..064ee4c 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
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
@@ -16,8 +15,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -105,12 +102,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forConstInstruction();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ... →
     // ..., value
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 b350e1f..6280303 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
@@ -6,7 +6,6 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -19,8 +18,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
@@ -124,12 +121,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forDexItemBasedConstString(item, context);
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ... →
     // ..., value
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 c8308b4..62a8f65 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
@@ -10,7 +10,6 @@
 import com.android.tools.r8.cf.code.frame.PreciseFrameType;
 import com.android.tools.r8.cf.code.frame.UninitializedFrameType;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -23,8 +22,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -309,12 +306,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return ConstraintWithTarget.ALWAYS;
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     return frame.check(config, this);
   }
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 29dd22a..cabbeeb 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
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -15,8 +14,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -109,12 +106,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forJumpInstruction();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     return frame;
   }
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 7a4ba0a..533657e 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
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -17,8 +16,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -99,12 +96,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return ConstraintWithTarget.ALWAYS;
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     return frame.readLocal(
         appView, config, getLocalIndex(), ValueType.INT, FunctionUtils::getFirst);
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 21608ae..4fa09ff 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
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
@@ -19,8 +18,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -117,12 +114,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forInitClass(clazz, context);
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., →
     // ..., value
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java
index 09ec0f5..1a7c2af 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java
@@ -6,17 +6,13 @@
 
 import com.android.tools.r8.dex.code.CfOrDexInstanceFieldRead;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import java.util.ListIterator;
@@ -70,12 +66,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forInstanceGet(getField(), context);
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., objectref →
     // ..., value
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldWrite.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldWrite.java
index 594f126..1f8e193 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldWrite.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldWrite.java
@@ -8,17 +8,13 @@
 
 import com.android.tools.r8.cf.code.frame.PreciseFrameType;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.optimize.interfaces.analysis.ErroneousCfFrameState;
@@ -74,12 +70,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forInstancePut(getField(), context);
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., objectref, value →
     // ...
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 ec34784..da879bd 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
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -18,8 +17,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -127,12 +124,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forInstanceOf(type, context);
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., objectref →
     // ..., result
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 ff95325..985fbc9 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
@@ -8,7 +8,6 @@
 import com.android.tools.r8.dex.code.DexInstruction;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.ClasspathMethod;
 import com.android.tools.r8.graph.DexClassAndMethod;
@@ -21,8 +20,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -420,9 +417,6 @@
     return true;
   }
 
-  public abstract ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context);
-
   public abstract CfFrameState evaluate(
       CfFrameState frame, AppView<?> appView, CfAnalysisConfig config);
 
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 bcbe536..fc2e576 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
@@ -7,11 +7,8 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
@@ -28,8 +25,6 @@
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -285,68 +280,6 @@
   }
 
   @Override
-  @SuppressWarnings("ReferenceEquality")
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    GraphLens graphLens = inliningConstraints.getGraphLens();
-    AppView<?> appView = inliningConstraints.getAppView();
-    DexMethod target = method;
-    // Find the DEX invocation type.
-    InvokeType type;
-    switch (opcode) {
-      case Opcodes.INVOKEINTERFACE:
-        // Could have changed to an invoke-virtual instruction due to vertical class merging
-        // (if an interface is merged into a class).
-        type =
-            graphLens.lookupMethod(target, context.getReference(), InvokeType.INTERFACE).getType();
-        assert type == InvokeType.INTERFACE || type == InvokeType.VIRTUAL;
-        break;
-
-      case Opcodes.INVOKESPECIAL:
-        {
-          InvokeType actualInvokeType =
-              computeInvokeTypeForInvokeSpecial(appView, method, context, code.getOriginalHolder());
-          type = graphLens.lookupMethod(target, context.getReference(), actualInvokeType).getType();
-        }
-        break;
-
-      case Opcodes.INVOKESTATIC:
-        {
-          // Static invokes may have changed as a result of horizontal class merging.
-          MethodLookupResult lookup =
-              graphLens.lookupMethod(target, context.getReference(), InvokeType.STATIC);
-          target = lookup.getReference();
-          type = lookup.getType();
-        }
-        break;
-
-      case Opcodes.INVOKEVIRTUAL:
-        {
-          type = InvokeType.VIRTUAL;
-          // Instructions that target a private method in the same class translates to
-          // invoke-direct.
-          if (target.holder == context.getHolderType()) {
-            DexClass clazz = appView.definitionFor(target.holder);
-            if (clazz != null && clazz.lookupDirectMethod(target) != null) {
-              type = InvokeType.DIRECT;
-            }
-          }
-
-          // Virtual invokes may have changed to interface invokes as a result of member rebinding.
-          MethodLookupResult lookup = graphLens.lookupMethod(target, context.getReference(), type);
-          target = lookup.getReference();
-          type = lookup.getType();
-        }
-        break;
-
-      default:
-        throw new Unreachable("Unexpected opcode " + opcode);
-    }
-
-    return inliningConstraints.forInvoke(target, type, context);
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., objectref, [arg1, [arg2 ...]] →
     // ... [ returnType ]
@@ -370,41 +303,4 @@
     }
     return frame.push(config, method.getReturnType());
   }
-
-  @SuppressWarnings("ReferenceEquality")
-  private InvokeType computeInvokeTypeForInvokeSpecial(
-      AppView<?> appView, DexMethod method, ProgramMethod context, DexType originalHolder) {
-    if (appView.dexItemFactory().isConstructor(method)) {
-      return InvokeType.DIRECT;
-    }
-    if (originalHolder != method.getHolderType()) {
-      return InvokeType.SUPER;
-    }
-    return invokeTypeForInvokeSpecialToNonInitMethodOnHolder(context, appView.graphLens());
-  }
-
-  private InvokeType invokeTypeForInvokeSpecialToNonInitMethodOnHolder(
-      ProgramMethod context, GraphLens graphLens) {
-    MethodLookupResult lookupResult =
-        graphLens.lookupMethod(method, context.getReference(), InvokeType.DIRECT);
-    DexEncodedMethod definition = context.getHolder().lookupMethod(lookupResult.getReference());
-    if (definition == null) {
-      return InvokeType.SUPER;
-    }
-
-    if (context.getHolder().isInterface()) {
-      // On interfaces invoke-special should be mapped to invoke-super if the invoke-special
-      // instruction is used to target a default interface method.
-      if (definition.belongsToVirtualPool()) {
-        return InvokeType.SUPER;
-      }
-    } else {
-      // Due to desugaring of invoke-special instructions that target virtual methods, this invoke
-      // should only target a virtual method if the method has been publicized in R8 (in which case
-      // the invoke instruction has a pending rewrite to invoke-virtual).
-      assert definition.isPrivate() || lookupResult.getType().isVirtual();
-    }
-
-    return InvokeType.DIRECT;
-  }
 }
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 79ba7f9..a36f61b 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
@@ -6,7 +6,6 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClassAndMethod;
@@ -24,8 +23,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -168,12 +165,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forInvokeCustom();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., [arg1, [arg2 ...]] →
     // ...
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 7ab3052..d0efa3c 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
@@ -6,7 +6,6 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -16,8 +15,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -84,12 +81,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    throw error();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     return CfFrameState.error("Unexpected JSR/RET instruction");
   }
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 29701e6..008b905 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
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -15,8 +14,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -97,12 +94,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return ConstraintWithTarget.ALWAYS;
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     return frame;
   }
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 a0b7fdc..0af1153 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
@@ -7,7 +7,6 @@
 import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -19,8 +18,6 @@
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -129,12 +126,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forLoad();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ... →
     // ..., objectref
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 14f30b1..97c4dce 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
@@ -6,7 +6,6 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -18,8 +17,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -182,12 +179,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forBinop();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., value1, value2 →
     // ..., result
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 d7ef8ee..6821e61 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
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -17,8 +16,6 @@
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -95,12 +92,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forMonitor();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., objectref →
     // ...
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 85f09af..449bdcb 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
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -18,8 +17,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -130,12 +127,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forInvokeMultiNewArray(type, context);
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., count1, [count2, ...] →
     // ..., arrayref
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 61216c1..9b047f8 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
@@ -6,7 +6,6 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -18,8 +17,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -120,12 +117,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forUnop();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., value →
     // ..., result
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 9e0af9c..f574223 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
@@ -6,7 +6,6 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -19,8 +18,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -143,12 +140,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forNewInstance(type, context);
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ... →
     // ..., objectref
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 943252d..f131164 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
@@ -6,7 +6,6 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -20,8 +19,6 @@
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -170,12 +167,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forNewArrayEmpty(type, context);
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., count →
     // ..., arrayref
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 11efcd1..11cab35 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
@@ -6,7 +6,6 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -19,8 +18,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -118,12 +115,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forNewUnboxedEnumInstance(type, context);
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ... →
     // ..., objectref
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 fd5d76c..7706a69 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
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -15,8 +14,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -78,12 +75,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return ConstraintWithTarget.ALWAYS;
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     return frame;
   }
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 32a1d4f..cadead1 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
@@ -6,7 +6,6 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -18,8 +17,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -191,12 +188,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forUnop();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., value →
     // ..., result
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 9e4bae3..b52cb87 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
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -16,8 +15,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -112,12 +109,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return ConstraintWithTarget.ALWAYS;
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     return frame;
   }
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 c34d876..ce358df 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
@@ -7,7 +7,6 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -19,8 +18,6 @@
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -111,12 +108,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forRecordFieldValues();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     for (DexField ignored : fields) {
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 725acb1..a99482d 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
@@ -6,7 +6,6 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -18,8 +17,6 @@
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -124,12 +121,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forReturn();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     assert !config.getCurrentContext().getReturnType().isVoidType();
     return frame
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 0197fc2..5b16578 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
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -15,8 +14,6 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -93,12 +90,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forReturn();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     return frame.clear();
   }
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 7679a02..c5c8f6e 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
@@ -7,7 +7,6 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -19,8 +18,6 @@
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -355,12 +352,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return ConstraintWithTarget.ALWAYS;
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     switch (opcode) {
       case Pop:
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
index 73d6349..f7ae0ff 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
@@ -6,16 +6,12 @@
 
 import com.android.tools.r8.dex.code.CfOrDexStaticFieldRead;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import java.util.ListIterator;
@@ -63,12 +59,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forStaticGet(getField(), context);
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., →
     // ..., value
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java
index c904d10..01d9bed 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java
@@ -5,17 +5,13 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import java.util.ListIterator;
@@ -69,12 +65,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forStaticPut(getField(), context);
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., value →
     // ...
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 f59bfb6..9e5449a 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
@@ -6,7 +6,6 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -18,8 +17,6 @@
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -126,12 +123,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forStore();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., ref →
     // ...
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 f190ae4..31e7ff3 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
@@ -6,7 +6,6 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -17,8 +16,6 @@
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -176,12 +173,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forJumpInstruction();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., index/key →
     // ...
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 f59f162..4b81a79 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
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -16,8 +15,6 @@
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
@@ -100,12 +97,6 @@
   }
 
   @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    return inliningConstraints.forJumpInstruction();
-  }
-
-  @Override
   public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., objectref →
     // objectref
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMergerSharedData.java b/src/main/java/com/android/tools/r8/classmerging/ClassMergerSharedData.java
similarity index 87%
rename from src/main/java/com/android/tools/r8/verticalclassmerging/ClassMergerSharedData.java
rename to src/main/java/com/android/tools/r8/classmerging/ClassMergerSharedData.java
index 7bc0e12..1948ef7 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMergerSharedData.java
+++ b/src/main/java/com/android/tools/r8/classmerging/ClassMergerSharedData.java
@@ -1,12 +1,11 @@
 // Copyright (c) 2024, 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.verticalclassmerging;
+package com.android.tools.r8.classmerging;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.base.Suppliers;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -17,7 +16,7 @@
   // The collection of types that should be used to generate fresh constructor signatures.
   private final List<Supplier<DexType>> extraUnusedArgumentTypes;
 
-  public ClassMergerSharedData(AppView<AppInfoWithLiveness> appView) {
+  public ClassMergerSharedData(AppView<?> appView) {
     DexItemFactory factory = appView.dexItemFactory();
     extraUnusedArgumentTypes =
         ImmutableList.<Supplier<DexType>>builder()
diff --git a/src/main/java/com/android/tools/r8/classmerging/ClassMergerTreeFixer.java b/src/main/java/com/android/tools/r8/classmerging/ClassMergerTreeFixer.java
index 198b611..fce660c 100644
--- a/src/main/java/com/android/tools/r8/classmerging/ClassMergerTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/classmerging/ClassMergerTreeFixer.java
@@ -21,11 +21,9 @@
 import com.android.tools.r8.graph.classmerging.MergedClasses;
 import com.android.tools.r8.graph.fixup.TreeFixerBase;
 import com.android.tools.r8.horizontalclassmerging.SubtypingForrestForClasses;
-import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
 import com.android.tools.r8.shaking.AnnotationFixer;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ArrayUtils;
-import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
@@ -50,8 +48,7 @@
 
   protected final LB lensBuilder;
   protected final MC mergedClasses;
-  private final ProfileCollectionAdditions profileCollectionAdditions;
-  private final SyntheticArgumentClass syntheticArgumentClass;
+  private final ClassMergerSharedData classMergerSharedData;
 
   private final Map<DexProgramClass, DexType> originalSuperTypes = new IdentityHashMap<>();
 
@@ -61,15 +58,13 @@
 
   public ClassMergerTreeFixer(
       AppView<?> appView,
+      ClassMergerSharedData classMergerSharedData,
       LB lensBuilder,
-      MC mergedClasses,
-      ProfileCollectionAdditions profileCollectionAdditions,
-      SyntheticArgumentClass syntheticArgumentClass) {
+      MC mergedClasses) {
     super(appView);
+    this.classMergerSharedData = classMergerSharedData;
     this.lensBuilder = lensBuilder;
     this.mergedClasses = mergedClasses;
-    this.profileCollectionAdditions = profileCollectionAdditions;
-    this.syntheticArgumentClass = syntheticArgumentClass;
   }
 
   public GL run(ExecutorService executorService, Timing timing) throws ExecutionException {
@@ -289,40 +284,18 @@
         if (keptSignatures.contains(newMethodReference)
             || newMethodSignatures.containsValue(newMethodReference.getSignature())) {
           // If the method collides with a direct method on the same class then rename it to a
-          // globally
-          // fresh name and record the signature.
+          // globally fresh name and record the signature.
           if (method.isInstanceInitializer()) {
-            // If the method is an instance initializer, then add extra nulls.
-            Box<Set<DexType>> usedSyntheticArgumentClasses = new Box<>();
+            // If the method is an instance initializer, then add extra unused arguments.
             newMethodReference =
                 dexItemFactory.createInstanceInitializerWithFreshProto(
                     newMethodReference,
-                    syntheticArgumentClass.getArgumentClasses(),
-                    tryMethod -> !newMethodSignatures.containsValue(tryMethod.getSignature()),
-                    usedSyntheticArgumentClasses::set);
+                    classMergerSharedData.getExtraUnusedArgumentTypes(),
+                    tryMethod -> !newMethodSignatures.containsValue(tryMethod.getSignature()));
             lensBuilder.addExtraParameters(
                 originalMethodReference,
                 newMethodReference,
                 computeExtraUnusedParameters(originalMethodReference, newMethodReference));
-
-            // Amend the art profile collection.
-            if (usedSyntheticArgumentClasses.isSet()) {
-              Set<DexMethod> previousMethodReferences =
-                  lensBuilder.getOriginalMethodReferences(originalMethodReference);
-              if (previousMethodReferences.isEmpty()) {
-                profileCollectionAdditions.applyIfContextIsInProfile(
-                    originalMethodReference,
-                    additionsBuilder ->
-                        usedSyntheticArgumentClasses.get().forEach(additionsBuilder::addRule));
-              } else {
-                for (DexMethod previousMethodReference : previousMethodReferences) {
-                  profileCollectionAdditions.applyIfContextIsInProfile(
-                      previousMethodReference,
-                      additionsBuilder ->
-                          usedSyntheticArgumentClasses.get().forEach(additionsBuilder::addRule));
-                }
-              }
-            }
           } else {
             newMethodReference =
                 dexItemFactory.createFreshMethodNameWithoutHolder(
diff --git a/src/main/java/com/android/tools/r8/classmerging/Policy.java b/src/main/java/com/android/tools/r8/classmerging/Policy.java
index 7655762..6170933 100644
--- a/src/main/java/com/android/tools/r8/classmerging/Policy.java
+++ b/src/main/java/com/android/tools/r8/classmerging/Policy.java
@@ -89,10 +89,12 @@
     }
     assert previousNumberOfRemovedClasses >= newNumberOfRemovedClasses;
     int change = previousNumberOfRemovedClasses - newNumberOfRemovedClasses;
-    if (isInterfaceGroup) {
-      numberOfRemovedInterfaces += change;
-    } else {
-      numberOfRemovedClasses += change;
+    synchronized (this) {
+      if (isInterfaceGroup) {
+        numberOfRemovedInterfaces += change;
+      } else {
+        numberOfRemovedClasses += change;
+      }
     }
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/classmerging/SyntheticArgumentClass.java b/src/main/java/com/android/tools/r8/classmerging/SyntheticArgumentClass.java
deleted file mode 100644
index 54cdd43..0000000
--- a/src/main/java/com/android/tools/r8/classmerging/SyntheticArgumentClass.java
+++ /dev/null
@@ -1,98 +0,0 @@
-// 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.classmerging;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.horizontalclassmerging.HorizontalMergeGroup;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.synthesis.SyntheticItems.SyntheticKindSelector;
-import com.google.common.base.Suppliers;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.function.Supplier;
-
-/**
- * Lets assume we are merging a class A that looks like:
- *
- * <pre>
- *   class A {
- *     A() { ... }
- *     A(int) { ... }
- *   }
- * </pre>
- *
- * If {@code A::<init>()} is merged with other constructors, then we need to prevent a conflict with
- * {@code A::<init>(int)}. To do this we introduce a synthetic class so that the new signature for
- * the merged constructor is {@code A::<init>(SyntheticClass, int)}, as this is guaranteed to be
- * unique.
- *
- * <p>This class generates a synthetic class in the package of the first class to be merged.
- */
-public class SyntheticArgumentClass {
-
-  private final List<Supplier<DexType>> syntheticClassTypes;
-
-  private SyntheticArgumentClass(List<Supplier<DexType>> syntheticClassTypes) {
-    this.syntheticClassTypes = syntheticClassTypes;
-  }
-
-  public List<Supplier<DexType>> getArgumentClasses() {
-    return syntheticClassTypes;
-  }
-
-  public static class Builder {
-
-    private final AppView<AppInfoWithLiveness> appView;
-
-    public Builder(AppView<AppInfoWithLiveness> appView) {
-      this.appView = appView;
-    }
-
-    private DexProgramClass synthesizeClass(
-        DexProgramClass context, SyntheticKindSelector syntheticKindSelector) {
-      return appView
-          .getSyntheticItems()
-          .createFixedClass(syntheticKindSelector, context, appView, builder -> {});
-    }
-
-    public SyntheticArgumentClass build(Collection<HorizontalMergeGroup> mergeGroups) {
-      return build(getDeterministicContext(mergeGroups));
-    }
-
-    public SyntheticArgumentClass build(DexProgramClass deterministicContext) {
-      List<Supplier<DexType>> syntheticArgumentTypes = new ArrayList<>();
-      syntheticArgumentTypes.add(
-          Suppliers.memoize(
-              () ->
-                  synthesizeClass(
-                          deterministicContext, kinds -> kinds.HORIZONTAL_INIT_TYPE_ARGUMENT_1)
-                      .getType()));
-      syntheticArgumentTypes.add(
-          Suppliers.memoize(
-              () ->
-                  synthesizeClass(
-                          deterministicContext, kinds -> kinds.HORIZONTAL_INIT_TYPE_ARGUMENT_2)
-                      .getType()));
-      syntheticArgumentTypes.add(
-          Suppliers.memoize(
-              () ->
-                  synthesizeClass(
-                          deterministicContext, kinds -> kinds.HORIZONTAL_INIT_TYPE_ARGUMENT_3)
-                      .getType()));
-      return new SyntheticArgumentClass(syntheticArgumentTypes);
-    }
-
-    private static DexProgramClass getDeterministicContext(
-        Collection<HorizontalMergeGroup> mergeGroups) {
-      // Relies on the determinism of the merge groups.
-      HorizontalMergeGroup mergeGroup = mergeGroups.iterator().next();
-      assert mergeGroup.hasTarget();
-      return mergeGroup.getTarget();
-    }
-  }
-}
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 c09b0fd..e3c04a8 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.graph;
 
+import com.android.build.shrinker.r8integration.R8ResourceShrinkerState;
 import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
 import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.classmerging.ClassMergerMode;
@@ -13,7 +14,6 @@
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis.InitializedClassesInInstanceMethods;
-import com.android.tools.r8.graph.analysis.ResourceAccessAnalysis.ResourceAnalysisResult;
 import com.android.tools.r8.graph.classmerging.MergedClassesCollection;
 import com.android.tools.r8.graph.lens.AppliedGraphLens;
 import com.android.tools.r8.graph.lens.ClearCodeRewritingGraphLens;
@@ -154,7 +154,7 @@
 
   private SeedMapper applyMappingSeedMapper;
 
-  private ResourceAnalysisResult resourceAnalysisResult = null;
+  private R8ResourceShrinkerState resourceShrinkerState = null;
 
   // When input has been (partially) desugared these are the classes which has been library
   // desugared. This information is populated in the IR converter.
@@ -438,12 +438,15 @@
     allCodeProcessed = true;
   }
 
-  public void clearCodeRewritings(ExecutorService executorService) throws ExecutionException {
+  public void clearCodeRewritings(ExecutorService executorService, Timing timing)
+      throws ExecutionException {
+    timing.begin("Clear code rewritings");
     setGraphLens(new ClearCodeRewritingGraphLens(withClassHierarchy()));
 
     MemberRebindingIdentityLens memberRebindingIdentityLens =
         MemberRebindingIdentityLensFactory.rebuild(withClassHierarchy(), executorService);
     setGraphLens(memberRebindingIdentityLens);
+    timing.end();
   }
 
   public void flattenGraphLenses() {
@@ -906,12 +909,12 @@
     testing().unboxedEnumsConsumer.accept(dexItemFactory(), unboxedEnums);
   }
 
-  public void setResourceAnalysisResult(ResourceAnalysisResult resourceAnalysisResult) {
-    this.resourceAnalysisResult = resourceAnalysisResult;
+  public R8ResourceShrinkerState getResourceShrinkerState() {
+    return resourceShrinkerState;
   }
 
-  public ResourceAnalysisResult getResourceAnalysisResult() {
-    return resourceAnalysisResult;
+  public void setResourceShrinkerState(R8ResourceShrinkerState resourceShrinkerState) {
+    this.resourceShrinkerState = resourceShrinkerState;
   }
 
   public boolean validateUnboxedEnumsHaveBeenPruned() {
@@ -1006,9 +1009,6 @@
       setProguardCompatibilityActions(
           getProguardCompatibilityActions().withoutPrunedItems(prunedItems, timing));
     }
-    if (resourceAnalysisResult != null) {
-      resourceAnalysisResult.withoutPrunedItems(prunedItems, timing);
-    }
     if (hasRootSet()) {
       rootSet.pruneItems(prunedItems, timing);
     }
@@ -1226,18 +1226,6 @@
               new ThreadTask() {
                 @Override
                 public void run(Timing threadTiming) {
-                  appView.resourceAnalysisResult.rewrittenWithLens(
-                      lens, appliedLensInModifiedLens, threadTiming);
-                }
-
-                @Override
-                public boolean shouldRun() {
-                  return appView.resourceAnalysisResult != null;
-                }
-              },
-              new ThreadTask() {
-                @Override
-                public void run(Timing threadTiming) {
                   appView.setRootSet(appView.rootSet().rewrittenWithLens(lens, threadTiming));
                 }
 
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 1deb035..33b02b2 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -38,11 +38,7 @@
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
@@ -587,11 +583,10 @@
   public IRCode buildIR(
       ProgramMethod method,
       AppView<?> appView,
-      Origin origin,
       MutableMethodConversionOptions conversionOptions) {
     verifyFramesOrRemove(method, appView, getCodeLens(appView));
     return internalBuildPossiblyWithLocals(
-        method, method, appView, appView.codeLens(), null, null, origin, null, conversionOptions);
+        method, method, appView, appView.codeLens(), null, null, null, conversionOptions);
   }
 
   @Override
@@ -602,7 +597,6 @@
       GraphLens codeLens,
       NumberGenerator valueNumberGenerator,
       Position callerPosition,
-      Origin origin,
       RewrittenPrototypeDescription protoChanges) {
     assert valueNumberGenerator != null;
     assert callerPosition != null;
@@ -615,7 +609,6 @@
         codeLens,
         valueNumberGenerator,
         callerPosition,
-        origin,
         protoChanges,
         MethodConversionOptions.nonConverting());
   }
@@ -637,7 +630,6 @@
       GraphLens codeLens,
       NumberGenerator valueNumberGenerator,
       Position callerPosition,
-      Origin origin,
       RewrittenPrototypeDescription protoChanges,
       MutableMethodConversionOptions conversionOptions) {
     if (!method.keepLocals(appView)) {
@@ -649,7 +641,6 @@
           codeLens,
           valueNumberGenerator,
           callerPosition,
-          origin,
           protoChanges,
           conversionOptions);
     } else {
@@ -660,7 +651,6 @@
           codeLens,
           valueNumberGenerator,
           callerPosition,
-          origin,
           protoChanges,
           conversionOptions);
     }
@@ -674,7 +664,6 @@
       GraphLens codeLens,
       NumberGenerator valueNumberGenerator,
       Position callerPosition,
-      Origin origin,
       RewrittenPrototypeDescription protoChanges,
       MutableMethodConversionOptions conversionOptions) {
     try {
@@ -686,11 +675,10 @@
           codeLens,
           valueNumberGenerator,
           callerPosition,
-          origin,
           protoChanges,
           conversionOptions);
     } catch (InvalidDebugInfoException e) {
-      appView.options().warningInvalidDebugInfo(method, origin, e);
+      appView.options().warningInvalidDebugInfo(method, e);
       return internalBuild(
           Collections.emptyList(),
           context,
@@ -699,7 +687,6 @@
           codeLens,
           valueNumberGenerator,
           callerPosition,
-          origin,
           protoChanges,
           conversionOptions);
     }
@@ -714,7 +701,6 @@
       GraphLens codeLens,
       NumberGenerator valueNumberGenerator,
       Position callerPosition,
-      Origin origin,
       RewrittenPrototypeDescription protoChanges,
       MutableMethodConversionOptions conversionOptions) {
     CfSourceCode source =
@@ -723,16 +709,15 @@
             localVariables,
             method,
             callerPosition,
-            origin,
             appView);
     IRBuilder builder;
     if (valueNumberGenerator == null) {
       assert protoChanges == null;
-      builder = IRBuilder.create(method, appView, source, origin);
+      builder = IRBuilder.create(method, appView, source);
     } else {
       builder =
           IRBuilder.createForInlining(
-              method, appView, codeLens, source, origin, valueNumberGenerator, protoChanges);
+              method, appView, codeLens, source, valueNumberGenerator, protoChanges);
     }
     return builder.build(context, conversionOptions);
   }
@@ -861,25 +846,6 @@
     return new CfPrinter(this, method, retracer).toString();
   }
 
-  @SuppressWarnings("ReferenceEquality")
-  public ConstraintWithTarget computeInliningConstraint(
-      AppView<AppInfoWithLiveness> appView,
-      GraphLens graphLens,
-      ProgramMethod context) {
-    InliningConstraints inliningConstraints = new InliningConstraints(appView, graphLens);
-    ConstraintWithTarget constraint = ConstraintWithTarget.ALWAYS;
-    assert inliningConstraints.forMonitor().isAlways();
-    for (CfInstruction insn : instructions) {
-      constraint =
-          ConstraintWithTarget.meet(
-              constraint, insn.inliningConstraint(inliningConstraints, this, context), appView);
-      if (constraint.isNever()) {
-        return constraint;
-      }
-    }
-    return constraint;
-  }
-
   void addFakeThisParameter(DexItemFactory factory) {
     if (localVariables == null || localVariables.isEmpty()) {
       // We have no debugging info in the code.
diff --git a/src/main/java/com/android/tools/r8/graph/CfCodeDiagnostics.java b/src/main/java/com/android/tools/r8/graph/CfCodeDiagnostics.java
index 8a664ec..1f0f01c 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCodeDiagnostics.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCodeDiagnostics.java
@@ -30,9 +30,9 @@
   private final MethodPosition methodPosition;
   private final String diagnosticMessage;
 
-  public CfCodeDiagnostics(Origin origin, DexMethod method, String diagnosticMessage) {
-    this.origin = origin;
-    this.methodPosition = new MethodPosition(method.asMethodReference());
+  public CfCodeDiagnostics(ProgramMethod method, String diagnosticMessage) {
+    this.origin = method.getOrigin();
+    this.methodPosition = new MethodPosition(method.getMethodReference());
     this.diagnosticMessage = diagnosticMessage;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/CfCodeStackMapValidatingException.java b/src/main/java/com/android/tools/r8/graph/CfCodeStackMapValidatingException.java
index 0670aeb..f37bced 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCodeStackMapValidatingException.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCodeStackMapValidatingException.java
@@ -16,7 +16,7 @@
     if (appView.enableWholeProgramOptimizations()) {
       sb.append(" In later version of R8, the method may be assumed not reachable.");
     }
-    return new CfCodeDiagnostics(method.getOrigin(), method.getReference(), sb.toString());
+    return new CfCodeDiagnostics(method, sb.toString());
   }
 
   public static CfCodeDiagnostics multipleFramesForLabel(ProgramMethod method, AppView<?> appView) {
@@ -24,7 +24,7 @@
     if (appView.enableWholeProgramOptimizations()) {
       sb.append(" In later version of R8, the method may be assumed not reachable.");
     }
-    return new CfCodeDiagnostics(method.getOrigin(), method.getReference(), sb.toString());
+    return new CfCodeDiagnostics(method, sb.toString());
   }
 
   public static CfCodeDiagnostics noFramesForMethodWithJumps(
@@ -34,7 +34,7 @@
     if (appView.enableWholeProgramOptimizations()) {
       sb.append(" In later version of R8, the method may be assumed not reachable.");
     }
-    return new CfCodeDiagnostics(method.getOrigin(), method.getReference(), sb.toString());
+    return new CfCodeDiagnostics(method, sb.toString());
   }
 
   public static CfCodeDiagnostics invalidTryCatchRange(
@@ -48,7 +48,7 @@
     if (appView.enableWholeProgramOptimizations()) {
       sb.append(" In later version of R8, the method may be assumed not reachable.");
     }
-    return new CfCodeDiagnostics(method.getOrigin(), method.getReference(), sb.toString());
+    return new CfCodeDiagnostics(method, sb.toString());
   }
 
   public static CfCodeDiagnostics invalidStackMapForInstruction(
@@ -68,6 +68,6 @@
     if (appView.enableWholeProgramOptimizations()) {
       sb.append(" In later version of R8, the method may be assumed not reachable.");
     }
-    return new CfCodeDiagnostics(method.getOrigin(), method.getReference(), sb.toString());
+    return new CfCodeDiagnostics(method, sb.toString());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index dfc2117..ee87e8a 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -17,21 +17,19 @@
 import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.lightir.LirCode;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.RetracerForCodePrinting;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import java.util.function.Consumer;
 
 public abstract class Code extends CachedHashValueDexItem {
 
-  public final IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
-    return buildIR(method, appView, origin, MethodConversionOptions.forLirPhase(appView));
+  public final IRCode buildIR(ProgramMethod method, AppView<?> appView) {
+    return buildIR(method, appView, MethodConversionOptions.forLirPhase(appView));
   }
 
   public abstract IRCode buildIR(
       ProgramMethod method,
       AppView<?> appView,
-      Origin origin,
       MutableMethodConversionOptions conversionOptions);
 
   public IRCode buildInliningIR(
@@ -41,7 +39,6 @@
       GraphLens codeLens,
       NumberGenerator valueNumberGenerator,
       Position callerPosition,
-      Origin origin,
       RewrittenPrototypeDescription protoChanges) {
     throw new Unreachable("Unexpected attempt to build IR graph for inlining from: "
         + getClass().getCanonicalName());
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 f43f02e..9b11e7e 100644
--- a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
@@ -38,7 +38,6 @@
 import com.android.tools.r8.lightir.LirEncodingStrategy;
 import com.android.tools.r8.lightir.LirStrategy;
 import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.HashingVisitor;
@@ -167,12 +166,11 @@
   public IRCode buildIR(
       ProgramMethod method,
       AppView<?> appView,
-      Origin origin,
       MutableMethodConversionOptions conversionOptions) {
     DefaultInstanceInitializerSourceCode source =
         new DefaultInstanceInitializerSourceCode(
             method.getReference(), method.getDefinition().isD8R8Synthesized());
-    return IRBuilder.create(method, appView, source, origin).build(method, conversionOptions);
+    return IRBuilder.create(method, appView, source).build(method, conversionOptions);
   }
 
   @Override
@@ -183,13 +181,12 @@
       GraphLens codeLens,
       NumberGenerator valueNumberGenerator,
       Position callerPosition,
-      Origin origin,
       RewrittenPrototypeDescription protoChanges) {
     DefaultInstanceInitializerSourceCode source =
         new DefaultInstanceInitializerSourceCode(
             method.getReference(), method.getDefinition().isD8R8Synthesized(), callerPosition);
     return IRBuilder.createForInlining(
-            method, appView, codeLens, source, origin, valueNumberGenerator, protoChanges)
+            method, appView, codeLens, source, valueNumberGenerator, protoChanges)
         .build(context, MethodConversionOptions.nonConverting());
   }
 
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 95d19ee..8232d93 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -39,7 +39,6 @@
 import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.lightir.ByteUtils;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.DexDebugUtils.PositionInfo;
 import com.android.tools.r8.utils.RetracerForCodePrinting;
@@ -531,7 +530,6 @@
   public IRCode buildIR(
       ProgramMethod method,
       AppView<?> appView,
-      Origin origin,
       MutableMethodConversionOptions conversionOptions) {
     DexSourceCode source =
         new DexSourceCode(
@@ -539,7 +537,7 @@
             method,
             null,
             appView.dexItemFactory());
-    return IRBuilder.create(method, appView, source, origin).build(method, conversionOptions);
+    return IRBuilder.create(method, appView, source).build(method, conversionOptions);
   }
 
   @Override
@@ -551,7 +549,6 @@
       GraphLens codeLens,
       NumberGenerator valueNumberGenerator,
       Position callerPosition,
-      Origin origin,
       RewrittenPrototypeDescription protoChanges) {
     DexSourceCode source =
         new DexSourceCode(
@@ -560,7 +557,7 @@
             callerPosition,
             appView.dexItemFactory());
     return IRBuilder.createForInlining(
-            method, appView, codeLens, source, origin, valueNumberGenerator, protoChanges)
+            method, appView, codeLens, source, valueNumberGenerator, protoChanges)
         .build(context, MethodConversionOptions.nonConverting());
   }
 
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 3c3533a..79d8789 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1354,7 +1354,7 @@
     DexClass newHolder = definitions.definitionFor(newMethodReference.getHolderType());
     assert newHolder != null;
     DexEncodedMethod newMethod = newHolder.lookupMethod(newMethodReference);
-    assert newMethod != null;
+    assert newMethod != null : newMethodReference.toSourceString();
     return newMethod;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/InvalidCode.java b/src/main/java/com/android/tools/r8/graph/InvalidCode.java
index ff2dc3d..2ce267b 100644
--- a/src/main/java/com/android/tools/r8/graph/InvalidCode.java
+++ b/src/main/java/com/android/tools/r8/graph/InvalidCode.java
@@ -6,7 +6,6 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.RetracerForCodePrinting;
 
 public class InvalidCode extends Code {
@@ -28,7 +27,6 @@
   public IRCode buildIR(
       ProgramMethod method,
       AppView<?> appView,
-      Origin origin,
       MutableMethodConversionOptions conversionOptions) {
     throw new Unreachable();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 0257cfd..2ad9817 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -260,9 +260,8 @@
   public IRCode buildIR(
       ProgramMethod method,
       AppView<?> appView,
-      Origin origin,
       MutableMethodConversionOptions conversionOptions) {
-    return asCfCode().buildIR(method, appView, origin, conversionOptions);
+    return asCfCode().buildIR(method, appView, conversionOptions);
   }
 
   @Override
@@ -273,7 +272,6 @@
       GraphLens codeLens,
       NumberGenerator valueNumberGenerator,
       Position callerPosition,
-      Origin origin,
       RewrittenPrototypeDescription protoChanges) {
     return asCfCode()
         .buildInliningIR(
@@ -283,7 +281,6 @@
             codeLens,
             valueNumberGenerator,
             callerPosition,
-            origin,
             protoChanges);
   }
 
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 a86dbc8..0c85af8 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.kotlin.KotlinMethodLevelInfo;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 
@@ -35,9 +34,7 @@
 
   public IRCode buildIR(AppView<?> appView, MutableMethodConversionOptions conversionOptions) {
     DexEncodedMethod method = getDefinition();
-    return method.hasCode()
-        ? method.getCode().buildIR(this, appView, getOrigin(), conversionOptions)
-        : null;
+    return method.hasCode() ? method.getCode().buildIR(this, appView, conversionOptions) : null;
   }
 
   public IRCode buildInliningIR(
@@ -45,7 +42,6 @@
       AppView<?> appView,
       NumberGenerator valueNumberGenerator,
       Position callerPosition,
-      Origin origin,
       MethodProcessor methodProcessor) {
     Code code = getDefinition().getCode();
     GraphLens codeLens = appView.graphLens();
@@ -61,7 +57,6 @@
         codeLens,
         valueNumberGenerator,
         callerPosition,
-        origin,
         protoChanges);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/PrunedItems.java b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
index 19ebb99..3e96a43 100644
--- a/src/main/java/com/android/tools/r8/graph/PrunedItems.java
+++ b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
@@ -4,11 +4,14 @@
 
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.utils.DequeUtils;
 import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.Sets;
 import java.util.Collection;
+import java.util.Deque;
 import java.util.IdentityHashMap;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
@@ -255,6 +258,9 @@
     }
 
     public PrunedItems build() {
+      if (hasFullyInlinedMethods()) {
+        compressInliningPaths();
+      }
       return new PrunedItems(
           prunedApp,
           additionalPinnedItems,
@@ -264,6 +270,38 @@
           removedFields,
           removedMethods);
     }
+
+    private void compressInliningPaths() {
+      Map<DexMethod, ProgramMethod> fullyInlinedMethodsUpdate = new IdentityHashMap<>();
+      for (Entry<DexMethod, ProgramMethod> entry : fullyInlinedMethods.entrySet()) {
+        DexMethod innermostCallee = entry.getKey();
+        if (fullyInlinedMethodsUpdate.containsKey(innermostCallee)) {
+          // Already processed as a result of previously processing a callee of the current callee.
+          continue;
+        }
+        ProgramMethod innermostCaller = entry.getValue();
+        ProgramMethod outermostCaller = fullyInlinedMethods.get(innermostCaller.getReference());
+        if (outermostCaller == null) {
+          continue;
+        }
+        Deque<DexMethod> fullyInlinedMethodChain =
+            DequeUtils.newArrayDeque(innermostCallee, innermostCaller.getReference());
+        while (true) {
+          DexMethod currentCallee = outermostCaller.getReference();
+          ProgramMethod currentCaller = fullyInlinedMethods.get(currentCallee);
+          if (currentCaller == null) {
+            break;
+          }
+          fullyInlinedMethodChain.addLast(currentCallee);
+          outermostCaller = currentCaller;
+        }
+        assert !removedMethods.contains(outermostCaller.getReference());
+        for (DexMethod callee : fullyInlinedMethodChain) {
+          fullyInlinedMethodsUpdate.put(callee, outermostCaller);
+        }
+      }
+      fullyInlinedMethods.putAll(fullyInlinedMethodsUpdate);
+    }
   }
 
   public static class ConcurrentBuilder extends Builder {
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 7e359ec..63f24c2 100644
--- a/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
+++ b/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
@@ -20,7 +20,6 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import java.nio.ShortBuffer;
@@ -53,7 +52,6 @@
   public IRCode buildIR(
       ProgramMethod method,
       AppView<?> appView,
-      Origin origin,
       MutableMethodConversionOptions conversionOptions) {
     throw new Unreachable("Should not be called");
   }
@@ -66,7 +64,6 @@
       GraphLens codeLens,
       NumberGenerator valueNumberGenerator,
       Position callerPosition,
-      Origin origin,
       RewrittenPrototypeDescription protoChanges) {
     throw new Unreachable("Should not be called");
   }
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 48fe289..af96367 100644
--- a/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
+++ b/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
@@ -24,7 +24,6 @@
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.SyntheticStraightLineSourceCode;
 import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.google.common.collect.ImmutableList;
@@ -70,10 +69,9 @@
   public IRCode buildIR(
       ProgramMethod method,
       AppView<?> appView,
-      Origin origin,
       MutableMethodConversionOptions conversionOptions) {
     ThrowNullSourceCode source = new ThrowNullSourceCode(method);
-    return IRBuilder.create(method, appView, source, origin).build(method, conversionOptions);
+    return IRBuilder.create(method, appView, source).build(method, conversionOptions);
   }
 
   @Override
@@ -84,11 +82,10 @@
       GraphLens codeLens,
       NumberGenerator valueNumberGenerator,
       Position callerPosition,
-      Origin origin,
       RewrittenPrototypeDescription protoChanges) {
     ThrowNullSourceCode source = new ThrowNullSourceCode(method, callerPosition);
     return IRBuilder.createForInlining(
-            method, appView, codeLens, source, origin, valueNumberGenerator, protoChanges)
+            method, appView, codeLens, source, valueNumberGenerator, protoChanges)
         .build(context, MethodConversionOptions.nonConverting());
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 1d9e498..d7d1b0b 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -158,6 +158,8 @@
     registerTypeReference(type);
   }
 
+  public void registerConstResourceNumber(int value) {}
+
   public void registerCheckCast(DexType type, boolean ignoreCompatRules) {
     registerTypeReference(type);
   }
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 d9547d9..6b64f53 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
@@ -5,10 +5,10 @@
 package com.android.tools.r8.graph.analysis;
 
 import com.android.build.shrinker.r8integration.R8ResourceShrinkerState;
-import com.android.build.shrinker.r8integration.R8ResourceShrinkerState.R8ResourceShrinkerModel;
 import com.android.tools.r8.AndroidResourceInput;
 import com.android.tools.r8.AndroidResourceInput.Kind;
 import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -19,8 +19,6 @@
 import com.android.tools.r8.graph.FieldResolutionResult.SingleFieldResolutionResult;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.PrunedItems;
-import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
@@ -29,14 +27,10 @@
 import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerWorklist;
-import com.android.tools.r8.shaking.KeepFieldInfo;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.Timing;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntList;
 import java.util.IdentityHashMap;
-import java.util.List;
 import java.util.Map;
 
 public class ResourceAccessAnalysis implements EnqueuerFieldAccessAnalysis {
@@ -66,21 +60,23 @@
 
   public static void register(
       AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
-    if (enabled(appView)) {
+    if (enabled(appView, enqueuer)) {
       enqueuer.registerFieldAccessAnalysis(new ResourceAccessAnalysis(appView, enqueuer));
     }
   }
 
   @Override
   public void done(Enqueuer enqueuer) {
-    appView.setResourceAnalysisResult(
-        new ResourceAnalysisResult(resourceShrinkerState, fieldToValueMapping));
     EnqueuerFieldAccessAnalysis.super.done(enqueuer);
   }
 
-  private static boolean enabled(AppView<? extends AppInfoWithClassHierarchy> appView) {
+  private static boolean enabled(
+      AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
     return appView.options().androidResourceProvider != null
-        && appView.options().resourceShrinkerConfiguration.isOptimizedShrinking();
+        && appView.options().resourceShrinkerConfiguration.isOptimizedShrinking()
+        // Only run this in the first round, we explicitly trace the resource values
+        // with ResourceConstNumber in the optimizing pipeline.
+        && enqueuer.getMode().isInitialTreeShaking();
   }
 
   @Override
@@ -101,8 +97,6 @@
       assert fieldToValueMapping.containsKey(holderType);
       RClassFieldToValueStore rClassFieldToValueStore = fieldToValueMapping.get(holderType);
       IntList integers = rClassFieldToValueStore.valueMapping.get(field);
-      enqueuer.applyMinimumKeepInfoWhenLive(
-          resolvedField, KeepFieldInfo.newEmptyJoiner().disallowOptimization());
       for (Integer integer : integers) {
         resourceShrinkerState.trace(integer);
       }
@@ -140,6 +134,8 @@
       if (definition.isConstNumber()) {
         values = new IntArrayList(1);
         values.add(definition.asConstNumber().getIntValue());
+      } else if (definition.isResourceConstNumber()) {
+        throw new Unreachable("Only running ResourceAccessAnalysis in initial tree shaking");
       } else if (definition.isNewArrayEmpty()) {
         NewArrayEmpty newArrayEmpty = definition.asNewArrayEmpty();
         values = new IntArrayList();
@@ -148,6 +144,8 @@
             Value constValue = uniqueUser.asArrayPut().value();
             if (constValue.isConstNumber()) {
               values.add(constValue.getDefinition().asConstNumber().getIntValue());
+            } else if (constValue.isConstResourceNumber()) {
+              throw new Unreachable("Only running ResourceAccessAnalysis in initial tree shaking");
             }
           } else {
             assert uniqueUser == staticPut;
@@ -162,6 +160,8 @@
           Instruction valueDefinition = inValue.definition;
           if (valueDefinition.isConstNumber()) {
             values.add(valueDefinition.asConstNumber().getIntValue());
+          } else if (valueDefinition.isResourceConstNumber()) {
+            throw new Unreachable("Only running ResourceAccessAnalysis in initial tree shaking");
           }
         }
       } else {
@@ -192,79 +192,11 @@
     if (result != null) {
       return result;
     }
-    String simpleClassName =
-        DescriptorUtils.getSimpleClassNameFromDescriptor(holder.getType().toDescriptorString());
-    List<String> split = StringUtils.split(simpleClassName, '$');
-
-    if (split.size() < 2) {
-      cachedClassLookups.put(holder, false);
-      return false;
-    }
-    String type = split.get(split.size() - 1);
-    String rClass = split.get(split.size() - 2);
-    // We match on R if:
-    // - The name of the Class is R$type - we allow R to be an inner class.
-    //   - The inner type should be with lower case
-    boolean isRClass = Character.isLowerCase(type.charAt(0)) && rClass.equals("R");
+    boolean isRClass = DescriptorUtils.isRClassDescriptor(holder.getType().toDescriptorString());
     cachedClassLookups.put(holder, isRClass);
     return isRClass;
   }
 
-  public static class ResourceAnalysisResult {
-
-    private final R8ResourceShrinkerState resourceShrinkerState;
-    private Map<DexType, RClassFieldToValueStore> rClassFieldToValueStoreMap;
-
-    private ResourceAnalysisResult(
-        R8ResourceShrinkerState resourceShrinkerState,
-        Map<DexType, RClassFieldToValueStore> rClassFieldToValueStoreMap) {
-      this.resourceShrinkerState = resourceShrinkerState;
-      this.rClassFieldToValueStoreMap = rClassFieldToValueStoreMap;
-    }
-
-    public R8ResourceShrinkerModel getModel() {
-      return resourceShrinkerState.getR8ResourceShrinkerModel();
-    }
-
-    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, appliedLens);
-        if (rewritten.isNotIdenticalTo(dexType)) {
-          changed.put(dexType, rewritten);
-        }
-      }
-      if (!changed.isEmpty()) {
-        Map<DexType, RClassFieldToValueStore> rewrittenMap = new IdentityHashMap<>();
-        rClassFieldToValueStoreMap.forEach(
-            (type, map) -> {
-              rewrittenMap.put(changed.getOrDefault(type, type), map);
-              map.rewrittenWithLens(lens);
-            });
-        rClassFieldToValueStoreMap = rewrittenMap;
-      }
-    }
-
-    public void withoutPrunedItems(PrunedItems prunedItems, Timing timing) {
-      rClassFieldToValueStoreMap.keySet().removeIf(prunedItems::isRemoved);
-      rClassFieldToValueStoreMap.values().forEach(store -> store.pruneItems(prunedItems));
-    }
-
-    public String getSingleStringValueForField(ProgramField programField) {
-      RClassFieldToValueStore rClassFieldToValueStore =
-          rClassFieldToValueStoreMap.get(programField.getHolderType());
-      if (rClassFieldToValueStore == null) {
-        return null;
-      }
-      if (!rClassFieldToValueStore.hasField(programField.getReference())) {
-        return null;
-      }
-      return getModel()
-          .getStringResourcesWithSingleValue()
-          .get(rClassFieldToValueStore.getResourceId(programField.getReference()));
-    }
-  }
-
   private static class RClassFieldToValueStore {
     private Map<DexField, IntList> valueMapping;
 
@@ -272,40 +204,6 @@
       this.valueMapping = valueMapping;
     }
 
-    boolean hasField(DexField field) {
-      return valueMapping.containsKey(field);
-    }
-
-    void pruneItems(PrunedItems prunedItems) {
-      valueMapping.keySet().removeIf(prunedItems::isRemoved);
-    }
-
-    int getResourceId(DexField field) {
-      IntList integers = valueMapping.get(field);
-      assert integers.size() == 1;
-      return integers.get(0);
-    }
-
-    @SuppressWarnings("ReferenceEquality")
-    public void rewrittenWithLens(GraphLens lens) {
-      Map<DexField, DexField> changed = new IdentityHashMap<>();
-      valueMapping
-          .keySet()
-          .forEach(
-              dexField -> {
-                DexField rewritten = lens.lookupField(dexField);
-                if (rewritten != dexField) {
-                  changed.put(dexField, rewritten);
-                }
-              });
-      if (changed.size() > 0) {
-        Map<DexField, IntList> rewrittenMapping = new IdentityHashMap<>();
-        valueMapping.forEach(
-            (key, value) -> rewrittenMapping.put(changed.getOrDefault(key, key), value));
-        valueMapping = rewrittenMapping;
-      }
-    }
-
     public static class Builder {
       private final Map<DexField, IntList> valueMapping = new IdentityHashMap<>();
 
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 96948b9..05bf1b7 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
@@ -401,6 +401,10 @@
     return false;
   }
 
+  public final boolean isClassMergerLens() {
+    return isHorizontalClassMergerGraphLens() || isVerticalClassMergerLens();
+  }
+
   public boolean isClearCodeRewritingLens() {
     return false;
   }
@@ -447,10 +451,6 @@
     return null;
   }
 
-  public boolean isPublicizerLens() {
-    return false;
-  }
-
   public boolean isVerticalClassMergerLens() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index cb4af41..7139797 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -8,7 +8,7 @@
 
 import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.classmerging.ClassMergerMode;
-import com.android.tools.r8.classmerging.SyntheticArgumentClass;
+import com.android.tools.r8.classmerging.ClassMergerSharedData;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinition;
@@ -109,11 +109,11 @@
   }
 
   void mergeDirectMethods(
+      ClassMergerSharedData classMergerSharedData,
       ProfileCollectionAdditions profileCollectionAdditions,
-      SyntheticArgumentClass syntheticArgumentClass,
       SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) {
     mergeInstanceInitializers(
-        profileCollectionAdditions, syntheticArgumentClass, syntheticInitializerConverterBuilder);
+        classMergerSharedData, profileCollectionAdditions, syntheticInitializerConverterBuilder);
     mergeStaticClassInitializers(syntheticInitializerConverterBuilder);
     group.forEach(this::mergeDirectMethods);
     if (!classInitializerMerger.isEmpty() && classInitializerMerger.isTrivialMerge()) {
@@ -203,26 +203,26 @@
   }
 
   void mergeInstanceInitializers(
+      ClassMergerSharedData classMergerSharedData,
       ProfileCollectionAdditions profileCollectionAdditions,
-      SyntheticArgumentClass syntheticArgumentClass,
       SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) {
     instanceInitializerMergers.forEach(
         merger ->
             merger.merge(
+                classMergerSharedData,
                 profileCollectionAdditions,
                 classMethodsBuilder,
-                syntheticArgumentClass,
                 syntheticInitializerConverterBuilder));
   }
 
   void mergeMethods(
+      ClassMergerSharedData classMergerSharedData,
       ProfileCollectionAdditions profileCollectionAdditions,
-      SyntheticArgumentClass syntheticArgumentClass,
       SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder,
       Consumer<VirtuallyMergedMethodsKeepInfo> virtuallyMergedMethodsKeepInfoConsumer) {
     mergeVirtualMethods(profileCollectionAdditions, virtuallyMergedMethodsKeepInfoConsumer);
     mergeDirectMethods(
-        profileCollectionAdditions, syntheticArgumentClass, syntheticInitializerConverterBuilder);
+        classMergerSharedData, profileCollectionAdditions, syntheticInitializerConverterBuilder);
     classMethodsBuilder.setClassMethods(group.getTarget());
   }
 
@@ -352,9 +352,9 @@
   }
 
   public void mergeGroup(
+      ClassMergerSharedData classMergerSharedData,
       ProfileCollectionAdditions profileCollectionAdditions,
       PrunedItems.Builder prunedItemsBuilder,
-      SyntheticArgumentClass syntheticArgumentClass,
       SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder,
       Consumer<VirtuallyMergedMethodsKeepInfo> virtuallyMergedMethodsKeepInfoConsumer) {
     fixAccessFlags();
@@ -363,8 +363,8 @@
     mergeInterfaces();
     mergeFields(prunedItemsBuilder);
     mergeMethods(
+        classMergerSharedData,
         profileCollectionAdditions,
-        syntheticArgumentClass,
         syntheticInitializerConverterBuilder,
         virtuallyMergedMethodsKeepInfoConsumer);
     group.getTarget().clearClassSignature();
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java
index 49f51d8..49532c6 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java
@@ -6,12 +6,14 @@
 
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.InvokeType;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.IntBox;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
@@ -35,17 +37,20 @@
  */
 public class ConstructorEntryPoint extends SyntheticSourceCode {
   private final DexField classIdField;
+  private final int extraNulls;
   private final Int2ReferenceSortedMap<DexMethod> typeConstructors;
 
   public ConstructorEntryPoint(
       Int2ReferenceSortedMap<DexMethod> typeConstructors,
       DexMethod newConstructor,
       DexField classIdField,
+      int extraNulls,
       Position position) {
     super(newConstructor.holder, newConstructor, position);
 
     this.typeConstructors = typeConstructors;
     this.classIdField = classIdField;
+    this.extraNulls = extraNulls;
   }
 
   private boolean hasClassIdField() {
@@ -55,16 +60,37 @@
   void addConstructorInvoke(DexMethod typeConstructor) {
     add(
         builder -> {
-          List<Value> arguments = new ArrayList<>(typeConstructor.getArity() + 1);
+          int originalNumberOfNonReceiverArguments =
+              builder.hasArgumentValues()
+                  ? (builder.getArgumentValues().size()
+                      - BooleanUtils.intValue(typeConstructors.size() > 1)
+                      - extraNulls)
+                  : 0;
+          int newNumberOfNonReceiverArguments = typeConstructor.getArity();
+          List<Value> arguments = new ArrayList<>(newNumberOfNonReceiverArguments + 1);
           arguments.add(builder.getReceiverValue());
-
-          // If there are any arguments add them to the list.
-          for (int i = 0; i < typeConstructor.getArity(); i++) {
-            arguments.add(builder.getArgumentValues().get(i));
+          if (originalNumberOfNonReceiverArguments >= newNumberOfNonReceiverArguments) {
+            for (int i = 0; i < newNumberOfNonReceiverArguments; i++) {
+              arguments.add(builder.getArgumentValues().get(i));
+            }
+          } else {
+            // Exclude the last argument if it is the synthetic class id parameter, since the
+            // original constructor we are calling does not have it.
+            for (int i = 0; i < originalNumberOfNonReceiverArguments; i++) {
+              arguments.add(builder.getArgumentValues().get(i));
+            }
+            int extraRegister = nextRegister(ValueType.INT);
+            ConstNumber constNumber = builder.addIntConst(extraRegister, 0);
+            while (arguments.size() <= newNumberOfNonReceiverArguments) {
+              assert ValueType.fromDexType(
+                      typeConstructor.getArgumentTypeForNonStaticMethod(arguments.size()))
+                  == ValueType.INT;
+              arguments.add(constNumber.outValue());
+            }
           }
-
+          assert arguments.size() == typeConstructor.getNumberOfArgumentsForNonStaticMethod();
           builder.addInvoke(
-              InvokeType.DIRECT, typeConstructor, typeConstructor.proto, arguments, false);
+              InvokeType.DIRECT, typeConstructor, typeConstructor.getProto(), arguments, false);
         });
   }
 
@@ -84,10 +110,8 @@
 
   protected void prepareMultiConstructorInstructions() {
     int typeConstructorCount = typeConstructors.size();
-    DexMethod exampleTargetConstructor = typeConstructors.values().iterator().next();
     // The class id register is always the first synthetic argument.
-    int idRegister = getParamRegister(exampleTargetConstructor.getArity());
-
+    int idRegister = getParamRegister(method.getArity() - 1 - extraNulls);
     if (hasClassIdField()) {
       addRegisterClassIdAssignment(idRegister);
     }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index 03d62ea..91ca758 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -7,11 +7,12 @@
 import static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull;
 
 import com.android.tools.r8.classmerging.ClassMergerMode;
+import com.android.tools.r8.classmerging.ClassMergerSharedData;
 import com.android.tools.r8.classmerging.Policy;
-import com.android.tools.r8.classmerging.SyntheticArgumentClass;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -130,20 +131,17 @@
 
     // Merge the classes.
     List<ClassMerger> classMergers = initializeClassMergers(codeProvider, lensBuilder, groups);
+    ClassMergerSharedData classMergerSharedData = new ClassMergerSharedData(appView);
     ProfileCollectionAdditions profileCollectionAdditions =
         ProfileCollectionAdditions.create(appView);
-    SyntheticArgumentClass syntheticArgumentClass =
-        mode.isInitial()
-            ? new SyntheticArgumentClass.Builder(appView.withLiveness()).build(groups)
-            : null;
     SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder =
         SyntheticInitializerConverter.builder(appView, codeProvider, mode);
     List<VirtuallyMergedMethodsKeepInfo> virtuallyMergedMethodsKeepInfos = new ArrayList<>();
     PrunedItems prunedItems =
         applyClassMergers(
             classMergers,
+            classMergerSharedData,
             profileCollectionAdditions,
-            syntheticArgumentClass,
             syntheticInitializerConverterBuilder,
             virtuallyMergedMethodsKeepInfos::add);
 
@@ -159,13 +157,7 @@
 
     HorizontalClassMergerGraphLens horizontalClassMergerGraphLens =
         createLens(
-            mergedClasses,
-            lensBuilder,
-            mode,
-            profileCollectionAdditions,
-            syntheticArgumentClass,
-            executorService,
-            timing);
+            classMergerSharedData, mergedClasses, lensBuilder, mode, executorService, timing);
     profileCollectionAdditions =
         profileCollectionAdditions.rewriteMethodReferences(
             horizontalClassMergerGraphLens::getNextMethodToInvoke);
@@ -298,12 +290,19 @@
                   definition.hasCode()
                       && definition.getCode().isIncompleteHorizontalClassMergerCode(),
               method -> {
+                // Transform the code object to CfCode. This may return null if the code object does
+                // not have support for generating CfCode. In this case, the call to toCfCode() will
+                // lens rewrite the references of the code object using the lens.
+                //
+                // This should be changed to generate non-null LirCode always.
                 IncompleteHorizontalClassMergerCode code =
                     (IncompleteHorizontalClassMergerCode) method.getDefinition().getCode();
-                method.setCode(
+                CfCode cfCode =
                     code.toCfCode(
-                        appView.withClassHierarchy(), method, horizontalClassMergerGraphLens),
-                    appView);
+                        appView.withClassHierarchy(), method, horizontalClassMergerGraphLens);
+                if (cfCode != null) {
+                  method.setCode(cfCode, appView);
+                }
               });
         },
         appView.options().getThreadingModule(),
@@ -387,16 +386,16 @@
   /** Merges all class groups using {@link ClassMerger}. */
   private PrunedItems applyClassMergers(
       Collection<ClassMerger> classMergers,
+      ClassMergerSharedData classMergerSharedData,
       ProfileCollectionAdditions profileCollectionAdditions,
-      SyntheticArgumentClass syntheticArgumentClass,
       SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder,
       Consumer<VirtuallyMergedMethodsKeepInfo> virtuallyMergedMethodsKeepInfoConsumer) {
     PrunedItems.Builder prunedItemsBuilder = PrunedItems.builder().setPrunedApp(appView.app());
     for (ClassMerger merger : classMergers) {
       merger.mergeGroup(
+          classMergerSharedData,
           profileCollectionAdditions,
           prunedItemsBuilder,
-          syntheticArgumentClass,
           syntheticInitializerConverterBuilder,
           virtuallyMergedMethodsKeepInfoConsumer);
     }
@@ -409,21 +408,15 @@
    */
   @SuppressWarnings("ReferenceEquality")
   private HorizontalClassMergerGraphLens createLens(
+      ClassMergerSharedData classMergerSharedData,
       HorizontallyMergedClasses mergedClasses,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
       ClassMergerMode mode,
-      ProfileCollectionAdditions profileCollectionAdditions,
-      SyntheticArgumentClass syntheticArgumentClass,
       ExecutorService executorService,
       Timing timing)
       throws ExecutionException {
     return new HorizontalClassMergerTreeFixer(
-            appView,
-            mergedClasses,
-            lensBuilder,
-            mode,
-            profileCollectionAdditions,
-            syntheticArgumentClass)
+            appView, classMergerSharedData, mergedClasses, lensBuilder, mode)
         .run(executorService, timing);
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerTreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerTreeFixer.java
index 5bc07a2..91f25d0 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerTreeFixer.java
@@ -5,10 +5,9 @@
 package com.android.tools.r8.horizontalclassmerging;
 
 import com.android.tools.r8.classmerging.ClassMergerMode;
+import com.android.tools.r8.classmerging.ClassMergerSharedData;
 import com.android.tools.r8.classmerging.ClassMergerTreeFixer;
-import com.android.tools.r8.classmerging.SyntheticArgumentClass;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
 import com.android.tools.r8.utils.Timing;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -28,12 +27,11 @@
 
   public HorizontalClassMergerTreeFixer(
       AppView<?> appView,
+      ClassMergerSharedData classMergerSharedData,
       HorizontallyMergedClasses mergedClasses,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
-      ClassMergerMode mode,
-      ProfileCollectionAdditions profileCollectionAdditions,
-      SyntheticArgumentClass syntheticArgumentClass) {
-    super(appView, lensBuilder, mergedClasses, profileCollectionAdditions, syntheticArgumentClass);
+      ClassMergerMode mode) {
+    super(appView, classMergerSharedData, lensBuilder, mergedClasses);
     this.mode = mode;
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java
index 6a35c88..95c4020 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java
@@ -66,7 +66,7 @@
       return method
           .getDefinition()
           .getCode()
-          .buildIR(method, appViewForConversion, method.getOrigin(), getConversionOptions.get());
+          .buildIR(method, appViewForConversion, getConversionOptions.get());
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteHorizontalClassMergerCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteHorizontalClassMergerCode.java
index 7e37921..7ca989f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteHorizontalClassMergerCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteHorizontalClassMergerCode.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.RetracerForCodePrinting;
 
 public abstract class IncompleteHorizontalClassMergerCode extends Code {
@@ -55,7 +54,6 @@
   public IRCode buildIR(
       ProgramMethod method,
       AppView<?> appView,
-      Origin origin,
       MutableMethodConversionOptions conversionOptions) {
     throw new Unreachable();
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
index 461cbc0..66b8a1e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
@@ -4,12 +4,11 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
-import static com.android.tools.r8.dex.Constants.TEMPORARY_INSTANCE_INITIALIZER_PREFIX;
 import static com.android.tools.r8.ir.conversion.ExtraUnusedParameter.computeExtraUnusedParameters;
 
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.classmerging.ClassMergerMode;
-import com.android.tools.r8.classmerging.SyntheticArgumentClass;
+import com.android.tools.r8.classmerging.ClassMergerSharedData;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
@@ -29,7 +28,6 @@
 import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.structural.Ordered;
@@ -160,7 +158,7 @@
     for (ProgramMethod instanceInitializer : instanceInitializers) {
       typeConstructorClassMap.put(
           classIdentifiers.getInt(instanceInitializer.getHolderType()),
-          lensBuilder.getRenamedMethodSignature(instanceInitializer.getReference()));
+          instanceInitializer.getReference());
     }
     return typeConstructorClassMap;
   }
@@ -245,27 +243,27 @@
   }
 
   private DexMethod moveInstanceInitializer(
-      ClassMethodsBuilder classMethodsBuilder, ProgramMethod instanceInitializer) {
-    DexMethod method =
-        dexItemFactory.createFreshMethodNameWithHolder(
-            TEMPORARY_INSTANCE_INITIALIZER_PREFIX,
-            instanceInitializer.getHolderType(),
-            instanceInitializer.getProto(),
-            group.getTarget().getType(),
-            classMethodsBuilder::isFresh);
-
-    DexEncodedMethod encodedMethod =
+      ClassMergerSharedData classMergerSharedData,
+      ClassMethodsBuilder classMethodsBuilder,
+      ProgramMethod instanceInitializer,
+      DexMethod reservedMethod) {
+    DexMethod newReference =
+        dexItemFactory.createInstanceInitializerWithFreshProto(
+            instanceInitializer.getReference().withHolder(group.getTarget(), dexItemFactory),
+            classMergerSharedData.getExtraUnusedArgumentTypes(),
+            candidate ->
+                classMethodsBuilder.isFresh(candidate)
+                    && candidate.isNotIdenticalTo(reservedMethod));
+    if (newReference.isIdenticalTo(instanceInitializer.getReference())) {
+      classMethodsBuilder.addDirectMethod(instanceInitializer.getDefinition());
+      return newReference;
+    }
+    DexEncodedMethod newMethod =
         instanceInitializer
             .getDefinition()
-            .toTypeSubstitutedMethodAsInlining(method, dexItemFactory);
-    encodedMethod.getMutableOptimizationInfo().markForceInline();
-    encodedMethod.getAccessFlags().unsetConstructor();
-    encodedMethod.getAccessFlags().unsetPublic();
-    encodedMethod.getAccessFlags().unsetProtected();
-    encodedMethod.getAccessFlags().setPrivate();
-    classMethodsBuilder.addDirectMethod(encodedMethod);
-
-    return method;
+            .toTypeSubstitutedMethodAsInlining(newReference, dexItemFactory);
+    classMethodsBuilder.addDirectMethod(newMethod);
+    return newReference;
   }
 
   private MethodAccessFlags getNewAccessFlags() {
@@ -288,7 +286,8 @@
     return new ConstructorEntryPointSynthesizedCode(
         createClassIdToInstanceInitializerMap(),
         newMethodReference,
-        group.hasClassIdField() ? group.getClassIdField() : null);
+        group.hasClassIdField() ? group.getClassIdField() : null,
+        extraNulls);
   }
 
   private boolean isSingleton() {
@@ -298,9 +297,9 @@
   /** Synthesize a new method which selects the constructor based on a parameter type. */
   @SuppressWarnings("ReferenceEquality")
   void merge(
+      ClassMergerSharedData classMergerSharedData,
       ProfileCollectionAdditions profileCollectionAdditions,
       ClassMethodsBuilder classMethodsBuilder,
-      SyntheticArgumentClass syntheticArgumentClass,
       SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) {
     ProgramMethod representative = ListUtils.first(instanceInitializers);
 
@@ -313,23 +312,13 @@
     DexMethod newMethodReferenceTemplate = getNewMethodReference(representative, needsClassId);
     assert mode.isInitial() || classMethodsBuilder.isFresh(newMethodReferenceTemplate);
 
-    Box<Set<DexType>> usedSyntheticArgumentClasses = new Box<>();
     DexMethod newMethodReference =
         dexItemFactory.createInstanceInitializerWithFreshProto(
             newMethodReferenceTemplate,
-            mode.isInitial() ? syntheticArgumentClass.getArgumentClasses() : ImmutableList.of(),
-            classMethodsBuilder::isFresh,
-            usedSyntheticArgumentClasses::set);
-
-    // Amend the art profile collection.
-    if (usedSyntheticArgumentClasses.isSet()) {
-      for (ProgramMethod instanceInitializer : instanceInitializers) {
-        profileCollectionAdditions.applyIfContextIsInProfile(
-            instanceInitializer.getReference(),
-            additionsBuilder ->
-                usedSyntheticArgumentClasses.get().forEach(additionsBuilder::addRule));
-      }
-    }
+            mode.isInitial()
+                ? classMergerSharedData.getExtraUnusedArgumentTypes()
+                : ImmutableList.of(),
+            classMethodsBuilder::isFresh);
 
     // Compute the extra unused null parameters.
     List<ExtraUnusedParameter> extraUnusedParameters =
@@ -346,7 +335,11 @@
     } else {
       for (ProgramMethod instanceInitializer : instanceInitializers) {
         DexMethod movedInstanceInitializer =
-            moveInstanceInitializer(classMethodsBuilder, instanceInitializer);
+            moveInstanceInitializer(
+                classMergerSharedData,
+                classMethodsBuilder,
+                instanceInitializer,
+                newMethodReference);
         lensBuilder.mapMethod(movedInstanceInitializer, movedInstanceInitializer);
         lensBuilder.recordNewMethodSignature(
             instanceInitializer.getReference(), movedInstanceInitializer);
@@ -361,7 +354,6 @@
     // Add a mapping from a synthetic name to the synthetic constructor.
     DexMethod syntheticMethodReference =
         getSyntheticMethodReference(classMethodsBuilder, newMethodReference);
-
     if (useSyntheticMethod()) {
       lensBuilder.recordNewMethodSignature(syntheticMethodReference, newMethodReference, true);
     }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
index a9fa5b6..e4df0c7 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -32,7 +32,6 @@
 import com.android.tools.r8.horizontalclassmerging.policies.NoDirectRuntimeTypeChecks;
 import com.android.tools.r8.horizontalclassmerging.policies.NoEnums;
 import com.android.tools.r8.horizontalclassmerging.policies.NoFailedResolutionTargets;
-import com.android.tools.r8.horizontalclassmerging.policies.NoIllegalInlining;
 import com.android.tools.r8.horizontalclassmerging.policies.NoIndirectRuntimeTypeChecks;
 import com.android.tools.r8.horizontalclassmerging.policies.NoInnerClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.NoInstanceFieldAnnotations;
@@ -125,7 +124,6 @@
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
       builder.add(
           new NoDeadEnumLiteMaps(appViewWithLiveness, mode),
-          new NoIllegalInlining(appViewWithLiveness, mode),
           new NoVerticallyMergedClasses(appViewWithLiveness, mode));
     }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
index 19efa91..d54026f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
@@ -35,7 +35,6 @@
 import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.CfVersionUtils;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.ListUtils;
@@ -208,7 +207,6 @@
     public IRCode buildIR(
         ProgramMethod method,
         AppView<?> appView,
-        Origin origin,
         MutableMethodConversionOptions conversionOptions) {
       assert !classInitializers.isEmpty();
 
@@ -249,7 +247,6 @@
               valueNumberGenerator,
               blockNumberGenerator,
               metadata,
-              origin,
               conversionOptions);
 
       ListIterator<BasicBlock> blockIterator = code.listIterator();
@@ -275,7 +272,6 @@
                     appView.codeLens(),
                     valueNumberGenerator,
                     preamblePosition,
-                    classInitializer.getOrigin(),
                     RewrittenPrototypeDescription.none());
         classInitializer.getDefinition().setObsolete();
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ConstructorEntryPointSynthesizedCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ConstructorEntryPointSynthesizedCode.java
index 0c0f059..fda1515 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ConstructorEntryPointSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ConstructorEntryPointSynthesizedCode.java
@@ -5,18 +5,20 @@
 package com.android.tools.r8.horizontalclassmerging.code;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.ClasspathMethod;
-import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
 import com.android.tools.r8.horizontalclassmerging.ConstructorEntryPoint;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerGraphLens;
+import com.android.tools.r8.horizontalclassmerging.IncompleteHorizontalClassMergerCode;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
@@ -25,23 +27,26 @@
 import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.SourceCode;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.RetracerForCodePrinting;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 
-public class ConstructorEntryPointSynthesizedCode extends Code {
+public class ConstructorEntryPointSynthesizedCode extends IncompleteHorizontalClassMergerCode {
 
   private final DexMethod newConstructor;
   private final DexField classIdField;
+  private final int extraNulls;
   private final Int2ReferenceSortedMap<DexMethod> typeConstructors;
 
   public ConstructorEntryPointSynthesizedCode(
       Int2ReferenceSortedMap<DexMethod> typeConstructors,
       DexMethod newConstructor,
-      DexField classIdField) {
+      DexField classIdField,
+      int extraNulls) {
     this.typeConstructors = typeConstructors;
     this.newConstructor = newConstructor;
     this.classIdField = classIdField;
+    this.extraNulls = extraNulls;
   }
 
   private void registerReachableDefinitions(UseRegistry<?> registry) {
@@ -55,11 +60,30 @@
   }
 
   @Override
+  public GraphLens getCodeLens(AppView<?> appView) {
+    return appView
+        .graphLens()
+        .asNonIdentityLens()
+        .find(GraphLens::isHorizontalClassMergerGraphLens);
+  }
+
+  @Override
   public boolean isHorizontalClassMergerCode() {
     return true;
   }
 
   @Override
+  public CfCode toCfCode(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      ProgramMethod method,
+      HorizontalClassMergerGraphLens lens) {
+    for (Int2ReferenceMap.Entry<DexMethod> entry : typeConstructors.int2ReferenceEntrySet()) {
+      entry.setValue(lens.getNextMethodSignature(entry.getValue()));
+    }
+    return null;
+  }
+
+  @Override
   public final boolean isEmptyVoidMethod() {
     return false;
   }
@@ -68,7 +92,6 @@
   public final IRCode buildIR(
       ProgramMethod method,
       AppView<?> appView,
-      Origin origin,
       MutableMethodConversionOptions conversionOptions) {
     SyntheticPosition position =
         SyntheticPosition.builder()
@@ -77,8 +100,9 @@
             .setIsD8R8Synthesized(true)
             .build();
     SourceCode sourceCode =
-        new ConstructorEntryPoint(typeConstructors, newConstructor, classIdField, position);
-    return IRBuilder.create(method, appView, sourceCode, origin).build(method, conversionOptions);
+        new ConstructorEntryPoint(
+            typeConstructors, newConstructor, classIdField, extraNulls, position);
+    return IRBuilder.create(method, appView, sourceCode).build(method, conversionOptions);
   }
 
   @Override
@@ -89,28 +113,16 @@
       GraphLens codeLens,
       NumberGenerator valueNumberGenerator,
       Position callerPosition,
-      Origin origin,
       RewrittenPrototypeDescription protoChanges) {
     SourceCode sourceCode =
-        new ConstructorEntryPoint(typeConstructors, newConstructor, classIdField, callerPosition);
+        new ConstructorEntryPoint(
+            typeConstructors, newConstructor, classIdField, extraNulls, callerPosition);
     return IRBuilder.createForInlining(
-            method, appView, codeLens, sourceCode, origin, valueNumberGenerator, protoChanges)
+            method, appView, codeLens, sourceCode, valueNumberGenerator, protoChanges)
         .build(context, MethodConversionOptions.nonConverting());
   }
 
   @Override
-  public final Code getCodeAsInlining(
-      DexMethod caller,
-      boolean isCallerD8R8Synthesized,
-      DexMethod callee,
-      boolean isCalleeD8R8Synthesized,
-      DexItemFactory factory) {
-    // This code object is synthesized so "inlining" just "strips" the callee position.
-    assert isCalleeD8R8Synthesized;
-    return this;
-  }
-
-  @Override
   public final String toString() {
     return toString(null, RetracerForCodePrinting.empty());
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIllegalInlining.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIllegalInlining.java
deleted file mode 100644
index 5126df8..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIllegalInlining.java
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.horizontalclassmerging.policies;
-
-import com.android.tools.r8.classmerging.ClassMergerMode;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.google.common.collect.Iterables;
-
-public class NoIllegalInlining extends SingleClassPolicy {
-
-  private final AppView<AppInfoWithLiveness> appView;
-
-  public NoIllegalInlining(AppView<AppInfoWithLiveness> appView, ClassMergerMode mode) {
-    // This policy is only relevant for the first round of horizontal class merging, since the final
-    // round of horizontal class merging may not require any inlining.
-    assert mode.isInitial();
-    this.appView = appView;
-  }
-
-  @SuppressWarnings("ReferenceEquality")
-  private boolean disallowInlining(ProgramMethod method) {
-    Code code = method.getDefinition().getCode();
-
-    if (!appView.getKeepInfo(method).isInliningAllowed(appView.options())) {
-      return true;
-    }
-
-    // For non-jar/cf code we currently cannot guarantee that markForceInline() will succeed.
-    if (code == null) {
-      return true;
-    }
-
-    if (code.isCfCode()) {
-      CfCode cfCode = code.asCfCode();
-      ConstraintWithTarget constraint =
-          cfCode.computeInliningConstraint(appView, appView.graphLens(), method);
-      return constraint.isNever();
-    } else if (code.isDefaultInstanceInitializerCode()) {
-      return false;
-    } else {
-      return true;
-    }
-  }
-
-  @Override
-  public boolean canMerge(DexProgramClass program) {
-    return !Iterables.any(
-        program.directProgramMethods(),
-        method -> method.getDefinition().isInstanceInitializer() && disallowInlining(method));
-  }
-
-  @Override
-  public String getName() {
-    return "DontInlinePolicy";
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
index abbb78c..924928b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.analysis;
 
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
@@ -13,7 +12,6 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.environmentdependence.ValueGraph;
@@ -305,8 +303,8 @@
       return false;
     }
 
-    NewInstance newInstance = value.definition.asNewInstance();
-    DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(newInstance.clazz));
+    NewInstance newInstance = value.getDefinition().asNewInstance();
+    DexClass clazz = appView.definitionFor(newInstance.getType(), context);
     if (clazz == null) {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index c71f037..e050ed4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -205,6 +205,14 @@
     return null;
   }
 
+  public boolean isSingleResourceNumberValue() {
+    return false;
+  }
+
+  public SingleResourceNumberValue asSingleResourceNumberValue() {
+    return null;
+  }
+
   public boolean isSingleStringValue() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
index 79eca14..567d39a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
@@ -26,6 +26,8 @@
       new ConcurrentHashMap<>();
   private final ConcurrentHashMap<Long, SingleNumberValue> singleNumberValues =
       new ConcurrentHashMap<>();
+  private final ConcurrentHashMap<Integer, SingleResourceNumberValue> singleResourceNumberValues =
+      new ConcurrentHashMap<>();
   private final ConcurrentHashMap<DexString, SingleStringValue> singleStringValues =
       new ConcurrentHashMap<>();
   private final ConcurrentHashMap<Integer, KnownLengthArrayState> knownArrayLengthStates =
@@ -122,6 +124,10 @@
     return createUncheckedSingleNumberValue(value);
   }
 
+  public SingleResourceNumberValue createSingleResourceNumberValue(int value) {
+    return singleResourceNumberValues.computeIfAbsent(value, SingleResourceNumberValue::new);
+  }
+
   public SingleNumberValue createUncheckedSingleNumberValue(long value) {
     return singleNumberValues.computeIfAbsent(value, SingleNumberValue::new);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleResourceNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleResourceNumberValue.java
new file mode 100644
index 0000000..8be7c10
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleResourceNumberValue.java
@@ -0,0 +1,119 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.value;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.MaterializingInstructionsInfo;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ResourceConstNumber;
+import com.android.tools.r8.ir.code.ValueFactory;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class SingleResourceNumberValue extends SingleConstValue {
+
+  private final int value;
+
+  /** Intentionally package private, use {@link AbstractValueFactory} instead. */
+  SingleResourceNumberValue(int value) {
+    this.value = value;
+  }
+
+  @Override
+  public boolean hasSingleMaterializingInstruction() {
+    return true;
+  }
+
+  @Override
+  public boolean isSingleResourceNumberValue() {
+    return true;
+  }
+
+  @Override
+  public SingleResourceNumberValue asSingleResourceNumberValue() {
+    return this;
+  }
+
+  public int getValue() {
+    return value;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    return this == o;
+  }
+
+  @Override
+  public int hashCode() {
+    return System.identityHashCode(this);
+  }
+
+  @Override
+  public String toString() {
+    return "SingleResourceNumberValue(" + value + ")";
+  }
+
+  @Override
+  public Instruction[] createMaterializingInstructions(
+      AppView<?> appView,
+      ProgramMethod context,
+      ValueFactory valueFactory,
+      MaterializingInstructionsInfo info) {
+    ResourceConstNumber materializingInstruction =
+        createMaterializingInstruction(appView, valueFactory, info);
+    return new Instruction[] {materializingInstruction};
+  }
+
+  public ResourceConstNumber createMaterializingInstruction(
+      AppView<?> appView, ValueFactory valueFactory, MaterializingInstructionsInfo info) {
+    return createMaterializingInstruction(
+        appView, valueFactory, info.getOutType(), info.getLocalInfo(), info.getPosition());
+  }
+
+  public ResourceConstNumber createMaterializingInstruction(
+      AppView<?> appView,
+      ValueFactory valueFactory,
+      TypeElement type,
+      DebugLocalInfo localInfo,
+      Position position) {
+    assert type.isInt();
+    return ResourceConstNumber.builder()
+        .setFreshOutValue(valueFactory, type, localInfo)
+        .setPositionForNonThrowingInstruction(position, appView.options())
+        .setValue(value)
+        .build();
+  }
+
+  @Override
+  boolean internalIsMaterializableInContext(
+      AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
+    return true;
+  }
+
+  @Override
+  public boolean isMaterializableInAllContexts(AppView<AppInfoWithLiveness> appView) {
+    return true;
+  }
+
+  @Override
+  public InstanceFieldInitializationInfo fixupAfterParametersChanged(
+      ArgumentInfoCollection argumentInfoCollection) {
+    return this;
+  }
+
+  @Override
+  public SingleValue rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, DexType newType, GraphLens lens, GraphLens codeLens) {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java b/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
index 7da900e..357828b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
@@ -10,6 +10,9 @@
 import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -113,9 +116,29 @@
   }
 
   public CatchHandlers<T> rewriteWithLens(GraphLens graphLens, GraphLens codeLens) {
+    IntList targetIndicesToRemove = new IntArrayList();
+    Set<DexType> seenGuards = Sets.newIdentityHashSet();
     List<DexType> newGuards =
-        ListUtils.mapOrElse(guards, guard -> graphLens.lookupType(guard, codeLens), null);
-    return newGuards != null ? new CatchHandlers<>(newGuards, targets) : this;
+        ListUtils.mapOrElse(
+            guards,
+            (index, guard) -> {
+              DexType newGuard = graphLens.lookupType(guard, codeLens);
+              if (seenGuards.add(newGuard)) {
+                return newGuard;
+              }
+              targetIndicesToRemove.add(index);
+              return null;
+            },
+            null);
+    if (newGuards == null) {
+      assert targetIndicesToRemove.isEmpty();
+      return this;
+    }
+    List<T> newTargets =
+        targetIndicesToRemove.isEmpty()
+            ? targets
+            : ListUtils.newArrayListWithoutIndices(targets, targetIndicesToRemove);
+    return new CatchHandlers<>(newGuards, newTargets);
   }
 
   public void forEach(BiConsumer<DexType, T> consumer) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java b/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java
index a21882a..a3bbdee 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java
@@ -95,6 +95,11 @@
   }
 
   @Override
+  public T visit(ResourceConstNumber instruction) {
+    return null;
+  }
+
+  @Override
   public T visit(ConstString instruction) {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index ae74a82..d5e82ff 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -29,7 +30,6 @@
 import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.optimize.AffectedValues;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DequeUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -137,8 +137,6 @@
   private final IRMetadata metadata;
   private final InternalOptions options;
 
-  public final Origin origin;
-
   public IRCode(
       InternalOptions options,
       ProgramMethod method,
@@ -147,7 +145,6 @@
       NumberGenerator valueNumberGenerator,
       NumberGenerator basicBlockNumberGenerator,
       IRMetadata metadata,
-      Origin origin,
       MutableMethodConversionOptions conversionOptions) {
     assert metadata != null;
     assert options != null;
@@ -161,7 +158,6 @@
     this.valueNumberGenerator = valueNumberGenerator;
     this.basicBlockNumberGenerator = basicBlockNumberGenerator;
     this.metadata = metadata;
-    this.origin = origin;
   }
 
   public IRMetadata metadata() {
@@ -622,6 +618,21 @@
     return true;
   }
 
+  public boolean verifyInvokeInterface(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    if (appView.testing().allowInvokeErrors) {
+      return true;
+    }
+    for (InvokeMethod invoke : this.<InvokeMethod>instructions(Instruction::isInvokeMethod)) {
+      DexType holderType = invoke.getInvokedMethod().getHolderType();
+      if (holderType.isArrayType()) {
+        continue;
+      }
+      DexClass holder = appView.definitionFor(holderType, context());
+      assert holder == null || invoke.getInterfaceBit() == holder.isInterface();
+    }
+    return true;
+  }
+
   public boolean hasNoMergedClasses(AppView<? extends AppInfoWithClassHierarchy> appView) {
     MergedClassesCollection mergedClasses = appView.allMergedClasses();
     if (mergedClasses == null) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
index 2d8cc3e..56f38c3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
@@ -103,6 +103,10 @@
     return get(Opcodes.CONST_NUMBER);
   }
 
+  public boolean mayHaveResourceConstNumber() {
+    return get(Opcodes.RESOURCE_CONST_NUMBER);
+  }
+
   public boolean mayHaveConstString() {
     return get(Opcodes.CONST_STRING);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IROpcodeUtils.java b/src/main/java/com/android/tools/r8/ir/code/IROpcodeUtils.java
new file mode 100644
index 0000000..b798181
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/IROpcodeUtils.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.code;
+
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKEDIRECT;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKEDIRECT_ITF;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKEINTERFACE;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKESTATIC;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKESTATIC_ITF;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKESUPER;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKESUPER_ITF;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKEVIRTUAL;
+
+import com.android.tools.r8.errors.Unreachable;
+
+public class IROpcodeUtils {
+
+  public static int fromLirInvokeOpcode(int opcode) {
+    switch (opcode) {
+      case INVOKEDIRECT:
+      case INVOKEDIRECT_ITF:
+        return Opcodes.INVOKE_DIRECT;
+      case INVOKEINTERFACE:
+        return Opcodes.INVOKE_INTERFACE;
+      case INVOKESTATIC:
+      case INVOKESTATIC_ITF:
+        return Opcodes.INVOKE_STATIC;
+      case INVOKESUPER:
+      case INVOKESUPER_ITF:
+        return Opcodes.INVOKE_SUPER;
+      case INVOKEVIRTUAL:
+        return Opcodes.INVOKE_VIRTUAL;
+      default:
+        throw new Unreachable();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index d0426ec..08143ae 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -869,6 +869,14 @@
     return null;
   }
 
+  public boolean isResourceConstNumber() {
+    return false;
+  }
+
+  public ResourceConstNumber asResourceConstNumber() {
+    return null;
+  }
+
   public boolean isConstInstruction() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java b/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
index 8318365..62bd3de 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
@@ -38,6 +38,8 @@
 
   T visit(ConstNumber instruction);
 
+  T visit(ResourceConstNumber instruction);
+
   T visit(ConstString instruction);
 
   T visit(DebugLocalRead instruction);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeType.java b/src/main/java/com/android/tools/r8/ir/code/InvokeType.java
index 8bedc64..28e3846 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeType.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.graph.lens.MethodLookupResult;
+import com.android.tools.r8.lightir.LirOpcodes;
 import org.objectweb.asm.Opcodes;
 
 public enum InvokeType {
@@ -186,6 +187,23 @@
     return dexOpcodeRange;
   }
 
+  public int getLirOpcode(boolean isInterface) {
+    switch (this) {
+      case DIRECT:
+        return isInterface ? LirOpcodes.INVOKEDIRECT_ITF : LirOpcodes.INVOKEDIRECT;
+      case INTERFACE:
+        return LirOpcodes.INVOKEINTERFACE;
+      case STATIC:
+        return isInterface ? LirOpcodes.INVOKESTATIC_ITF : LirOpcodes.INVOKESTATIC;
+      case SUPER:
+        return isInterface ? LirOpcodes.INVOKESUPER_ITF : LirOpcodes.INVOKESUPER;
+      case VIRTUAL:
+        return LirOpcodes.INVOKEVIRTUAL;
+      default:
+        throw new Unreachable();
+    }
+  }
+
   public boolean isDirect() {
     return this == DIRECT;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index 1bd40d4..a7c3397 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -88,7 +88,11 @@
       AbstractValueSupplier abstractValueSupplier,
       SideEffectAssumption assumption) {
     assert type.isArrayType();
-    return isArrayTypeInaccessible(appView, context) || isArraySizeMaybeNegative();
+    return isArrayTypeInaccessible(appView, context)
+        || isArraySizeMaybeNegative()
+        // Cts test relying on dead array allocations of size Integer.MAX_VALUE not getting
+        // removed See b/322478366.
+        || (appView.options().debug && sizeIfConst() == Integer.MAX_VALUE);
   }
 
   private boolean isArrayTypeInaccessible(AppView<?> appView, ProgramMethod context) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Opcodes.java b/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
index 44ad346..58889ac 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
@@ -77,4 +77,5 @@
   int XOR = 68;
   int UNINITIALIZED_THIS_LOCAL_READ = 69;
   int RECORD_FIELD_VALUES = 70;
+  int RESOURCE_CONST_NUMBER = 71;
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index 6197751..341ca0b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -12,15 +12,14 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock.EdgeType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.TypeConstraintResolver;
 import com.android.tools.r8.ir.optimize.AffectedValues;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.DequeUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.Reporter;
@@ -102,11 +101,11 @@
 
   @Override
   public void constrainType(
-      ValueTypeConstraint constraint, DexMethod method, Origin origin, Reporter reporter) {
+      ValueTypeConstraint constraint, ProgramMethod method, Reporter reporter) {
     if (readType == RegisterReadType.DEBUG) {
       abortOnInvalidDebugInfo(constraint);
     }
-    super.constrainType(constraint, method, origin, reporter);
+    super.constrainType(constraint, method, reporter);
   }
 
   private void abortOnInvalidDebugInfo(ValueTypeConstraint constraint) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/ResourceConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ResourceConstNumber.java
new file mode 100644
index 0000000..d18a410
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/ResourceConstNumber.java
@@ -0,0 +1,162 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.code;
+
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
+
+/**
+ * Instruction representing the SSA value an R class field value.
+ *
+ * <p>This instruction allows us to correctly trace inlined resource values in the second round of
+ * tree shaking.
+ *
+ * <p>The instruction is simple converted back to a const number before writing.
+ */
+public class ResourceConstNumber extends ConstInstruction {
+
+  private final int value;
+
+  public ResourceConstNumber(Value dest, int value) {
+    super(dest);
+    assert dest.type.isPrimitiveType();
+    this.value = value;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  public int opcode() {
+    return Opcodes.RESOURCE_CONST_NUMBER;
+  }
+
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
+  public boolean instructionTypeCanBeCanonicalized() {
+    return true;
+  }
+
+  @Override
+  public void buildDex(DexBuilder builder) {
+    throw new Unreachable("We never write out ResourceConstNumber");
+  }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    throw new Unreachable("We never write cf code with resource numbers");
+  }
+
+  public Value dest() {
+    return outValue;
+  }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    throw new Unreachable("We never write out a resource const number");
+  }
+
+  @Override
+  public int maxInValueRegister() {
+    throw new Unreachable("We map out of ResourceConstNumber before register allocation");
+  }
+
+  @Override
+  public int maxOutValueRegister() {
+    throw new Unreachable("We map out of ResourceConstNumber before register allocation");
+  }
+
+  @Override
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
+    if (other == this) {
+      return true;
+    }
+    if (!other.isResourceConstNumber()) {
+      return false;
+    }
+    ResourceConstNumber otherNumber = other.asResourceConstNumber();
+    return otherNumber.getValue() == getValue();
+  }
+
+  @Override
+  public boolean isOutConstant() {
+    return true;
+  }
+
+  @Override
+  public boolean isResourceConstNumber() {
+    return true;
+  }
+
+  @Override
+  public TypeElement evaluate(AppView<?> appView) {
+    assert getOutType().isInt();
+    return TypeElement.getInt();
+  }
+
+  @Override
+  public AbstractValue getAbstractValue(
+      AppView<?> appView, ProgramMethod context, AbstractValueSupplier abstractValueSupplier) {
+    if (outValue.hasLocalInfo()) {
+      return AbstractValue.unknown();
+    }
+    return appView.abstractValueFactory().createSingleResourceNumberValue(getValue());
+  }
+
+  @Override
+  public void buildLir(LirBuilder<Value, ?> builder) {
+    builder.addResourceConstNumber(getValue());
+  }
+
+  public static ResourceConstNumber copyOf(IRCode code, ResourceConstNumber original) {
+    Value newValue = code.createValue(TypeElement.getInt(), original.getLocalInfo());
+    return new ResourceConstNumber(newValue, original.getValue());
+  }
+
+  @Override
+  public ResourceConstNumber asResourceConstNumber() {
+    return this;
+  }
+
+  public int getValue() {
+    return value;
+  }
+
+  public static class Builder extends BuilderBase<Builder, ResourceConstNumber> {
+
+    private int value;
+
+    public Builder setValue(int value) {
+      this.value = value;
+      return this;
+    }
+
+    @Override
+    public ResourceConstNumber build() {
+      return amend(new ResourceConstNumber(outValue, value));
+    }
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
+    protected boolean verifyInstructionTypeCannotThrow() {
+      return true;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index f7d98af..0a16889 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -31,12 +30,12 @@
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.optimize.AffectedValues;
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.LongInterval;
+import com.android.tools.r8.utils.ObjectUtils;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.base.Predicates;
@@ -53,9 +52,8 @@
 
 public class Value implements Comparable<Value> {
 
-  @SuppressWarnings("ReferenceEquality")
   public void constrainType(
-      ValueTypeConstraint constraint, DexMethod method, Origin origin, Reporter reporter) {
+      ValueTypeConstraint constraint, ProgramMethod method, Reporter reporter) {
     TypeElement constrainedType = constrainedType(constraint);
     if (constrainedType == null) {
       throw reporter.fatalError(
@@ -66,9 +64,9 @@
                   + this
                   + " by constraint: "
                   + constraint,
-              origin,
-              new MethodPosition(method.asMethodReference())));
-    } else if (constrainedType != type) {
+              method.getOrigin(),
+              new MethodPosition(method.getMethodReference())));
+    } else if (ObjectUtils.notIdentical(constrainedType, type)) {
       setType(constrainedType);
     }
   }
@@ -844,6 +842,10 @@
     return isConstant() && getConstInstruction().isConstNumber();
   }
 
+  public boolean isConstResourceNumber() {
+    return isConstant() && getConstInstruction().isResourceConstNumber();
+  }
+
   public boolean isConstNumber(long rawValue) {
     return isConstant()
         && getConstInstruction().isConstNumber()
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index de8a584..48241f6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -40,7 +40,6 @@
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.CfState.Snapshot;
 import com.android.tools.r8.ir.conversion.IRBuilder.BlockInfo;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOutputMode;
 import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -190,7 +189,6 @@
   private final List<CfCode.LocalVariableInfo> localVariables;
   private final CfCode code;
   private final ProgramMethod method;
-  private final Origin origin;
   private final AppView<?> appView;
 
   private final Reference2IntMap<CfLabel> labelOffsets = new Reference2IntOpenHashMap<>();
@@ -207,15 +205,13 @@
 
   public CfSourceCode(
       CfCode code,
-      List<CfCode.LocalVariableInfo> localVariables,
+      List<LocalVariableInfo> localVariables,
       ProgramMethod method,
       Position callerPosition,
-      Origin origin,
       AppView<?> appView) {
     this.code = code;
     this.localVariables = localVariables;
     this.method = method;
-    this.origin = origin;
     this.appView = appView;
     int cfPositionCount = 0;
     for (int i = 0; i < code.getInstructions().size(); i++) {
@@ -227,7 +223,7 @@
         ++cfPositionCount;
       }
     }
-    this.state = new CfState(origin);
+    this.state = new CfState(method);
     canonicalPositions =
         new CanonicalPositions(
             callerPosition,
@@ -247,14 +243,6 @@
     return method.getDefinition();
   }
 
-  public Origin getOrigin() {
-    return origin;
-  }
-
-  public DexType getOriginalHolder() {
-    return code.getOriginalHolder();
-  }
-
   @Override
   public int instructionCount() {
     return code.getInstructions().size();
@@ -573,7 +561,7 @@
 
   private void recordStateForTarget(int target, Snapshot snapshot) {
     Snapshot existing = incomingState.get(target);
-    Snapshot merged = CfState.merge(existing, snapshot, origin);
+    Snapshot merged = CfState.merge(existing, snapshot, method);
     if (merged != existing) {
       incomingState.put(target, merged);
     }
@@ -689,8 +677,7 @@
           .reporter
           .warning(
               new CfCodeDiagnostics(
-                  origin,
-                  method.getReference(),
+                  method,
                   "Could not find stack map for block at offset "
                       + blockOffset
                       + ". This is most likely due to invalid"
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfState.java b/src/main/java/com/android/tools/r8/ir/conversion/CfState.java
index 353faac..d7d39f8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfState.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfState.java
@@ -5,9 +5,9 @@
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.origin.Origin;
 
 public class CfState {
 
@@ -74,12 +74,12 @@
     }
   }
 
-  private final Origin origin;
+  private final ProgramMethod method;
   private Snapshot current;
   private Position position;
 
-  public CfState(Origin origin) {
-    this.origin = origin;
+  public CfState(ProgramMethod method) {
+    this.method = method;
   }
 
   private static final int MAX_UPDATES = 4;
@@ -110,7 +110,7 @@
     if (current == null) {
       current = snapshot == null ? new BaseSnapshot() : snapshot;
     } else {
-      current = merge(current, snapshot, origin);
+      current = merge(current, snapshot, method);
     }
   }
 
@@ -118,22 +118,22 @@
     return current;
   }
 
-  public static Snapshot merge(Snapshot current, Snapshot update, Origin origin) {
+  public static Snapshot merge(Snapshot current, Snapshot update, ProgramMethod method) {
     assert update != null;
     if (current == null) {
       return update;
     }
-    return merge(current.asBase(), update.asBase(), origin);
+    return merge(current.asBase(), update.asBase(), method);
   }
 
-  private static Snapshot merge(BaseSnapshot current, BaseSnapshot update, Origin origin) {
+  private static Snapshot merge(BaseSnapshot current, BaseSnapshot update, ProgramMethod method) {
     if (current.stack.length != update.stack.length) {
       throw new CompilationError(
           "Different stack heights at jump target: "
               + current.stack.length
               + " != "
               + update.stack.length,
-          origin);
+          method.getOrigin());
     }
     // At this point, JarState checks if `current` has special "NULL" or "BYTE/BOOL" types
     // that `update` does not have, and if so it computes a refinement.
@@ -149,7 +149,7 @@
                 + current.stack[i]
                 + " and "
                 + update.stack[i],
-            origin);
+            method.getOrigin());
       }
     }
     // We could check that locals are compatible, but that doesn't make sense since locals can be
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index a3ca7c5..994ef53 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -121,7 +121,6 @@
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.Pair;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
@@ -405,7 +404,6 @@
   private ProgramMethod context;
   public final AppView<?> appView;
   private final GraphLens codeLens;
-  private final Origin origin;
   private final RewrittenPrototypeDescription prototypeChanges;
   private Value receiverValue;
   private List<Value> argumentValues;
@@ -437,15 +435,13 @@
   // then the IR does not necessarily contain a const-string instruction).
   private final IRMetadata metadata = new IRMetadata();
 
-  public static IRBuilder create(
-      ProgramMethod method, AppView<?> appView, SourceCode source, Origin origin) {
+  public static IRBuilder create(ProgramMethod method, AppView<?> appView, SourceCode source) {
     GraphLens codeLens = method.getDefinition().getCode().getCodeLens(appView);
     return new IRBuilder(
         method,
         appView,
         codeLens,
         source,
-        origin,
         lookupPrototypeChanges(appView, method, codeLens),
         new NumberGenerator());
   }
@@ -455,11 +451,9 @@
       AppView<?> appView,
       GraphLens codeLens,
       SourceCode source,
-      Origin origin,
       NumberGenerator valueNumberGenerator,
       RewrittenPrototypeDescription protoChanges) {
-    return new IRBuilder(
-        method, appView, codeLens, source, origin, protoChanges, valueNumberGenerator);
+    return new IRBuilder(method, appView, codeLens, source, protoChanges, valueNumberGenerator);
   }
 
   public static RewrittenPrototypeDescription lookupPrototypeChanges(
@@ -474,7 +468,6 @@
       AppView<?> appView,
       GraphLens codeLens,
       SourceCode source,
-      Origin origin,
       RewrittenPrototypeDescription prototypeChanges,
       NumberGenerator valueNumberGenerator) {
     assert source != null;
@@ -482,7 +475,6 @@
     this.method = method;
     this.appView = appView;
     this.source = source;
-    this.origin = origin;
     this.codeLens = codeLens;
     this.prototypeChanges = prototypeChanges;
     this.valueNumberGenerator = valueNumberGenerator;
@@ -517,6 +509,11 @@
     return targets;
   }
 
+  public boolean hasArgumentValues() {
+    assert argumentValues == null || !argumentValues.isEmpty();
+    return argumentValues != null;
+  }
+
   public List<Value> getArgumentValues() {
     return argumentValues;
   }
@@ -718,7 +715,6 @@
             valueNumberGenerator,
             basicBlockNumberGenerator,
             metadata,
-            origin,
             conversionOptions);
 
     // Verify critical edges are split so we have a place to insert phi moves if necessary.
@@ -774,7 +770,7 @@
   }
 
   public void constrainType(Value value, ValueTypeConstraint constraint) {
-    value.constrainType(constraint, method.getReference(), origin, appView.options().reporter);
+    value.constrainType(constraint, method, appView.reporter());
   }
 
   private void addImpreciseInstruction(ImpreciseMemberTypeInstruction instruction) {
@@ -1219,8 +1215,11 @@
     add(new ConstNumber(writeRegister(dest, getDouble(), ThrowingInfo.NO_THROW), value));
   }
 
-  public void addIntConst(int dest, long value) {
-    add(new ConstNumber(writeRegister(dest, getInt(), ThrowingInfo.NO_THROW), value));
+  public ConstNumber addIntConst(int dest, long value) {
+    ConstNumber constNumber =
+        new ConstNumber(writeRegister(dest, getInt(), ThrowingInfo.NO_THROW), value);
+    add(constNumber);
+    return constNumber;
   }
 
   public void addFloatConst(int dest, long value) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 676fcc5..72e5dac 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -161,7 +161,7 @@
   private final DexItemFactory factory;
   private final InternalOptions options;
 
-  LensCodeRewriter(AppView<? extends AppInfoWithClassHierarchy> appView) {
+  public LensCodeRewriter(AppView<? extends AppInfoWithClassHierarchy> appView) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
     this.options = appView.options();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
index 19bb6dc..5f5a9c9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
@@ -11,8 +11,9 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
 import com.android.tools.r8.graph.lens.GraphLens;
-import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.conversion.passes.ConstResourceNumberRemover;
+import com.android.tools.r8.ir.conversion.passes.ConstResourceNumberRewriter;
 import com.android.tools.r8.ir.conversion.passes.FilledNewArrayRewriter;
 import com.android.tools.r8.ir.optimize.ConstantCanonicalizer;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover;
@@ -23,6 +24,7 @@
 import com.android.tools.r8.utils.ObjectUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.verticalclassmerging.IncompleteVerticalClassMergerBridgeCode;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
@@ -34,6 +36,8 @@
     assert appView.testing().canUseLir(appView);
     assert appView.testing().isPreLirPhase();
     appView.testing().enterLirSupportedPhase();
+    ConstResourceNumberRewriter constResourceNumberRewriter =
+        new ConstResourceNumberRewriter(appView);
     // Convert code objects to LIR.
     ThreadUtils.processItems(
         appView.appInfo().classes(),
@@ -45,10 +49,11 @@
           clazz.forEachProgramMethodMatching(
               method ->
                   method.hasCode()
-                      && !method.isInitializer()
+                      && !method.isInstanceInitializer()
                       && !appView.isCfByteCodePassThrough(method),
               method -> {
                 IRCode code = method.buildIR(appView, MethodConversionOptions.forLirPhase(appView));
+                constResourceNumberRewriter.run(code, Timing.empty());
                 LirCode<Integer> lirCode =
                     IR2LirConverter.translate(
                         code,
@@ -90,7 +95,7 @@
 
     GraphLens graphLens = appView.graphLens();
     assert graphLens.isNonIdentityLens();
-    assert appView.codeLens().isAppliedLens();
+    assert appView.codeLens().isAppliedLens() || appView.codeLens().isClearCodeRewritingLens();
 
     MemberRebindingIdentityLens memberRebindingIdentityLens =
         graphLens.asNonIdentityLens().find(GraphLens::isMemberRebindingIdentityLens);
@@ -104,9 +109,6 @@
     timing.begin("LIR->LIR@" + graphLens.getClass().getTypeName());
     rewriteLirWithUnappliedLens(appView, executorService);
     timing.end();
-
-    // At this point all code has been mapped according to the graph lens.
-    updateCodeLens(appView);
   }
 
   private static void rewriteLirWithUnappliedLens(
@@ -117,10 +119,7 @@
         appView.appInfo().classes(),
         clazz ->
             clazz.forEachProgramMethodMatching(
-                m ->
-                    m.hasCode()
-                        && !m.getCode().isSharedCodeObject()
-                        && !appView.isCfByteCodePassThrough(m),
+                m -> m.hasCode() && m.getCode().isLirCode(),
                 m -> rewriteLirMethodWithLens(m, appView, rewriterUtils)),
         appView.options().getThreadingModule(),
         executorService);
@@ -133,14 +132,8 @@
       ProgramMethod method,
       AppView<? extends AppInfoWithClassHierarchy> appView,
       LensCodeRewriterUtils rewriterUtils) {
-    Code code = method.getDefinition().getCode();
-    if (!code.isLirCode()) {
-      assert false;
-      return;
-    }
-    LirCode<Integer> lirCode = code.asLirCode();
-    LirCode<Integer> rewrittenLirCode =
-        lirCode.rewriteWithSimpleLens(method, appView, rewriterUtils);
+    LirCode<Integer> lirCode = method.getDefinition().getCode().asLirCode();
+    LirCode<Integer> rewrittenLirCode = lirCode.rewriteWithLens(method, appView, rewriterUtils);
     if (ObjectUtils.notIdentical(lirCode, rewrittenLirCode)) {
       method.setCode(rewrittenLirCode, appView);
     }
@@ -171,67 +164,7 @@
     // Clear the reference type cache after conversion to reduce memory pressure.
     appView.dexItemFactory().clearTypeElementsCache();
     // At this point all code has been mapped according to the graph lens.
-    updateCodeLens(appView);
-  }
-
-  private static void updateCodeLens(AppView<? extends AppInfoWithClassHierarchy> appView) {
-    final NonIdentityGraphLens lens = appView.graphLens().asNonIdentityLens();
-    if (lens == null) {
-      assert false;
-      return;
-    }
-
-    // If the current graph lens is the member rebinding identity lens then code lens is simply
-    // the previous lens. This is the same structure as the more complicated case below but where
-    // there is no need to rewrite any previous pointers.
-    if (lens.isMemberRebindingIdentityLens()) {
-      appView.setCodeLens(lens.getPrevious());
-      return;
-    }
-
-    // Otherwise search out where the lens pointing to the member rebinding identity lens.
-    NonIdentityGraphLens lensAfterMemberRebindingIdentityLens =
-        lens.find(p -> p.getPrevious().isMemberRebindingIdentityLens());
-    if (lensAfterMemberRebindingIdentityLens == null) {
-      // With the current compiler structure we expect to always find the lens.
-      assert false;
-      appView.setCodeLens(lens);
-      return;
-    }
-
-    GraphLens codeLens = appView.codeLens();
-    MemberRebindingIdentityLens memberRebindingIdentityLens =
-        lensAfterMemberRebindingIdentityLens.getPrevious().asMemberRebindingIdentityLens();
-
-    // We are assuming that the member rebinding identity lens is always installed after the current
-    // applied lens/code lens and also that there should not be a rebinding lens from the compilers
-    // first phase (this subroutine is only used after IR conversion for now).
-    assert memberRebindingIdentityLens
-        == lens.findPrevious(
-            p -> p == memberRebindingIdentityLens || p == codeLens || p.isMemberRebindingLens());
-
-    // Rewrite the graph lens effects from 'lens' and up to the member rebinding identity lens.
-    MemberRebindingIdentityLens rewrittenMemberRebindingLens =
-        memberRebindingIdentityLens.toRewrittenMemberRebindingIdentityLens(
-            appView, lens, memberRebindingIdentityLens, lens);
-
-    // The current previous pointers for the graph lenses are:
-    //   lens -> ... -> lensAfterMemberRebindingIdentityLens -> memberRebindingIdentityLens -> g
-    // we rewrite them now to:
-    //   rewrittenMemberRebindingLens -> lens -> ... -> lensAfterMemberRebindingIdentityLens -> g
-
-    // The above will construct the new member rebinding lens such that it points to the new
-    // code-lens point already.
-    assert rewrittenMemberRebindingLens.getPrevious() == lens;
-
-    // Update the previous pointer on the new code lens to jump over the old member rebinding
-    // identity lens.
-    lensAfterMemberRebindingIdentityLens.setPrevious(memberRebindingIdentityLens.getPrevious());
-
-    // The applied lens can now be updated and the rewritten member rebinding lens installed as
-    // the current "unapplied lens".
-    appView.setCodeLens(lens);
-    appView.setGraphLens(rewrittenMemberRebindingLens);
+    appView.clearCodeRewritings(executorService, timing);
   }
 
   private static void finalizeLirMethodToOutputFormat(
@@ -245,12 +178,14 @@
     }
     Timing onThreadTiming = Timing.empty();
     LirCode<Integer> lirCode = code.asLirCode();
-    LirCode<Integer> rewrittenLirCode =
-        lirCode.rewriteWithSimpleLens(method, appView, rewriterUtils);
+    LirCode<Integer> rewrittenLirCode = lirCode.rewriteWithLens(method, appView, rewriterUtils);
     if (ObjectUtils.notIdentical(lirCode, rewrittenLirCode)) {
       method.setCode(rewrittenLirCode, appView);
     }
     IRCode irCode = method.buildIR(appView, MethodConversionOptions.forPostLirPhase(appView));
+    assert irCode.verifyInvokeInterface(appView);
+    ConstResourceNumberRemover constResourceNumberRemover = new ConstResourceNumberRemover(appView);
+    constResourceNumberRemover.run(irCode, onThreadTiming);
     FilledNewArrayRewriter filledNewArrayRewriter = new FilledNewArrayRewriter(appView);
     boolean changed = filledNewArrayRewriter.run(irCode, onThreadTiming).hasChanged().toBoolean();
     if (appView.options().isGeneratingDex() && changed) {
@@ -275,6 +210,7 @@
       for (DexEncodedMethod method : clazz.methods(DexEncodedMethod::hasCode)) {
         assert method.getCode().isLirCode()
             || method.getCode().isSharedCodeObject()
+            || method.getCode() instanceof IncompleteVerticalClassMergerBridgeCode
             || appView.isCfByteCodePassThrough(method)
             || appView.options().skipIR;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 8f4c441..c2fb0e6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -23,8 +23,6 @@
 
 public interface MethodOptimizationFeedback {
 
-  void markForceInline(DexEncodedMethod method);
-
   void markInlinedIntoSingleCallSite(DexEncodedMethod method);
 
   void markMethodCannotBeKept(DexEncodedMethod method);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
index f782127..436969d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
@@ -118,7 +118,7 @@
     // All the code has been processed so the rewriting required by the lenses is done everywhere,
     // we clear lens code rewriting so that the lens rewriter can be re-executed in phase 2 if new
     // lenses with code rewriting are added.
-    appView.clearCodeRewritings(executorService);
+    appView.clearCodeRewritings(executorService, Timing.empty());
 
     // Commit synthetics from the primary optimization pass.
     commitPendingSyntheticItems(appView);
@@ -203,7 +203,7 @@
     // All the code that should be impacted by the lenses inserted between phase 1 and phase 2
     // have now been processed and rewritten, we clear code lens rewriting so that the class
     // staticizer and phase 3 does not perform again the rewriting.
-    appView.clearCodeRewritings(executorService);
+    appView.clearCodeRewritings(executorService, Timing.empty());
 
     // Commit synthetics before creating a builder (otherwise the builder will not include the
     // synthetics.)
@@ -263,17 +263,12 @@
       onWaveDoneActions.forEach(com.android.tools.r8.utils.Action::execute);
       onWaveDoneActions = null;
     }
-    if (prunedItemsBuilder.hasFullyInlinedMethods() || prunedItemsBuilder.hasRemovedMethods()) {
-      appView.pruneItems(
-          prunedItemsBuilder.setPrunedApp(appView.app()).build(), executorService, timing);
-      prunedItemsBuilder.clearFullyInlinedMethods();
-      prunedItemsBuilder.clearRemovedMethods();
-    }
   }
 
   private void lastWaveDone(
       PostMethodProcessor.Builder postMethodProcessorBuilder, ExecutorService executorService)
       throws ExecutionException {
+    pruneItems(executorService);
     if (assertionErrorTwoArgsConstructorRewriter != null) {
       assertionErrorTwoArgsConstructorRewriter.onLastWaveDone(postMethodProcessorBuilder);
       assertionErrorTwoArgsConstructorRewriter = null;
@@ -289,4 +284,13 @@
     // Ensure determinism of method-to-reprocess set.
     appView.testing().checkDeterminism(postMethodProcessorBuilder::dump);
   }
+
+  public void pruneItems(ExecutorService executorService) throws ExecutionException {
+    if (prunedItemsBuilder.hasFullyInlinedMethods() || prunedItemsBuilder.hasRemovedMethods()) {
+      appView.pruneItems(
+          prunedItemsBuilder.setPrunedApp(appView.app()).build(), executorService, timing);
+      prunedItemsBuilder.clearFullyInlinedMethods();
+      prunedItemsBuilder.clearRemovedMethods();
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java b/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
index 925f84c..edd3db2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
@@ -144,16 +144,15 @@
     ArrayList<Value> stillImprecise = constrainValues(true, remainingImpreciseValues);
     if (!stillImprecise.isEmpty()) {
       throw appView
-          .options()
-          .reporter
+          .reporter()
           .fatalError(
               new StringDiagnostic(
                   "Cannot determine precise type for value: "
                       + stillImprecise.get(0)
                       + ", its imprecise type is: "
                       + stillImprecise.get(0).getType(),
-                  code.origin,
-                  new MethodPosition(code.method().getReference().asMethodReference())));
+                  code.context().getOrigin(),
+                  new MethodPosition(code.context().getMethodReference())));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ConstResourceNumberRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ConstResourceNumberRemover.java
new file mode 100644
index 0000000..ca379dd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ConstResourceNumberRemover.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.conversion.passes;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.ResourceConstNumber;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
+
+public class ConstResourceNumberRemover extends CodeRewriterPass<AppInfo> {
+
+  public ConstResourceNumberRemover(AppView<?> appView) {
+    super(appView);
+  }
+
+  @Override
+  protected String getRewriterId() {
+    return "ConstResourceNumberRemover";
+  }
+
+  @Override
+  protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
+    return code.metadata().mayHaveResourceConstNumber();
+  }
+
+  @Override
+  protected CodeRewriterResult rewriteCode(IRCode code) {
+    boolean hasChanged = false;
+    InstructionListIterator iterator = code.instructionListIterator();
+    while (iterator.hasNext()) {
+      Instruction current = iterator.next();
+      if (current.isResourceConstNumber()) {
+        ResourceConstNumber resourceConstNumber = current.asResourceConstNumber();
+        iterator.replaceCurrentInstruction(
+            new ConstNumber(resourceConstNumber.dest(), resourceConstNumber.getValue()));
+        hasChanged = true;
+      }
+    }
+    return CodeRewriterResult.hasChanged(hasChanged);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ConstResourceNumberRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ConstResourceNumberRewriter.java
new file mode 100644
index 0000000..e6806fc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ConstResourceNumberRewriter.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.conversion.passes;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.ResourceConstNumber;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
+import com.android.tools.r8.utils.DescriptorUtils;
+
+public class ConstResourceNumberRewriter extends CodeRewriterPass<AppInfo> {
+  public ConstResourceNumberRewriter(AppView<?> appView) {
+    super(appView);
+  }
+
+  @Override
+  protected String getRewriterId() {
+    return "ConstResourceNumberRewriter";
+  }
+
+  @Override
+  protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
+    return appView.options().isOptimizedResourceShrinking()
+        && code.context().getDefinition().isClassInitializer()
+        && isRClass(code.context().getHolder());
+  }
+
+  private boolean isRClass(DexProgramClass holder) {
+    return DescriptorUtils.isRClassDescriptor(holder.getType().toDescriptorString());
+  }
+
+  @Override
+  protected CodeRewriterResult rewriteCode(IRCode code) {
+    boolean hasChanged = false;
+    InstructionListIterator iterator = code.instructionListIterator();
+    while (iterator.hasNext()) {
+      Instruction current = iterator.next();
+      if (current.isConstNumber()) {
+        ConstNumber constNumber = current.asConstNumber();
+        // The resource const numbers should always have a single value here
+        Value currentValue = current.outValue();
+        if (currentValue.hasSingleUniqueUser() && !currentValue.hasPhiUsers()) {
+          Instruction singleUser = currentValue.singleUniqueUser();
+          if (singleUser.isStaticPut()
+              || singleUser.isNewArrayFilled()
+              || (singleUser.isArrayPut() && singleUser.asArrayPut().value() == currentValue)) {
+            iterator.replaceCurrentInstruction(
+                new ResourceConstNumber(constNumber.dest(), constNumber.getIntValue()));
+            hasChanged = true;
+          }
+        }
+      }
+    }
+    return CodeRewriterResult.hasChanged(hasChanged);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java
index 9584e8a..a213f7f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java
@@ -9,6 +9,7 @@
 import static com.android.tools.r8.ir.code.Opcodes.CONST_STRING;
 import static com.android.tools.r8.ir.code.Opcodes.DEX_ITEM_BASED_CONST_STRING;
 import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
+import static com.android.tools.r8.ir.code.Opcodes.RESOURCE_CONST_NUMBER;
 import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET;
 
 import com.android.tools.r8.errors.Unreachable;
@@ -29,6 +30,7 @@
 import com.android.tools.r8.ir.code.InstructionOrPhi;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ResourceConstNumber;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
@@ -466,6 +468,9 @@
         case CONST_NUMBER:
           copy = ConstNumber.copyOf(code, instruction.asConstNumber());
           break;
+        case RESOURCE_CONST_NUMBER:
+          copy = ResourceConstNumber.copyOf(code, instruction.asResourceConstNumber());
+          break;
         case CONST_STRING:
           copy = ConstString.copyOf(code, instruction.asConstString());
           break;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java
index 40b217e..71e0780 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java
@@ -69,7 +69,6 @@
             .buildIR(
                 programMethod,
                 appView,
-                programMethod.getOrigin(),
                 MethodConversionOptions.forLirPhase(appView));
     boolean done = false;
     ListIterator<BasicBlock> blockIterator = irCode.listIterator();
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 6880ed1..f10854f 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
@@ -440,7 +440,7 @@
                     isWrittenBefore.remove(fieldReference);
                   }
                   continue;
-                } else if (fieldReference.type.isPrimitiveType()
+                } else if ((fieldReference.type.isPrimitiveType() && !hasPutOfConstResource(put))
                     || fieldReference.type == dexItemFactory.stringType) {
                   finalFieldPuts.put(field, put);
                   unnecessaryStaticPuts.add(put);
@@ -507,6 +507,10 @@
     return validateFinalFieldPuts(finalFieldPuts, isWrittenBefore);
   }
 
+  private boolean hasPutOfConstResource(StaticPut put) {
+    return put.value().isConstResourceNumber();
+  }
+
   private Map<DexEncodedField, StaticPut> validateFinalFieldPuts(
       Map<DexEncodedField, StaticPut> finalFieldPuts,
       Map<DexField, Set<StaticPut>> isWrittenBefore) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index 1ef539a..c1f203a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -8,6 +8,7 @@
 import static com.android.tools.r8.ir.code.Opcodes.CONST_STRING;
 import static com.android.tools.r8.ir.code.Opcodes.DEX_ITEM_BASED_CONST_STRING;
 import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
+import static com.android.tools.r8.ir.code.Opcodes.RESOURCE_CONST_NUMBER;
 import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET;
 import static com.android.tools.r8.utils.MapUtils.ignoreKey;
 
@@ -39,6 +40,7 @@
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ResourceConstNumber;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.passes.BranchSimplifier;
@@ -164,6 +166,8 @@
                   case CONST_NUMBER:
                     return Long.hashCode(candidate.asConstNumber().getRawValue())
                         + 13 * candidate.outType().hashCode();
+                  case RESOURCE_CONST_NUMBER:
+                    return Integer.hashCode(candidate.asResourceConstNumber().getValue());
                   case CONST_STRING:
                     return candidate.asConstString().getValue().hashCode();
                   case DEX_ITEM_BASED_CONST_STRING:
@@ -349,6 +353,7 @@
     switch (newInstruction.opcode()) {
       case CONST_CLASS:
       case CONST_NUMBER:
+      case RESOURCE_CONST_NUMBER:
       case CONST_STRING:
       case DEX_ITEM_BASED_CONST_STRING:
       case STATIC_GET:
@@ -388,6 +393,8 @@
         return ConstClass.copyOf(code, canonicalizedConstant.asConstClass());
       case CONST_NUMBER:
         return ConstNumber.copyOf(code, canonicalizedConstant.asConstNumber());
+      case RESOURCE_CONST_NUMBER:
+        return ResourceConstNumber.copyOf(code, canonicalizedConstant.asResourceConstNumber());
       case CONST_STRING:
         return ConstString.copyOf(code, canonicalizedConstant.asConstString());
       case DEX_ITEM_BASED_CONST_STRING:
@@ -434,6 +441,7 @@
           return false;
         }
         break;
+      case RESOURCE_CONST_NUMBER:
       case CONST_NUMBER:
         break;
       case CONST_STRING:
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 734004b..c6bd2a2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -134,6 +134,7 @@
 
   @Override
   public boolean passesInliningConstraints(
+      IRCode code,
       SingleResolutionResult<?> resolutionResult,
       ProgramMethod singleTarget,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
@@ -158,7 +159,7 @@
       return false;
     }
 
-    if (canHaveIssuesWithMonitors(singleTarget, method)) {
+    if (canHaveIssuesWithMonitors(code, singleTarget)) {
       return false;
     }
 
@@ -198,16 +199,20 @@
     return true;
   }
 
-  private boolean canHaveIssuesWithMonitors(ProgramMethod singleTarget, ProgramMethod context) {
-    if (options.canHaveIssueWithInlinedMonitors() && hasMonitorsOrIsSynchronized(singleTarget)) {
-      return context.getOptimizationInfo().forceInline() || hasMonitorsOrIsSynchronized(context);
-    }
-    return false;
+  private boolean canHaveIssuesWithMonitors(IRCode code, ProgramMethod singleTarget) {
+    return options.canHaveIssueWithInlinedMonitors()
+        && hasMonitorsOrIsSynchronized(code)
+        && hasMonitorsOrIsSynchronized(singleTarget);
   }
 
-  public static boolean hasMonitorsOrIsSynchronized(ProgramMethod method) {
-    return method.getAccessFlags().isSynchronized()
-        || method.getDefinition().getCode().hasMonitorInstructions();
+  public static boolean hasMonitorsOrIsSynchronized(IRCode code) {
+    return code.context().getAccessFlags().isSynchronized()
+        || code.metadata().mayHaveMonitorInstruction();
+  }
+
+  public static boolean hasMonitorsOrIsSynchronized(ProgramMethod singleTarget) {
+    return singleTarget.getAccessFlags().isSynchronized()
+        || singleTarget.getDefinition().getCode().hasMonitorInstructions();
   }
 
   public boolean satisfiesRequirementsForSimpleInlining(
@@ -438,9 +443,7 @@
     }
 
     if (!passesInliningConstraints(
-        resolutionResult,
-        singleTarget,
-        whyAreYouNotInliningReporter)) {
+        code, resolutionResult, singleTarget, whyAreYouNotInliningReporter)) {
       return null;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index 5dc2341..a3fb7e7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -44,6 +44,7 @@
 
   @Override
   public boolean passesInliningConstraints(
+      IRCode code,
       SingleResolutionResult<?> resolutionResult,
       ProgramMethod candidate,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index ed9da5c..2a1fc09 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -22,7 +22,6 @@
 import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.proto.ProtoInliningReasonStrategy;
 import com.android.tools.r8.ir.analysis.type.Nullability;
@@ -151,8 +150,7 @@
     }
 
     ConstraintWithTarget result = ConstraintWithTarget.ALWAYS;
-    InliningConstraints inliningConstraints =
-        new InliningConstraints(appView, GraphLens.getIdentityLens());
+    InliningConstraints inliningConstraints = new InliningConstraints(appView);
     for (Instruction instruction : code.instructions()) {
       ConstraintWithTarget state =
           instructionAllowedForInlining(instruction, inliningConstraints, context);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index a3ea046..8f61ce7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.graph.FieldResolutionResult.SingleFieldResolutionResult;
 import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.ir.code.InvokeType;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -28,31 +27,14 @@
 
   private AppView<AppInfoWithLiveness> appView;
 
-  // Currently used only by the vertical class merger (in all other cases this is the identity).
-  //
-  // When merging a type A into its subtype B we need to inline A.<init>() into B.<init>().
-  // Therefore, we need to be sure that A.<init>() can in fact be inlined into B.<init>() *before*
-  // we merge the two classes. However, at this point, we may reject the method A.<init>() from
-  // being inlined into B.<init>() only because it is not declared in the same class as B (which
-  // it would be after merging A and B).
-  //
-  // To circumvent this problem, the vertical class merger creates a graph lens that maps the
-  // type A to B, to create a temporary view of what the world would look like after class merging.
-  private GraphLens graphLens;
-
-  public InliningConstraints(AppView<AppInfoWithLiveness> appView, GraphLens graphLens) {
+  public InliningConstraints(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
-    this.graphLens = graphLens; // Note: Intentionally *not* appView.graphLens().
   }
 
   public AppView<AppInfoWithLiveness> getAppView() {
     return appView;
   }
 
-  public GraphLens getGraphLens() {
-    return graphLens;
-  }
-
   public ConstraintWithTarget forAlwaysMaterializingUser() {
     return ConstraintWithTarget.ALWAYS;
   }
@@ -156,13 +138,11 @@
   }
 
   public ConstraintWithTarget forInvokeDirect(DexMethod method, ProgramMethod context) {
-    DexMethod lookup =
-        graphLens.lookupMethod(method, context.getReference(), InvokeType.DIRECT).getReference();
-    if (lookup.holder.isArrayType()) {
+    if (method.holder.isArrayType()) {
       return ConstraintWithTarget.ALWAYS;
     }
     SingleResolutionResult<?> resolutionResult =
-        appView.appInfo().unsafeResolveMethodDueToDexFormatLegacy(lookup).asSingleResolution();
+        appView.appInfo().unsafeResolveMethodDueToDexFormatLegacy(method).asSingleResolution();
     if (resolutionResult == null) {
       return ConstraintWithTarget.NEVER;
     }
@@ -175,9 +155,7 @@
   }
 
   public ConstraintWithTarget forInvokeInterface(DexMethod method, ProgramMethod context) {
-    DexMethod lookup =
-        graphLens.lookupMethod(method, context.getReference(), InvokeType.INTERFACE).getReference();
-    return forVirtualInvoke(lookup, context, true);
+    return forVirtualInvoke(method, context, true);
   }
 
   public ConstraintWithTarget forInvokeMultiNewArray(DexType type, ProgramMethod context) {
@@ -193,13 +171,11 @@
   }
 
   public ConstraintWithTarget forInvokeStatic(DexMethod method, ProgramMethod context) {
-    DexMethod lookup =
-        graphLens.lookupMethod(method, context.getReference(), InvokeType.STATIC).getReference();
-    if (lookup.holder.isArrayType()) {
+    if (method.holder.isArrayType()) {
       return ConstraintWithTarget.ALWAYS;
     }
     SingleResolutionResult<?> resolutionResult =
-        appView.appInfo().unsafeResolveMethodDueToDexFormatLegacy(lookup).asSingleResolution();
+        appView.appInfo().unsafeResolveMethodDueToDexFormatLegacy(method).asSingleResolution();
     if (resolutionResult == null) {
       return ConstraintWithTarget.NEVER;
     }
@@ -208,9 +184,6 @@
     if (target == null) {
       return ConstraintWithTarget.NEVER;
     }
-    if (target != null) {
-      return forResolvedMember(resolutionResult.getInitialResolutionHolder(), context, target);
-    }
     return forResolvedMember(resolutionResult.getInitialResolutionHolder(), context, target);
   }
 
@@ -220,9 +193,7 @@
   }
 
   public ConstraintWithTarget forInvokeVirtual(DexMethod method, ProgramMethod context) {
-    DexMethod lookup =
-        graphLens.lookupMethod(method, context.getReference(), InvokeType.VIRTUAL).getReference();
-    return forVirtualInvoke(lookup, context, false);
+    return forVirtualInvoke(method, context, false);
   }
 
   public ConstraintWithTarget forJumpInstruction() {
@@ -310,9 +281,8 @@
   }
 
   private ConstraintWithTarget forFieldInstruction(DexField field, ProgramMethod context) {
-    DexField lookup = graphLens.lookupField(field);
     SingleFieldResolutionResult<?> fieldResolutionResult =
-        appView.appInfo().resolveField(lookup).asSingleFieldResolutionResult();
+        appView.appInfo().resolveField(field).asSingleFieldResolutionResult();
     if (fieldResolutionResult == null) {
       return ConstraintWithTarget.NEVER;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index 69d5ecf..a684ab7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -77,6 +77,7 @@
   void markInlined(IRCode inlinee);
 
   boolean passesInliningConstraints(
+      IRCode code,
       SingleResolutionResult<?> resolutionResult,
       ProgramMethod candidate,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
index ee03422..d9acaba 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
@@ -442,6 +442,7 @@
                       || instruction.isConstMethodHandle()
                       || instruction.isConstMethodType()
                       || instruction.isConstNumber()
+                      || instruction.isResourceConstNumber()
                       || instruction.isConstString()
                       || instruction.isDebugInstruction()
                       || instruction.isDexItemBasedConstString()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index e39ebf6..9e7e988 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -26,7 +26,6 @@
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.PostMethodProcessor;
 import com.android.tools.r8.ir.desugar.ServiceLoaderSourceCode;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.ListUtils;
@@ -114,7 +113,7 @@
       // Check that the first argument is a const class.
       Value argument = serviceLoaderLoad.inValues().get(0).getAliasedValue();
       if (argument.isPhi() || !argument.definition.isConstClass()) {
-        report(code.origin, null, "The service loader type could not be determined");
+        report(code.context(), null, "The service loader type could not be determined");
         continue;
       }
 
@@ -122,7 +121,7 @@
 
       if (invokedMethod != serviceLoaderMethods.loadWithClassLoader) {
         report(
-            code.origin,
+            code.context(),
             constClass.getType(),
             "Inlining is only support for `java.util.ServiceLoader.load(java.lang.Class,"
                 + " java.lang.ClassLoader)`");
@@ -134,7 +133,7 @@
               + " java.lang.ServiceLoader.iterator()`";
       Value serviceLoaderLoadOut = serviceLoaderLoad.outValue();
       if (serviceLoaderLoadOut.numberOfAllUsers() != 1 || serviceLoaderLoadOut.hasPhiUsers()) {
-        report(code.origin, constClass.getType(), invalidUserMessage);
+        report(code.context(), constClass.getType(), invalidUserMessage);
         continue;
       }
 
@@ -142,13 +141,14 @@
       if (!serviceLoaderLoadOut.singleUniqueUser().isInvokeVirtual()
           || serviceLoaderLoadOut.singleUniqueUser().asInvokeVirtual().getInvokedMethod()
               != serviceLoaderMethods.iterator) {
-        report(code.origin, constClass.getType(), invalidUserMessage + ", but found other usages");
+        report(
+            code.context(), constClass.getType(), invalidUserMessage + ", but found other usages");
         continue;
       }
 
       // Check that the service is not kept.
       if (appView.appInfo().isPinnedWithDefinitionLookup(constClass.getValue())) {
-        report(code.origin, constClass.getType(), "The service loader type is kept");
+        report(code.context(), constClass.getType(), "The service loader type is kept");
         continue;
       }
 
@@ -162,7 +162,7 @@
       // Check that we are not service loading anything from a feature into base.
       if (appServices.hasServiceImplementationsInFeature(appView, constClass.getValue())) {
         report(
-            code.origin,
+            code.context(),
             constClass.getType(),
             "The service loader type has implementations in a feature split");
         continue;
@@ -172,7 +172,7 @@
       // that we are instantiating or NULL.
       if (serviceLoaderLoad.inValues().get(1).isPhi()) {
         report(
-            code.origin,
+            code.context(),
             constClass.getType(),
             "The java.lang.ClassLoader argument must be defined locally as null or "
                 + constClass.getType()
@@ -195,7 +195,7 @@
                       == constClass.getValue());
       if (!isGetClassLoaderOnConstClassOrNull) {
         report(
-            code.origin,
+            code.context(),
             constClass.getType(),
             "The java.lang.ClassLoader argument must be defined locally as null or "
                 + constClass.getType()
@@ -210,7 +210,7 @@
         DexClass serviceImplementation = appView.definitionFor(serviceImpl);
         if (serviceImplementation == null) {
           report(
-              code.origin,
+              code.context(),
               constClass.getType(),
               "Unable to find definition for service implementation " + serviceImpl.getTypeName());
           seenNull = true;
@@ -247,11 +247,11 @@
     postMethodProcessorBuilder.addAll(synthesizedServiceLoadMethods, appView.graphLens());
   }
 
-  private void report(Origin origin, DexType serviceLoaderType, String message) {
+  private void report(ProgramMethod method, DexType serviceLoaderType, String message) {
     if (reporter != null) {
       reporter.info(
           new ServiceLoaderRewriterDiagnostic(
-              origin,
+              method.getOrigin(),
               "Could not inline ServiceLoader.load"
                   + (serviceLoaderType == 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 b192f8a..a359197 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
@@ -153,12 +153,7 @@
       while (rootsIterator.hasNext()) {
         Instruction root = rootsIterator.next();
         InlineCandidateProcessor processor =
-            new InlineCandidateProcessor(
-                appView,
-                inliner,
-                methodProcessor,
-                method,
-                root);
+            new InlineCandidateProcessor(appView, code, inliner, methodProcessor, method, root);
 
         // Assess eligibility of instance and class.
         EligibilityStatus status = processor.isInstanceEligible();
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 256abbb..46d4883 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
@@ -90,6 +90,7 @@
       AssumeAndCheckCastAliasedValueConfiguration.getInstance();
 
   private final AppView<AppInfoWithLiveness> appView;
+  private final IRCode code;
   private final DexItemFactory dexItemFactory;
   private final Inliner inliner;
   private final MethodProcessor methodProcessor;
@@ -113,11 +114,13 @@
 
   InlineCandidateProcessor(
       AppView<AppInfoWithLiveness> appView,
+      IRCode code,
       Inliner inliner,
       MethodProcessor methodProcessor,
       ProgramMethod method,
       Instruction root) {
     this.appView = appView;
+    this.code = code;
     this.dexItemFactory = appView.dexItemFactory();
     this.inliner = inliner;
     this.method = method;
@@ -1167,9 +1170,7 @@
     // Check if the method is inline-able by standard inliner.
     InliningOracle oracle = defaultOracle.computeIfAbsent();
     if (!oracle.passesInliningConstraints(
-        resolutionResult,
-        singleTarget,
-        NopWhyAreYouNotInliningReporter.getInstance())) {
+        code, resolutionResult, singleTarget, NopWhyAreYouNotInliningReporter.getInstance())) {
       return false;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java
index b17f4b6..5c37f51 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java
@@ -22,7 +22,6 @@
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxerImpl;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.RetracerForCodePrinting;
 
@@ -48,7 +47,6 @@
   public IRCode buildIR(
       ProgramMethod checkNotZeroMethod,
       AppView<?> appView,
-      Origin origin,
       MutableMethodConversionOptions conversionOptions) {
     // Build IR from the checkNotNull() method.
     Position callerPosition =
@@ -69,7 +67,6 @@
                 appView.graphLens(),
                 valueNumberGenerator,
                 callerPosition,
-                checkNotZeroMethod.getOrigin(),
                 appView
                     .graphLens()
                     .lookupPrototypeChangesForMethodDefinition(checkNotNullMethod.getReference()));
@@ -112,7 +109,6 @@
         code.valueNumberGenerator,
         code.basicBlockNumberGenerator,
         code.metadata(),
-        checkNotZeroMethod.getOrigin(),
         conversionOptions);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index f4de742..8add480 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -162,11 +162,6 @@
   // METHOD OPTIMIZATION INFO:
 
   @Override
-  public void markForceInline(DexEncodedMethod method) {
-    getMethodOptimizationInfoForUpdating(method).markForceInline();
-  }
-
-  @Override
   public synchronized void markInlinedIntoSingleCallSite(DexEncodedMethod method) {
     getMethodOptimizationInfoForUpdating(method).markInlinedIntoSingleCallSite();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index 05a1cdc..bb8f841 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -55,9 +55,6 @@
   // METHOD OPTIMIZATION INFO:
 
   @Override
-  public void markForceInline(DexEncodedMethod method) {}
-
-  @Override
   public void markInlinedIntoSingleCallSite(DexEncodedMethod method) {}
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 339d9eb..34bb23b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -83,11 +83,6 @@
   }
 
   @Override
-  public void markForceInline(DexEncodedMethod method) {
-    // Ignored.
-  }
-
-  @Override
   public void markInlinedIntoSingleCallSite(DexEncodedMethod method) {
     method.getMutableOptimizationInfo().markInlinedIntoSingleCallSite();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
index c62a014..e63c416 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.conversion.LensCodeRewriter;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
-import com.android.tools.r8.origin.Origin;
 import java.util.IdentityHashMap;
 import java.util.Map;
 
@@ -82,14 +81,12 @@
     if (cached != null) {
       return cached;
     }
-    Origin origin = method.getOrigin();
     IRCode code =
         method.buildInliningIR(
             context,
             appView,
             valueNumberGenerator,
             Position.getPositionForInlining(invoke, context),
-            origin,
             methodProcessor);
     if (lensCodeRewriter != null && methodProcessor.shouldApplyCodeRewritings(method)) {
       lensCodeRewriter.rewrite(code, method, methodProcessor);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ResourcesMemberOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ResourcesMemberOptimizer.java
index 1246516..207d757 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/ResourcesMemberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ResourcesMemberOptimizer.java
@@ -8,13 +8,10 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.FieldResolutionResult;
-import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlockIterator;
@@ -127,19 +124,16 @@
       assert invokedMethod.isIdenticalTo(dexItemFactory.androidResourcesGetStringMethod);
       assert invoke.inValues().size() == 2;
       Instruction valueDefinition = invoke.getLastArgument().definition;
-      if (valueDefinition != null && valueDefinition.isStaticGet()) {
-        DexField field = valueDefinition.asStaticGet().getField();
-        FieldResolutionResult fieldResolutionResult =
-            appView.appInfo().resolveField(field, code.context());
-        ProgramField resolvedField = fieldResolutionResult.getProgramField();
-        if (resolvedField != null) {
-          String singleStringValueForField =
-              appView.getResourceAnalysisResult().getSingleStringValueForField(resolvedField);
-          if (singleStringValueForField != null) {
-            DexString value = dexItemFactory.createString(singleStringValueForField);
-            instructionIterator.replaceCurrentInstructionWithConstString(
-                appView, code, value, affectedValues);
-          }
+      if (valueDefinition.isResourceConstNumber()) {
+        String singleStringValue =
+            appView
+                .getResourceShrinkerState()
+                .getR8ResourceShrinkerModel()
+                .getSingleStringValueOrNull(valueDefinition.asResourceConstNumber().getValue());
+        if (singleStringValue != null) {
+          DexString value = dexItemFactory.createString(singleStringValue);
+          instructionIterator.replaceCurrentInstructionWithConstString(
+              appView, code, value, affectedValues);
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java
index 9297330..20f7598 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java
@@ -71,7 +71,6 @@
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
@@ -1360,7 +1359,7 @@
   public OutlinerImpl(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
     this.dexItemFactory = appView.dexItemFactory();
-    this.inliningConstraints = new InliningConstraints(appView, GraphLens.getIdentityLens());
+    this.inliningConstraints = new InliningConstraints(appView);
   }
 
   @Override
@@ -1855,10 +1854,9 @@
     public IRCode buildIR(
         ProgramMethod method,
         AppView<?> appView,
-        Origin origin,
         MutableMethodConversionOptions conversionOptions) {
       OutlineSourceCode source = new OutlineSourceCode(outline, method.getReference());
-      return IRBuilder.create(method, appView, source, origin).build(method, conversionOptions);
+      return IRBuilder.create(method, appView, source).build(method, conversionOptions);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
index a6b6c18..7c5240b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
@@ -140,8 +140,7 @@
               method.getHolderType(), abstractParentReturnValue.isTrue()),
           appView);
       // Rebuild inlining constraints.
-      IRCode code =
-          parentMethodDefinition.getCode().buildIR(parentMethod, appView, parentMethod.getOrigin());
+      IRCode code = parentMethodDefinition.getCode().buildIR(parentMethod, appView);
       converter.markProcessed(code, feedback);
       // Fixup method optimization info (the method no longer returns a constant).
       feedback.fixupUnusedArguments(parentMethod, unusedArguments -> unusedArguments.clear(0));
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 9307108..2acd209 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -621,9 +621,9 @@
     if (intervals == null) {
       throw new CompilationError(
           "Unexpected attempt to get register for a value without a register in method `"
-              + code.method().getReference().toSourceString()
+              + code.context().toSourceString()
               + "`.",
-          code.origin);
+          code.context().getOrigin());
     }
     if (intervals.hasSplits()) {
       intervals = intervals.getSplitCovering(instructionNumber);
diff --git a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
index 1bd59ff..d7ee8d3 100644
--- a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
+++ b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
@@ -83,6 +83,7 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.RecordFieldValues;
 import com.android.tools.r8.ir.code.Rem;
+import com.android.tools.r8.ir.code.ResourceConstNumber;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.SafeCheckCast;
 import com.android.tools.r8.ir.code.Shl;
@@ -352,7 +353,6 @@
           strategy.getValueNumberGenerator(),
           basicBlockNumberGenerator,
           code.getMetadataForIR(),
-          method.getOrigin(),
           conversionOptions);
     }
 
@@ -519,6 +519,12 @@
     }
 
     @Override
+    public void onConstResourceNumber(int value) {
+      Value dest = getOutValueForNextInstruction(TypeElement.getInt());
+      addInstruction(new ResourceConstNumber(dest, value));
+    }
+
+    @Override
     public void onAdd(NumericType type, EV leftValueIndex, EV rightValueIndex) {
       Value dest = getOutValueForNextInstruction(valueTypeElement(type));
       addInstruction(
diff --git a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
index 5d988d8..c243c7b 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
@@ -579,6 +579,13 @@
     }
   }
 
+  public LirBuilder<V, EV> addResourceConstNumber(int value) {
+    advanceInstructionState();
+    writer.writeInstruction(LirOpcodes.RESOURCENUMBER, ByteUtils.intEncodingSize(value));
+    ByteUtils.writeEncodedInt(value, writer::writeOperand);
+    return this;
+  }
+
   public LirBuilder<V, EV> addConstString(DexString string) {
     return addOneItemInstruction(LirOpcodes.LDC, string);
   }
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 29c7ca6..a571e49 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirCode.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirCode.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.dex.code.CfOrDexInstruction;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ArgumentUse;
 import com.android.tools.r8.graph.ClasspathMethod;
@@ -33,11 +34,11 @@
 import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.lightir.LirConstant.LirConstantStructuralAcceptor;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.ComparatorUtils;
 import com.android.tools.r8.utils.FastMapUtils;
 import com.android.tools.r8.utils.IntBox;
+import com.android.tools.r8.utils.IntObjPredicate;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
@@ -47,6 +48,7 @@
 import com.android.tools.r8.utils.structural.StructuralMapping;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import java.util.ArrayList;
@@ -183,6 +185,12 @@
       this.tryCatchHandlers = new Int2ReferenceOpenHashMap<>(tryCatchHandlers);
     }
 
+    public boolean hasHandlerThatMatches(IntObjPredicate<CatchHandlers<Integer>> predicate) {
+      return Iterables.any(
+          tryCatchHandlers.int2ReferenceEntrySet(),
+          entry -> predicate.test(entry.getIntKey(), entry.getValue()));
+    }
+
     public CatchHandlers<Integer> getHandlersForBlock(int blockIndex) {
       return tryCatchHandlers.get(blockIndex);
     }
@@ -490,6 +498,10 @@
     return positionTable;
   }
 
+  public boolean hasTryCatchTable() {
+    return tryCatchTable != null;
+  }
+
   public TryCatchTable getTryCatchTable() {
     return tryCatchTable;
   }
@@ -532,7 +544,6 @@
   public IRCode buildIR(
       ProgramMethod method,
       AppView<?> appView,
-      Origin origin,
       MutableMethodConversionOptions conversionOptions) {
     RewrittenPrototypeDescription protoChanges =
         appView.graphLens().lookupPrototypeChangesForMethodDefinition(method.getReference());
@@ -548,7 +559,6 @@
       GraphLens codeLens,
       NumberGenerator valueNumberGenerator,
       Position callerPosition,
-      Origin origin,
       RewrittenPrototypeDescription protoChanges) {
     assert valueNumberGenerator != null;
     assert callerPosition != null;
@@ -802,17 +812,18 @@
         metadataMap);
   }
 
-  public LirCode<EV> rewriteWithSimpleLens(
-      ProgramMethod context, AppView<?> appView, LensCodeRewriterUtils rewriterUtils) {
+  public LirCode<EV> rewriteWithLens(
+      ProgramMethod context,
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      LensCodeRewriterUtils rewriterUtils) {
     GraphLens graphLens = appView.graphLens();
     assert graphLens.isNonIdentityLens();
     if (graphLens.isMemberRebindingIdentityLens()) {
       return this;
     }
 
-    GraphLens codeLens = context.getDefinition().getCode().getCodeLens(appView);
-    SimpleLensLirRewriter<EV> rewriter =
-        new SimpleLensLirRewriter<>(this, context, graphLens, codeLens, rewriterUtils);
+    LirLensCodeRewriter<EV> rewriter =
+        new LirLensCodeRewriter<>(appView, this, context, rewriterUtils);
     return rewriter.rewrite();
   }
 
diff --git a/src/main/java/com/android/tools/r8/lightir/LirLensCodeRewriter.java b/src/main/java/com/android/tools/r8/lightir/LirLensCodeRewriter.java
new file mode 100644
index 0000000..b4a6cb7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/LirLensCodeRewriter.java
@@ -0,0 +1,481 @@
+// Copyright (c) 2024, 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.lightir;
+
+import static com.android.tools.r8.graph.UseRegistry.MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.graph.lens.FieldLookupResult;
+import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.graph.lens.MethodLookupResult;
+import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.IRMetadata;
+import com.android.tools.r8.ir.code.IROpcodeUtils;
+import com.android.tools.r8.ir.code.InvokeType;
+import com.android.tools.r8.ir.conversion.IRToLirFinalizer;
+import com.android.tools.r8.ir.conversion.LensCodeRewriter;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.optimize.AffectedValues;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover;
+import com.android.tools.r8.lightir.LirBuilder.RecordFieldValuesPayload;
+import com.android.tools.r8.lightir.LirCode.TryCatchTable;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.verticalclassmerging.VerticalClassMergerGraphLens;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class LirLensCodeRewriter<EV> extends LirParsedInstructionCallback<EV> {
+
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final ProgramMethod context;
+  private final DexMethod contextReference;
+  private final GraphLens graphLens;
+  private final GraphLens codeLens;
+  private final LensCodeRewriterUtils helper;
+
+  private int numberOfInvokeOpcodeChanges = 0;
+  private Map<LirConstant, LirConstant> constantPoolMapping = null;
+
+  private boolean hasNonTrivialRewritings = false;
+
+  public LirLensCodeRewriter(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      LirCode<EV> code,
+      ProgramMethod context,
+      LensCodeRewriterUtils helper) {
+    super(code);
+    this.appView = appView;
+    this.context = context;
+    this.contextReference = context.getReference();
+    this.graphLens = appView.graphLens();
+    this.codeLens = context.getDefinition().getCode().getCodeLens(appView);
+    this.helper = helper;
+  }
+
+  @Override
+  public int getCurrentValueIndex() {
+    // We do not need to interpret values.
+    return -1;
+  }
+
+  public void onTypeReference(DexType type) {
+    addRewrittenMapping(type, graphLens.lookupType(type, codeLens));
+  }
+
+  public void onFieldReference(DexField field) {
+    FieldLookupResult result = graphLens.lookupFieldResult(field, codeLens);
+    assert !result.hasReadCastType();
+    assert !result.hasWriteCastType();
+    addRewrittenMapping(field, result.getReference());
+  }
+
+  public void onCallSiteReference(DexCallSite callSite) {
+    addRewrittenMapping(callSite, helper.rewriteCallSite(callSite, context));
+  }
+
+  public void onMethodHandleReference(DexMethodHandle methodHandle) {
+    addRewrittenMapping(
+        methodHandle,
+        helper.rewriteDexMethodHandle(methodHandle, NOT_ARGUMENT_TO_LAMBDA_METAFACTORY, context));
+  }
+
+  public void onProtoReference(DexProto proto) {
+    addRewrittenMapping(proto, helper.rewriteProto(proto));
+  }
+
+  private void onInvoke(DexMethod method, InvokeType type, boolean isInterface) {
+    MethodLookupResult result = graphLens.lookupMethod(method, contextReference, type, codeLens);
+    if (hasPotentialNonTrivialInvokeRewriting(method, type, result)) {
+      hasNonTrivialRewritings = true;
+      return;
+    }
+    int opcode = type.getLirOpcode(isInterface);
+    DexMethod newMethod = result.getReference();
+    InvokeType newType = result.getType();
+    boolean newIsInterface = lookupIsInterface(method, opcode, result);
+    int newOpcode = newType.getLirOpcode(newIsInterface);
+    assert newMethod.getArity() == method.getArity();
+    if (newOpcode != opcode) {
+      assert type == newType
+              || (type.isVirtual() && newType.isInterface())
+              || (type.isInterface() && newType.isVirtual())
+              || (type.isSuper() && newType.isVirtual())
+          : type + " -> " + newType;
+      numberOfInvokeOpcodeChanges++;
+    } else {
+      // All non-type dependent mappings are just rewritten in the content pool.
+      addRewrittenMapping(method, newMethod);
+    }
+  }
+
+  private boolean hasPotentialNonTrivialInvokeRewriting(
+      DexMethod method, InvokeType type, MethodLookupResult result) {
+    VerticalClassMergerGraphLens verticalClassMergerLens = graphLens.asVerticalClassMergerLens();
+    if (verticalClassMergerLens != null) {
+      if (!result.getPrototypeChanges().isEmpty()) {
+        return true;
+      }
+      for (int argumentIndex = 0;
+          argumentIndex < method.getNumberOfArguments(type.isStatic());
+          argumentIndex++) {
+        DexType argumentType = method.getArgumentType(argumentIndex, type.isStatic());
+        if (verticalClassMergerLens.hasInterfaceBeenMergedIntoClass(argumentType)) {
+          return true;
+        }
+      }
+    }
+    assert result.getPrototypeChanges().isEmpty();
+    return false;
+  }
+
+  private void addRewrittenMapping(LirConstant item, LirConstant rewrittenItem) {
+    if (item == rewrittenItem) {
+      return;
+    }
+    if (constantPoolMapping == null) {
+      constantPoolMapping =
+          new IdentityHashMap<>(
+              // Avoid using initial capacity larger than the number of actual constants.
+              Math.min(getCode().getConstantPool().length, 32));
+    }
+    LirConstant old = constantPoolMapping.put(item, rewrittenItem);
+    if (old != null && old != rewrittenItem) {
+      throw new Unreachable(
+          "Unexpected rewriting of item: "
+              + item
+              + " to two distinct items: "
+              + rewrittenItem
+              + " and "
+              + old);
+    }
+  }
+
+  @Override
+  public void onInstancePut(DexField field, EV object, EV value) {
+    onFieldPut(field);
+  }
+
+  @Override
+  public void onStaticPut(DexField field, EV value) {
+    onFieldPut(field);
+  }
+
+  private void onFieldPut(DexField field) {
+    if (hasPotentialNonTrivialFieldPutRewriting(field)) {
+      hasNonTrivialRewritings = true;
+    }
+  }
+
+  private boolean hasPotentialNonTrivialFieldPutRewriting(DexField field) {
+    VerticalClassMergerGraphLens verticalClassMergerLens = graphLens.asVerticalClassMergerLens();
+    if (verticalClassMergerLens != null
+        && verticalClassMergerLens.hasInterfaceBeenMergedIntoClass(field.getType())) {
+      return true;
+    }
+    return false;
+  }
+
+  @Override
+  public void onInvokeDirect(DexMethod method, List<EV> arguments, boolean isInterface) {
+    onInvoke(method, InvokeType.DIRECT, isInterface);
+  }
+
+  @Override
+  public void onInvokeSuper(DexMethod method, List<EV> arguments, boolean isInterface) {
+    onInvoke(method, InvokeType.SUPER, isInterface);
+  }
+
+  @Override
+  public void onInvokeVirtual(DexMethod method, List<EV> arguments) {
+    onInvoke(method, InvokeType.VIRTUAL, false);
+  }
+
+  @Override
+  public void onInvokeStatic(DexMethod method, List<EV> arguments, boolean isInterface) {
+    onInvoke(method, InvokeType.STATIC, isInterface);
+  }
+
+  @Override
+  public void onInvokeInterface(DexMethod method, List<EV> arguments) {
+    onInvoke(method, InvokeType.INTERFACE, true);
+  }
+
+  private InvokeType getInvokeTypeThatMayChange(int opcode) {
+    if (opcode == LirOpcodes.INVOKEVIRTUAL) {
+      return InvokeType.VIRTUAL;
+    }
+    if (opcode == LirOpcodes.INVOKEINTERFACE) {
+      return InvokeType.INTERFACE;
+    }
+    if (graphLens.isVerticalClassMergerLens()) {
+      if (opcode == LirOpcodes.INVOKESTATIC_ITF) {
+        return InvokeType.STATIC;
+      }
+      if (opcode == LirOpcodes.INVOKESUPER) {
+        return InvokeType.SUPER;
+      }
+    }
+    return null;
+  }
+
+  public LirCode<EV> rewrite() {
+    if (hasNonTrivialMethodChanges()) {
+      return rewriteWithLensCodeRewriter();
+    }
+    assert !hasNonTrivialRewritings;
+    LirCode<EV> rewritten = rewriteConstantPoolAndScanForTypeChanges(getCode());
+    if (hasNonTrivialRewritings) {
+      return rewriteWithLensCodeRewriter();
+    }
+    rewritten = rewriteInstructionsWithInvokeTypeChanges(rewritten);
+    rewritten = rewriteTryCatchTable(rewritten);
+    // In the unusual case where a catch handler has been eliminated as a result of class merging
+    // we remove the unreachable blocks.
+    if (hasPrunedCatchHandlers(rewritten)) {
+      rewritten = removeUnreachableBlocks(rewritten);
+    }
+    return rewritten;
+  }
+
+  private boolean hasNonTrivialMethodChanges() {
+    VerticalClassMergerGraphLens verticalClassMergerLens = graphLens.asVerticalClassMergerLens();
+    if (verticalClassMergerLens != null) {
+      DexMethod previousReference =
+          verticalClassMergerLens.getPreviousMethodSignature(contextReference);
+      if (verticalClassMergerLens.hasInterfaceBeenMergedIntoClass(
+          previousReference.getReturnType())) {
+        return true;
+      }
+      RewrittenPrototypeDescription prototypeChanges =
+          graphLens.lookupPrototypeChangesForMethodDefinition(context.getReference(), codeLens);
+      if (!prototypeChanges.isEmpty()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private boolean hasPrunedCatchHandlers(LirCode<EV> rewritten) {
+    if (!getCode().hasTryCatchTable()) {
+      return false;
+    }
+    if (!appView.graphLens().isClassMergerLens()) {
+      assert !internalHasPrunedCatchHandlers(rewritten);
+      return false;
+    }
+    return internalHasPrunedCatchHandlers(rewritten);
+  }
+
+  private boolean internalHasPrunedCatchHandlers(LirCode<EV> rewritten) {
+    TryCatchTable tryCatchTable = getCode().getTryCatchTable();
+    TryCatchTable rewrittenTryCatchTable = rewritten.getTryCatchTable();
+    return tryCatchTable.hasHandlerThatMatches(
+        (blockIndex, handlers) ->
+            handlers.size() > rewrittenTryCatchTable.getHandlersForBlock(blockIndex).size());
+  }
+
+  @SuppressWarnings("unchecked")
+  private LirCode<EV> removeUnreachableBlocks(LirCode<EV> rewritten) {
+    IRCode code =
+        rewritten.buildIR(
+            context,
+            appView,
+            MethodConversionOptions.forLirPhase(appView).disableStringSwitchConversion());
+    AffectedValues affectedValues = code.removeUnreachableBlocks();
+    affectedValues.narrowingWithAssumeRemoval(appView, code);
+    DeadCodeRemover deadCodeRemover = new DeadCodeRemover(appView);
+    deadCodeRemover.run(code, Timing.empty());
+    LirCode<Integer> result =
+        new IRToLirFinalizer(appView, deadCodeRemover)
+            .finalizeCode(code, BytecodeMetadataProvider.empty(), Timing.empty());
+    return (LirCode<EV>) result;
+  }
+
+  @SuppressWarnings("unchecked")
+  private LirCode<EV> rewriteWithLensCodeRewriter() {
+    IRCode code =
+        context.buildIR(
+            appView,
+            MethodConversionOptions.forLirPhase(appView)
+                .disableStringSwitchConversion()
+                .setFinalizeAfterLensCodeRewriter());
+    // MethodProcessor argument is only used by unboxing lenses.
+    MethodProcessor methodProcessor = null;
+    new LensCodeRewriter(appView).rewrite(code, context, methodProcessor);
+    DeadCodeRemover deadCodeRemover = new DeadCodeRemover(appView);
+    deadCodeRemover.run(code, Timing.empty());
+    IRToLirFinalizer finalizer = new IRToLirFinalizer(appView, deadCodeRemover);
+    LirCode<?> rewritten =
+        finalizer.finalizeCode(code, BytecodeMetadataProvider.empty(), Timing.empty());
+    return (LirCode<EV>) rewritten;
+  }
+
+  private LirCode<EV> rewriteConstantPoolAndScanForTypeChanges(LirCode<EV> code) {
+    // The code may need to be rewritten by the lens.
+    // First pass scans just the constant pool to see if any types change or if there are any
+    // fields/methods that need to be examined.
+    boolean hasFieldReference = false;
+    boolean hasPotentialRewrittenMethod = false;
+    for (LirConstant constant : code.getConstantPool()) {
+      // RecordFieldValuesPayload is lowered to NewArrayEmpty before lens code rewriting any LIR.
+      assert !(constant instanceof RecordFieldValuesPayload);
+      if (constant instanceof DexType) {
+        onTypeReference((DexType) constant);
+      } else if (constant instanceof DexField) {
+        onFieldReference((DexField) constant);
+        hasFieldReference = true;
+      } else if (constant instanceof DexCallSite) {
+        onCallSiteReference((DexCallSite) constant);
+      } else if (constant instanceof DexMethodHandle) {
+        onMethodHandleReference((DexMethodHandle) constant);
+      } else if (constant instanceof DexProto) {
+        onProtoReference((DexProto) constant);
+      } else if (!hasPotentialRewrittenMethod && constant instanceof DexMethod) {
+        // We might be able to still fast-case this if we can guarantee the method is never
+        // rewritten. Say it is an java.lang.Object reference or if the lens can fast-check it.
+        hasPotentialRewrittenMethod = true;
+      }
+    }
+
+    // If there are potential method rewritings then we need to iterate the instructions as the
+    // rewriting is instruction-sensitive (i.e., may be dependent on the invoke type).
+    boolean hasPotentialNonTrivialFieldPutRewriting =
+        hasFieldReference && graphLens.isVerticalClassMergerLens();
+    if (hasPotentialNonTrivialFieldPutRewriting || hasPotentialRewrittenMethod) {
+      for (LirInstructionView view : code) {
+        view.accept(this);
+        if (hasNonTrivialRewritings) {
+          return null;
+        }
+      }
+    }
+
+    if (constantPoolMapping == null) {
+      return code;
+    }
+
+    return code.newCodeWithRewrittenConstantPool(
+        item -> constantPoolMapping.getOrDefault(item, item));
+  }
+
+  private LirCode<EV> rewriteInstructionsWithInvokeTypeChanges(LirCode<EV> code) {
+    if (numberOfInvokeOpcodeChanges == 0) {
+      return code;
+    }
+    // Build a small map from method refs to index in case the type-dependent methods are already
+    // in the constant pool.
+    Reference2IntMap<DexMethod> methodIndices = new Reference2IntOpenHashMap<>();
+    LirConstant[] rewrittenConstants = code.getConstantPool();
+    for (int i = 0, length = rewrittenConstants.length; i < length; i++) {
+      LirConstant constant = rewrittenConstants[i];
+      if (constant instanceof DexMethod) {
+        methodIndices.put((DexMethod) constant, i);
+      }
+    }
+
+    IRMetadata irMetadata = code.getMetadataForIR();
+    ByteArrayWriter byteWriter = new ByteArrayWriter();
+    LirWriter lirWriter = new LirWriter(byteWriter);
+    List<LirConstant> methodsToAppend = new ArrayList<>(numberOfInvokeOpcodeChanges);
+    for (LirInstructionView view : code) {
+      int opcode = view.getOpcode();
+      // Instructions that do not have an invoke-type change are just mapped via identity.
+      if (LirOpcodes.isOneByteInstruction(opcode)) {
+        lirWriter.writeOneByteInstruction(opcode);
+        continue;
+      }
+      InvokeType type = getInvokeTypeThatMayChange(opcode);
+      if (type == null) {
+        int size = view.getRemainingOperandSizeInBytes();
+        lirWriter.writeInstruction(opcode, size);
+        while (size-- > 0) {
+          lirWriter.writeOperand(view.getNextU1());
+        }
+        continue;
+      }
+      // This is potentially an invoke with a type change, in such cases the method is mapped with
+      // the instruction updated to the new type. The constant pool is amended with the mapped
+      // method if needed.
+      int constantIndex = view.getNextConstantOperand();
+      DexMethod method = (DexMethod) code.getConstantItem(constantIndex);
+      MethodLookupResult result =
+          graphLens.lookupMethod(method, context.getReference(), type, codeLens);
+      boolean newIsInterface = lookupIsInterface(method, opcode, result);
+      InvokeType newType = result.getType();
+      int newOpcode = newType.getLirOpcode(newIsInterface);
+      if (newOpcode != opcode) {
+        --numberOfInvokeOpcodeChanges;
+        if (newType != type) {
+          irMetadata.record(IROpcodeUtils.fromLirInvokeOpcode(newOpcode));
+        }
+        constantIndex =
+            methodIndices.computeIfAbsent(
+                result.getReference(),
+                ref -> {
+                  methodsToAppend.add(ref);
+                  return rewrittenConstants.length + methodsToAppend.size() - 1;
+                });
+      }
+      int constantIndexSize = ByteUtils.intEncodingSize(constantIndex);
+      int remainingSize = view.getRemainingOperandSizeInBytes();
+      lirWriter.writeInstruction(newOpcode, constantIndexSize + remainingSize);
+      ByteUtils.writeEncodedInt(constantIndex, lirWriter::writeOperand);
+      while (remainingSize-- > 0) {
+        lirWriter.writeOperand(view.getNextU1());
+      }
+    }
+    assert numberOfInvokeOpcodeChanges == 0;
+    // Note that since we assume 'null' in the mapping is identity this may end up with a stale
+    // reference to a no longer used method. That is not an issue as it will be pruned when
+    // building IR again, it is just a small and size overhead.
+    LirCode<EV> newCode =
+        code.copyWithNewConstantsAndInstructions(
+            irMetadata,
+            ArrayUtils.appendElements(code.getConstantPool(), methodsToAppend),
+            byteWriter.toByteArray());
+    return newCode;
+  }
+
+  // TODO(b/157111832): This should be part of the graph lens lookup result.
+  private boolean lookupIsInterface(DexMethod method, int opcode, MethodLookupResult result) {
+    VerticalClassMergerGraphLens verticalClassMergerLens = graphLens.asVerticalClassMergerLens();
+    if (verticalClassMergerLens != null
+        && verticalClassMergerLens.hasInterfaceBeenMergedIntoClass(method.getHolderType())) {
+      DexClass clazz = appView.definitionFor(result.getReference().getHolderType());
+      if (clazz != null) {
+        return clazz.isInterface();
+      }
+    }
+    return LirOpcodeUtils.getInterfaceBitFromInvokeOpcode(opcode);
+  }
+
+  private LirCode<EV> rewriteTryCatchTable(LirCode<EV> code) {
+    TryCatchTable tryCatchTable = code.getTryCatchTable();
+    if (tryCatchTable == null) {
+      return code;
+    }
+    TryCatchTable newTryCatchTable = tryCatchTable.rewriteWithLens(graphLens, codeLens);
+    return code.newCodeWithRewrittenTryCatchTable(newTryCatchTable);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/lightir/LirOpcodeUtils.java b/src/main/java/com/android/tools/r8/lightir/LirOpcodeUtils.java
new file mode 100644
index 0000000..944bfce
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/LirOpcodeUtils.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2024, 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.lightir;
+
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKEDIRECT;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKEDIRECT_ITF;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKEINTERFACE;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKESTATIC;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKESTATIC_ITF;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKESUPER;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKESUPER_ITF;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKEVIRTUAL;
+
+public class LirOpcodeUtils {
+
+  public static boolean getInterfaceBitFromInvokeOpcode(int opcode) {
+    switch (opcode) {
+      case INVOKEDIRECT_ITF:
+      case INVOKEINTERFACE:
+      case INVOKESTATIC_ITF:
+      case INVOKESUPER_ITF:
+        return true;
+      default:
+        assert opcode == INVOKEDIRECT
+            || opcode == INVOKESTATIC
+            || opcode == INVOKESUPER
+            || opcode == INVOKEVIRTUAL;
+        return false;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java b/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
index 393cd05..702dbba 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
@@ -212,6 +212,7 @@
   int CHECKCAST_IGNORE_COMPAT = 225;
   int CONSTCLASS_IGNORE_COMPAT = 226;
   int STRINGSWITCH = 227;
+  int RESOURCENUMBER = 228;
 
   static String toString(int opcode) {
     switch (opcode) {
@@ -551,6 +552,8 @@
         return "CONSTCLASS_IGNORE_COMPAT";
       case STRINGSWITCH:
         return "STRINGSWITCH";
+      case RESOURCENUMBER:
+        return "RESOURCENUMBER";
 
       default:
         throw new Unreachable("Unexpected LIR opcode: " + opcode);
diff --git a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
index cdd50cc..4fe7d83 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
@@ -82,6 +82,10 @@
     onConstNumber(NumericType.INT, value);
   }
 
+  public void onConstResourceNumber(int value) {
+    onInstruction();
+  }
+
   public void onConstFloat(int value) {
     onConstNumber(NumericType.FLOAT, value);
   }
@@ -1278,6 +1282,12 @@
           onRecordFieldValues(payload.fields, values);
           return;
         }
+      case LirOpcodes.RESOURCENUMBER:
+        {
+          int value = view.getNextIntegerOperand();
+          onConstResourceNumber(value);
+          return;
+        }
       default:
         throw new Unimplemented("No dispatch for opcode " + LirOpcodes.toString(opcode));
     }
diff --git a/src/main/java/com/android/tools/r8/lightir/LirPrinter.java b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
index 2e0273d..c27b1ed 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
@@ -153,6 +153,11 @@
   }
 
   @Override
+  public void onConstResourceNumber(int value) {
+    appendOutValue().append(value);
+  }
+
+  @Override
   public void onConstFloat(int value) {
     appendOutValue().append(Float.intBitsToFloat(value));
   }
diff --git a/src/main/java/com/android/tools/r8/lightir/LirSizeEstimation.java b/src/main/java/com/android/tools/r8/lightir/LirSizeEstimation.java
index 7b78efa..3c0961e 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirSizeEstimation.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirSizeEstimation.java
@@ -352,6 +352,9 @@
       case CONSTCLASS_IGNORE_COMPAT:
         return DexConstClass.SIZE;
 
+      case RESOURCENUMBER:
+        return DexConst4.SIZE;
+
       default:
         throw new Unreachable("Unexpected LIR opcode: " + opcode);
     }
diff --git a/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java b/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java
index 83a34f1..d97b0a9 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java
@@ -227,6 +227,11 @@
   }
 
   @Override
+  public void onConstResourceNumber(int value) {
+    registry.registerConstResourceNumber(value);
+  }
+
+  @Override
   public void onRecordFieldValues(DexField[] fields, List<EV> values) {
     registry.registerRecordFieldValues(fields);
   }
diff --git a/src/main/java/com/android/tools/r8/lightir/SimpleLensLirRewriter.java b/src/main/java/com/android/tools/r8/lightir/SimpleLensLirRewriter.java
deleted file mode 100644
index 1a4d065..0000000
--- a/src/main/java/com/android/tools/r8/lightir/SimpleLensLirRewriter.java
+++ /dev/null
@@ -1,293 +0,0 @@
-// 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.lightir;
-
-import static com.android.tools.r8.graph.UseRegistry.MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
-
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.DexCallSite;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexMethodHandle;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.lens.GraphLens;
-import com.android.tools.r8.graph.lens.MethodLookupResult;
-import com.android.tools.r8.ir.code.IRMetadata;
-import com.android.tools.r8.ir.code.InvokeType;
-import com.android.tools.r8.ir.code.Opcodes;
-import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.lightir.LirBuilder.RecordFieldValuesPayload;
-import com.android.tools.r8.lightir.LirCode.TryCatchTable;
-import com.android.tools.r8.utils.ArrayUtils;
-import it.unimi.dsi.fastutil.objects.Reference2IntMap;
-import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
-import java.util.ArrayList;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
-
-public class SimpleLensLirRewriter<EV> extends LirParsedInstructionCallback<EV> {
-
-  private final ProgramMethod context;
-  private final DexMethod contextReference;
-  private final GraphLens graphLens;
-  private final GraphLens codeLens;
-  private final LensCodeRewriterUtils helper;
-
-  private int numberOfInvokeTypeChanges = 0;
-  private Map<LirConstant, LirConstant> constantPoolMapping = null;
-
-  public SimpleLensLirRewriter(
-      LirCode<EV> code,
-      ProgramMethod context,
-      GraphLens graphLens,
-      GraphLens codeLens,
-      LensCodeRewriterUtils helper) {
-    super(code);
-    this.context = context;
-    this.contextReference = context.getReference();
-    this.graphLens = graphLens;
-    this.codeLens = codeLens;
-    this.helper = helper;
-  }
-
-  @Override
-  public int getCurrentValueIndex() {
-    // We do not need to interpret values.
-    return -1;
-  }
-
-  public void onTypeReference(DexType type) {
-    addRewrittenMapping(type, graphLens.lookupType(type, codeLens));
-  }
-
-  public void onFieldReference(DexField field) {
-    addRewrittenMapping(field, graphLens.lookupField(field, codeLens));
-  }
-
-  public void onCallSiteReference(DexCallSite callSite) {
-    addRewrittenMapping(callSite, helper.rewriteCallSite(callSite, context));
-  }
-
-  public void onMethodHandleReference(DexMethodHandle methodHandle) {
-    addRewrittenMapping(
-        methodHandle,
-        helper.rewriteDexMethodHandle(methodHandle, NOT_ARGUMENT_TO_LAMBDA_METAFACTORY, context));
-  }
-
-  public void onProtoReference(DexProto proto) {
-    addRewrittenMapping(proto, helper.rewriteProto(proto));
-  }
-
-  private void onInvoke(DexMethod method, InvokeType type) {
-    MethodLookupResult result = graphLens.lookupMethod(method, contextReference, type, codeLens);
-    if (result.getType() != type) {
-      assert (type == InvokeType.VIRTUAL && result.getType() == InvokeType.INTERFACE)
-          || (type == InvokeType.INTERFACE && result.getType() == InvokeType.VIRTUAL);
-      numberOfInvokeTypeChanges++;
-    } else {
-      // All non-type dependent mappings are just rewritten in the content pool.
-      addRewrittenMapping(method, result.getReference());
-    }
-  }
-
-  private void addRewrittenMapping(LirConstant item, LirConstant rewrittenItem) {
-    if (item == rewrittenItem) {
-      return;
-    }
-    if (constantPoolMapping == null) {
-      constantPoolMapping =
-          new IdentityHashMap<>(
-              // Avoid using initial capacity larger than the number of actual constants.
-              Math.min(getCode().getConstantPool().length, 32));
-    }
-    LirConstant old = constantPoolMapping.put(item, rewrittenItem);
-    if (old != null && old != rewrittenItem) {
-      throw new Unreachable(
-          "Unexpected rewriting of item: "
-              + item
-              + " to two distinct items: "
-              + rewrittenItem
-              + " and "
-              + old);
-    }
-  }
-
-  @Override
-  public void onInvokeDirect(DexMethod method, List<EV> arguments, boolean isInterface) {
-    onInvoke(method, InvokeType.DIRECT);
-  }
-
-  @Override
-  public void onInvokeSuper(DexMethod method, List<EV> arguments, boolean isInterface) {
-    onInvoke(method, InvokeType.SUPER);
-  }
-
-  @Override
-  public void onInvokeVirtual(DexMethod method, List<EV> arguments) {
-    onInvoke(method, InvokeType.VIRTUAL);
-  }
-
-  @Override
-  public void onInvokeStatic(DexMethod method, List<EV> arguments, boolean isInterface) {
-    onInvoke(method, InvokeType.STATIC);
-  }
-
-  @Override
-  public void onInvokeInterface(DexMethod method, List<EV> arguments) {
-    onInvoke(method, InvokeType.INTERFACE);
-  }
-
-  private InvokeType getInvokeTypeThatMayChange(int opcode) {
-    if (opcode == LirOpcodes.INVOKEVIRTUAL) {
-      return InvokeType.VIRTUAL;
-    }
-    if (opcode == LirOpcodes.INVOKEINTERFACE) {
-      return InvokeType.INTERFACE;
-    }
-    return null;
-  }
-
-  public LirCode<EV> rewrite() {
-    LirCode<EV> rewritten = rewriteConstantPoolAndScanForTypeChanges(getCode());
-    rewritten = rewriteInstructionsWithInvokeTypeChanges(rewritten);
-    return rewriteTryCatchTable(rewritten);
-  }
-
-  private LirCode<EV> rewriteConstantPoolAndScanForTypeChanges(LirCode<EV> code) {
-    // The code may need to be rewritten by the lens.
-    // First pass scans just the constant pool to see if any types change or if there are any
-    // fields/methods that need to be examined.
-    boolean hasPotentialRewrittenMethod = false;
-    for (LirConstant constant : code.getConstantPool()) {
-      // RecordFieldValuesPayload is lowered to NewArrayEmpty before lens code rewriting any LIR.
-      assert !(constant instanceof RecordFieldValuesPayload);
-      if (constant instanceof DexType) {
-        onTypeReference((DexType) constant);
-      } else if (constant instanceof DexField) {
-        onFieldReference((DexField) constant);
-      } else if (constant instanceof DexCallSite) {
-        onCallSiteReference((DexCallSite) constant);
-      } else if (constant instanceof DexMethodHandle) {
-        onMethodHandleReference((DexMethodHandle) constant);
-      } else if (constant instanceof DexProto) {
-        onProtoReference((DexProto) constant);
-      } else if (!hasPotentialRewrittenMethod && constant instanceof DexMethod) {
-        // We might be able to still fast-case this if we can guarantee the method is never
-        // rewritten. Say it is an java.lang.Object reference or if the lens can fast-check it.
-        hasPotentialRewrittenMethod = true;
-      }
-    }
-
-    // If there are potential method rewritings then we need to iterate the instructions as the
-    // rewriting is instruction-sensitive (i.e., may be dependent on the invoke type).
-    if (hasPotentialRewrittenMethod) {
-      for (LirInstructionView view : code) {
-        view.accept(this);
-      }
-    }
-
-    if (constantPoolMapping == null) {
-      return code;
-    }
-
-    return code.newCodeWithRewrittenConstantPool(
-        item -> constantPoolMapping.getOrDefault(item, item));
-  }
-
-  private LirCode<EV> rewriteInstructionsWithInvokeTypeChanges(LirCode<EV> code) {
-    if (numberOfInvokeTypeChanges == 0) {
-      return code;
-    }
-    // Build a small map from method refs to index in case the type-dependent methods are already
-    // in the constant pool.
-    Reference2IntMap<DexMethod> methodIndices = new Reference2IntOpenHashMap<>();
-    LirConstant[] rewrittenConstants = code.getConstantPool();
-    for (int i = 0, length = rewrittenConstants.length; i < length; i++) {
-      LirConstant constant = rewrittenConstants[i];
-      if (constant instanceof DexMethod) {
-        methodIndices.put((DexMethod) constant, i);
-      }
-    }
-
-    IRMetadata irMetadata = code.getMetadataForIR();
-    ByteArrayWriter byteWriter = new ByteArrayWriter();
-    LirWriter lirWriter = new LirWriter(byteWriter);
-    List<LirConstant> methodsToAppend = new ArrayList<>(numberOfInvokeTypeChanges);
-    for (LirInstructionView view : code) {
-      int opcode = view.getOpcode();
-      // Instructions that do not have an invoke-type change are just mapped via identity.
-      if (LirOpcodes.isOneByteInstruction(opcode)) {
-        lirWriter.writeOneByteInstruction(opcode);
-        continue;
-      }
-      InvokeType type = getInvokeTypeThatMayChange(opcode);
-      if (type == null) {
-        int size = view.getRemainingOperandSizeInBytes();
-        lirWriter.writeInstruction(opcode, size);
-        while (size-- > 0) {
-          lirWriter.writeOperand(view.getNextU1());
-        }
-        continue;
-      }
-      // This is potentially an invoke with a type change, in such cases the method is mapped with
-      // the instruction updated to the new type. The constant pool is amended with the mapped
-      // method if needed.
-      int constantIndex = view.getNextConstantOperand();
-      DexMethod method = (DexMethod) code.getConstantItem(constantIndex);
-      MethodLookupResult result =
-          graphLens.lookupMethod(method, context.getReference(), type, codeLens);
-      if (result.getType() != type) {
-        --numberOfInvokeTypeChanges;
-        if (result.getType().isVirtual()) {
-          opcode = LirOpcodes.INVOKEVIRTUAL;
-          irMetadata.record(Opcodes.INVOKE_VIRTUAL);
-        } else if (result.getType().isInterface()) {
-          opcode = LirOpcodes.INVOKEINTERFACE;
-          irMetadata.record(Opcodes.INVOKE_INTERFACE);
-        } else {
-          throw new Unreachable(
-              "Unexpected change of invoke that may need an interface bit set: "
-                  + result.getType());
-        }
-        constantIndex =
-            methodIndices.computeIfAbsent(
-                result.getReference(),
-                ref -> {
-                  methodsToAppend.add(ref);
-                  return rewrittenConstants.length + methodsToAppend.size() - 1;
-                });
-      }
-      int constantIndexSize = ByteUtils.intEncodingSize(constantIndex);
-      int remainingSize = view.getRemainingOperandSizeInBytes();
-      lirWriter.writeInstruction(opcode, constantIndexSize + remainingSize);
-      ByteUtils.writeEncodedInt(constantIndex, lirWriter::writeOperand);
-      while (remainingSize-- > 0) {
-        lirWriter.writeOperand(view.getNextU1());
-      }
-    }
-    assert numberOfInvokeTypeChanges == 0;
-    // Note that since we assume 'null' in the mapping is identity this may end up with a stale
-    // reference to a no longer used method. That is not an issue as it will be pruned when
-    // building IR again, it is just a small and size overhead.
-    LirCode<EV> newCode =
-        code.copyWithNewConstantsAndInstructions(
-            irMetadata,
-            ArrayUtils.appendElements(code.getConstantPool(), methodsToAppend),
-            byteWriter.toByteArray());
-    return newCode;
-  }
-
-  private LirCode<EV> rewriteTryCatchTable(LirCode<EV> code) {
-    TryCatchTable tryCatchTable = code.getTryCatchTable();
-    if (tryCatchTable == null) {
-      return code;
-    }
-    TryCatchTable newTryCatchTable = tryCatchTable.rewriteWithLens(graphLens, codeLens);
-    return code.newCodeWithRewrittenTryCatchTable(newTryCatchTable);
-  }
-}
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 0b947a3..4a19396 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
@@ -88,6 +88,7 @@
       converter.onMethodPruned(prunedMethod);
       postMethodProcessorBuilder.remove(prunedMethod, appView.graphLens());
     }
+    converter.pruneItems(executorService);
     converter.waveDone(ProgramMethodSet.empty(), executorService);
   }
 
diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
index 5272500..d6bc517 100644
--- a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
+++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
@@ -14,7 +14,6 @@
 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.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.InnerClassAttribute;
@@ -22,7 +21,6 @@
 import com.android.tools.r8.graph.ProgramPackageCollection;
 import com.android.tools.r8.graph.SortedProgramPackageCollection;
 import com.android.tools.r8.graph.fixup.TreeFixerBase;
-import com.android.tools.r8.graph.lens.NestedGraphLens;
 import com.android.tools.r8.naming.Minifier.MinificationPackageNamingStrategy;
 import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
 import com.android.tools.r8.repackaging.RepackagingLens.Builder;
@@ -113,18 +111,7 @@
             assert false;
           }
         }.fixupClasses(appView.appInfo().classesWithDeterministicOrder());
-    NestedGraphLens emptyRepackagingLens =
-        new NestedGraphLens(appView) {
-          @Override
-          protected boolean isLegitimateToHaveEmptyMappings() {
-            return true;
-          }
-
-          @Override
-          public <T extends DexReference> boolean isSimpleRenaming(T from, T to) {
-            return getPrevious().isSimpleRenaming(from, to);
-          }
-        };
+    RepackagingLens emptyRepackagingLens = new RepackagingLens.Builder().buildEmpty(appView);
     DirectMappedDexApplication newApplication =
         appView
             .appInfo()
@@ -479,58 +466,4 @@
           || appView.appInfo().getMissingClasses().contains(type);
     }
   }
-
-  /** Testing only. */
-  public static class SuffixRenamingRepackagingConfiguration implements RepackagingConfiguration {
-
-    private final String classNameSuffix;
-    private final DexItemFactory dexItemFactory;
-
-    public SuffixRenamingRepackagingConfiguration(
-        String classNameSuffix, DexItemFactory dexItemFactory) {
-      this.classNameSuffix = classNameSuffix;
-      this.dexItemFactory = dexItemFactory;
-    }
-
-    @Override
-    public String getNewPackageDescriptor(ProgramPackage pkg, Set<String> seenPackageDescriptors) {
-      // Don't change the package of classes.
-      return pkg.getPackageDescriptor();
-    }
-
-    @Override
-    public boolean isPackageInTargetLocation(ProgramPackage pkg) {
-      return true;
-    }
-
-    @Override
-    public DexType getRepackagedType(
-        DexProgramClass clazz,
-        DexProgramClass outerClass,
-        String newPackageDescriptor,
-        BiMap<DexType, DexType> mappings) {
-      DexType repackagedDexType = clazz.getType();
-      // Rename the class consistently with its outer class.
-      if (outerClass != null) {
-        String simpleName = clazz.getType().getSimpleName();
-        String outerClassSimpleName = outerClass.getType().getSimpleName();
-        if (simpleName.startsWith(outerClassSimpleName + INNER_CLASS_SEPARATOR)) {
-          String newSimpleName =
-              mappings.get(outerClass.getType()).getSimpleName()
-                  + simpleName.substring(outerClassSimpleName.length());
-          repackagedDexType = repackagedDexType.withSimpleName(newSimpleName, dexItemFactory);
-        }
-      }
-      // Append the class name suffix to all classes.
-      repackagedDexType = repackagedDexType.addSuffix(classNameSuffix, dexItemFactory);
-      // Ensure that the generated name is unique.
-      DexType finalRepackagedDexType = repackagedDexType;
-      for (int i = 1; mappings.inverse().containsKey(finalRepackagedDexType); i++) {
-        finalRepackagedDexType =
-            repackagedDexType.addSuffix(
-                Character.toString(INNER_CLASS_SEPARATOR) + i, dexItemFactory);
-      }
-      return finalRepackagedDexType;
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
index 0b40c72..acc7ed6 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
 import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
+import java.util.Collections;
 import java.util.Map;
 
 public class RepackagingLens extends NestedGraphLens {
@@ -36,7 +37,8 @@
 
   @Override
   public String lookupPackageName(String pkg) {
-    return packageRenamings.getOrDefault(getPrevious().lookupPackageName(pkg), pkg);
+    String previousPkg = getPrevious().lookupPackageName(pkg);
+    return packageRenamings.getOrDefault(previousPkg, previousPkg);
   }
 
   @Override
@@ -101,5 +103,16 @@
       return new RepackagingLens(
           appView, newFieldSignatures, newMethodSignatures, newTypes, packageRenamings);
     }
+
+    public RepackagingLens buildEmpty(AppView<AppInfoWithLiveness> appView) {
+      return new RepackagingLens(
+          appView, EMPTY_FIELD_MAP, EMPTY_METHOD_MAP, EMPTY_TYPE_MAP, Collections.emptyMap()) {
+
+        @Override
+        protected boolean isLegitimateToHaveEmptyMappings() {
+          return true;
+        }
+      };
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index 7e988c4..736f285 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -57,6 +57,12 @@
   }
 
   @Override
+  public void registerConstResourceNumber(int value) {
+    super.registerConstResourceNumber(value);
+    enqueuer.traceResourceValue(value);
+  }
+
+  @Override
   public void registerInvokeVirtual(DexMethod invokedMethod) {
     super.registerInvokeVirtual(invokedMethod);
     enqueuer.traceInvokeVirtual(invokedMethod, getContext());
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 5279f1d..34cb2ec 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -17,7 +17,11 @@
 import static com.android.tools.r8.utils.MapUtils.ignoreKey;
 import static java.util.Collections.emptySet;
 
+import com.android.build.shrinker.r8integration.R8ResourceShrinkerState;
+import com.android.tools.r8.AndroidResourceInput;
+import com.android.tools.r8.AndroidResourceInput.Kind;
 import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
@@ -479,6 +483,8 @@
 
   private final ProfileCollectionAdditions profileCollectionAdditions;
 
+  private final R8ResourceShrinkerState r8ResourceShrinkerState;
+
   Enqueuer(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       ProfileCollectionAdditions profileCollectionAdditions,
@@ -537,6 +543,27 @@
 
     objectAllocationInfoCollection =
         ObjectAllocationInfoCollectionImpl.builder(mode.isInitialTreeShaking(), graphReporter);
+    r8ResourceShrinkerState = setupResourceShrinkerState(appView);
+  }
+
+  private R8ResourceShrinkerState setupResourceShrinkerState(
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    R8ResourceShrinkerState r8ResourceShrinkerState = new R8ResourceShrinkerState();
+    if (options.resourceShrinkerConfiguration.isOptimizedShrinking()
+        && options.androidResourceProvider != null) {
+      try {
+        for (AndroidResourceInput androidResource :
+            options.androidResourceProvider.getAndroidResources()) {
+          if (androidResource.getKind() == Kind.RESOURCE_TABLE) {
+            r8ResourceShrinkerState.setResourceTableInput(androidResource.getByteStream());
+            break;
+          }
+        }
+      } catch (ResourceException e) {
+        throw appView.reporter().fatalError("Failed initializing resource table");
+      }
+    }
+    return r8ResourceShrinkerState;
   }
 
   private AppInfoWithClassHierarchy appInfo() {
@@ -1107,6 +1134,10 @@
     }
   }
 
+  public void traceResourceValue(int value) {
+    r8ResourceShrinkerState.trace(value);
+  }
+
   public void traceReflectiveFieldWrite(ProgramField field, ProgramMethod context) {
     deferredTracing.notifyReflectiveFieldAccess(field, context);
     if (registerReflectiveFieldWrite(field, context)) {
@@ -3740,6 +3771,7 @@
     EnqueuerResult result = createEnqueuerResult(appInfo, timing);
     profileCollectionAdditions.commit(appView);
     timing.end();
+    appView.setResourceShrinkerState(r8ResourceShrinkerState);
     return result;
   }
 
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index a1795a7..a9f9957 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -49,12 +49,6 @@
   public final SyntheticKind WRAPPER = generator.forFixedClass("$Wrapper");
   public final SyntheticKind VIVIFIED_WRAPPER = generator.forFixedClass("$VivifiedWrapper");
   public final SyntheticKind INIT_TYPE_ARGUMENT = generator.forFixedClass("-IA");
-  public final SyntheticKind HORIZONTAL_INIT_TYPE_ARGUMENT_1 =
-      generator.forFixedClass(SYNTHETIC_CLASS_SEPARATOR + "IA$1");
-  public final SyntheticKind HORIZONTAL_INIT_TYPE_ARGUMENT_2 =
-      generator.forFixedClass(SYNTHETIC_CLASS_SEPARATOR + "IA$2");
-  public final SyntheticKind HORIZONTAL_INIT_TYPE_ARGUMENT_3 =
-      generator.forFixedClass(SYNTHETIC_CLASS_SEPARATOR + "IA$3");
   public final SyntheticKind ENUM_CONVERSION = generator.forFixedClass("$EnumConversion");
 
   // Locally generated synthetic classes.
diff --git a/src/main/java/com/android/tools/r8/utils/DequeUtils.java b/src/main/java/com/android/tools/r8/utils/DequeUtils.java
index 6745d6b..986a6a2 100644
--- a/src/main/java/com/android/tools/r8/utils/DequeUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DequeUtils.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.utils;
 
 import java.util.ArrayDeque;
+import java.util.Collections;
 import java.util.Deque;
 
 public class DequeUtils {
@@ -14,4 +15,10 @@
     deque.add(element);
     return deque;
   }
+
+  public static <T> Deque<T> newArrayDeque(T... elements) {
+    Deque<T> deque = new ArrayDeque<>();
+    Collections.addAll(deque, elements);
+    return deque;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index df0ac25..7d8e995 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -17,6 +17,7 @@
 import com.google.common.collect.ImmutableMap;
 import java.io.File;
 import java.nio.file.Path;
+import java.util.List;
 import java.util.Map;
 import java.util.function.Function;
 
@@ -805,6 +806,22 @@
     return new ModuleAndDescriptor(module, 'L' + descriptor + ';');
   }
 
+  public static boolean isRClassDescriptor(String descriptor) {
+    String simpleClassName = DescriptorUtils.getSimpleClassNameFromDescriptor(descriptor);
+    List<String> split = StringUtils.split(simpleClassName, '$');
+
+    if (split.size() < 2) {
+      return false;
+    }
+    String type = split.get(split.size() - 1);
+    String rClass = split.get(split.size() - 2);
+    // We match on R if:
+    // - The name of the Class is R$type - we allow R to be an inner class.
+    //   - The inner type should be with lower case
+    boolean isRClass = Character.isLowerCase(type.charAt(0)) && rClass.equals("R");
+    return isRClass;
+  }
+
   public static String getPathFromDescriptor(String descriptor) {
     // We are quite loose on names here to support testing illegal names, too.
     assert descriptor.startsWith("L");
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 838558f..29f4710 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -229,6 +229,10 @@
     return proguardConfiguration != null;
   }
 
+  public boolean isOptimizedResourceShrinking() {
+    return androidResourceProvider != null && resourceShrinkerConfiguration.isOptimizedShrinking();
+  }
+
   public ProguardConfiguration getProguardConfiguration() {
     return proguardConfiguration;
   }
@@ -1399,14 +1403,14 @@
     }
   }
 
-  public void warningInvalidDebugInfo(
-      ProgramMethod method, Origin origin, InvalidDebugInfoException e) {
+  public void warningInvalidDebugInfo(ProgramMethod method, InvalidDebugInfoException e) {
     if (invalidDebugInfoFatal) {
       throw new CompilationError("Fatal warning: Invalid debug info", e);
     }
     synchronized (warningInvalidDebugInfo) {
-      warningInvalidDebugInfo.computeIfAbsent(
-          origin, k -> new ArrayList<>()).add(new Pair<>(method, e.getMessage()));
+      warningInvalidDebugInfo
+          .computeIfAbsent(method.getOrigin(), k -> new ArrayList<>())
+          .add(new Pair<>(method, e.getMessage()));
     }
   }
 
@@ -1877,7 +1881,7 @@
         }
       }
       if (mode.isInitial()) {
-        return enableInitial && inlinerOptions.enableInlining;
+        return enableInitial;
       }
       assert mode.isFinal();
       return true;
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index 1210325..22d19f7 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -7,6 +7,8 @@
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
+import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.ints.IntListIterator;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -208,6 +210,35 @@
     return list;
   }
 
+  public static <T> List<T> newArrayListWithoutIndices(List<T> list, IntList indicesToRemove) {
+    // Verify each index to remove is unique and in bounds.
+    assert indicesToRemove.stream().distinct().count() == indicesToRemove.size();
+    assert indicesToRemove.stream().allMatch(indexToRemove -> indexToRemove < list.size());
+    if (indicesToRemove.size() == list.size()) {
+      return new ArrayList<>();
+    }
+    List<T> result = new ArrayList<>(list.size() - indicesToRemove.size());
+    IntListIterator indicesToRemoveIterator = indicesToRemove.iterator();
+    int nextIndexToRemove = indicesToRemoveIterator.nextInt();
+    for (int i = 0; i < list.size(); i++) {
+      assert i <= nextIndexToRemove;
+      if (i == nextIndexToRemove) {
+        if (indicesToRemoveIterator.hasNext()) {
+          nextIndexToRemove = indicesToRemoveIterator.nextInt();
+          assert nextIndexToRemove > i;
+        } else {
+          for (int j = i + 1; j < list.size(); j++) {
+            result.add(list.get(j));
+          }
+          break;
+        }
+      } else {
+        result.add(list.get(i));
+      }
+    }
+    return result;
+  }
+
   public static <T> ArrayList<T> newInitializedArrayList(int size, T element) {
     ArrayList<T> list = new ArrayList<>(size);
     for (int i = 0; i < size; i++) {
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMerger.java
index 6f89166..2fdda52 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMerger.java
@@ -9,6 +9,7 @@
 import static java.util.function.Predicate.not;
 
 import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.classmerging.ClassMergerSharedData;
 import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.AppView;
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/ConnectedComponentVerticalClassMerger.java b/src/main/java/com/android/tools/r8/verticalclassmerging/ConnectedComponentVerticalClassMerger.java
index 72547ec..56e619f 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/ConnectedComponentVerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/ConnectedComponentVerticalClassMerger.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.verticalclassmerging;
 
+import com.android.tools.r8.classmerging.ClassMergerSharedData;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ListUtils;
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/IncompleteVerticalClassMergerBridgeCode.java b/src/main/java/com/android/tools/r8/verticalclassmerging/IncompleteVerticalClassMergerBridgeCode.java
index f13c3c6..ffe892f 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/IncompleteVerticalClassMergerBridgeCode.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/IncompleteVerticalClassMergerBridgeCode.java
@@ -28,7 +28,6 @@
 import com.android.tools.r8.lightir.LirCode;
 import com.android.tools.r8.lightir.LirEncodingStrategy;
 import com.android.tools.r8.lightir.LirStrategy;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.RetracerForCodePrinting;
 import java.util.ArrayList;
@@ -148,7 +147,6 @@
   public IRCode buildIR(
       ProgramMethod method,
       AppView<?> appView,
-      Origin origin,
       MutableMethodConversionOptions conversionOptions) {
     throw new Unreachable();
   }
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 26ff3b6..8ef8743 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
@@ -6,7 +6,8 @@
 import static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull;
 
 import com.android.tools.r8.classmerging.ClassMergerMode;
-import com.android.tools.r8.classmerging.SyntheticArgumentClass;
+import com.android.tools.r8.classmerging.ClassMergerSharedData;
+import com.android.tools.r8.classmerging.Policy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -17,18 +18,13 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.lens.GraphLens;
-import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions;
-import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer;
-import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
+import com.android.tools.r8.ir.conversion.LirConverter;
 import com.android.tools.r8.optimize.argumentpropagation.utils.ProgramClassesBidirectedGraph;
 import com.android.tools.r8.profile.art.ArtProfileCompletenessChecker;
 import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.Timing.TimingMerger;
@@ -36,7 +32,6 @@
 import com.google.common.collect.Streams;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Comparator;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -76,7 +71,7 @@
 
   public void runIfNecessary(ExecutorService executorService, Timing timing)
       throws ExecutionException {
-    timing.begin("VerticalClassMerger");
+    timing.begin("VerticalClassMerger (" + mode.toString() + ")");
     if (shouldRun()) {
       run(executorService, timing);
     } else {
@@ -93,7 +88,6 @@
   }
 
   private void run(ExecutorService executorService, Timing timing) throws ExecutionException {
-    appView.appInfo().getMethodAccessInfoCollection().verifyNoNonResolving(appView);
     timing.begin("Setup");
     ImmediateProgramSubtypingInfo immediateSubtypingInfo =
         ImmediateProgramSubtypingInfo.create(appView);
@@ -108,45 +102,49 @@
     timing.end();
 
     // Apply class merging concurrently in disjoint class hierarchies.
+    ClassMergerSharedData classMergerSharedData = new ClassMergerSharedData(appView);
     VerticalClassMergerResult verticalClassMergerResult =
         mergeClassesInConnectedComponents(
-            connectedComponents, immediateSubtypingInfo, executorService, timing);
+            classMergerSharedData,
+            connectedComponents,
+            immediateSubtypingInfo,
+            executorService,
+            timing);
     appView.setVerticallyMergedClasses(
         verticalClassMergerResult.getVerticallyMergedClasses(), mode);
     if (verticalClassMergerResult.isEmpty()) {
       return;
     }
-    ProfileCollectionAdditions profileCollectionAdditions =
-        ProfileCollectionAdditions.create(appView);
     VerticalClassMergerGraphLens lens =
-        runFixup(profileCollectionAdditions, verticalClassMergerResult, executorService, timing);
+        runFixup(classMergerSharedData, verticalClassMergerResult, executorService, timing);
     assert verifyGraphLens(lens, verticalClassMergerResult);
 
     // Update keep info and art profiles.
-    updateKeepInfoForMergedClasses(verticalClassMergerResult);
-    updateArtProfiles(profileCollectionAdditions, lens, verticalClassMergerResult);
+    updateKeepInfoForMergedClasses(verticalClassMergerResult, timing);
+    updateArtProfiles(lens, verticalClassMergerResult, timing);
 
     // Remove merged classes and rewrite AppView with the new lens.
     appView.rewriteWithLens(lens, executorService, timing);
 
     // The code must be rewritten before we remove the merged classes from the app. Otherwise we
     // can't build IR.
-    rewriteCodeWithLens(executorService);
+    rewriteCodeWithLens(executorService, timing);
 
     // Remove force inlined constructors.
-    removeFullyInlinedInstanceInitializers(executorService);
-    removeMergedClasses(verticalClassMergerResult.getVerticallyMergedClasses());
+    removeFullyInlinedInstanceInitializers(executorService, timing);
+    removeMergedClasses(verticalClassMergerResult.getVerticallyMergedClasses(), timing);
 
     // Convert the (incomplete) synthesized bridges to CF or LIR.
-    finalizeSynthesizedBridges(verticalClassMergerResult.getSynthesizedBridges(), lens);
+    finalizeSynthesizedBridges(verticalClassMergerResult.getSynthesizedBridges(), lens, timing);
 
     // Finally update the code lens to signal that the code is fully up to date.
-    markRewrittenWithLens(executorService);
+    markRewrittenWithLens(executorService, timing);
 
     appView.notifyOptimizationFinishedForTesting();
   }
 
   private VerticalClassMergerResult mergeClassesInConnectedComponents(
+      ClassMergerSharedData classMergerSharedData,
       List<Set<DexProgramClass>> connectedComponents,
       ImmediateProgramSubtypingInfo immediateSubtypingInfo,
       ExecutorService executorService,
@@ -155,7 +153,8 @@
     Collection<ConnectedComponentVerticalClassMerger> connectedComponentMergers =
         getConnectedComponentMergers(
             connectedComponents, immediateSubtypingInfo, executorService, timing);
-    return applyConnectedComponentMergers(connectedComponentMergers, executorService, timing);
+    return applyConnectedComponentMergers(
+        classMergerSharedData, connectedComponentMergers, executorService, timing);
   }
 
   private Collection<ConnectedComponentVerticalClassMerger> getConnectedComponentMergers(
@@ -164,17 +163,19 @@
       ExecutorService executorService,
       Timing timing)
       throws ExecutionException {
+    timing.begin("Compute classes to merge");
     TimingMerger merger = timing.beginMerger("Compute classes to merge", executorService);
     List<ConnectedComponentVerticalClassMerger> connectedComponentMergers =
         new ArrayList<>(connectedComponents.size());
+    Collection<Policy> policies = VerticalClassMergerPolicyScheduler.getPolicies(appView, mode);
     Collection<Timing> timings =
         ThreadUtils.processItemsWithResults(
             connectedComponents,
             connectedComponent -> {
               Timing threadTiming = Timing.create("Compute classes to merge in component", options);
               ConnectedComponentVerticalClassMerger connectedComponentMerger =
-                  new VerticalClassMergerPolicyExecutor(appView, immediateSubtypingInfo, mode)
-                      .run(connectedComponent, executorService, threadTiming);
+                  new VerticalClassMergerPolicyExecutor(appView, immediateSubtypingInfo)
+                      .run(connectedComponent, policies, executorService, threadTiming);
               if (!connectedComponentMerger.isEmpty()) {
                 synchronized (connectedComponentMergers) {
                   connectedComponentMergers.add(connectedComponentMerger);
@@ -187,16 +188,18 @@
             executorService);
     merger.add(timings);
     merger.end();
+    timing.end();
     return connectedComponentMergers;
   }
 
   private VerticalClassMergerResult applyConnectedComponentMergers(
+      ClassMergerSharedData classMergerSharedData,
       Collection<ConnectedComponentVerticalClassMerger> connectedComponentMergers,
       ExecutorService executorService,
       Timing timing)
       throws ExecutionException {
+    timing.begin("Merge classes");
     TimingMerger merger = timing.beginMerger("Merge classes", executorService);
-    ClassMergerSharedData sharedData = new ClassMergerSharedData(appView);
     VerticalClassMergerResult.Builder verticalClassMergerResult =
         VerticalClassMergerResult.builder(appView);
     Collection<Timing> timings =
@@ -205,7 +208,7 @@
             connectedComponentMerger -> {
               Timing threadTiming = Timing.create("Merge classes in component", options);
               VerticalClassMergerResult.Builder verticalClassMergerComponentResult =
-                  connectedComponentMerger.run(sharedData);
+                  connectedComponentMerger.run(classMergerSharedData);
               verticalClassMergerResult.merge(verticalClassMergerComponentResult);
               threadTiming.end();
               return threadTiming;
@@ -214,82 +217,40 @@
             executorService);
     merger.add(timings);
     merger.end();
+    timing.end();
     return verticalClassMergerResult.build();
   }
 
   private VerticalClassMergerGraphLens runFixup(
-      ProfileCollectionAdditions profileCollectionAdditions,
+      ClassMergerSharedData classMergerSharedData,
       VerticalClassMergerResult verticalClassMergerResult,
       ExecutorService executorService,
       Timing timing)
       throws ExecutionException {
-    DexProgramClass deterministicContext =
-        appView
-            .definitionFor(
-                ListUtils.first(
-                    ListUtils.sort(
-                        verticalClassMergerResult.getVerticallyMergedClasses().getTargets(),
-                        Comparator.naturalOrder())))
-            .asProgramClass();
-    SyntheticArgumentClass syntheticArgumentClass =
-        new SyntheticArgumentClass.Builder(appView).build(deterministicContext);
-    VerticalClassMergerGraphLens lens =
-        new VerticalClassMergerTreeFixer(
-                appView,
-                profileCollectionAdditions,
-                syntheticArgumentClass,
-                verticalClassMergerResult)
-            .run(executorService, timing);
-    return lens;
+    return new VerticalClassMergerTreeFixer(
+            appView, classMergerSharedData, verticalClassMergerResult)
+        .run(executorService, timing);
   }
 
-  // TODO(b/320432664): For code objects where the rewriting is an alpha renaming we can rewrite the
-  //  LIR directly without building IR.
-  private void rewriteCodeWithLens(ExecutorService executorService) throws ExecutionException {
+  private void rewriteCodeWithLens(ExecutorService executorService, Timing timing)
+      throws ExecutionException {
     if (mode.isInitial()) {
       return;
     }
-
-    MethodProcessorEventConsumer eventConsumer = MethodProcessorEventConsumer.empty();
-    OneTimeMethodProcessor.Builder methodProcessorBuilder =
-        OneTimeMethodProcessor.builder(eventConsumer, appView);
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
-      clazz.forEachProgramMethodMatching(
-          method ->
-              method.hasCode()
-                  && !(method.getCode() instanceof IncompleteVerticalClassMergerBridgeCode),
-          methodProcessorBuilder::add);
-    }
-
-    IRConverter converter = new IRConverter(appView);
-    converter.clearEnumUnboxer();
-    converter.clearServiceLoaderRewriter();
-    OneTimeMethodProcessor methodProcessor = methodProcessorBuilder.build();
-    methodProcessor.forEachWaveWithExtension(
-        (method, methodProcessingContext) ->
-            converter.processDesugaredMethod(
-                method,
-                OptimizationFeedbackIgnore.getInstance(),
-                methodProcessor,
-                methodProcessingContext,
-                MethodConversionOptions.forLirPhase(appView)
-                    .disableStringSwitchConversion()
-                    .setFinalizeAfterLensCodeRewriter()),
-        options.getThreadingModule(),
-        executorService);
-
-    // Clear type elements created during IR processing.
-    dexItemFactory.clearTypeElementsCache();
+    LirConverter.rewriteLirWithLens(appView, timing, executorService);
   }
 
   private void updateArtProfiles(
-      ProfileCollectionAdditions profileCollectionAdditions,
       VerticalClassMergerGraphLens verticalClassMergerLens,
-      VerticalClassMergerResult verticalClassMergerResult) {
+      VerticalClassMergerResult verticalClassMergerResult,
+      Timing timing) {
     // Include bridges in art profiles.
+    ProfileCollectionAdditions profileCollectionAdditions =
+        ProfileCollectionAdditions.create(appView);
     if (profileCollectionAdditions.isNop()) {
       return;
     }
+    timing.begin("Update ART profiles");
     List<IncompleteVerticalClassMergerBridgeCode> synthesizedBridges =
         verticalClassMergerResult.getSynthesizedBridges();
     for (IncompleteVerticalClassMergerBridgeCode synthesizedBridge : synthesizedBridges) {
@@ -298,9 +259,12 @@
           additionsBuilder -> additionsBuilder.addRule(synthesizedBridge.getMethod()));
     }
     profileCollectionAdditions.commit(appView);
+    timing.end();
   }
 
-  private void updateKeepInfoForMergedClasses(VerticalClassMergerResult verticalClassMergerResult) {
+  private void updateKeepInfoForMergedClasses(
+      VerticalClassMergerResult verticalClassMergerResult, Timing timing) {
+    timing.begin("Update keep info");
     KeepInfoCollection keepInfo = appView.getKeepInfo();
     keepInfo.mutate(
         mutator -> {
@@ -311,13 +275,15 @@
                   .setRemovedClasses(verticallyMergedClasses.getSources())
                   .build());
         });
+    timing.end();
   }
 
-  private void removeFullyInlinedInstanceInitializers(ExecutorService executorService)
-      throws ExecutionException {
+  private void removeFullyInlinedInstanceInitializers(
+      ExecutorService executorService, Timing timing) throws ExecutionException {
     if (mode.isInitial()) {
       return;
     }
+    timing.begin("Remove fully inlined instance initializers");
     PrunedItems.Builder prunedItemsBuilder =
         PrunedItems.concurrentBuilder().setPrunedApp(appView.app());
     ThreadUtils.<DexProgramClass, Exception>processItems(
@@ -341,13 +307,15 @@
     PrunedItems prunedItems = prunedItemsBuilder.build();
     appView.pruneItems(prunedItems, executorService, Timing.empty());
     appView.appInfo().getMethodAccessInfoCollection().withoutPrunedItems(prunedItems);
+    timing.end();
   }
 
-  private void removeMergedClasses(VerticallyMergedClasses verticallyMergedClasses) {
+  private void removeMergedClasses(VerticallyMergedClasses verticallyMergedClasses, Timing timing) {
     if (mode.isInitial()) {
       return;
     }
 
+    timing.begin("Remove merged classes");
     DirectMappedDexApplication newApplication =
         appView
             .app()
@@ -356,10 +324,14 @@
             .removeProgramClasses(clazz -> verticallyMergedClasses.isMergeSource(clazz.getType()))
             .build();
     appView.setAppInfo(appView.appInfo().rebuildWithLiveness(newApplication));
+    timing.end();
   }
 
   private void finalizeSynthesizedBridges(
-      List<IncompleteVerticalClassMergerBridgeCode> bridges, VerticalClassMergerGraphLens lens) {
+      List<IncompleteVerticalClassMergerBridgeCode> bridges,
+      VerticalClassMergerGraphLens lens,
+      Timing timing) {
+    timing.begin("Finalize synthesized bridges");
     KeepInfoCollection keepInfo = appView.getKeepInfo();
     for (IncompleteVerticalClassMergerBridgeCode code : bridges) {
       ProgramMethod bridge = asProgramMethodOrNull(appView.definitionFor(code.getMethod()));
@@ -380,13 +352,17 @@
           mutator ->
               mutator.joinMethod(bridge, info -> info.merge(appView.getKeepInfo(target).joiner())));
     }
+    timing.end();
   }
 
-  private void markRewrittenWithLens(ExecutorService executorService) throws ExecutionException {
+  private void markRewrittenWithLens(ExecutorService executorService, Timing timing)
+      throws ExecutionException {
     if (mode.isInitial()) {
       return;
     }
-    appView.clearCodeRewritings(executorService);
+    timing.begin("Mark rewritten with lens");
+    appView.clearCodeRewritings(executorService, timing);
+    timing.end();
   }
 
   private boolean verifyGraphLens(
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
index 7fa9f56..fcbf5eb 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
@@ -100,6 +100,10 @@
     this.staticizedMethods = staticizedMethods;
   }
 
+  public boolean hasInterfaceBeenMergedIntoClass(DexType type) {
+    return mergedClasses.hasInterfaceBeenMergedIntoClass(type);
+  }
+
   private boolean isMerged(DexMethod method) {
     return mergedMethods.contains(method);
   }
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerOptions.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerOptions.java
index 31d25ab..dbe28ca 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerOptions.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerOptions.java
@@ -11,6 +11,7 @@
   private final InternalOptions options;
 
   private boolean enabled = true;
+  private boolean enableInitial = true;
 
   public VerticalClassMergerOptions(InternalOptions options) {
     this.options = options;
@@ -20,9 +21,18 @@
     setEnabled(false);
   }
 
+  public void disableInitial() {
+    enableInitial = false;
+  }
+
   public boolean isEnabled(ClassMergerMode mode) {
-    assert mode != null;
-    return enabled && options.isOptimizing() && options.isShrinking();
+    if (!enabled || !options.isOptimizing() || !options.isShrinking()) {
+      return false;
+    }
+    if (mode.isInitial() && !enableInitial) {
+      return false;
+    }
+    return true;
   }
 
   public void setEnabled(boolean enabled) {
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerPolicyExecutor.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerPolicyExecutor.java
index 264441e..1b5f45a 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerPolicyExecutor.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerPolicyExecutor.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.verticalclassmerging;
 
-import com.android.tools.r8.classmerging.ClassMergerMode;
 import com.android.tools.r8.classmerging.Policy;
 import com.android.tools.r8.classmerging.PolicyExecutor;
 import com.android.tools.r8.graph.AppView;
@@ -12,33 +11,10 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.Timing;
-import com.android.tools.r8.verticalclassmerging.policies.NoAbstractMethodsOnAbstractClassesPolicy;
-import com.android.tools.r8.verticalclassmerging.policies.NoAnnotationClassesPolicy;
-import com.android.tools.r8.verticalclassmerging.policies.NoClassInitializationChangesPolicy;
-import com.android.tools.r8.verticalclassmerging.policies.NoDirectlyInstantiatedClassesPolicy;
-import com.android.tools.r8.verticalclassmerging.policies.NoEnclosingMethodAttributesPolicy;
-import com.android.tools.r8.verticalclassmerging.policies.NoFieldResolutionChangesPolicy;
-import com.android.tools.r8.verticalclassmerging.policies.NoIllegalAccessesPolicy;
-import com.android.tools.r8.verticalclassmerging.policies.NoInnerClassAttributesPolicy;
-import com.android.tools.r8.verticalclassmerging.policies.NoInterfacesWithInvokeSpecialToDefaultMethodIntoClassPolicy;
-import com.android.tools.r8.verticalclassmerging.policies.NoInterfacesWithUnknownSubtypesPolicy;
-import com.android.tools.r8.verticalclassmerging.policies.NoInvokeSuperNoSuchMethodErrorsPolicy;
-import com.android.tools.r8.verticalclassmerging.policies.NoKeptClassesPolicy;
-import com.android.tools.r8.verticalclassmerging.policies.NoLockMergingPolicy;
-import com.android.tools.r8.verticalclassmerging.policies.NoMethodResolutionChangesPolicy;
-import com.android.tools.r8.verticalclassmerging.policies.NoNestedMergingPolicy;
-import com.android.tools.r8.verticalclassmerging.policies.NoNonSerializableClassIntoSerializableClassPolicy;
-import com.android.tools.r8.verticalclassmerging.policies.NoServiceInterfacesPolicy;
-import com.android.tools.r8.verticalclassmerging.policies.SafeConstructorInliningPolicy;
-import com.android.tools.r8.verticalclassmerging.policies.SameApiReferenceLevelPolicy;
-import com.android.tools.r8.verticalclassmerging.policies.SameFeatureSplitPolicy;
-import com.android.tools.r8.verticalclassmerging.policies.SameMainDexGroupPolicy;
-import com.android.tools.r8.verticalclassmerging.policies.SameNestPolicy;
-import com.android.tools.r8.verticalclassmerging.policies.SameStartupPartitionPolicy;
-import com.android.tools.r8.verticalclassmerging.policies.SuccessfulVirtualMethodResolutionInTargetPolicy;
 import com.android.tools.r8.verticalclassmerging.policies.VerticalClassMergerPolicyWithPreprocessing;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.LinkedList;
 import java.util.List;
@@ -50,48 +26,21 @@
 
   private final AppView<AppInfoWithLiveness> appView;
   private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
-  private final ClassMergerMode mode;
 
   VerticalClassMergerPolicyExecutor(
-      AppView<AppInfoWithLiveness> appView,
-      ImmediateProgramSubtypingInfo immediateSubtypingInfo,
-      ClassMergerMode mode) {
+      AppView<AppInfoWithLiveness> appView, ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
     this.appView = appView;
     this.immediateSubtypingInfo = immediateSubtypingInfo;
-    this.mode = mode;
   }
 
   ConnectedComponentVerticalClassMerger run(
-      Set<DexProgramClass> connectedComponent, ExecutorService executorService, Timing timing)
+      Set<DexProgramClass> connectedComponent,
+      Collection<Policy> policies,
+      ExecutorService executorService,
+      Timing timing)
       throws ExecutionException {
     Collection<VerticalMergeGroup> groups =
         createInitialMergeGroupsWithDeterministicOrder(connectedComponent);
-    Collection<Policy> policies =
-        List.of(
-            new NoDirectlyInstantiatedClassesPolicy(appView),
-            new NoInterfacesWithUnknownSubtypesPolicy(appView),
-            new NoKeptClassesPolicy(appView),
-            new SameFeatureSplitPolicy(appView),
-            new SameStartupPartitionPolicy(appView),
-            new NoServiceInterfacesPolicy(appView),
-            new NoAnnotationClassesPolicy(),
-            new NoNonSerializableClassIntoSerializableClassPolicy(appView),
-            new SafeConstructorInliningPolicy(appView),
-            new NoEnclosingMethodAttributesPolicy(),
-            new NoInnerClassAttributesPolicy(),
-            new SameNestPolicy(),
-            new SameMainDexGroupPolicy(appView),
-            new NoLockMergingPolicy(appView),
-            new SameApiReferenceLevelPolicy(appView),
-            new NoFieldResolutionChangesPolicy(appView),
-            new NoMethodResolutionChangesPolicy(appView),
-            new NoIllegalAccessesPolicy(appView),
-            new NoClassInitializationChangesPolicy(appView),
-            new NoInterfacesWithInvokeSpecialToDefaultMethodIntoClassPolicy(appView, mode),
-            new NoInvokeSuperNoSuchMethodErrorsPolicy(appView),
-            new SuccessfulVirtualMethodResolutionInTargetPolicy(appView),
-            new NoAbstractMethodsOnAbstractClassesPolicy(appView),
-            new NoNestedMergingPolicy());
     groups = run(groups, policies, executorService, timing);
     return new ConnectedComponentVerticalClassMerger(appView, groups);
   }
@@ -121,7 +70,15 @@
       VerticalClassMergerPolicyWithPreprocessing<T> policy,
       LinkedList<VerticalMergeGroup> linkedGroups) {
     T data = policy.preprocess(linkedGroups);
-    linkedGroups.removeIf(group -> !policy.canMerge(group, data));
+    linkedGroups.removeIf(
+        group -> {
+          if (policy.canMerge(group, data)) {
+            return false;
+          }
+          assert policy.recordRemovedClassesForDebugging(
+              group.getSource().isInterface(), group.size(), Collections.emptyList());
+          return true;
+        });
     return linkedGroups;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerPolicyScheduler.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerPolicyScheduler.java
new file mode 100644
index 0000000..6d58316
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerPolicyScheduler.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2024, 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.verticalclassmerging;
+
+import com.android.tools.r8.classmerging.ClassMergerMode;
+import com.android.tools.r8.classmerging.Policy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.verticalclassmerging.policies.NoAbstractMethodsOnAbstractClassesPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoAnnotationClassesPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoClassInitializationChangesPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoDirectlyInstantiatedClassesPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoEnclosingMethodAttributesPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoFieldResolutionChangesPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoIllegalAccessesPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoInnerClassAttributesPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoInterfacesWithInvokeSpecialToDefaultMethodIntoClassPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoInterfacesWithUnknownSubtypesPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoInvokeSuperNoSuchMethodErrorsPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoKeptClassesPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoLockMergingPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoMethodResolutionChangesPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoNestedMergingPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoNonSerializableClassIntoSerializableClassPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoServiceInterfacesPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.SameApiReferenceLevelPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.SameFeatureSplitPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.SameMainDexGroupPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.SameNestPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.SameStartupPartitionPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.SuccessfulVirtualMethodResolutionInTargetPolicy;
+import java.util.List;
+
+public class VerticalClassMergerPolicyScheduler {
+
+  public static List<Policy> getPolicies(
+      AppView<AppInfoWithLiveness> appView, ClassMergerMode mode) {
+    return List.of(
+        new NoDirectlyInstantiatedClassesPolicy(appView),
+        new NoInterfacesWithUnknownSubtypesPolicy(appView),
+        new NoKeptClassesPolicy(appView),
+        new SameFeatureSplitPolicy(appView),
+        new SameStartupPartitionPolicy(appView),
+        new NoServiceInterfacesPolicy(appView),
+        new NoAnnotationClassesPolicy(),
+        new NoNonSerializableClassIntoSerializableClassPolicy(appView),
+        new NoEnclosingMethodAttributesPolicy(),
+        new NoInnerClassAttributesPolicy(),
+        new SameNestPolicy(),
+        new SameMainDexGroupPolicy(appView),
+        new NoLockMergingPolicy(appView),
+        new SameApiReferenceLevelPolicy(appView),
+        new NoFieldResolutionChangesPolicy(appView),
+        new NoMethodResolutionChangesPolicy(appView),
+        new NoIllegalAccessesPolicy(appView),
+        new NoClassInitializationChangesPolicy(appView),
+        new NoInterfacesWithInvokeSpecialToDefaultMethodIntoClassPolicy(appView, mode),
+        new NoInvokeSuperNoSuchMethodErrorsPolicy(appView),
+        new SuccessfulVirtualMethodResolutionInTargetPolicy(appView),
+        new NoAbstractMethodsOnAbstractClassesPolicy(appView),
+        new NoNestedMergingPolicy());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerTreeFixer.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerTreeFixer.java
index c6c1f4d..dc6763b 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerTreeFixer.java
@@ -3,10 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.verticalclassmerging;
 
+import com.android.tools.r8.classmerging.ClassMergerSharedData;
 import com.android.tools.r8.classmerging.ClassMergerTreeFixer;
-import com.android.tools.r8.classmerging.SyntheticArgumentClass;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Timing;
 import java.util.List;
@@ -23,15 +22,13 @@
 
   VerticalClassMergerTreeFixer(
       AppView<AppInfoWithLiveness> appView,
-      ProfileCollectionAdditions profileCollectionAdditions,
-      SyntheticArgumentClass syntheticArgumentClass,
+      ClassMergerSharedData classMergerSharedData,
       VerticalClassMergerResult verticalClassMergerResult) {
     super(
         appView,
+        classMergerSharedData,
         VerticalClassMergerGraphLens.Builder.createBuilderForFixup(verticalClassMergerResult),
-        verticalClassMergerResult.getVerticallyMergedClasses(),
-        profileCollectionAdditions,
-        syntheticArgumentClass);
+        verticalClassMergerResult.getVerticallyMergedClasses());
     this.synthesizedBridges = verticalClassMergerResult.getSynthesizedBridges();
   }
 
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoKeptClassesPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoKeptClassesPolicy.java
index c9b0cb5..f9a91a3 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoKeptClassesPolicy.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoKeptClassesPolicy.java
@@ -20,23 +20,23 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 
-public class NoKeptClassesPolicy
-    extends VerticalClassMergerPolicyWithPreprocessing<Set<DexProgramClass>> {
+public class NoKeptClassesPolicy extends VerticalClassMergerPolicy {
 
   private final AppView<AppInfoWithLiveness> appView;
   private final InternalOptions options;
+  private final Set<DexProgramClass> keptClasses;
 
   public NoKeptClassesPolicy(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
     this.options = appView.options();
+    this.keptClasses = getPinnedClasses();
   }
 
   @Override
-  public boolean canMerge(VerticalMergeGroup group, Set<DexProgramClass> keptClasses) {
+  public boolean canMerge(VerticalMergeGroup group) {
     DexProgramClass sourceClass = group.getSource();
     return appView.getKeepInfo(sourceClass).isVerticalClassMergingAllowed(options)
         && !keptClasses.contains(sourceClass);
@@ -47,11 +47,6 @@
     return "NoKeptClassesPolicy";
   }
 
-  @Override
-  public Set<DexProgramClass> preprocess(Collection<VerticalMergeGroup> groups) {
-    return getPinnedClasses();
-  }
-
   // Returns a set of types that must not be merged into other types.
   private Set<DexProgramClass> getPinnedClasses() {
     Set<DexProgramClass> pinnedClasses = Sets.newIdentityHashSet();
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/SafeConstructorInliningPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/SafeConstructorInliningPolicy.java
deleted file mode 100644
index 15bcf11..0000000
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/SafeConstructorInliningPolicy.java
+++ /dev/null
@@ -1,72 +0,0 @@
-// 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.verticalclassmerging.policies;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.MainDexInfo;
-import com.android.tools.r8.utils.TraversalContinuation;
-import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
-import com.google.common.collect.Iterables;
-
-public class SafeConstructorInliningPolicy extends VerticalClassMergerPolicy {
-
-  private final AppView<AppInfoWithLiveness> appView;
-  private final MainDexInfo mainDexInfo;
-
-  public SafeConstructorInliningPolicy(AppView<AppInfoWithLiveness> appView) {
-    this.appView = appView;
-    this.mainDexInfo = appView.appInfo().getMainDexInfo();
-  }
-
-  @Override
-  public boolean canMerge(VerticalMergeGroup group) {
-    DexProgramClass sourceClass = group.getSource();
-    DexProgramClass targetClass = group.getTarget();
-    // If there is a constructor in the target, make sure that all source constructors can be
-    // inlined.
-    if (Iterables.isEmpty(targetClass.programInstanceInitializers())) {
-      return true;
-    }
-    TraversalContinuation<?, ?> result =
-        sourceClass.traverseProgramInstanceInitializers(
-            method -> TraversalContinuation.breakIf(disallowInlining(method, targetClass)));
-    return result.shouldContinue();
-  }
-
-  private boolean disallowInlining(ProgramMethod method, DexProgramClass context) {
-    if (!appView.options().inlinerOptions().enableInlining) {
-      return true;
-    }
-    Code code = method.getDefinition().getCode();
-    if (code.isCfCode()) {
-      CfCode cfCode = code.asCfCode();
-      ConstraintWithTarget constraint =
-          cfCode.computeInliningConstraint(appView, appView.graphLens(), method);
-      if (constraint.isNever()) {
-        return true;
-      }
-      // Constructors can have references beyond the root main dex classes. This can increase the
-      // size of the main dex dependent classes and we should bail out.
-      if (mainDexInfo.disallowInliningIntoContext(appView, context, method)) {
-        return true;
-      }
-      return false;
-    }
-    if (code.isDefaultInstanceInitializerCode()) {
-      return false;
-    }
-    return true;
-  }
-
-  @Override
-  public String getName() {
-    return "SafeConstructorInliningPolicy";
-  }
-}
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/ResourceShrinkerImpl.kt b/src/resourceshrinker/java/com/android/build/shrinker/ResourceShrinkerImpl.kt
index d8f6fa2..d404e2e6 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/ResourceShrinkerImpl.kt
+++ b/src/resourceshrinker/java/com/android/build/shrinker/ResourceShrinkerImpl.kt
@@ -171,7 +171,9 @@
     private fun removeResourceUnusedTableEntries(zis: InputStream,
                                                  zos: JarOutputStream,
                                                  srcEntry: ZipEntry) {
-        val resourceIdsToRemove = unused.map { resource -> resource.value }
+        val resourceIdsToRemove = unused
+            .filterNot { it.type == ResourceType.ID }
+            .map { resource -> resource.value }
         val shrunkenResourceTable = Resources.ResourceTable.parseFrom(zis)
                 .nullOutEntriesWithIds(resourceIdsToRemove)
         val bytes = shrunkenResourceTable.toByteArray()
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java
index ab97ce2..f37d381 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java
+++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java
@@ -24,6 +24,7 @@
 import com.android.ide.common.resources.ResourcesUtil;
 import com.android.ide.common.resources.usage.ResourceStore;
 import com.android.ide.common.resources.usage.ResourceUsageModel.Resource;
+import com.android.resources.ResourceType;
 import com.android.tools.r8.FeatureSplit;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -210,10 +211,14 @@
       R8ResourceShrinkerModel model,
       boolean exactMatchingOfStyleablesAndAttr) {
     if (!exactMatchingOfStyleablesAndAttr) {
-      return unusedResources.stream().map(resource -> resource.value).collect(Collectors.toList());
+      return unusedResources.stream()
+          .filter(s -> s.type != ResourceType.ID)
+          .map(resource -> resource.value)
+          .collect(Collectors.toList());
     }
     return model.getResourceStore().getResources().stream()
         .filter(r -> !r.isReachable())
+        .filter(r -> r.type != ResourceType.ID)
         .map(r -> r.value)
         .collect(Collectors.toList());
   }
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
index 088547e..5e0a8ae 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
+++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
@@ -51,8 +51,8 @@
       super(debugReporter, supportMultipackages);
     }
 
-    public Map<Integer, String> getStringResourcesWithSingleValue() {
-      return stringResourcesWithSingleValue;
+    public String getSingleStringValueOrNull(int id) {
+      return stringResourcesWithSingleValue.get(id);
     }
 
     // Similar to instantiation in ProtoResourceTableGatherer, but using an inputstream.
diff --git a/src/test/java/com/android/tools/r8/ProguardVersion.java b/src/test/java/com/android/tools/r8/ProguardVersion.java
index b3ff1a6..28f0cf6 100644
--- a/src/test/java/com/android/tools/r8/ProguardVersion.java
+++ b/src/test/java/com/android/tools/r8/ProguardVersion.java
@@ -13,7 +13,8 @@
   V5_2_1("5.2.1"),
   V6_0_1("6.0.1"),
   V7_0_0("7.0.0"),
-  V7_3_2("7.3.2");
+  V7_3_2("7.3.2"),
+  V7_4_1("7.4.1");
 
   private final String version;
 
@@ -22,7 +23,7 @@
   }
 
   public static ProguardVersion getLatest() {
-    return V7_3_2;
+    return V7_4_1;
   }
 
   public Path getProguardScript() {
@@ -39,7 +40,7 @@
 
   private Path getScriptDirectory() {
     Path scriptDirectory = Paths.get(ToolHelper.THIRD_PARTY_DIR).resolve("proguard");
-    if (this == V7_0_0 || this == V7_3_2) {
+    if (version.startsWith("7.")) {
       scriptDirectory = scriptDirectory.resolve("proguard-" + version).resolve("bin");
     } else {
       scriptDirectory = scriptDirectory.resolve("proguard" + version).resolve("bin");
diff --git a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
index 384043c..228536e 100644
--- a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
@@ -36,7 +36,7 @@
   @Parameters(name = "{0}: {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        getTestParameters().withDexRuntimes().withAllApiLevels().build(), tests.keySet());
+        getTestParameters().withDexRuntimesAndAllApiLevels().build(), tests.keySet());
   }
 
   private static final String SMALI_DIR = ToolHelper.SMALI_BUILD_DIR;
@@ -106,6 +106,8 @@
     tests = testsBuilder.build();
   }
 
+  private static Set<String> invokeErrors = ImmutableSet.of("bad-codegen", "illegal-invokes");
+
   private static Map<String, Set<String>> missingClasses =
       ImmutableMap.of(
           "try-catch", ImmutableSet.of("test.X"),
@@ -244,6 +246,10 @@
         .addProgramDexFileData(Files.readAllBytes(originalDexFile))
         .applyIf(testJarExists, p -> p.addProgramFiles(testJar))
         .addDontWarn(missingClasses.getOrDefault(directoryName, Collections.emptySet()))
+        .addOptionsModification(
+            options ->
+                options.getTestingOptions().allowInvokeErrors =
+                    invokeErrors.contains(directoryName))
         .setMinApi(parameters)
         .compile()
         .run(parameters.getRuntime(), "Test")
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index f803d78..c9b9401 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -946,4 +946,12 @@
     return addProgramClassFileData(testResource.getRClass().getClassFileData());
   }
 
+  public T setAndroidResourcesFromPath(Path input, Path output) {
+    resourceShrinkerOutput = output;
+    getBuilder()
+        .setAndroidResourceProvider(
+            new ArchiveProtoAndroidResourceProvider(input, new PathOrigin(input)));
+    getBuilder().setAndroidResourceConsumer(new ArchiveProtoAndroidResourceConsumer(output, input));
+    return self();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
index 1792e97..fc90c26 100644
--- a/src/test/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -280,7 +280,7 @@
         "No need to use is/assumeR8TestParameters() when not using api levels for CF",
         isCfRuntime() && apiLevel == null);
     assertTrue(apiLevel != null || representativeApiLevelForRuntime);
-    return isDexRuntime() || representativeApiLevelForRuntime;
+    return (isDexRuntime() || representativeApiLevelForRuntime) && !isNoneRuntime();
   }
 
   public TestParameters assumeRuntimeTestParameters() {
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 9b4e4fd..2c07a9a 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.dexsplitter.SplitterTestBase.RunInterface;
 import com.android.tools.r8.references.ClassReference;
@@ -480,6 +482,10 @@
 
   public abstract T addApplyMapping(String proguardMap);
 
+  public T addApplyMapping(Path proguardMap) throws IOException {
+    return addApplyMapping(FileUtils.readTextFile(proguardMap, UTF_8));
+  }
+
   public final T addAlwaysClassInlineAnnotation() {
     return addTestingAnnotation(AlwaysClassInline.class);
   }
diff --git a/src/test/java/com/android/tools/r8/androidresources/AlwaysKeepIDsInLegacyMode.java b/src/test/java/com/android/tools/r8/androidresources/AlwaysKeepIDsInLegacyMode.java
new file mode 100644
index 0000000..605a771
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/AlwaysKeepIDsInLegacyMode.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2024, 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 com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+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 AlwaysKeepIDsInLegacyMode extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public boolean optimized;
+
+  @Parameters(name = "{0}, optimized: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDefaultDexRuntime().withAllApiLevels().build(),
+        BooleanUtils.values());
+  }
+
+  public static AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception {
+    return new AndroidTestResourceBuilder()
+        .withSimpleManifestAndAppNameString()
+        .addRClassInitializeWithDefaultValues(R.string.class, R.id.class)
+        .build(temp);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters)
+        .addProgramClasses(FooBar.class)
+        .addAndroidResources(getTestResources(temp))
+        .addKeepMainRule(FooBar.class)
+        .applyIf(optimized, R8TestBuilder::enableOptimizedShrinking)
+        .compile()
+        .inspectShrunkenResources(
+            resourceTableInspector -> {
+              resourceTableInspector.assertContainsResourceWithName("string", "foo");
+              // The id resource should not be removed in any mode, even though there are no
+              // references to it.
+              resourceTableInspector.assertContainsResourceWithName("id", "the_id");
+            })
+        .run(parameters.getRuntime(), FooBar.class)
+        .assertSuccess();
+  }
+
+  public static class FooBar {
+
+    public static void main(String[] args) {
+      System.out.println(R.string.foo);
+    }
+  }
+
+  public static class R {
+    public static class string {
+      public static int foo;
+    }
+
+    public static class id {
+      public static int the_id;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java b/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
index e0a3a59..5040ed3 100644
--- a/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
+++ b/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
@@ -52,7 +52,8 @@
     STRING,
     DRAWABLE,
     STYLEABLE,
-    XML;
+    XML,
+    ID;
 
     public static RClassType fromClass(Class clazz) {
       String type = rClassWithoutNamespaceAndOuter(clazz).substring(2);
@@ -119,6 +120,13 @@
           + "        path=\"let/it/be\" />\n"
           + "</paths>";
 
+  public static String XML_FILE_WITH_ID =
+      "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+          + "    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n"
+          + "    xmlns:tools=\"http://schemas.android.com/tools\">\n"
+          + "    <item android:id=\"@+id/%s\"/>\n"
+          + "</menu>";
+
   public static class AndroidTestRClass {
     // The original aapt2 generated R.java class
     private final Path javaFilePath;
@@ -256,6 +264,7 @@
   public static class AndroidTestResourceBuilder {
     private String manifest;
     private final Map<String, String> stringValues = new TreeMap<>();
+    private final Set<String> idValues = new TreeSet<>();
     private final Set<String> stringValuesWithExtraLanguage = new TreeSet<>();
     private final Map<String, String> overlayableValues = new TreeMap<>();
     private final Map<String, Integer> styleables = new TreeMap<>();
@@ -285,6 +294,9 @@
             // Add 4 different values, i.e., the array will be 4 integers.
             addStyleable(name, 4);
           }
+          if (rClassType == RClassType.ID) {
+            addIdValue(name);
+          }
         }
       }
       return this;
@@ -317,6 +329,11 @@
       return this;
     }
 
+    AndroidTestResourceBuilder addIdValue(String name) {
+      xmlFiles.put(name + ".xml", String.format(XML_FILE_WITH_ID, name));
+      return this;
+    }
+
     AndroidTestResourceBuilder addExtraLanguageString(String name) {
       stringValuesWithExtraLanguage.add(name);
       return this;
diff --git a/src/test/java/com/android/tools/r8/androidresources/ResourceIDCannonicalizationTest.java b/src/test/java/com/android/tools/r8/androidresources/ResourceIDCannonicalizationTest.java
new file mode 100644
index 0000000..60ce942
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/ResourceIDCannonicalizationTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2024, 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.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder;
+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 ResourceIDCannonicalizationTest extends TestBase {
+  public static final int EXPECTED_RESOURCE_NUMBER = 0x7f010001;
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withDefaultDexRuntime().withAllApiLevels().build();
+  }
+
+  public static AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception {
+    return new AndroidTestResourceBuilder()
+        .withSimpleManifestAndAppNameString()
+        .addRClassInitializeWithDefaultValues(R.string.class)
+        .build(temp);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters)
+        .addProgramClasses(FooBar.class)
+        .addAndroidResources(getTestResources(temp))
+        .addKeepMainRule(FooBar.class)
+        .enableOptimizedShrinking()
+        .compile()
+        .inspect(
+            codeInspector -> {
+              // We should canonicalize the resource numbers separately from the normal const
+              // numbers.
+              // This implies that the output have two distinct const numbers with the same value.
+              long constNumbers =
+                  codeInspector
+                      .clazz(FooBar.class)
+                      .mainMethod()
+                      .streamInstructions()
+                      .filter(i -> i.isConstNumber(EXPECTED_RESOURCE_NUMBER))
+                      .count();
+              assertEquals(2, constNumbers);
+            })
+        .inspectShrunkenResources(
+            resourceTableInspector -> {
+              resourceTableInspector.assertContainsResourceWithName("string", "foo");
+            });
+  }
+
+  public static class FooBar {
+
+    public static void main(String[] args) {
+      if (System.currentTimeMillis() == 0) {
+        System.out.println(EXPECTED_RESOURCE_NUMBER);
+        System.out.println(R.string.foo);
+        System.out.println(EXPECTED_RESOURCE_NUMBER);
+        System.out.println(R.string.foo);
+        System.out.println(EXPECTED_RESOURCE_NUMBER);
+        System.out.println(EXPECTED_RESOURCE_NUMBER);
+      }
+    }
+  }
+
+  public static class R {
+    public static class string {
+      public static int foo;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/androidresources/optimizedshrinking/TestOptimizedShrinking.java b/src/test/java/com/android/tools/r8/androidresources/optimizedshrinking/TestOptimizedShrinking.java
index 662d00f..74c7102 100644
--- a/src/test/java/com/android/tools/r8/androidresources/optimizedshrinking/TestOptimizedShrinking.java
+++ b/src/test/java/com/android/tools/r8/androidresources/optimizedshrinking/TestOptimizedShrinking.java
@@ -71,7 +71,7 @@
               resourceTableInspector.assertContainsResourceWithName("drawable", "foobar");
               // In debug mode legacy shrinker will not attribute our $R inner class as an R class
               // (this is only used for testing, _real_ R classes are not inner classes.
-              if (!debug || optimized) {
+              if (!debug) {
                 resourceTableInspector.assertDoesNotContainResourceWithName(
                     "string", "unused_string");
                 resourceTableInspector.assertDoesNotContainResourceWithName(
@@ -107,7 +107,7 @@
 
                 // In optimized mode we track these correctly, so we should not unconditionally keep
                 // all attributes.
-                if (optimized) {
+                if (optimized && !debug) {
                   resourceTableInspector.assertDoesNotContainResourceWithName(
                       "attr", "attr_unused_styleable" + i);
                 } else {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorCantInlineTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorCantInlineTest.java
index f2db164..3e84ae4 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorCantInlineTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorCantInlineTest.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -33,7 +34,7 @@
         .inspect(
             codeInspector -> {
               assertThat(codeInspector.clazz(A.class), isAbsent());
-              assertThat(codeInspector.clazz(B.class), isAbsent());
+              assertThat(codeInspector.clazz(B.class), isPresentIf(parameters.isCfRuntime()));
               assertThat(codeInspector.clazz(C.class), isPresent());
               assertThat(codeInspector.clazz(D.class), isAbsent());
             });
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java
index 7dee4de..e6e1d11 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java
@@ -45,7 +45,8 @@
               assertThat(firstInitSubject, isPresent());
               assertThat(firstInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
 
-              MethodSubject otherInitSubject = aClassSubject.init("int", "int");
+              MethodSubject otherInitSubject =
+                  aClassSubject.init("int", parameters.isCfRuntime() ? "java.lang.Object" : "int");
               assertThat(otherInitSubject, isPresent());
               assertThat(otherInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java
index 2be25d3..ecb49d6 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java
@@ -45,7 +45,8 @@
               assertThat(firstInitSubject, isPresent());
               assertThat(firstInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
 
-              MethodSubject otherInitSubject = aClassSubject.init("int", "int");
+              MethodSubject otherInitSubject =
+                  aClassSubject.init("int", parameters.isCfRuntime() ? "java.lang.Object" : "int");
               assertThat(otherInitSubject, isPresent());
               assertThat(otherInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentArgumentAndAssignmentOrderMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentArgumentAndAssignmentOrderMergingTest.java
index f319d54..c8b807f 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentArgumentAndAssignmentOrderMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentArgumentAndAssignmentOrderMergingTest.java
@@ -53,9 +53,10 @@
               ClassSubject aClassSubject = inspector.clazz(A.class);
               assertThat(aClassSubject, isPresent());
               // TODO(b/189296638): Enable constructor merging by changing the constructor
-              // arguments.
+              //  arguments.
               assertEquals(
-                  2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+                  parameters.isCfRuntime() ? 4 : 2,
+                  aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("CD", "CD");
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentArgumentOrderMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentArgumentOrderMergingTest.java
index ba0b57d..19ed979 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentArgumentOrderMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentArgumentOrderMergingTest.java
@@ -54,9 +54,10 @@
               assertThat(aClassSubject, isPresent());
 
               // TODO(b/189296638): Enable constructor merging by changing the constructor
-              // arguments.
+              //  arguments.
               assertEquals(
-                  2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+                  parameters.isCfRuntime() ? 4 : 2,
+                  aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("C0", "D1");
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndExtraNullsMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndExtraNullsMergingTest.java
index 5e15d11..fa03ace 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndExtraNullsMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndExtraNullsMergingTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.classmerging.horizontal;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
@@ -53,12 +54,13 @@
               ClassSubject aClassSubject = inspector.clazz(A.class);
               assertThat(aClassSubject, isPresent());
               assertEquals(
-                  2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+                  parameters.isCfRuntime() ? 3 : 2,
+                  aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+              assertThat(aClassSubject.init("java.lang.Object", "int"), isPresent());
+              assertThat(aClassSubject.init("java.lang.Object", "int", "int"), isPresent());
               assertThat(
-                  aClassSubject.method("void", "<init>", "java.lang.Object", "int"), isPresent());
-              assertThat(
-                  aClassSubject.method("void", "<init>", "java.lang.Object", "int", "int"),
-                  isPresent());
+                  aClassSubject.init("java.lang.Object", "int", "java.lang.Object"),
+                  isPresentIf(parameters.isCfRuntime()));
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("C", "0", "C", "D");
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java
index 0d09fd4..0d912b3 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java
@@ -26,9 +26,9 @@
         .addProgramClasses(C.class)
         .addProgramClasses(D.class)
         .addKeepMainRule(Main.class)
-        .allowAccessModification(false)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoAccessModificationAnnotationsForMembers()
         .setMinApi(parameters)
         .compile()
         .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/SuperConstructorCallsVirtualMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/SuperConstructorCallsVirtualMethodTest.java
index f3335fa..fe4a7d8 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/SuperConstructorCallsVirtualMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/SuperConstructorCallsVirtualMethodTest.java
@@ -4,9 +4,9 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.IsNot.not;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
@@ -23,6 +23,9 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters)
@@ -30,9 +33,9 @@
         .assertSuccessWithOutputLines("5", "foo hello", "B", "bar world", "5", "B")
         .inspect(
             codeInspector -> {
-              assertThat(codeInspector.clazz(Parent.class), isPresent());
+              assertThat(codeInspector.clazz(Parent.class), isAbsent());
               assertThat(codeInspector.clazz(A.class), isPresent());
-              assertThat(codeInspector.clazz(B.class), not(isPresent()));
+              assertThat(codeInspector.clazz(B.class), isAbsent());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/C.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/C.java
index f2220c0..003bc93 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/C.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/C.java
@@ -6,11 +6,14 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoAccessModification;
 
 @NeverClassInline
 public class C {
-  String field;
 
+  @NoAccessModification String field;
+
+  @NoAccessModification
   protected C(String field) {
     this.field = field;
   }
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/ExceptionTablesTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/ExceptionTablesTest.java
index 1ba7dcf..4025566 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/ExceptionTablesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/ExceptionTablesTest.java
@@ -4,18 +4,19 @@
 
 package com.android.tools.r8.classmerging.vertical;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.android.tools.r8.utils.codeinspector.TypeSubject;
 import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
+import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.junit.Test;
@@ -28,13 +29,17 @@
 @RunWith(Parameterized.class)
 public class ExceptionTablesTest extends VerticalClassMergerTestBase {
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return TestBase.getTestParameters().withAllRuntimesAndApiLevels().build();
+  private final boolean disableInitial;
+
+  @Parameters(name = "{1}, disable initial: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), TestBase.getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
-  public ExceptionTablesTest(TestParameters parameters) {
+  public ExceptionTablesTest(boolean disableInitial, TestParameters parameters) {
     super(parameters);
+    this.disableInitial = disableInitial;
   }
 
   @Test
@@ -42,7 +47,14 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(ExceptionTablesTest.class)
         .addKeepMainRule(TestClass.class)
-        .addVerticallyMergedClassesInspector(this::inspectVerticallyMergedClasses)
+        .applyIf(
+            disableInitial,
+            testBuilder ->
+                testBuilder.addOptionsModification(
+                    options -> options.getVerticalClassMergerOptions().disableInitial()),
+            testBuilder ->
+                testBuilder.addVerticallyMergedClassesInspector(
+                    this::inspectVerticallyMergedClasses))
         .setMinApi(parameters)
         .compile()
         .inspect(this::inspect)
@@ -58,8 +70,8 @@
     assertThat(inspector.clazz(TestClass.class), isPresent());
     assertThat(inspector.clazz(ExceptionB.class), isPresent());
     assertThat(inspector.clazz(Exception2.class), isPresent());
-    assertThat(inspector.clazz(ExceptionA.class), not(isPresent()));
-    assertThat(inspector.clazz(Exception1.class), not(isPresent()));
+    assertThat(inspector.clazz(ExceptionA.class), isAbsent());
+    assertThat(inspector.clazz(Exception1.class), isAbsent());
 
     // Check that only two exception guard types remain.
     MethodSubject mainMethodSubject = inspector.clazz(TestClass.class).mainMethod();
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/InvokeStaticToInterfaceVerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/InvokeStaticToInterfaceVerticalClassMergerTest.java
new file mode 100644
index 0000000..ba6a83c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/InvokeStaticToInterfaceVerticalClassMergerTest.java
@@ -0,0 +1,96 @@
+// Copyright (c) 2024, 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.classmerging.vertical;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+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 InvokeStaticToInterfaceVerticalClassMergerTest extends TestBase {
+
+  @Parameter(0)
+  public boolean disableInitialRoundOfVerticalClassMerging;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, disable initial: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .applyIf(
+            disableInitialRoundOfVerticalClassMerging,
+            b ->
+                b.addOptionsModification(
+                    options -> options.getVerticalClassMergerOptions().disableInitial()))
+        .enableInliningAnnotations()
+        .setMinApi(parameters)
+        .compile()
+        .inspect(
+            inspector -> {
+              // Check interface is removed due to class merging and the static interface method is
+              // present on the subclass except when moved as a result of static interface method
+              // desugaring.
+              ClassSubject iClassSubject = inspector.clazz(I.class);
+              assertThat(iClassSubject, isAbsent());
+
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+
+              MethodSubject fooMethodSubject = aClassSubject.uniqueMethodWithOriginalName("hello");
+              assertThat(
+                  fooMethodSubject,
+                  isPresentIf(parameters.canUseDefaultAndStaticInterfaceMethods()));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      // After vertical class merging, invoke should have the is-interface bit set to false.
+      I.hello();
+      System.out.println(new A());
+    }
+  }
+
+  interface I {
+
+    @NeverInline
+    static void hello() {
+      System.out.print("Hello");
+    }
+  }
+
+  static class A implements I {
+
+    @Override
+    public String toString() {
+      return ", world!";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerInitTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerInitTest.java
index 466e9fd..f1b4b6a 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerInitTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerInitTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.classmerging.vertical;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 
@@ -13,13 +14,12 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
-import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
 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)
@@ -28,38 +28,29 @@
 // force-inlining, so the renamed constructor broke the init chain.
 public class VerticalClassMergerInitTest extends TestBase {
 
-  private final TestParameters parameters;
+  @Parameter(0)
+  public TestParameters parameters;
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().build();
-  }
-
-  public VerticalClassMergerInitTest(TestParameters parameters) {
-    this.parameters = parameters;
+    return getTestParameters().withDexRuntimes().withApiLevel(AndroidApiLevel.K_WATCH).build();
   }
 
   @Test
   public void testMergingClassWithConstructorNotInMainDex()
       throws IOException, CompilationFailedException, ExecutionException {
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend())
         .addInnerClasses(VerticalClassMergerInitTest.class)
         .addKeepMainRule(Main.class)
         .addMainDexRules("-keep class " + Main.class.getTypeName())
-        .enableNeverClassInliningAnnotations()
-        .setMinApi(AndroidApiLevel.K_WATCH)
-        .addOptionsModification(
-            options -> {
-              options.forceProguardCompatibility = true;
-            })
-        // The initializer is small in this example so only allow FORCE inlining.
-        .addOptionsModification(InlinerOptions::setOnlyForceInlining)
         .addVerticallyMergedClassesInspector(
-            VerticallyMergedClassesInspector::assertNoClassesMerged)
+            inspector -> inspector.assertMergedIntoSubtype(Base.class))
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters)
         .compile()
         .inspect(
             inspector -> {
-              assertThat(inspector.clazz(Base.class), isPresent());
+              assertThat(inspector.clazz(Base.class), isAbsent());
               assertThat(inspector.clazz(Child.class), isPresent());
             })
         .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingFieldsTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingFieldsTest.java
index 4a1e82c..121a0f3 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingFieldsTest.java
@@ -89,7 +89,11 @@
         .setMinApi(parameters)
         .addOptionsModification(options -> options.emitNestAnnotationsInDex = true)
         .compile()
-        .inspect(inspector -> assertEquals(1, inspector.allClasses().size()))
+        // TODO(b/136250031, b/136458109): Allow inlining of constructors into constructors when
+        //  compiling to CF.
+        .inspect(
+            inspector ->
+                assertEquals(parameters.isCfRuntime() ? 2 : 1, inspector.allClasses().size()))
         .run(parameters.getRuntime(), "Host")
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticInterfaceNestedTest.java b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticInterfaceNestedTest.java
index a15add8..f683135 100644
--- a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticInterfaceNestedTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticInterfaceNestedTest.java
@@ -5,39 +5,43 @@
 package com.android.tools.r8.desugar.staticinterfacemethod;
 
 import static com.android.tools.r8.desugar.staticinterfacemethod.InvokeStaticInterfaceNestedTest.Library.foo;
-import static org.junit.Assert.assertThrows;
+import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertFailsCompilationIf;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.DesugarTestConfiguration;
-import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.BooleanUtils;
+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 InvokeStaticInterfaceNestedTest extends TestBase {
 
-  private final TestParameters parameters;
-  private final String UNEXPECTED_SUCCESS = "Hello World!";
+  private static final String UNEXPECTED_SUCCESS = "Hello World!";
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  @Parameter(0)
+  public boolean allowInvokeErrors;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, allow invalid invokes: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
   }
 
-  public InvokeStaticInterfaceNestedTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  private void checkDexResult(TestRunResult<?> runResult, boolean isDesugared) {
+  private void inspectRunResult(TestRunResult<?> runResult, boolean isDesugared) {
     boolean didDesugarInterfaceMethods =
         isDesugared && !parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring();
     if (parameters.isCfRuntime()) {
@@ -67,6 +71,7 @@
 
   @Test
   public void testDesugar() throws Exception {
+    assumeFalse(allowInvokeErrors);
     testForDesugaring(parameters)
         .addProgramClassFileData(
             rewriteToUseNonInterfaceMethodReference(Main.class, "main"),
@@ -76,27 +81,27 @@
             result ->
                 result.applyIf(
                     DesugarTestConfiguration::isDesugared,
-                    r -> checkDexResult(r, true),
-                    r -> checkDexResult(r, false)));
+                    r -> inspectRunResult(r, true),
+                    r -> inspectRunResult(r, false)));
   }
 
   @Test
   public void testR8() throws Exception {
     parameters.assumeR8TestParameters();
-    R8FullTestBuilder testBuilder =
-        testForR8(parameters.getBackend())
-            .addProgramClassFileData(
-                rewriteToUseNonInterfaceMethodReference(Main.class, "main"),
-                rewriteToUseNonInterfaceMethodReference(Library.class, "foo"))
-            .addKeepAllClassesRule()
-            .setMinApi(parameters)
-            .addKeepMainRule(Main.class);
-    if (parameters.isDexRuntime()) {
-      checkDexResult(testBuilder.run(parameters.getRuntime(), Main.class), true);
-    } else {
-      // TODO(b/166213037): Should not throw an error.
-      assertThrows(CompilationFailedException.class, testBuilder::compile);
-    }
+    assertFailsCompilationIf(
+        !allowInvokeErrors,
+        () ->
+            testForR8(parameters.getBackend())
+                .addProgramClassFileData(
+                    rewriteToUseNonInterfaceMethodReference(Main.class, "main"),
+                    rewriteToUseNonInterfaceMethodReference(Library.class, "foo"))
+                .addKeepAllClassesRule()
+                .addOptionsModification(
+                    options -> options.getTestingOptions().allowInvokeErrors = allowInvokeErrors)
+                .setMinApi(parameters)
+                .compile()
+                .run(parameters.getRuntime(), Main.class)
+                .apply(runResult -> inspectRunResult(runResult, parameters.isDexRuntime())));
   }
 
   private byte[] rewriteToUseNonInterfaceMethodReference(Class<?> clazz, String methodName)
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge3Test.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge3Test.java
index e070f46..0e3f590 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge3Test.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge3Test.java
@@ -13,13 +13,13 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.StringUtils;
 import java.io.IOException;
 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)
@@ -27,25 +27,21 @@
 
   private static final String EXPECTED = StringUtils.lines("Hello World!");
 
-  private final TestParameters parameters;
+  @Parameter(0)
+  public TestParameters parameters;
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public InvokeSpecialInterfaceWithBridge3Test(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
   @Test
   public void testRuntime() throws Exception {
-    TestRunResult<?> result =
-        testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
-            .addProgramClasses(I.class, A.class, Main.class)
-            .addProgramClassFileData(getClassWithTransformedInvoked())
-            .run(parameters.getRuntime(), Main.class)
-            .apply(this::inspectRunResult);
+    testForRuntime(parameters)
+        .addProgramClasses(I.class, A.class, Main.class)
+        .addProgramClassFileData(getClassWithTransformedInvoked())
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::inspectRunResult);
   }
 
   private void inspectRunResult(SingleTestRunResult<?> runResult) {
@@ -77,6 +73,7 @@
         .addProgramClasses(I.class, A.class, Main.class)
         .addProgramClassFileData(getClassWithTransformedInvoked())
         .addKeepMainRule(Main.class)
+        .addOptionsModification(options -> options.getTestingOptions().allowInvokeErrors = true)
         .setMinApi(parameters)
         .run(parameters.getRuntime(), Main.class)
         .applyIf(
diff --git a/src/test/java/com/android/tools/r8/gson/GsonDefaultConstructorTest.java b/src/test/java/com/android/tools/r8/gson/GsonDefaultConstructorTest.java
index 9d0b65d..fd6deea 100644
--- a/src/test/java/com/android/tools/r8/gson/GsonDefaultConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/gson/GsonDefaultConstructorTest.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.ProguardVersion;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.gson.GsonNoDefaultConstructorTest.Data;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/NewObjectCanBePostponedTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/NewObjectCanBePostponedTest.java
new file mode 100644
index 0000000..12249de
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/NewObjectCanBePostponedTest.java
@@ -0,0 +1,59 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.analysis.sideeffect;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+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.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NewObjectCanBePostponedTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters)
+        .compile()
+        .inspect(
+            inspector -> {
+              MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
+              assertThat(mainMethodSubject, isPresent());
+              assertTrue(mainMethodSubject.getMethod().getCode().isEmptyVoidMethod());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithEmptyOutput();
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      Object o = A.f;
+    }
+  }
+
+  static class A {
+
+    static final Object f = new Object();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
index 93ad7a7..eff9d7a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -27,7 +27,6 @@
 import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.LinkedList;
@@ -166,7 +165,6 @@
             new NumberGenerator(),
             basicBlockNumberGenerator,
             IRMetadata.unknown(),
-            Origin.unknown(),
             MethodConversionOptions.nonConverting());
     PeepholeOptimizer.optimize(appView, code, new MockLinearScanRegisterAllocator(appView, code));
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
index 18c5121..997f0e0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -30,7 +30,6 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.conversion.passes.TrivialGotosCollapser;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
@@ -107,7 +106,6 @@
             new NumberGenerator(),
             basicBlockNumberGenerator,
             IRMetadata.unknown(),
-            Origin.unknown(),
             MethodConversionOptions.forD8(appView));
     new TrivialGotosCollapser(appView).run(code, Timing.empty());
     assertTrue(code.entryBlock().isTrivialGoto());
@@ -196,7 +194,6 @@
             new NumberGenerator(),
             basicBlockNumberGenerator,
             IRMetadata.unknown(),
-            Origin.unknown(),
             MethodConversionOptions.forD8(appView));
     new TrivialGotosCollapser(appView).run(code, Timing.empty());
     assertTrue(block0.getInstructions().get(1).isIf());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineWithMonitorInConstructorInline.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineWithMonitorInConstructorInline.java
index de3375c..a2b9b48 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineWithMonitorInConstructorInline.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineWithMonitorInConstructorInline.java
@@ -4,9 +4,13 @@
 
 package com.android.tools.r8.ir.optimize.inliner.sync;
 
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.containsConstString;
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -14,50 +18,91 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InlineWithMonitorInConstructorInline extends TestBase {
 
-  private final TestParameters parameters;
+  @Parameter(0)
+  public TestParameters parameters;
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public InlineWithMonitorInConstructorInline(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
   @Test
   public void test() throws Exception {
     testForR8(parameters.getBackend())
         .addInnerClasses(InlineWithMonitorInConstructorInline.class)
         .addKeepMainRule(TestClass.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector
+                    .assertIsCompleteMergeGroup(Foo.class, Bar.class)
+                    .assertNoOtherClassesMerged())
         .setMinApi(parameters)
         .run(parameters.getRuntime(), TestClass.class)
         .inspect(this::inspect)
         .assertSuccessWithOutputLines("foo", "monitor", "bar", "monitor2");
   }
 
+  // Check we at most inline one method with monitor instructions into synthetic constructors
+  // created by class merging. See also b/238399429.
   private void inspect(CodeInspector inspector) {
     ClassSubject classSubject = inspector.clazz(TestClass.class);
     assertThat(classSubject, isPresent());
+
+    ClassSubject fooClassSubject = inspector.clazz(Bar.class);
+    assertThat(fooClassSubject, isPresent());
+
+    // When compiling to CF we do not allow inlining methods into constructors.
+    // TODO(b/136458109): Util class should be absent when compiling to CF.
     ClassSubject utilClassSubject = inspector.clazz(Util.class);
-    if (parameters.isCfRuntime()
-        || parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M)) {
-      // We disallow merging methods with monitors into constructors which will be class merged
-      // on pre M devices, see b/238399429
-      assertThat(utilClassSubject, isPresent());
-    } else {
-      assertThat(utilClassSubject, not(isPresent()));
+    assertThat(utilClassSubject, isPresentIf(parameters.isCfRuntime()));
+
+    // The Util class is fully inlined when compiling to DEX. Verify that the two monitor
+    // instructions are not inlined into the same method.
+    if (parameters.isDexRuntime()) {
+      if (parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M)) {
+        // Find the synthetic constructor with an added `int classId` parameter. Verify that only
+        // Foo.<init> has been inlined this constructor.
+        MethodSubject syntheticInit = fooClassSubject.init("int");
+        assertThat(syntheticInit, isPresent());
+        assertThat(syntheticInit, containsConstString("foo"));
+        assertThat(syntheticInit, not(containsConstString("bar")));
+        assertEquals(1, numberOfMonitorEnterInstructions(syntheticInit));
+
+        // Find the non-synthetic constructor corresponding to Bar.<init>.
+        MethodSubject barInit = fooClassSubject.init();
+        assertThat(barInit, isPresent());
+        assertThat(barInit, containsConstString("bar"));
+        assertThat(barInit, not(containsConstString("foo")));
+        assertEquals(1, numberOfMonitorEnterInstructions(barInit));
+
+        // Finally verify that the synthetic constructor calls the non-synthetic constructor, due to
+        // inlining being prohibited.
+        assertThat(syntheticInit, invokesMethod(barInit));
+      } else {
+        MethodSubject syntheticInit = fooClassSubject.uniqueInstanceInitializer();
+        assertThat(syntheticInit, isPresent());
+        assertThat(syntheticInit, containsConstString("bar"));
+        assertThat(syntheticInit, containsConstString("foo"));
+        assertEquals(2, numberOfMonitorEnterInstructions(syntheticInit));
+      }
     }
   }
 
+  private static long numberOfMonitorEnterInstructions(MethodSubject methodSubject) {
+    return methodSubject.streamInstructions().filter(InstructionSubject::isMonitorEnter).count();
+  }
+
   static class Foo {
     public Foo() {
       System.out.println("foo");
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestUtils.java b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestUtils.java
index 7335975..176c263 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestUtils.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestUtils.java
@@ -24,11 +24,11 @@
 
 public class KeepAnnoTestUtils {
 
-  // TODO(b/321674067): Update this to PG 7.4.
-  public static ProguardVersion PG_VERSION = ProguardVersion.V7_3_2;
+  public static ProguardVersion PG_VERSION = ProguardVersion.V7_4_1;
 
-  // TODO(b/321674067): Downgrade this to oldest supported AGP, such as R8 8.0.35.
-  public static Path R8_LIB = Paths.get(ToolHelper.THIRD_PARTY_DIR, "r8", "r8lib_8.2.20-dev.jar");
+  // Track support for R8 version 8.0.46 which is included in AGP 8.0.2
+  public static Path R8_LIB =
+      Paths.get(ToolHelper.THIRD_PARTY_DIR, "r8-releases", "8.0.46", "r8lib.jar");
 
   public static Path getKeepAnnoLib(TemporaryFolder temp) throws IOException {
     Path archive = temp.newFolder().toPath().resolve("keepanno.jar");
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepSameMethodTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepSameMethodTest.java
index aebeeb2..b0c50d2 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepSameMethodTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepSameMethodTest.java
@@ -27,9 +27,6 @@
 
   static final String EXPECTED = StringUtils.lines("foo");
 
-  // TODO(b/265893433): The use of backreferences does not work in PG.
-  static final String UNEXPECTED_PG = StringUtils.lines("main");
-
   @Parameter public KeepAnnoParameters parameters;
 
   @Parameterized.Parameters(name = "{0}")
@@ -47,7 +44,7 @@
         // The "all members" target will create an unused "all fields" rule.
         .allowUnusedProguardConfigurationRules()
         .run(TestClass.class)
-        .assertSuccessWithOutput(parameters.isPG() ? UNEXPECTED_PG : EXPECTED)
+        .assertSuccessWithOutput(EXPECTED)
         .applyIf(parameters.isR8(), r -> r.inspect(this::checkOutput));
   }
 
diff --git a/src/test/java/com/android/tools/r8/keepanno/MembersAnnotatedByPatternsTest.java b/src/test/java/com/android/tools/r8/keepanno/MembersAnnotatedByPatternsTest.java
index 0f2aadf..52850da 100644
--- a/src/test/java/com/android/tools/r8/keepanno/MembersAnnotatedByPatternsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/MembersAnnotatedByPatternsTest.java
@@ -8,9 +8,6 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-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.AnnotationPattern;
 import com.android.tools.r8.keepanno.annotations.ClassNamePattern;
 import com.android.tools.r8.keepanno.annotations.KeepItemKind;
@@ -29,44 +26,32 @@
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.util.List;
-import org.checkerframework.checker.units.qual.A;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
 
 @RunWith(Parameterized.class)
-public class MembersAnnotatedByPatternsTest extends TestBase {
+public class MembersAnnotatedByPatternsTest extends KeepAnnoTestBase {
 
   static final String EXPECTED = StringUtils.lines("b", "bar", "a", "foo");
 
-  private final TestParameters parameters;
+  @Parameter public KeepAnnoParameters parameters;
 
   @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
-  }
-
-  public MembersAnnotatedByPatternsTest(TestParameters parameters) {
-    this.parameters = parameters;
+  public static List<KeepAnnoParameters> data() {
+    return createParameters(
+        getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build());
   }
 
   @Test
-  public void testReference() throws Exception {
-    testForRuntime(parameters)
+  public void test() throws Exception {
+    testForKeepAnno(parameters)
         .addProgramClasses(getInputClasses())
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(EXPECTED);
-  }
-
-  @Test
-  public void testR8() throws Exception {
-    testForR8(parameters.getBackend())
-        .enableExperimentalKeepAnnotations()
-        .addProgramClasses(getInputClasses())
-        .setMinApi(parameters)
-        .run(parameters.getRuntime(), TestClass.class)
+        .setExcludedOuterClass(getClass())
+        .run(TestClass.class)
         .assertSuccessWithOutput(EXPECTED)
-        .inspect(this::checkOutput);
+        .applyIf(parameters.isShrinker(), r -> r.inspect(this::checkOutput));
   }
 
   public List<Class<?>> getInputClasses() {
diff --git a/src/test/java/com/android/tools/r8/keepanno/MethodNameStringPatternsTest.java b/src/test/java/com/android/tools/r8/keepanno/MethodNameStringPatternsTest.java
index 2444ccf..9306ca4 100644
--- a/src/test/java/com/android/tools/r8/keepanno/MethodNameStringPatternsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/MethodNameStringPatternsTest.java
@@ -8,9 +8,6 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-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.KeepItemKind;
 import com.android.tools.r8.keepanno.annotations.KeepTarget;
 import com.android.tools.r8.keepanno.annotations.StringPattern;
@@ -25,40 +22,29 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
 
 @RunWith(Parameterized.class)
-public class MethodNameStringPatternsTest extends TestBase {
+public class MethodNameStringPatternsTest extends KeepAnnoTestBase {
 
   static final String EXPECTED = StringUtils.lines("1");
 
-  private final TestParameters parameters;
+  @Parameter public KeepAnnoParameters parameters;
 
   @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
-  }
-
-  public MethodNameStringPatternsTest(TestParameters parameters) {
-    this.parameters = parameters;
+  public static List<KeepAnnoParameters> data() {
+    return createParameters(
+        getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build());
   }
 
   @Test
-  public void testReference() throws Exception {
-    testForRuntime(parameters)
+  public void test() throws Exception {
+    testForKeepAnno(parameters)
         .addProgramClasses(getInputClasses())
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(EXPECTED);
-  }
-
-  @Test
-  public void testR8() throws Exception {
-    testForR8(parameters.getBackend())
-        .enableExperimentalKeepAnnotations()
-        .addProgramClasses(getInputClasses())
-        .setMinApi(parameters)
-        .run(parameters.getRuntime(), TestClass.class)
+        .setExcludedOuterClass(getClass())
+        .run(TestClass.class)
         .assertSuccessWithOutput(EXPECTED)
-        .inspect(this::checkOutput);
+        .applyIf(parameters.isShrinker(), r -> r.inspect(this::checkOutput));
   }
 
   public List<Class<?>> getInputClasses() {
diff --git a/src/test/java/com/android/tools/r8/keepanno/MethodPatternsTest.java b/src/test/java/com/android/tools/r8/keepanno/MethodPatternsTest.java
index 86f2f9c..7654721 100644
--- a/src/test/java/com/android/tools/r8/keepanno/MethodPatternsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/MethodPatternsTest.java
@@ -8,9 +8,6 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-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.KeepItemKind;
 import com.android.tools.r8.keepanno.annotations.KeepTarget;
 import com.android.tools.r8.keepanno.annotations.TypePattern;
@@ -25,41 +22,30 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
 
 @RunWith(Parameterized.class)
-public class MethodPatternsTest extends TestBase {
+public class MethodPatternsTest extends KeepAnnoTestBase {
 
   static final String EXPECTED =
       StringUtils.lines("Hello 42", "Hello 12", "int", "long", "class java.lang.Integer");
 
-  private final TestParameters parameters;
+  @Parameter public KeepAnnoParameters parameters;
 
   @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
-  }
-
-  public MethodPatternsTest(TestParameters parameters) {
-    this.parameters = parameters;
+  public static List<KeepAnnoParameters> data() {
+    return createParameters(
+        getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build());
   }
 
   @Test
-  public void testReference() throws Exception {
-    testForRuntime(parameters)
+  public void test() throws Exception {
+    testForKeepAnno(parameters)
         .addProgramClasses(getInputClasses())
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(EXPECTED);
-  }
-
-  @Test
-  public void testR8() throws Exception {
-    testForR8(parameters.getBackend())
-        .enableExperimentalKeepAnnotations()
-        .addProgramClasses(getInputClasses())
-        .setMinApi(parameters)
-        .run(parameters.getRuntime(), TestClass.class)
+        .setExcludedOuterClass(getClass())
+        .run(TestClass.class)
         .assertSuccessWithOutput(EXPECTED)
-        .inspect(this::checkOutput);
+        .applyIf(parameters.isShrinker(), r -> r.inspect(this::checkOutput));
   }
 
   public List<Class<?>> getInputClasses() {
diff --git a/src/test/java/com/android/tools/r8/keepanno/compatissues/BackReferenceIssuesTest.java b/src/test/java/com/android/tools/r8/keepanno/compatissues/BackReferenceIssuesTest.java
new file mode 100644
index 0000000..63692c5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/compatissues/BackReferenceIssuesTest.java
@@ -0,0 +1,249 @@
+// Copyright (c) 2024, 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.compatissues;
+
+import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertThrowsIf;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+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.CompilationFailedException;
+import com.android.tools.r8.ProguardVersion;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+@RunWith(Parameterized.class)
+public class BackReferenceIssuesTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("42");
+
+  public enum Shrinker {
+    PG,
+    R8;
+
+    public boolean isPG() {
+      return this == PG;
+    }
+  }
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public Shrinker shrinker;
+
+  @Parameterized.Parameters(name = "{0}, {1}")
+  public static List<Object[]> data() {
+    return buildParameters(getTestParameters().withDefaultCfRuntime().build(), Shrinker.values());
+  }
+
+  private TestRunResult<?> runTest(String... keepRules) throws Exception {
+    TestShrinkerBuilder<?, ?, ?, ?, ?> builder;
+    if (shrinker.isPG()) {
+      assumeTrue(parameters.isCfRuntime());
+      builder = testForProguard(ProguardVersion.getLatest()).addDontWarn(getClass());
+    } else {
+      builder = testForR8(parameters.getBackend());
+    }
+    return builder
+        .addProgramClasses(A.class, TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules(keepRules)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testMissingReturnTypeBackref() throws Exception {
+    runTest(
+            "-if class **$A {",
+            "  *** *(int);",
+            "}",
+            "-keepclassmembers class <1>$A {",
+            // This should have been `<2> <3>(int);` but wildcard on the return type is gone.
+            // TODO(b/322114141): Update this rule if fixed in future version of PG.
+            "  *** <2>(int);",
+            "}")
+        .inspect(
+            inspector ->
+                assertThat(
+                    inspector.clazz(A.class).uniqueMethodWithOriginalName("foo"),
+                    // The rule is not valid and does not keep the method in R8.
+                    shrinker.isPG() ? isPresentAndNotRenamed() : isPresentAndRenamed()));
+  }
+
+  @Test
+  public void testMissingMethodParameterBackref() throws Exception {
+    runTest(
+            "-if class **$A {",
+            "  void foo(***);",
+            "}",
+            "-keepclassmembers class <1>$A {",
+            "  void foo(<2>);",
+            "}")
+        .inspect(
+            inspector ->
+                assertThat(
+                    inspector.clazz(A.class).uniqueMethodWithOriginalName("foo"),
+                    // The above rule should have retained foo.
+                    // TODO(b/322114141): Update the status if fixed in future version of PG.
+                    shrinker.isPG() ? isAbsent() : isPresentAndNotRenamed()));
+  }
+
+  @Test
+  public void testMissingMethodAnyParametersBackref() throws Exception {
+    // TODO(b/265892343): R8 will throw when parsing a backref to `...`
+    assertThrowsIf(
+        !shrinker.isPG(),
+        CompilationFailedException.class,
+        () ->
+            runTest(
+                    "-if class **$A {",
+                    "  void foo(...);",
+                    "}",
+                    "-keepclassmembers class <1>$A {",
+                    "  void foo(<2>);",
+                    "}")
+                .inspect(
+                    inspector ->
+                        assertThat(
+                            inspector.clazz(A.class).uniqueMethodWithOriginalName("foo"),
+                            // The above rule should have retained foo.
+                            // TODO(b/322114141): Update the status if fixed in future version of
+                            // PG.
+                            isAbsent())));
+  }
+
+  @Test
+  public void testMissingFieldTypeBackref() throws Exception {
+    runTest(
+            "-if class **$A {",
+            "  *** x;",
+            "}",
+            // Just a comment to avoid a one-line formatting.
+            "-keepclassmembers class <1>$A {",
+            "  <2> y;",
+            "}")
+        .inspect(
+            inspector ->
+                assertThat(
+                    inspector.clazz(A.class).uniqueFieldWithOriginalName("y"),
+                    // TODO(b/322114141): Update this status if fixed in future version of PG.
+                    // TODO(b/322910135): R8 should not throw away a kept static final field.
+                    isAbsent()));
+  }
+
+  @Test
+  public void testMissingFieldTypeBackrefPrefixPg() throws Exception {
+    runTest(
+            "-if class **$A {",
+            "  !static *** *;",
+            "}",
+            "-keepclassmembers class <1>$A {",
+            // This should have been `<2> <3>y;` but the field type is skipped.
+            // TODO(b/322114141): Update this rule if fixed in future version of PG.
+            "  *** <2>y;",
+            "}")
+        .inspect(
+            inspector ->
+                assertThat(
+                    inspector.clazz(A.class).uniqueFieldWithOriginalName("xy"),
+                    // The rule does not match with R8.
+                    shrinker.isPG() ? isPresentAndNotRenamed() : isAbsent()));
+  }
+
+  @Test
+  public void testTypeRefInMember() throws Exception {
+    runTest(
+            "-if class **$A {",
+            "  " + typeName(A.class) + " *(" + typeName(A.class) + ");",
+            "}",
+            "-keepclassmembers class <1>$A {",
+            // This test shows that we can reference the class type wildcard in the member rule.
+            // 1: outer(A)
+            // 2: bar
+            "  <1>$A <2>(<1>$A);",
+            "}")
+        .inspect(
+            inspector ->
+                assertThat(
+                    inspector.clazz(A.class).uniqueMethodWithOriginalName("bar"), isPresent()));
+  }
+
+  @Test
+  public void testTypeAndReturnTypeInMember() throws Exception {
+    runTest(
+            "-if class **$A {",
+            "  *** *(" + typeName(A.class) + ");",
+            "}",
+            "-keepclassmembers class <1>$A {",
+            // This test shows that adding the return type wildcard does not add to the wildcard
+            // refs.
+            // 1: outer(A)
+            // 2: bar -- expected A
+            // 3: undefined -- expected bar
+            // TODO(b/322114141): Update this rule if fixed in future version of PG.
+            "  <1>$A <2>(<1>$A);",
+            "}")
+        .inspect(
+            inspector ->
+                assertThat(
+                    inspector.clazz(A.class).uniqueMethodWithOriginalName("bar"),
+                    // The rule does not match with R8.
+                    shrinker.isPG() ? isPresentAndNotRenamed() : isAbsent()));
+  }
+
+  @Test
+  public void testTypeAndReturnTypeInMemberAbsent() throws Exception {
+    runTest(
+            "-if class **$A {",
+            "  *** *(" + typeName(A.class) + ");",
+            "}",
+            "-keepclassmembers class <1>$A {",
+            "  <2> <3>(<2>);",
+            "}")
+        .inspect(
+            inspector ->
+                // The above pattern should have worked if <2> had been the return type.
+                // TODO(b/322114141): Update this status if fixed in future version of PG.
+                assertThat(
+                    inspector.clazz(A.class).uniqueMethodWithOriginalName("bar"),
+                    shrinker.isPG() ? isAbsent() : isPresentAndNotRenamed()));
+  }
+
+  public static class A {
+
+    // Use two fields to test removal as PG won't optimize out the non-static by default.
+    private final int x = 42;
+    private static final int y = 42;
+    private static final int xy = 42;
+
+    public void foo(int arg) {
+      System.out.println(arg + x);
+    }
+
+    public A bar(A arg) {
+      return arg;
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new A().bar(new A()).foo(args.length);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepClassAndDefaultConstructorSource.java b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepClassAndDefaultConstructorSource.java
deleted file mode 100644
index b9fb2b6..0000000
--- a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepClassAndDefaultConstructorSource.java
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (c) 2022, 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.testsource;
-
-import com.android.tools.r8.keepanno.annotations.KeepEdge;
-import com.android.tools.r8.keepanno.annotations.KeepTarget;
-
-@KeepEdge(
-    consequences = {
-      // Keep the class to allow lookup of it.
-      @KeepTarget(classConstant = KeepClassAndDefaultConstructorSource.A.class),
-      // Keep the default constructor.
-      @KeepTarget(
-          classConstant = KeepClassAndDefaultConstructorSource.A.class,
-          methodName = "<init>")
-    })
-public class KeepClassAndDefaultConstructorSource {
-
-  public static class A {
-    public A() {
-      System.out.println("A is alive!");
-    }
-  }
-
-  public static void main(String[] args) throws Exception {
-    Class<?> aClass =
-        Class.forName(
-            KeepClassAndDefaultConstructorSource.class.getPackage().getName()
-                + (args.length > 0 ? ".." : ".")
-                + "KeepClassAndDefaultConstructorSource$A");
-    aClass.getDeclaredConstructor().newInstance();
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepDependentFieldSource.java b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepDependentFieldSource.java
deleted file mode 100644
index 4a0df32..0000000
--- a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepDependentFieldSource.java
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (c) 2022, 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.testsource;
-
-import com.android.tools.r8.keepanno.annotations.KeepCondition;
-import com.android.tools.r8.keepanno.annotations.KeepEdge;
-import com.android.tools.r8.keepanno.annotations.KeepTarget;
-import java.lang.reflect.Field;
-
-public class KeepDependentFieldSource {
-
-  public static class A {
-
-    public int f;
-
-    public A(int x) {
-      f = x;
-    }
-  }
-
-  // The keep edge is context independent, but natural to place close to the reflection usage.
-  @KeepEdge(
-      preconditions = {
-        // The edge is only needed if the main method that uses reflection is actually present.
-        @KeepCondition(classConstant = KeepDependentFieldSource.class, methodName = "main")
-      },
-      consequences = {
-        // Keep the reflectively accessed field.
-        @KeepTarget(classConstant = KeepDependentFieldSource.A.class, fieldName = "f")
-      })
-  public static void main(String[] args) throws Exception {
-    int x = 42 + args.length;
-    Object o = System.nanoTime() > 0 ? new A(x) : null;
-    Field f = o.getClass().getDeclaredField("f");
-    int y = f.getInt(o);
-    if (x == y) {
-      System.out.println("The values match!");
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepFieldSource.java b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepFieldSource.java
deleted file mode 100644
index 6f577e4..0000000
--- a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepFieldSource.java
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) 2022, 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.testsource;
-
-import com.android.tools.r8.keepanno.annotations.KeepEdge;
-import com.android.tools.r8.keepanno.annotations.KeepTarget;
-import java.lang.reflect.Field;
-
-@KeepEdge(
-    consequences = {
-      // Keep the reflectively accessed field.
-      @KeepTarget(classConstant = KeepFieldSource.A.class, fieldName = "f")
-    })
-public class KeepFieldSource {
-
-  public static class A {
-
-    public int f;
-
-    public A(int x) {
-      f = x;
-    }
-  }
-
-  public static void main(String[] args) throws Exception {
-    int x = 42 + args.length;
-    Object o = System.nanoTime() > 0 ? new A(x) : null;
-    Field f = o.getClass().getDeclaredField("f");
-    int y = f.getInt(o);
-    if (x == y) {
-      System.out.println("The values match!");
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
deleted file mode 100644
index 0b2265a..0000000
--- a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
+++ /dev/null
@@ -1,174 +0,0 @@
-// Copyright (c) 2022, 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.testsource;
-
-import com.android.tools.r8.keepanno.ast.KeepClassItemPattern;
-import com.android.tools.r8.keepanno.ast.KeepClassItemReference;
-import com.android.tools.r8.keepanno.ast.KeepCondition;
-import com.android.tools.r8.keepanno.ast.KeepConsequences;
-import com.android.tools.r8.keepanno.ast.KeepConsequences.Builder;
-import com.android.tools.r8.keepanno.ast.KeepEdge;
-import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
-import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
-import com.android.tools.r8.keepanno.ast.KeepItemPattern;
-import com.android.tools.r8.keepanno.ast.KeepMemberItemPattern;
-import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
-import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
-import com.android.tools.r8.keepanno.ast.KeepPreconditions;
-import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
-import com.android.tools.r8.keepanno.ast.KeepTarget;
-import com.android.tools.r8.utils.StringUtils;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Utility to get the AST edges for the various annotation test sources.
- *
- * <p>This makes it easier to share the test sources among tests (e.g., processor and asm tests).
- */
-public class KeepSourceEdges {
-
-  private static class SourceData {
-    final Class<?> clazz;
-    final String expected;
-    final List<KeepEdge> edges;
-
-    public SourceData(Class<?> clazz, String expected, List<KeepEdge> edges) {
-      this.clazz = clazz;
-      this.expected = expected;
-      this.edges = edges;
-    }
-  }
-
-  private static final List<SourceData> SOURCES = new ArrayList<>();
-
-  static {
-    SOURCES.add(
-        new SourceData(
-            KeepClassAndDefaultConstructorSource.class,
-            getKeepClassAndDefaultConstructorSourceExpected(),
-            getKeepClassAndDefaultConstructorSourceEdges()));
-    SOURCES.add(
-        new SourceData(
-            KeepFieldSource.class, getKeepFieldSourceExpected(), getKeepFieldSourceEdges()));
-    SOURCES.add(
-        new SourceData(
-            KeepDependentFieldSource.class,
-            getKeepDependentFieldSourceExpected(),
-            getKeepDependentFieldSourceEdges()));
-  }
-
-  public static List<KeepEdge> getExpectedEdges(Class<?> clazz) {
-    for (SourceData source : SOURCES) {
-      if (source.clazz == clazz) {
-        return source.edges;
-      }
-    }
-    throw new RuntimeException();
-  }
-
-  public static String getExpected(Class<?> clazz) {
-    for (SourceData source : SOURCES) {
-      if (source.clazz == clazz) {
-        return source.expected;
-      }
-    }
-    throw new RuntimeException();
-  }
-
-  public static String getKeepClassAndDefaultConstructorSourceExpected() {
-    return StringUtils.lines("A is alive!");
-  }
-
-  public static List<KeepEdge> getKeepClassAndDefaultConstructorSourceEdges() {
-    Class<?> clazz = KeepClassAndDefaultConstructorSource.A.class;
-    return Collections.singletonList(
-        mkEdge(mkConsequences(mkTarget(mkClass(clazz)), mkTarget(mkMethod(clazz, "<init>")))));
-  }
-
-  public static String getKeepFieldSourceExpected() {
-    return StringUtils.lines("The values match!");
-  }
-
-  public static List<KeepEdge> getKeepFieldSourceEdges() {
-    return Collections.singletonList(
-        mkEdge(mkConsequences(mkTarget(mkField(KeepFieldSource.A.class, "f")))));
-  }
-
-  public static String getKeepDependentFieldSourceExpected() {
-    return getKeepFieldSourceExpected();
-  }
-
-  public static List<KeepEdge> getKeepDependentFieldSourceEdges() {
-    return Collections.singletonList(
-        mkDepEdge(
-            mkPreconditions(mkCondition(mkMethod(KeepDependentFieldSource.class, "main"))),
-            mkConsequences(mkTarget(mkField(KeepDependentFieldSource.A.class, "f")))));
-  }
-
-  // Ast helpers.
-
-  static KeepClassItemPattern mkClass(Class<?> clazz) {
-    KeepQualifiedClassNamePattern name = KeepQualifiedClassNamePattern.exact(clazz.getTypeName());
-    return KeepClassItemPattern.builder().setClassNamePattern(name).build();
-  }
-
-  static KeepMemberItemPattern mkMethod(Class<?> clazz, String methodName) {
-    KeepQualifiedClassNamePattern name = KeepQualifiedClassNamePattern.exact(clazz.getTypeName());
-    KeepClassItemReference classReference =
-        KeepClassItemPattern.builder().setClassNamePattern(name).build().toClassItemReference();
-    KeepMethodPattern methodPattern =
-        KeepMethodPattern.builder().setNamePattern(KeepMethodNamePattern.exact(methodName)).build();
-    KeepMemberItemPattern methodItem =
-        KeepMemberItemPattern.builder()
-            .setClassReference(classReference)
-            .setMemberPattern(methodPattern)
-            .build();
-    return methodItem;
-  }
-
-  static KeepItemPattern mkField(Class<?> clazz, String fieldName) {
-    KeepQualifiedClassNamePattern name = KeepQualifiedClassNamePattern.exact(clazz.getTypeName());
-    KeepClassItemReference classReference =
-        KeepClassItemPattern.builder().setClassNamePattern(name).build().toClassItemReference();
-    KeepFieldPattern fieldPattern =
-        KeepFieldPattern.builder().setNamePattern(KeepFieldNamePattern.exact(fieldName)).build();
-    KeepMemberItemPattern fieldItem =
-        KeepMemberItemPattern.builder()
-            .setClassReference(classReference)
-            .setMemberPattern(fieldPattern)
-            .build();
-    return fieldItem;
-  }
-
-  static KeepTarget mkTarget(KeepItemPattern item) {
-    return KeepTarget.builder().setItemPattern(item).build();
-  }
-
-  static KeepCondition mkCondition(KeepItemPattern item) {
-    return KeepCondition.builder().setItemPattern(item).build();
-  }
-
-  static KeepConsequences mkConsequences(KeepTarget... targets) {
-    Builder builder = KeepConsequences.builder();
-    Arrays.asList(targets).forEach(builder::addTarget);
-    return builder.build();
-  }
-
-  static KeepPreconditions mkPreconditions(KeepCondition... conditions) {
-    KeepPreconditions.Builder builder = KeepPreconditions.builder();
-    Arrays.asList(conditions).forEach(builder::addCondition);
-    return builder.build();
-  }
-
-  static KeepEdge mkEdge(KeepConsequences consequences) {
-    return KeepEdge.builder().setConsequences(consequences).build();
-  }
-
-  static KeepEdge mkDepEdge(KeepPreconditions preconditions, KeepConsequences consequences) {
-    return KeepEdge.builder().setPreconditions(preconditions).setConsequences(consequences).build();
-  }
-}
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 c2dec45..b543b8d 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
@@ -36,7 +36,6 @@
 import java.lang.reflect.Method;
 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;
@@ -49,9 +48,9 @@
 
 public class KeepAnnoMarkdownGenerator {
 
-  public static void generateMarkdownDoc(Generator generator) {
+  public static void generateMarkdownDoc(Generator generator, Path projectRoot) {
     try {
-      new KeepAnnoMarkdownGenerator(generator).internalGenerateMarkdownDoc();
+      new KeepAnnoMarkdownGenerator(generator).internalGenerateMarkdownDoc(projectRoot);
     } catch (IOException e) {
       throw new RuntimeException(e);
     }
@@ -202,10 +201,11 @@
     generator.println(line);
   }
 
-  private void internalGenerateMarkdownDoc() throws IOException {
-    Path template = Paths.get("doc/keepanno-guide.template.md");
+  private void internalGenerateMarkdownDoc(Path projectRoot) throws IOException {
+    String relativeUnixPath = "doc/keepanno-guide.template.md";
+    Path template = projectRoot.resolve(relativeUnixPath);
     println("[comment]: <> (DO NOT EDIT - GENERATED FILE)");
-    println("[comment]: <> (Changes should be made in " + template + ")");
+    println("[comment]: <> (Changes should be made in " + relativeUnixPath + ")");
     println();
     List<String> readAllLines = FileUtils.readAllLines(template);
     TableEntry root = new TableEntry(0, "root", "root", null);
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 475813b..f1d26bb 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
@@ -4,30 +4,13 @@
 
 package com.android.tools.r8.keepanno.utils;
 
-import com.android.tools.r8.TestBase;
+import static com.android.tools.r8.references.Reference.classFromClass;
+import static com.android.tools.r8.references.Reference.classFromTypeName;
+
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.cfmethodgeneration.CodeGenerationBase;
-import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
-import com.android.tools.r8.keepanno.annotations.CheckOptimizedOut;
-import com.android.tools.r8.keepanno.annotations.CheckRemoved;
-import com.android.tools.r8.keepanno.annotations.ClassNamePattern;
-import com.android.tools.r8.keepanno.annotations.FieldAccessFlags;
-import com.android.tools.r8.keepanno.annotations.KeepBinding;
-import com.android.tools.r8.keepanno.annotations.KeepCondition;
-import com.android.tools.r8.keepanno.annotations.KeepConstraint;
-import com.android.tools.r8.keepanno.annotations.KeepEdge;
-import com.android.tools.r8.keepanno.annotations.KeepForApi;
 import com.android.tools.r8.keepanno.annotations.KeepItemKind;
-import com.android.tools.r8.keepanno.annotations.KeepOption;
-import com.android.tools.r8.keepanno.annotations.KeepTarget;
-import com.android.tools.r8.keepanno.annotations.MemberAccessFlags;
-import com.android.tools.r8.keepanno.annotations.MethodAccessFlags;
-import com.android.tools.r8.keepanno.annotations.StringPattern;
-import com.android.tools.r8.keepanno.annotations.TypePattern;
-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.ast.AnnotationConstants;
+import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.StringUtils.BraceType;
 import com.google.common.base.Strings;
@@ -39,6 +22,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -46,28 +30,194 @@
 import java.util.Arrays;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 public class KeepItemAnnotationGenerator {
 
   public static void main(String[] args) throws IOException {
     Generator.class.getClassLoader().setDefaultAssertionStatus(true);
-    Generator.run();
+    Generator.run(
+        (file, content) -> {
+          try {
+            Files.write(file, content.getBytes(StandardCharsets.UTF_8));
+          } catch (IOException e) {
+            throw new RuntimeException(e);
+          }
+        });
   }
 
+  private static class EnumReference {
+    public final ClassReference enumClass;
+    public final String enumValue;
+
+    public EnumReference(ClassReference enumClass, String enumValue) {
+      this.enumClass = enumClass;
+      this.enumValue = enumValue;
+    }
+
+    public String name() {
+      return enumValue;
+    }
+  }
+
+  private static final ClassReference JAVA_STRING = classFromClass(String.class);
+  private static final ClassReference JAVA_RETENTION_POLICY = classFromClass(RetentionPolicy.class);
+
+  private static final String PKG = "com.android.tools.r8.keepanno.";
+  private static final String AST_PKG = PKG + "ast.";
+  private static final String ANNO_PKG = PKG + "annotations.";
+
+  private static final ClassReference ANNOTATION_CONSTANTS = astClass("AnnotationConstants");
+
+  private static final ClassReference STRING_PATTERN = annoClass("StringPattern");
+  private static final ClassReference TYPE_PATTERN = annoClass("TypePattern");
+  private static final ClassReference CLASS_NAME_PATTERN = annoClass("ClassNamePattern");
+  private static final ClassReference ANNOTATION_PATTERN = annoClass("AnnotationPattern");
+  private static final ClassReference USES_REFLECTION = annoClass("UsesReflection");
+  private static final ClassReference USED_BY_REFLECTION = annoClass("UsedByReflection");
+  private static final ClassReference USED_BY_NATIVE = annoClass("UsedByNative");
+  private static final ClassReference CHECK_REMOVED = annoClass("CheckRemoved");
+  private static final ClassReference CHECK_OPTIMIZED_OUT = annoClass("CheckOptimizedOut");
+  private static final ClassReference KEEP_EDGE = annoClass("KeepEdge");
+  private static final ClassReference KEEP_BINDING = annoClass("KeepBinding");
+  private static final ClassReference KEEP_TARGET = annoClass("KeepTarget");
+  private static final ClassReference KEEP_CONDITION = annoClass("KeepCondition");
+  private static final ClassReference KEEP_FOR_API = annoClass("KeepForApi");
+
+  private static final ClassReference KEEP_ITEM_KIND = annoClass("KeepItemKind");
+  private static final EnumReference KIND_ONLY_CLASS = enumRef(KEEP_ITEM_KIND, "ONLY_CLASS");
+  private static final EnumReference KIND_ONLY_MEMBERS = enumRef(KEEP_ITEM_KIND, "ONLY_MEMBERS");
+  private static final EnumReference KIND_ONLY_METHODS = enumRef(KEEP_ITEM_KIND, "ONLY_METHODS");
+  private static final EnumReference KIND_ONLY_FIELDS = enumRef(KEEP_ITEM_KIND, "ONLY_FIELDS");
+  private static final EnumReference KIND_CLASS_AND_MEMBERS =
+      enumRef(KEEP_ITEM_KIND, "CLASS_AND_MEMBERS");
+  private static final EnumReference KIND_CLASS_AND_METHODS =
+      enumRef(KEEP_ITEM_KIND, "CLASS_AND_METHODS");
+  private static final EnumReference KIND_CLASS_AND_FIELDS =
+      enumRef(KEEP_ITEM_KIND, "CLASS_AND_FIELDS");
+  private static final List<EnumReference> KEEP_ITEM_KIND_VALUES =
+      ImmutableList.of(
+          KIND_ONLY_CLASS,
+          KIND_ONLY_MEMBERS,
+          KIND_ONLY_METHODS,
+          KIND_ONLY_FIELDS,
+          KIND_CLASS_AND_MEMBERS,
+          KIND_CLASS_AND_METHODS,
+          KIND_CLASS_AND_FIELDS);
+
+  private static final ClassReference KEEP_CONSTRAINT = annoClass("KeepConstraint");
+  private static final EnumReference CONSTRAINT_LOOKUP = enumRef(KEEP_CONSTRAINT, "LOOKUP");
+  private static final EnumReference CONSTRAINT_NAME = enumRef(KEEP_CONSTRAINT, "NAME");
+  private static final EnumReference CONSTRAINT_VISIBILITY_RELAX =
+      enumRef(KEEP_CONSTRAINT, "VISIBILITY_RELAX");
+  private static final EnumReference CONSTRAINT_VISIBILITY_RESTRICT =
+      enumRef(KEEP_CONSTRAINT, "VISIBILITY_RESTRICT");
+  private static final EnumReference CONSTRAINT_VISIBILITY_INVARIANT =
+      enumRef(KEEP_CONSTRAINT, "VISIBILITY_INVARIANT");
+  private static final EnumReference CONSTRAINT_CLASS_INSTANTIATE =
+      enumRef(KEEP_CONSTRAINT, "CLASS_INSTANTIATE");
+  private static final EnumReference CONSTRAINT_METHOD_INVOKE =
+      enumRef(KEEP_CONSTRAINT, "METHOD_INVOKE");
+  private static final EnumReference CONSTRAINT_FIELD_GET = enumRef(KEEP_CONSTRAINT, "FIELD_GET");
+  private static final EnumReference CONSTRAINT_FIELD_SET = enumRef(KEEP_CONSTRAINT, "FIELD_SET");
+  private static final EnumReference CONSTRAINT_METHOD_REPLACE =
+      enumRef(KEEP_CONSTRAINT, "METHOD_REPLACE");
+  private static final EnumReference CONSTRAINT_FIELD_REPLACE =
+      enumRef(KEEP_CONSTRAINT, "FIELD_REPLACE");
+  private static final EnumReference CONSTRAINT_NEVER_INLINE =
+      enumRef(KEEP_CONSTRAINT, "NEVER_INLINE");
+  private static final EnumReference CONSTRAINT_CLASS_OPEN_HIERARCHY =
+      enumRef(KEEP_CONSTRAINT, "CLASS_OPEN_HIERARCHY");
+  private static final List<EnumReference> KEEP_CONSTRAINT_VALUES =
+      ImmutableList.of(
+          CONSTRAINT_LOOKUP,
+          CONSTRAINT_NAME,
+          CONSTRAINT_VISIBILITY_RELAX,
+          CONSTRAINT_VISIBILITY_RESTRICT,
+          CONSTRAINT_VISIBILITY_INVARIANT,
+          CONSTRAINT_CLASS_INSTANTIATE,
+          CONSTRAINT_METHOD_INVOKE,
+          CONSTRAINT_FIELD_GET,
+          CONSTRAINT_FIELD_SET,
+          CONSTRAINT_METHOD_REPLACE,
+          CONSTRAINT_FIELD_REPLACE,
+          CONSTRAINT_NEVER_INLINE,
+          CONSTRAINT_CLASS_OPEN_HIERARCHY);
+
+  private static final ClassReference MEMBER_ACCESS_FLAGS = annoClass("MemberAccessFlags");
+  private static final EnumReference MEMBER_ACCESS_PUBLIC = enumRef(MEMBER_ACCESS_FLAGS, "PUBLIC");
+  private static final EnumReference MEMBER_ACCESS_PROTECTED =
+      enumRef(MEMBER_ACCESS_FLAGS, "PROTECTED");
+  private static final EnumReference MEMBER_ACCESS_PACKAGE_PRIVATE =
+      enumRef(MEMBER_ACCESS_FLAGS, "PACKAGE_PRIVATE");
+  private static final EnumReference MEMBER_ACCESS_PRIVATE =
+      enumRef(MEMBER_ACCESS_FLAGS, "PRIVATE");
+  private static final EnumReference MEMBER_ACCESS_STATIC = enumRef(MEMBER_ACCESS_FLAGS, "STATIC");
+  private static final EnumReference MEMBER_ACCESS_FINAL = enumRef(MEMBER_ACCESS_FLAGS, "FINAL");
+  private static final EnumReference MEMBER_ACCESS_SYNTHETIC =
+      enumRef(MEMBER_ACCESS_FLAGS, "SYNTHETIC");
+  private static final List<EnumReference> MEMBER_ACCESS_VALUES =
+      ImmutableList.of(
+          MEMBER_ACCESS_PUBLIC,
+          MEMBER_ACCESS_PROTECTED,
+          MEMBER_ACCESS_PACKAGE_PRIVATE,
+          MEMBER_ACCESS_PRIVATE,
+          MEMBER_ACCESS_STATIC,
+          MEMBER_ACCESS_FINAL,
+          MEMBER_ACCESS_SYNTHETIC);
+
+  private static final ClassReference METHOD_ACCESS_FLAGS = annoClass("MethodAccessFlags");
+  private static final EnumReference METHOD_ACCESS_SYNCHRONIZED =
+      enumRef(METHOD_ACCESS_FLAGS, "SYNCHRONIZED");
+  private static final EnumReference METHOD_ACCESS_BRIDGE = enumRef(METHOD_ACCESS_FLAGS, "BRIDGE");
+  private static final EnumReference METHOD_ACCESS_NATIVE = enumRef(METHOD_ACCESS_FLAGS, "NATIVE");
+  private static final EnumReference METHOD_ACCESS_ABSTRACT =
+      enumRef(METHOD_ACCESS_FLAGS, "ABSTRACT");
+  private static final EnumReference METHOD_ACCESS_STRICT_FP =
+      enumRef(METHOD_ACCESS_FLAGS, "STRICT_FP");
+  private static final List<EnumReference> METHOD_ACCESS_VALUES =
+      ImmutableList.of(
+          METHOD_ACCESS_SYNCHRONIZED,
+          METHOD_ACCESS_BRIDGE,
+          METHOD_ACCESS_NATIVE,
+          METHOD_ACCESS_ABSTRACT,
+          METHOD_ACCESS_STRICT_FP);
+
+  private static final ClassReference FIELD_ACCESS_FLAGS = annoClass("FieldAccessFlags");
+  private static final EnumReference FIELD_ACCESS_VOLATILE =
+      enumRef(FIELD_ACCESS_FLAGS, "VOLATILE");
+  private static final EnumReference FIELD_ACCESS_TRANSIENT =
+      enumRef(FIELD_ACCESS_FLAGS, "TRANSIENT");
+  private static final List<EnumReference> FIELD_ACCESS_VALUES =
+      ImmutableList.of(FIELD_ACCESS_VOLATILE, FIELD_ACCESS_TRANSIENT);
+
   private static final String DEFAULT_INVALID_STRING_PATTERN =
-      "@" + simpleName(StringPattern.class) + "(exact = \"\")";
+      "@" + simpleName(STRING_PATTERN) + "(exact = \"\")";
   private static final String DEFAULT_INVALID_TYPE_PATTERN =
-      "@" + simpleName(TypePattern.class) + "(name = \"\")";
+      "@" + simpleName(TYPE_PATTERN) + "(name = \"\")";
   private static final String DEFAULT_INVALID_CLASS_NAME_PATTERN =
-      "@" + simpleName(ClassNamePattern.class) + "(simpleName = \"\")";
+      "@" + simpleName(CLASS_NAME_PATTERN) + "(simpleName = \"\")";
+
+  private static ClassReference astClass(String simpleName) {
+    return classFromTypeName(AST_PKG + simpleName);
+  }
+
+  private static ClassReference annoClass(String simpleName) {
+    return classFromTypeName(ANNO_PKG + simpleName);
+  }
+
+  private static EnumReference enumRef(ClassReference enumClass, String valueName) {
+    return new EnumReference(enumClass, valueName);
+  }
 
   public static String quote(String str) {
     return "\"" + str + "\"";
   }
 
-  private static String simpleName(Class<?> clazz) {
-    return clazz.getSimpleName();
+  private static String simpleName(ClassReference clazz) {
+    String binaryName = clazz.getBinaryName();
+    return binaryName.substring(binaryName.lastIndexOf('/') + 1);
   }
 
   private static class GroupMember extends DocPrinterBase<GroupMember> {
@@ -111,39 +261,39 @@
       generator.println("public static final String " + name + " = " + quote(name) + ";");
     }
 
-    public GroupMember requiredValue(Class<?> type) {
+    public GroupMember requiredValue(ClassReference type) {
       assert valueDefault == null;
       return setType(simpleName(type));
     }
 
-    public GroupMember requiredArrayValue(Class<?> type) {
+    public GroupMember requiredArrayValue(ClassReference type) {
       assert valueDefault == null;
       return setType(simpleName(type) + "[]");
     }
 
     public GroupMember requiredStringValue() {
-      return requiredValue(String.class);
+      return requiredValue(JAVA_STRING);
     }
 
-    public GroupMember defaultValue(Class<?> type, String value) {
+    public GroupMember defaultValue(ClassReference type, String value) {
       setType(simpleName(type));
       return setValue(value);
     }
 
-    public GroupMember defaultArrayValue(Class<?> type, String value) {
+    public GroupMember defaultArrayValue(ClassReference type, String value) {
       setType(simpleName(type) + "[]");
       return setValue("{" + value + "}");
     }
 
     public GroupMember defaultEmptyString() {
-      return defaultValue(String.class, quote(""));
+      return defaultValue(JAVA_STRING, quote(""));
     }
 
     public GroupMember defaultObjectClass() {
       return setType("Class<?>").setValue("Object.class");
     }
 
-    public GroupMember defaultArrayEmpty(Class<?> type) {
+    public GroupMember defaultArrayEmpty(ClassReference type) {
       return defaultArrayValue(type, "");
     }
   }
@@ -315,7 +465,7 @@
 
     private Group createBindingsGroup() {
       return new Group("bindings")
-          .addMember(new GroupMember("bindings").defaultArrayEmpty(KeepBinding.class));
+          .addMember(new GroupMember("bindings").defaultArrayEmpty(KEEP_BINDING));
     }
 
     private Group createPreconditionsGroup() {
@@ -327,7 +477,7 @@
                   .setDocReturn(
                       "The list of preconditions. "
                           + "Defaults to no conditions, thus trivially/unconditionally satisfied.")
-                  .defaultArrayEmpty(KeepCondition.class));
+                  .defaultArrayEmpty(KEEP_CONDITION));
     }
 
     private Group createConsequencesGroup() {
@@ -336,7 +486,7 @@
               new GroupMember("consequences")
                   .setDocTitle("Consequences that must be kept if the annotation is in effect.")
                   .setDocReturn("The list of target consequences.")
-                  .requiredArrayValue(KeepTarget.class));
+                  .requiredArrayValue(KEEP_TARGET));
     }
 
     private Group createConsequencesAsValueGroup() {
@@ -345,7 +495,7 @@
               new GroupMember("value")
                   .setDocTitle("Consequences that must be kept if the annotation is in effect.")
                   .setDocReturn("The list of target consequences.")
-                  .requiredArrayValue(KeepTarget.class));
+                  .requiredArrayValue(KEEP_TARGET));
     }
 
     private Group createAdditionalPreconditionsGroup() {
@@ -356,7 +506,7 @@
                   .setDocReturn(
                       "The list of additional preconditions. "
                           + "Defaults to no additional preconditions.")
-                  .defaultArrayEmpty(KeepCondition.class));
+                  .defaultArrayEmpty(KEEP_CONDITION));
     }
 
     private Group createAdditionalTargetsGroup(String docTitle) {
@@ -367,7 +517,7 @@
                   .setDocReturn(
                       "List of additional target consequences. "
                           + "Defaults to no additional target consequences.")
-                  .defaultArrayEmpty(KeepTarget.class));
+                  .defaultArrayEmpty(KEEP_TARGET));
     }
 
     private Group stringPatternExactGroup() {
@@ -417,7 +567,7 @@
           .addMember(
               new GroupMember("classNamePattern")
                   .setDocTitle("Classes matching the class-name pattern.")
-                  .defaultValue(ClassNamePattern.class, DEFAULT_INVALID_CLASS_NAME_PATTERN));
+                  .defaultValue(CLASS_NAME_PATTERN, DEFAULT_INVALID_CLASS_NAME_PATTERN));
       // TODO(b/248408342): Add more injections on type pattern variants.
       // /** Exact type name as a string to match any array with that type as member. */
       // String arrayOf() default "";
@@ -468,25 +618,25 @@
 
     private static GroupMember getKindMember() {
       return new GroupMember("kind")
-          .defaultValue(KeepItemKind.class, "KeepItemKind.DEFAULT")
+          .defaultValue(KEEP_ITEM_KIND, "KeepItemKind.DEFAULT")
           .setDocTitle("Specify the kind of this item pattern.")
           .setDocReturn("The kind for this pattern.")
           .addParagraph("Possible values are:")
           .addUnorderedList(
-              docLink(KeepItemKind.ONLY_CLASS),
-              docLink(KeepItemKind.ONLY_MEMBERS),
-              docLink(KeepItemKind.ONLY_METHODS),
-              docLink(KeepItemKind.ONLY_FIELDS),
-              docLink(KeepItemKind.CLASS_AND_MEMBERS),
-              docLink(KeepItemKind.CLASS_AND_METHODS),
-              docLink(KeepItemKind.CLASS_AND_FIELDS))
+              docEnumLink(KIND_ONLY_CLASS),
+              docEnumLink(KIND_ONLY_MEMBERS),
+              docEnumLink(KIND_ONLY_METHODS),
+              docEnumLink(KIND_ONLY_FIELDS),
+              docEnumLink(KIND_CLASS_AND_MEMBERS),
+              docEnumLink(KIND_CLASS_AND_METHODS),
+              docEnumLink(KIND_CLASS_AND_FIELDS))
           .addParagraph(
               "If unspecified the default kind for an item depends on its member patterns:")
           .addUnorderedList(
-              docLink(KeepItemKind.ONLY_CLASS) + " if no member patterns are defined",
-              docLink(KeepItemKind.ONLY_METHODS) + " if method patterns are defined",
-              docLink(KeepItemKind.ONLY_FIELDS) + " if field patterns are defined",
-              docLink(KeepItemKind.ONLY_MEMBERS) + " otherwise.");
+              docEnumLink(KIND_ONLY_CLASS) + " if no member patterns are defined",
+              docEnumLink(KIND_ONLY_METHODS) + " if method patterns are defined",
+              docEnumLink(KIND_ONLY_FIELDS) + " if field patterns are defined",
+              docEnumLink(KIND_ONLY_MEMBERS) + " otherwise.");
     }
 
     private void forEachKeepConstraintGroups(Consumer<Group> fn) {
@@ -506,17 +656,17 @@
               "The default constraints depend on the kind of the target.",
               "For all targets the default constraints include:")
           .addUnorderedList(
-              docLink(KeepConstraint.LOOKUP),
-              docLink(KeepConstraint.NAME),
-              docLink(KeepConstraint.VISIBILITY_RELAX))
+              docEnumLink(CONSTRAINT_LOOKUP),
+              docEnumLink(CONSTRAINT_NAME),
+              docEnumLink(CONSTRAINT_VISIBILITY_RELAX))
           .addParagraph("For classes the default constraints also include:")
-          .addUnorderedList(docLink(KeepConstraint.CLASS_INSTANTIATE))
+          .addUnorderedList(docEnumLink(CONSTRAINT_CLASS_INSTANTIATE))
           .addParagraph("For methods the default constraints also include:")
-          .addUnorderedList(docLink(KeepConstraint.METHOD_INVOKE))
+          .addUnorderedList(docEnumLink(CONSTRAINT_METHOD_INVOKE))
           .addParagraph("For fields the default constraints also include:")
-          .addUnorderedList(docLink(KeepConstraint.FIELD_GET), docLink(KeepConstraint.FIELD_SET))
+          .addUnorderedList(docEnumLink(CONSTRAINT_FIELD_GET), docEnumLink(CONSTRAINT_FIELD_SET))
           .setDocReturn("Usage constraints for the target.")
-          .defaultArrayEmpty(KeepConstraint.class);
+          .defaultArrayEmpty(KEEP_CONSTRAINT);
     }
 
     private static GroupMember constraintAdditions() {
@@ -527,7 +677,7 @@
               "in addition to the default constraints.")
           .addParagraph("The default constraints are documented in " + docLink(constraints()))
           .setDocReturn("Additional usage constraints for the target.")
-          .defaultArrayEmpty(KeepConstraint.class);
+          .defaultArrayEmpty(KEEP_CONSTRAINT);
     }
 
     private static GroupMember constrainAnnotations() {
@@ -545,7 +695,7 @@
               "By default no annotation patterns are defined and no annotations are required to",
               "remain.")
           .setDocReturn("Annotation patterns")
-          .defaultArrayEmpty(AnnotationPattern.class);
+          .defaultArrayEmpty(ANNOTATION_PATTERN);
     }
 
     private Group annotationNameGroup() {
@@ -582,7 +732,7 @@
                   + ANNOTATION_NAME_GROUP
                   + " pattern by reference to a class-name pattern.")
           .setDocReturn("The class-name pattern that defines the annotation.")
-          .defaultValue(ClassNamePattern.class, DEFAULT_INVALID_CLASS_NAME_PATTERN);
+          .defaultValue(CLASS_NAME_PATTERN, DEFAULT_INVALID_CLASS_NAME_PATTERN);
     }
 
     private static GroupMember annotationRetention() {
@@ -590,7 +740,7 @@
           .setDocTitle("Specify which retention policies must be set for the annotations.")
           .addParagraph("Matches annotations with matching retention policies")
           .setDocReturn("Retention policies. By default {@code RetentionPolicy.RUNTIME}.")
-          .defaultArrayValue(RetentionPolicy.class, "RetentionPolicy.RUNTIME");
+          .defaultArrayValue(JAVA_RETENTION_POLICY, "RetentionPolicy.RUNTIME");
     }
 
     private GroupMember bindingName() {
@@ -636,7 +786,7 @@
           .setDocTitle(
               "Define the " + CLASS_NAME_GROUP + " pattern by reference to a class-name pattern.")
           .setDocReturn("The class-name pattern that defines the class.")
-          .defaultValue(ClassNamePattern.class, DEFAULT_INVALID_CLASS_NAME_PATTERN);
+          .defaultValue(CLASS_NAME_PATTERN, DEFAULT_INVALID_CLASS_NAME_PATTERN);
     }
 
     private Group createClassNamePatternGroup() {
@@ -732,7 +882,7 @@
                   .setDocTitle(
                       "Define the " + groupName + " pattern by reference to a class-name pattern.")
                   .setDocReturn("The class-name pattern that defines the annotation.")
-                  .defaultValue(ClassNamePattern.class, DEFAULT_INVALID_CLASS_NAME_PATTERN));
+                  .defaultValue(CLASS_NAME_PATTERN, DEFAULT_INVALID_CLASS_NAME_PATTERN));
     }
 
     private Group createClassAnnotatedByPatternGroup() {
@@ -769,7 +919,7 @@
                   .setDocTitle("Define the member-access pattern by matching on access flags.")
                   .addParagraph(getMutuallyExclusiveForMemberProperties())
                   .setDocReturn("The member access-flag constraints that must be met.")
-                  .defaultArrayEmpty(MemberAccessFlags.class));
+                  .defaultArrayEmpty(MEMBER_ACCESS_FLAGS));
     }
 
     private String getMutuallyExclusiveForMemberProperties() {
@@ -812,7 +962,7 @@
                   .addParagraph(getMutuallyExclusiveForMethodProperties())
                   .addParagraph(getMethodDefaultDoc("any method-access flags"))
                   .setDocReturn("The method access-flag constraints that must be met.")
-                  .defaultArrayEmpty(MethodAccessFlags.class));
+                  .defaultArrayEmpty(METHOD_ACCESS_FLAGS));
     }
 
     private Group createMethodNameGroup() {
@@ -830,7 +980,7 @@
                   .addParagraph(getMutuallyExclusiveForMethodProperties())
                   .addParagraph(getMethodDefaultDoc("any method name"))
                   .setDocReturn("The string pattern of the method name.")
-                  .defaultValue(StringPattern.class, DEFAULT_INVALID_STRING_PATTERN));
+                  .defaultValue(STRING_PATTERN, DEFAULT_INVALID_STRING_PATTERN));
     }
 
     private Group createMethodReturnTypeGroup() {
@@ -856,7 +1006,7 @@
                   .addParagraph(getMutuallyExclusiveForMethodProperties())
                   .addParagraph(getMethodDefaultDoc("any return type"))
                   .setDocReturn("The pattern of the method return type.")
-                  .defaultValue(TypePattern.class, DEFAULT_INVALID_TYPE_PATTERN));
+                  .defaultValue(TYPE_PATTERN, DEFAULT_INVALID_TYPE_PATTERN));
     }
 
     private Group createMethodParametersGroup() {
@@ -868,7 +1018,7 @@
                   .addParagraph(getMutuallyExclusiveForMethodProperties())
                   .addParagraph(getMethodDefaultDoc("any parameters"))
                   .setDocReturn("The list of qualified type names of the method parameters.")
-                  .defaultArrayValue(String.class, quote("")))
+                  .defaultArrayValue(JAVA_STRING, quote("")))
           .addMember(
               new GroupMember("methodParameterTypePatterns")
                   .setDocTitle(
@@ -876,7 +1026,7 @@
                   .addParagraph(getMutuallyExclusiveForMethodProperties())
                   .addParagraph(getMethodDefaultDoc("any parameters"))
                   .setDocReturn("The list of type patterns for the method parameters.")
-                  .defaultArrayValue(TypePattern.class, DEFAULT_INVALID_TYPE_PATTERN));
+                  .defaultArrayValue(TYPE_PATTERN, DEFAULT_INVALID_TYPE_PATTERN));
     }
 
     private Group createFieldAnnotatedByGroup() {
@@ -894,7 +1044,7 @@
                   .addParagraph(getMutuallyExclusiveForFieldProperties())
                   .addParagraph(getFieldDefaultDoc("any field-access flags"))
                   .setDocReturn("The field access-flag constraints that must be met.")
-                  .defaultArrayEmpty(FieldAccessFlags.class));
+                  .defaultArrayEmpty(FIELD_ACCESS_FLAGS));
     }
 
     private Group createFieldNameGroup() {
@@ -912,7 +1062,7 @@
                   .addParagraph(getMutuallyExclusiveForFieldProperties())
                   .addParagraph(getFieldDefaultDoc("any field name"))
                   .setDocReturn("The string pattern of the field name.")
-                  .defaultValue(StringPattern.class, DEFAULT_INVALID_STRING_PATTERN));
+                  .defaultValue(STRING_PATTERN, DEFAULT_INVALID_STRING_PATTERN));
     }
 
     private Group createFieldTypeGroup() {
@@ -937,7 +1087,7 @@
                   .addParagraph(getMutuallyExclusiveForFieldProperties())
                   .addParagraph(getFieldDefaultDoc("any type"))
                   .setDocReturn("The type pattern for the field type.")
-                  .defaultValue(TypePattern.class, DEFAULT_INVALID_TYPE_PATTERN));
+                  .defaultValue(TYPE_PATTERN, DEFAULT_INVALID_TYPE_PATTERN));
     }
 
     private void generateClassAndMemberPropertiesWithClassAndMemberBinding() {
@@ -1030,7 +1180,7 @@
           .printDoc(this::println);
       println("@Target(ElementType.ANNOTATION_TYPE)");
       println("@Retention(RetentionPolicy.CLASS)");
-      println("public @interface " + simpleName(StringPattern.class) + " {");
+      println("public @interface " + simpleName(STRING_PATTERN) + " {");
       println();
       withIndent(
           () -> {
@@ -1059,7 +1209,7 @@
           .printDoc(this::println);
       println("@Target(ElementType.ANNOTATION_TYPE)");
       println("@Retention(RetentionPolicy.CLASS)");
-      println("public @interface " + simpleName(TypePattern.class) + " {");
+      println("public @interface " + simpleName(TYPE_PATTERN) + " {");
       println();
       withIndent(() -> typePatternGroup().generate(this));
       println();
@@ -1078,7 +1228,7 @@
           .printDoc(this::println);
       println("@Target(ElementType.ANNOTATION_TYPE)");
       println("@Retention(RetentionPolicy.CLASS)");
-      println("public @interface " + simpleName(ClassNamePattern.class) + " {");
+      println("public @interface " + simpleName(CLASS_NAME_PATTERN) + " {");
       println();
       withIndent(
           () -> {
@@ -1102,7 +1252,7 @@
           .printDoc(this::println);
       println("@Target(ElementType.ANNOTATION_TYPE)");
       println("@Retention(RetentionPolicy.CLASS)");
-      println("public @interface " + simpleName(AnnotationPattern.class) + " {");
+      println("public @interface " + simpleName(ANNOTATION_PATTERN) + " {");
       println();
       withIndent(
           () -> {
@@ -1231,17 +1381,17 @@
                 .clearDocLines()
                 .addParagraph(
                     "Default kind is",
-                    KeepItemKind.CLASS_AND_MEMBERS.name(),
-                    ", meaning the annotated class and/or member is to be kept.",
+                    docEnumLink(KIND_CLASS_AND_MEMBERS) + ",",
+                    "meaning the annotated class and/or member is to be kept.",
                     "When annotating a class this can be set to",
-                    KeepItemKind.ONLY_CLASS.name(),
+                    docEnumLink(KIND_ONLY_CLASS),
                     "to avoid patterns on any members.",
                     "That can be useful when the API members are themselves explicitly annotated.")
                 .addParagraph(
                     "It is not possible to use",
-                    KeepItemKind.ONLY_CLASS.name(),
+                    docEnumLink(KIND_ONLY_CLASS),
                     "if annotating a member. Also, it is never valid to use kind",
-                    KeepItemKind.ONLY_MEMBERS.name(),
+                    docEnumLink(KIND_ONLY_MEMBERS),
                     "as the API surface must keep the class if any member is to be accessible.")
                 .generate(this);
             println();
@@ -1269,18 +1419,18 @@
                   + " conditions that should be satisfied for the annotation to be in effect.")
           .addParagraph(
               "The translation of the "
-                  + docLink(UsesReflection.class)
+                  + docLink(USES_REFLECTION)
                   + " annotation into a "
-                  + docLink(KeepEdge.class)
+                  + docLink(KEEP_EDGE)
                   + " is as follows:")
           .addParagraph(
               "Assume the item of the annotation is denoted by 'CTX' and referred to as its"
                   + " context.")
           .addCodeBlock(
-              annoSimpleName(UsesReflection.class)
+              annoSimpleName(USES_REFLECTION)
                   + "(value = targets, [additionalPreconditions = preconditions])",
               "==>",
-              annoSimpleName(KeepEdge.class) + "(",
+              annoSimpleName(KEEP_EDGE) + "(",
               "  consequences = targets,",
               "  preconditions = {createConditionFromContext(CTX)} + preconditions",
               ")",
@@ -1310,7 +1460,7 @@
           "@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD,"
               + " ElementType.CONSTRUCTOR})");
       println("@Retention(RetentionPolicy.CLASS)");
-      println("public @interface " + simpleName(UsesReflection.class) + " {");
+      println("public @interface " + simpleName(USES_REFLECTION) + " {");
       println();
       withIndent(
           () -> {
@@ -1332,12 +1482,12 @@
           .addParagraph(
               "Note: Before using this annotation, consider if instead you can annotate the code"
                   + " that is doing reflection with "
-                  + docLink(UsesReflection.class)
+                  + docLink(USES_REFLECTION)
                   + ". Annotating the"
                   + " reflecting code is generally more clear and maintainable, and it also"
                   + " naturally gives rise to edges that describe just the reflected aspects of the"
                   + " program. The "
-                  + docLink(UsedByReflection.class)
+                  + docLink(USED_BY_REFLECTION)
                   + " annotation is suitable for cases where"
                   + " the reflecting code is not under user control, or in migrating away from"
                   + " rules.")
@@ -1371,19 +1521,18 @@
                 .addParagraph("If unspecified the default kind depends on the annotated item.")
                 .addParagraph("When annotating a class the default kind is:")
                 .addUnorderedList(
-                    docLink(KeepItemKind.ONLY_CLASS) + " if no member patterns are defined;",
-                    docLink(KeepItemKind.CLASS_AND_METHODS) + " if method patterns are defined;",
-                    docLink(KeepItemKind.CLASS_AND_FIELDS) + " if field patterns are defined;",
-                    docLink(KeepItemKind.CLASS_AND_MEMBERS) + "otherwise.")
+                    docEnumLink(KIND_ONLY_CLASS) + " if no member patterns are defined;",
+                    docEnumLink(KIND_CLASS_AND_METHODS) + " if method patterns are defined;",
+                    docEnumLink(KIND_CLASS_AND_FIELDS) + " if field patterns are defined;",
+                    docEnumLink(KIND_CLASS_AND_MEMBERS) + "otherwise.")
                 .addParagraph(
                     "When annotating a method the default kind is: "
-                        + docLink(KeepItemKind.ONLY_METHODS))
+                        + docEnumLink(KIND_ONLY_METHODS))
                 .addParagraph(
-                    "When annotating a field the default kind is: "
-                        + docLink(KeepItemKind.ONLY_FIELDS))
+                    "When annotating a field the default kind is: " + docEnumLink(KIND_ONLY_FIELDS))
                 .addParagraph(
                     "It is not possible to use "
-                        + docLink(KeepItemKind.ONLY_CLASS)
+                        + docEnumLink(KIND_ONLY_CLASS)
                         + " if annotating a member.")
                 .generate(this);
             println();
@@ -1398,11 +1547,11 @@
       println("}");
     }
 
-    private static String annoSimpleName(Class<?> clazz) {
+    private static String annoSimpleName(ClassReference clazz) {
       return "@" + simpleName(clazz);
     }
 
-    private static String docLink(Class<?> clazz) {
+    private static String docLink(ClassReference clazz) {
       return "{@link " + simpleName(clazz) + "}";
     }
 
@@ -1410,12 +1559,12 @@
       return "{@link #" + member.name + "}";
     }
 
-    private static String docLink(Enum<?> kind) {
-      return "{@link " + simpleName(kind.getClass()) + "#" + kind.name() + "}";
+    private static String docEnumLink(EnumReference enumRef) {
+      return "{@link " + simpleName(enumRef.enumClass) + "#" + enumRef.enumValue + "}";
     }
 
-    private static String docLinkList(Enum<?>... values) {
-      return StringUtils.join(", ", values, v -> docLink(v), BraceType.TUBORG);
+    private static String docEnumLinkList(EnumReference... values) {
+      return StringUtils.join(", ", values, v -> docEnumLink(v), BraceType.TUBORG);
     }
 
     private void generateConstants() {
@@ -1448,7 +1597,6 @@
             generateTargetConstants();
             generateKindConstants();
             generateConstraintConstants();
-            generateOptionConstants();
             generateMemberAccessConstants();
             generateMethodAccessConstants();
             generateFieldAccessConstants();
@@ -1461,8 +1609,8 @@
       println("}");
     }
 
-    private void generateAnnotationConstants(Class<?> clazz) {
-      String desc = TestBase.descriptor(clazz);
+    private void generateAnnotationConstants(ClassReference clazz) {
+      String desc = clazz.getDescriptor();
       println("public static final String DESCRIPTOR = " + quote(desc) + ";");
     }
 
@@ -1470,7 +1618,7 @@
       println("public static final class Edge {");
       withIndent(
           () -> {
-            generateAnnotationConstants(KeepEdge.class);
+            generateAnnotationConstants(KEEP_EDGE);
             createDescriptionGroup().generateConstants(this);
             createBindingsGroup().generateConstants(this);
             createPreconditionsGroup().generateConstants(this);
@@ -1484,7 +1632,7 @@
       println("public static final class ForApi {");
       withIndent(
           () -> {
-            generateAnnotationConstants(KeepForApi.class);
+            generateAnnotationConstants(KEEP_FOR_API);
             createDescriptionGroup().generateConstants(this);
             createAdditionalTargetsGroup(".").generateConstants(this);
             createMemberAccessGroup().generateConstants(this);
@@ -1497,7 +1645,7 @@
       println("public static final class UsesReflection {");
       withIndent(
           () -> {
-            generateAnnotationConstants(UsesReflection.class);
+            generateAnnotationConstants(USES_REFLECTION);
             createDescriptionGroup().generateConstants(this);
             createConsequencesAsValueGroup().generateConstants(this);
             createAdditionalPreconditionsGroup().generateConstants(this);
@@ -1510,7 +1658,7 @@
       println("public static final class UsedByReflection {");
       withIndent(
           () -> {
-            generateAnnotationConstants(UsedByReflection.class);
+            generateAnnotationConstants(USED_BY_REFLECTION);
             createDescriptionGroup().generateConstants(this);
             createPreconditionsGroup().generateConstants(this);
             createAdditionalTargetsGroup(".").generateConstants(this);
@@ -1523,8 +1671,8 @@
       println("public static final class UsedByNative {");
       withIndent(
           () -> {
-            generateAnnotationConstants(UsedByNative.class);
-            println("// Content is the same as " + simpleName(UsedByReflection.class) + ".");
+            generateAnnotationConstants(USED_BY_NATIVE);
+            println("// Content is the same as " + simpleName(USED_BY_REFLECTION) + ".");
           });
       println("}");
       println();
@@ -1534,7 +1682,7 @@
       println("public static final class CheckRemoved {");
       withIndent(
           () -> {
-            generateAnnotationConstants(CheckRemoved.class);
+            generateAnnotationConstants(CHECK_REMOVED);
           });
       println("}");
       println();
@@ -1544,7 +1692,7 @@
       println("public static final class CheckOptimizedOut {");
       withIndent(
           () -> {
-            generateAnnotationConstants(CheckOptimizedOut.class);
+            generateAnnotationConstants(CHECK_OPTIMIZED_OUT);
           });
       println("}");
       println();
@@ -1587,7 +1735,7 @@
       println("public static final class Binding {");
       withIndent(
           () -> {
-            generateAnnotationConstants(KeepBinding.class);
+            generateAnnotationConstants(KEEP_BINDING);
             bindingName().generateConstants(this);
           });
       println("}");
@@ -1598,7 +1746,7 @@
       println("public static final class Condition {");
       withIndent(
           () -> {
-            generateAnnotationConstants(KeepCondition.class);
+            generateAnnotationConstants(KEEP_CONDITION);
           });
       println("}");
       println();
@@ -1608,7 +1756,7 @@
       println("public static final class Target {");
       withIndent(
           () -> {
-            generateAnnotationConstants(KeepTarget.class);
+            generateAnnotationConstants(KEEP_TARGET);
             getKindGroup().generateConstants(this);
             forEachKeepConstraintGroups(g -> g.generateConstants(this));
           });
@@ -1620,7 +1768,7 @@
       println("public static final class Kind {");
       withIndent(
           () -> {
-            generateAnnotationConstants(KeepItemKind.class);
+            generateAnnotationConstants(KEEP_ITEM_KIND);
             for (KeepItemKind value : KeepItemKind.values()) {
               if (value != KeepItemKind.DEFAULT) {
                 println(
@@ -1640,33 +1788,27 @@
       println("public static final class Constraints {");
       withIndent(
           () -> {
-            generateAnnotationConstants(KeepConstraint.class);
-            for (KeepConstraint value : KeepConstraint.values()) {
+            generateAnnotationConstants(KEEP_CONSTRAINT);
+            for (EnumReference constraint : KEEP_CONSTRAINT_VALUES) {
               println(
-                  "public static final String " + value.name() + " = " + quote(value.name()) + ";");
+                  "public static final String "
+                      + constraint.enumValue
+                      + " = "
+                      + quote(constraint.enumValue)
+                      + ";");
             }
           });
       println("}");
       println();
     }
 
-    private void generateOptionConstants() {
-      println("public static final class Option {");
-      withIndent(
-          () -> {
-            generateAnnotationConstants(KeepOption.class);
-            for (KeepOption value : KeepOption.values()) {
-              println(
-                  "public static final String " + value.name() + " = " + quote(value.name()) + ";");
-            }
-          });
-      println("}");
-      println();
+    private boolean isAccessPropertyNegation(EnumReference enumReference) {
+      return enumReference.name().startsWith("NON_");
     }
 
-    private boolean isMemberAccessProperty(String name) {
-      for (MemberAccessFlags value : MemberAccessFlags.values()) {
-        if (value.name().equals(name)) {
+    private boolean isMemberAccessProperty(EnumReference enumReference) {
+      for (EnumReference memberAccessValue : MEMBER_ACCESS_VALUES) {
+        if (memberAccessValue.enumValue.equals(enumReference.enumValue)) {
           return true;
         }
       }
@@ -1677,17 +1819,13 @@
       println("public static final class MemberAccess {");
       withIndent(
           () -> {
-            generateAnnotationConstants(MemberAccessFlags.class);
+            generateAnnotationConstants(MEMBER_ACCESS_FLAGS);
             println("public static final String NEGATION_PREFIX = \"NON_\";");
-            for (MemberAccessFlags value : MemberAccessFlags.values()) {
-              if (!value.name().startsWith("NON_")) {
-                println(
-                    "public static final String "
-                        + value.name()
-                        + " = "
-                        + quote(value.name())
-                        + ";");
-              }
+            for (EnumReference value : MEMBER_ACCESS_VALUES) {
+              assert !isAccessPropertyNegation(value);
+              assert isMemberAccessProperty(value);
+              println(
+                  "public static final String " + value.name() + " = " + quote(value.name()) + ";");
             }
           });
       println("}");
@@ -1698,11 +1836,10 @@
       println("public static final class MethodAccess {");
       withIndent(
           () -> {
-            generateAnnotationConstants(MethodAccessFlags.class);
-            for (MethodAccessFlags value : MethodAccessFlags.values()) {
-              if (value.name().startsWith("NON_") || isMemberAccessProperty(value.name())) {
-                continue;
-              }
+            generateAnnotationConstants(METHOD_ACCESS_FLAGS);
+            for (EnumReference value : METHOD_ACCESS_VALUES) {
+              assert !isAccessPropertyNegation(value);
+              assert !isMemberAccessProperty(value);
               println(
                   "public static final String " + value.name() + " = " + quote(value.name()) + ";");
             }
@@ -1715,11 +1852,10 @@
       println("public static final class FieldAccess {");
       withIndent(
           () -> {
-            generateAnnotationConstants(FieldAccessFlags.class);
-            for (FieldAccessFlags value : FieldAccessFlags.values()) {
-              if (value.name().startsWith("NON_") || isMemberAccessProperty(value.name())) {
-                continue;
-              }
+            generateAnnotationConstants(FIELD_ACCESS_FLAGS);
+            for (EnumReference value : FIELD_ACCESS_VALUES) {
+              assert !isAccessPropertyNegation(value);
+              assert !isMemberAccessProperty(value);
               println(
                   "public static final String " + value.name() + " = " + quote(value.name()) + ";");
             }
@@ -1732,7 +1868,7 @@
       println("public static final class StringPattern {");
       withIndent(
           () -> {
-            generateAnnotationConstants(StringPattern.class);
+            generateAnnotationConstants(STRING_PATTERN);
             stringPatternExactGroup().generateConstants(this);
             stringPatternPrefixGroup().generateConstants(this);
             stringPatternSuffixGroup().generateConstants(this);
@@ -1745,7 +1881,7 @@
       println("public static final class TypePattern {");
       withIndent(
           () -> {
-            generateAnnotationConstants(TypePattern.class);
+            generateAnnotationConstants(TYPE_PATTERN);
             typePatternGroup().generateConstants(this);
           });
       println("}");
@@ -1756,7 +1892,7 @@
       println("public static final class ClassNamePattern {");
       withIndent(
           () -> {
-            generateAnnotationConstants(ClassNamePattern.class);
+            generateAnnotationConstants(CLASS_NAME_PATTERN);
             classNamePatternSimpleNameGroup().generateConstants(this);
             classNamePatternPackageGroup().generateConstants(this);
           });
@@ -1768,7 +1904,7 @@
       println("public static final class AnnotationPattern {");
       withIndent(
           () -> {
-            generateAnnotationConstants(AnnotationPattern.class);
+            generateAnnotationConstants(ANNOTATION_PATTERN);
             annotationNameGroup().generateConstants(this);
             annotationRetention().generateConstants(this);
           });
@@ -1776,7 +1912,8 @@
       println();
     }
 
-    private static void writeFile(Path file, Consumer<Generator> fn) throws IOException {
+    private static void writeFile(Path file, Consumer<Generator> fn, BiConsumer<Path, String> write)
+        throws IOException {
       ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
       PrintStream printStream = new PrintStream(byteStream);
       Generator generator = new Generator(printStream);
@@ -1785,38 +1922,39 @@
       if (file.toString().endsWith(".java")) {
         formatted = CodeGenerationBase.formatRawOutput(formatted);
       }
-      Files.write(Paths.get(ToolHelper.getProjectRoot()).resolve(file), formatted.getBytes());
+      Path resolved = Paths.get(ToolHelper.getProjectRoot()).resolve(file);
+      write.accept(resolved, formatted);
     }
 
-    public static Path source(Path pkg, Class<?> clazz) {
-      return pkg.resolve(simpleName(clazz) + ".java");
+    public static Path source(ClassReference clazz) {
+      return Paths.get("src", "keepanno", "java").resolve(clazz.getBinaryName() + ".java");
     }
 
-    public static void run() throws IOException {
-      writeFile(Paths.get("doc/keepanno-guide.md"), KeepAnnoMarkdownGenerator::generateMarkdownDoc);
-
-      Path keepAnnoRoot = Paths.get("src/keepanno/java/com/android/tools/r8/keepanno");
-
-      Path astPkg = keepAnnoRoot.resolve("ast");
-      writeFile(source(astPkg, AnnotationConstants.class), Generator::generateConstants);
-
-      Path annoPkg = Paths.get("src/keepanno/java/com/android/tools/r8/keepanno/annotations");
-      writeFile(source(annoPkg, StringPattern.class), Generator::generateStringPattern);
-      writeFile(source(annoPkg, TypePattern.class), Generator::generateTypePattern);
-      writeFile(source(annoPkg, ClassNamePattern.class), Generator::generateClassNamePattern);
-      writeFile(source(annoPkg, AnnotationPattern.class), Generator::generateAnnotationPattern);
-      writeFile(source(annoPkg, KeepBinding.class), Generator::generateKeepBinding);
-      writeFile(source(annoPkg, KeepTarget.class), Generator::generateKeepTarget);
-      writeFile(source(annoPkg, KeepCondition.class), Generator::generateKeepCondition);
-      writeFile(source(annoPkg, KeepForApi.class), Generator::generateKeepForApi);
-      writeFile(source(annoPkg, UsesReflection.class), Generator::generateUsesReflection);
+    public static void run(BiConsumer<Path, String> write) throws IOException {
+      Path projectRoot = Paths.get(ToolHelper.getProjectRoot());
       writeFile(
-          source(annoPkg, UsedByReflection.class),
-          g -> g.generateUsedByX("UsedByReflection", "accessed reflectively"));
+          Paths.get("doc/keepanno-guide.md"),
+          generator -> KeepAnnoMarkdownGenerator.generateMarkdownDoc(generator, projectRoot),
+          write);
+
+      writeFile(source(ANNOTATION_CONSTANTS), Generator::generateConstants, write);
+      writeFile(source(STRING_PATTERN), Generator::generateStringPattern, write);
+      writeFile(source(TYPE_PATTERN), Generator::generateTypePattern, write);
+      writeFile(source(CLASS_NAME_PATTERN), Generator::generateClassNamePattern, write);
+      writeFile(source(ANNOTATION_PATTERN), Generator::generateAnnotationPattern, write);
+      writeFile(source(KEEP_BINDING), Generator::generateKeepBinding, write);
+      writeFile(source(KEEP_TARGET), Generator::generateKeepTarget, write);
+      writeFile(source(KEEP_CONDITION), Generator::generateKeepCondition, write);
+      writeFile(source(KEEP_FOR_API), Generator::generateKeepForApi, write);
+      writeFile(source(USES_REFLECTION), Generator::generateUsesReflection, write);
       writeFile(
-          source(annoPkg, UsedByNative.class),
-          g -> g.generateUsedByX("UsedByNative", "accessed from native code via JNI"));
+          source(USED_BY_REFLECTION),
+          g -> g.generateUsedByX("UsedByReflection", "accessed reflectively"),
+          write);
+      writeFile(
+          source(USED_BY_NATIVE),
+          g -> g.generateUsedByX("UsedByNative", "accessed from native code via JNI"),
+          write);
     }
   }
-
 }
diff --git a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemGeneratedFilesTest.java b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemGeneratedFilesTest.java
new file mode 100644
index 0000000..28f1c57
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemGeneratedFilesTest.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2024, 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.utils;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.keepanno.utils.KeepItemAnnotationGenerator.Generator;
+import com.android.tools.r8.utils.FileUtils;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import org.junit.Test;
+
+public class KeepItemGeneratedFilesTest {
+
+  @Test
+  public void checkUpToDate() throws IOException {
+    Generator.run(
+        (file, content) -> {
+          try {
+            String expectedContent = FileUtils.readTextFile(file, StandardCharsets.UTF_8);
+            assertEquals(expectedContent, content);
+          } catch (IOException e) {
+            throw new RuntimeException(e);
+          }
+        });
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/CatchAllRangeWithNoLineNumberTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/CatchAllRangeWithNoLineNumberTest.java
index f7f1410..a0ce500 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/CatchAllRangeWithNoLineNumberTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/CatchAllRangeWithNoLineNumberTest.java
@@ -119,10 +119,7 @@
         return retraced5_2_1;
       case V6_0_1:
         return retraced6_0_1;
-      case V7_0_0:
-        return retraced7_0_0;
       default:
-        assertEquals(ProguardVersion.V7_3_2, proguardVersion);
         return retraced7_0_0;
     }
   }
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/HorizontallyMergedConstructorMethodProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/HorizontallyMergedConstructorMethodProfileRewritingTest.java
index 1c3a805..faea413 100644
--- a/src/test/java/com/android/tools/r8/profile/art/completeness/HorizontallyMergedConstructorMethodProfileRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/HorizontallyMergedConstructorMethodProfileRewritingTest.java
@@ -51,14 +51,36 @@
       ClassSubject aClassSubject = inspector.clazz(A.class);
       assertThat(aClassSubject, isPresent());
 
-      MethodSubject syntheticConstructorSubject = aClassSubject.uniqueMethod();
+      MethodSubject syntheticConstructorSubject =
+          aClassSubject.uniqueMethodThatMatches(
+              method -> method.isInstanceInitializer() && method.isSynthetic());
       assertThat(syntheticConstructorSubject, isPresent());
       assertEquals(2, syntheticConstructorSubject.getParameters().size());
       assertEquals(aClassSubject.asTypeSubject(), syntheticConstructorSubject.getParameter(0));
       assertEquals("int", syntheticConstructorSubject.getParameter(1).getTypeName());
 
+      MethodSubject aConstructorSubject =
+          aClassSubject.uniqueMethodThatMatches(
+              method ->
+                  method.isInstanceInitializer()
+                      && !method.isSynthetic()
+                      && method.getProgramMethod().getReference().getArity() == 1);
+      assertThat(syntheticConstructorSubject, isPresent());
+
+      MethodSubject bConstructorSubject =
+          aClassSubject.uniqueMethodThatMatches(
+              method ->
+                  method.isInstanceInitializer()
+                      && !method.isSynthetic()
+                      && method.getProgramMethod().getReference().getArity() == 2);
+      assertThat(syntheticConstructorSubject, isPresent());
+
       profileInspector
-          .assertContainsMethodRule(syntheticConstructorSubject)
+          .assertContainsMethodRules(syntheticConstructorSubject)
+          .applyIf(
+              this == A_CONSTRUCTOR,
+              i -> i.assertContainsMethodRule(aConstructorSubject),
+              i -> i.assertContainsMethodRule(bConstructorSubject))
           .assertContainsNoOtherRules();
     }
   }
@@ -83,7 +105,7 @@
         .addArtProfileForRewriting(artProfileInputOutput.getArtProfile())
         .addHorizontallyMergedClassesInspector(
             inspector -> inspector.assertMergedInto(B.class, A.class).assertNoOtherClassesMerged())
-        .addOptionsModification(InlinerOptions::setOnlyForceInlining)
+        .addOptionsModification(InlinerOptions::disableInlining)
         .addOptionsModification(
             options -> options.callSiteOptimizationOptions().disableOptimization())
         .enableNeverClassInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java
index 43808b8..cd72b73 100644
--- a/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java
@@ -166,6 +166,7 @@
         inspector,
         SyntheticItemsTestUtils.syntheticRecordTagClass(),
         false,
+        false,
         parameters.canUseNestBasedAccessesWhenDesugaring(),
         !isRecordsDesugaredForD8(parameters));
   }
@@ -176,6 +177,7 @@
         inspector,
         RECORD_REFERENCE,
         parameters.canHaveNonReboundConstructorInvoke(),
+        true,
         parameters.canUseNestBasedAccesses(),
         !isRecordsDesugaredForR8(parameters));
   }
@@ -185,6 +187,7 @@
       CodeInspector inspector,
       ClassReference recordClassReference,
       boolean canHaveNonReboundConstructorInvoke,
+      boolean canMergeRecordTag,
       boolean canUseNestBasedAccesses,
       boolean canUseRecords) {
     ClassSubject mainClassSubject = inspector.clazz(MAIN_REFERENCE);
@@ -194,8 +197,7 @@
     assertThat(mainMethodSubject, isPresent());
 
     ClassSubject recordTagClassSubject = inspector.clazz(recordClassReference);
-    assertThat(
-        recordTagClassSubject, isAbsentIf(canHaveNonReboundConstructorInvoke || canUseRecords));
+    assertThat(recordTagClassSubject, isAbsentIf(canMergeRecordTag || canUseRecords));
     if (recordTagClassSubject.isPresent()) {
       assertEquals(
           canHaveNonReboundConstructorInvoke ? 0 : 1, recordTagClassSubject.allMethods().size());
@@ -207,16 +209,25 @@
     ClassSubject personRecordClassSubject = inspector.clazz(PERSON_REFERENCE);
     assertThat(personRecordClassSubject, isPresent());
     assertEquals(
-        canHaveNonReboundConstructorInvoke
-            ? inspector.getTypeSubject(Object.class.getTypeName())
-            : canUseRecords
-                ? inspector.getTypeSubject(RECORD_REFERENCE.getTypeName())
+        canUseRecords
+            ? inspector.getTypeSubject(RECORD_REFERENCE.getTypeName())
+            : canMergeRecordTag
+                ? inspector.getTypeSubject(Object.class.getTypeName())
                 : recordTagClassSubject.asTypeSubject(),
         personRecordClassSubject.getSuperType());
-    assertEquals(canUseRecords ? 6 : 10, personRecordClassSubject.allMethods().size());
+    assertEquals(
+        canUseRecords ? 6 : canHaveNonReboundConstructorInvoke || !canMergeRecordTag ? 10 : 11,
+        personRecordClassSubject.allMethods().size());
+
+    MethodSubject personDefaultInstanceInitializerSubject = personRecordClassSubject.init();
+    assertThat(
+        personDefaultInstanceInitializerSubject,
+        isPresentIf(!canHaveNonReboundConstructorInvoke && canMergeRecordTag && !canUseRecords));
 
     MethodSubject personInstanceInitializerSubject =
-        personRecordClassSubject.uniqueInstanceInitializer();
+        canMergeRecordTag
+            ? personRecordClassSubject.init(String.class.getTypeName())
+            : personRecordClassSubject.init(String.class.getTypeName(), "int");
     assertThat(personInstanceInitializerSubject, isPresent());
 
     // Name getters.
@@ -306,6 +317,9 @@
                 i.assertContainsMethodRules(
                     nameNestAccessorMethodSubject, ageNestAccessorMethodSubject))
         .applyIf(
+            !canHaveNonReboundConstructorInvoke && canMergeRecordTag && !canUseRecords,
+            i -> i.assertContainsMethodRule(personDefaultInstanceInitializerSubject))
+        .applyIf(
             !canUseRecords,
             i ->
                 i.assertContainsClassRules(hashCodeHelperClassSubject, toStringHelperClassSubject)
@@ -315,7 +329,7 @@
                         hashCodeHelperMethodSubject,
                         toStringHelperMethodSubject)
                     .applyIf(
-                        !canHaveNonReboundConstructorInvoke,
+                        !canMergeRecordTag,
                         j ->
                             j.assertContainsClassRules(recordTagClassSubject)
                                 .assertContainsMethodRule(recordTagInstanceInitializerSubject)))
diff --git a/src/test/java/com/android/tools/r8/profile/art/utils/ArtProfileInspector.java b/src/test/java/com/android/tools/r8/profile/art/utils/ArtProfileInspector.java
index 3b75886..e6f9092 100644
--- a/src/test/java/com/android/tools/r8/profile/art/utils/ArtProfileInspector.java
+++ b/src/test/java/com/android/tools/r8/profile/art/utils/ArtProfileInspector.java
@@ -30,9 +30,22 @@
     this.artProfile = artProfile;
   }
 
-  public ArtProfileInspector applyIf(boolean condition, Consumer<ArtProfileInspector> fn) {
+  public ArtProfileInspector applyIf(
+      boolean condition, Consumer<ArtProfileInspector> thenConsumer) {
     if (condition) {
-      fn.accept(this);
+      thenConsumer.accept(this);
+    }
+    return this;
+  }
+
+  public ArtProfileInspector applyIf(
+      boolean condition,
+      Consumer<ArtProfileInspector> thenConsumer,
+      Consumer<ArtProfileInspector> elseConsumer) {
+    if (condition) {
+      thenConsumer.accept(this);
+    } else {
+      elseConsumer.accept(this);
     }
     return this;
   }
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/B322478366RegressionTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/B322478366RegressionTest.java
new file mode 100644
index 0000000..d4b1d3d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/B322478366RegressionTest.java
@@ -0,0 +1,119 @@
+// Copyright (c) 2024, 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.rewrite.arrays;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import 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 B322478366RegressionTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public CompilationMode mode;
+
+  @Parameters(name = "{0}, mode {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), CompilationMode.values());
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    parameters.assumeDexRuntime();
+    testForD8()
+        .addInnerClasses(getClass())
+        .setMinApi(parameters)
+        .setMode(mode)
+        .compile()
+        .inspect(inspector -> inspect(inspector, true));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters)
+        .setMode(mode)
+        .enableInliningAnnotations()
+        .compile()
+        .inspect(inspector -> inspect(inspector, false));
+  }
+
+  private void inspect(CodeInspector inspector, boolean isD8) {
+    if (isD8 || mode.isDebug()) {
+      MethodSubject m1 = inspector.clazz(Main.class).uniqueMethodWithOriginalName("m1");
+      assertEquals(0, m1.streamInstructions().filter(InstructionSubject::isNewArray).count());
+      MethodSubject m2 = inspector.clazz(Main.class).uniqueMethodWithOriginalName("m2");
+      // TODO(b/322478366): Cts test CtsPerfettoTestCases.HeapprofdJavaCtsTest#DebuggableAppOom
+      //  requires that the array allocation stays.
+      assertEquals(
+          mode.isDebug() ? 1 : 0,
+          m2.streamInstructions().filter(InstructionSubject::isNewArray).count());
+      MethodSubject m3 = inspector.clazz(Main.class).uniqueMethodWithOriginalName("m3");
+      assertEquals(
+          mode.isDebug() ? 1 : 0,
+          m3.streamInstructions().filter(InstructionSubject::isNewArray).count());
+    } else {
+      assertThat(inspector.clazz(Main.class).uniqueMethodWithOriginalName("m1"), isAbsent());
+      assertThat(inspector.clazz(Main.class).uniqueMethodWithOriginalName("m2"), isAbsent());
+      assertThat(inspector.clazz(Main.class).uniqueMethodWithOriginalName("m3"), isAbsent());
+    }
+  }
+
+  public static class Main {
+    @NeverInline
+    public static void m1() {
+      try {
+        byte[] bytes = new byte[1];
+        // No local information from javac for bytes, as local not observable in a debugger.
+      } catch (OutOfMemoryError e) {
+      }
+    }
+
+    @NeverInline
+    public static void m2() {
+      try {
+        byte[] bytes = new byte[Integer.MAX_VALUE];
+        // No local information from javac for bytes, as local not observable in a debugger.
+      } catch (OutOfMemoryError e) {
+      }
+    }
+
+    @NeverInline
+    public static void m3() {
+      try {
+        byte[] bytes = new byte[1];
+        // Local information from javac for bytes, as the return statement makes it observable
+        // in a debugger.
+        return;
+      } catch (OutOfMemoryError e) {
+      }
+    }
+
+    public static void main(String[] args) {
+      m1();
+      m2();
+      m3();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ConditionalKeepOnUsedFieldTest.java b/src/test/java/com/android/tools/r8/shaking/ConditionalKeepOnUsedFieldTest.java
new file mode 100644
index 0000000..7d8a957
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ConditionalKeepOnUsedFieldTest.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2024, 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.shaking;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ConditionalKeepOnUsedFieldTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("0");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultRuntimes().withMinimumApiLevel().build();
+  }
+
+  public ConditionalKeepOnUsedFieldTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ConditionalKeepOnUsedFieldTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters)
+        .addKeepRules("-if class **$A { *** used; } -keepclassmembers class <1>$A { <2> x; }")
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(
+            inspector ->
+                assertThat(
+                    inspector.clazz(A.class).uniqueFieldWithOriginalName("x"),
+                    // TODO(b/322910135): The conditional rule should have kept x.
+                    isAbsent()));
+  }
+
+  static class A {
+    // This field is used but its value trivial.
+    public final int used = 0;
+    public static final int x = 1;
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      A a = System.nanoTime() < 0 ? null : new A();
+      System.out.println(a.used);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ConditionalKeepOnUsedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/ConditionalKeepOnUsedMethodTest.java
new file mode 100644
index 0000000..8401f7b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ConditionalKeepOnUsedMethodTest.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2024, 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.shaking;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ConditionalKeepOnUsedMethodTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("0");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultRuntimes().withMinimumApiLevel().build();
+  }
+
+  public ConditionalKeepOnUsedMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ConditionalKeepOnUsedMethodTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters)
+        .addKeepRules("-if class **$A { *** used(); } -keepclassmembers class <1>$A { <2> x; }")
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(
+            inspector ->
+                assertThat(
+                    inspector.clazz(A.class).uniqueFieldWithOriginalName("x"),
+                    // TODO(b/322910135): The conditional rule should have kept x.
+                    isAbsent()));
+  }
+
+  static class A {
+    // This method is used but its value trivial.
+    public static int used() {
+      return 0;
+    }
+
+    public static final int x = 1;
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      A a = System.nanoTime() < 0 ? null : new A();
+      System.out.println(a.used());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
index c0211ef..86f4fd6 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -252,7 +252,8 @@
             .apply(this::suppressZipFileAssignmentsToJavaLangAutoCloseable)
             .compile()
             .graphInspector();
-    assertRetainedClassesEqual(referenceInspector, ifThenKeepClassesWithMembersInspector);
+    assertRetainedClassesEqual(
+        referenceInspector, ifThenKeepClassesWithMembersInspector, true, true);
 
     GraphInspector ifHasMemberThenKeepClassInspector =
         testForR8(Backend.CF)
@@ -273,7 +274,7 @@
             .apply(this::suppressZipFileAssignmentsToJavaLangAutoCloseable)
             .compile()
             .graphInspector();
-    assertRetainedClassesEqual(referenceInspector, ifHasMemberThenKeepClassInspector);
+    assertRetainedClassesEqual(referenceInspector, ifHasMemberThenKeepClassInspector, true, true);
   }
 
   private void configureHorizontalClassMerging(R8FullTestBuilder testBuilder) {
diff --git a/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
index da8944c..dd7d5da 100644
--- a/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
@@ -141,8 +141,7 @@
             .inspector();
 
     ClassSubject superInterface1 = inspector.clazz(B112452064SuperInterface1.class);
-    assertThat(
-        superInterface1, isAbsentIf(enableUnusedInterfaceRemoval && enableVerticalClassMerging));
+    assertThat(superInterface1, isAbsentIf(enableVerticalClassMerging));
 
     MethodSubject foo = superInterface1.uniqueMethodWithOriginalName("foo");
     assertThat(foo, isAbsent());
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
index fdb513c..9f63d3a 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
@@ -175,11 +175,11 @@
 
   private static void inspectShaking9(PrintUsageInspector inspector) {
     Optional<ClassSubject> superClass = inspector.clazz("shaking9.Superclass");
-    assertFalse(superClass.isPresent());
+    assertTrue(superClass.isPresent());
     Optional<ClassSubject> subClass = inspector.clazz("shaking9.Subclass");
     assertTrue(subClass.isPresent());
     assertTrue(subClass.get().method("void", "aMethod", ImmutableList.of()));
-    assertFalse(subClass.get().method("void", "<init>", ImmutableList.of()));
+    assertTrue(subClass.get().method("void", "<init>", ImmutableList.of()));
   }
 
   private static void inspectShaking12(PrintUsageInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java
index 4733d3c..610a9c6 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java
@@ -3,13 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
-import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -48,12 +50,9 @@
 
   private static void shaking14EnsureRightStaticMethodsLive(CodeInspector inspector) {
     ClassSubject superclass = inspector.clazz("shaking14.Superclass");
-    Assert.assertFalse(superclass.method("int", "aMethod", ImmutableList.of("int")).isPresent());
-    Assert.assertFalse(
-        superclass.method("double", "anotherMethod", ImmutableList.of("double")).isPresent());
+    assertThat(superclass, isAbsent());
+
     ClassSubject subclass = inspector.clazz("shaking14.Subclass");
-    Assert.assertTrue(subclass.method("int", "aMethod", ImmutableList.of("int")).isPresent());
-    Assert.assertTrue(
-        subclass.method("double", "anotherMethod", ImmutableList.of("double")).isPresent());
+    assertThat(subclass, isAbsent());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java
index 3b80981..99cbc57 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java
@@ -4,9 +4,7 @@
 package com.android.tools.r8.shaking.examples;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isAbstract;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.TestParameters;
@@ -45,10 +43,7 @@
   @Test
   public void testKeeprules() throws Exception {
     runTest(
-        this::shaking9OnlySuperMethodsKept,
-        null,
-        null,
-        ImmutableList.of("src/test/examples/shaking9/keep-rules.txt"));
+        this::inspect, null, null, ImmutableList.of("src/test/examples/shaking9/keep-rules.txt"));
   }
 
   @Test
@@ -57,19 +52,12 @@
         null, null, null, ImmutableList.of("src/test/examples/shaking9/keep-rules-printusage.txt"));
   }
 
-  private void shaking9OnlySuperMethodsKept(CodeInspector inspector) {
+  private void inspect(CodeInspector inspector) {
     ClassSubject superclass = inspector.clazz("shaking9.Superclass");
-    if (parameters.canHaveNonReboundConstructorInvoke()) {
-      assertThat(superclass, isAbsent());
-    } else {
-      assertThat(superclass, isAbstract());
-      assertThat(superclass.method("void", "aMethod", ImmutableList.of()), isPresent());
-    }
+    assertThat(superclass, isAbsent());
 
     ClassSubject subclass = inspector.clazz("shaking9.Subclass");
     assertThat(subclass, isPresent());
-    assertThat(
-        subclass.method("void", "aMethod", ImmutableList.of()),
-        isPresentIf(parameters.canHaveNonReboundConstructorInvoke() && !getMinify().isMinify()));
+    assertThat(subclass.method("void", "aMethod", ImmutableList.of()), isAbsent());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index e69bb0f..47d969c 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -139,10 +139,6 @@
     return syntheticClass(reference, naming.RECORD_HELPER, id);
   }
 
-  public static ClassReference syntheticTwrCloseResourceClass(Class<?> clazz, int id) {
-    return syntheticClass(clazz, naming.TWR_CLOSE_RESOURCE, id);
-  }
-
   public static ClassReference syntheticTwrCloseResourceClass(ClassReference reference, int id) {
     return syntheticClass(reference, naming.TWR_CLOSE_RESOURCE, id);
   }
@@ -302,12 +298,6 @@
         reference, Phase.EXTERNAL, naming.NON_FIXED_INIT_TYPE_ARGUMENT);
   }
 
-  public static boolean isHorizontalInitializerTypeArgument(ClassReference reference) {
-    return SyntheticNaming.isSynthetic(reference, null, naming.HORIZONTAL_INIT_TYPE_ARGUMENT_1)
-        || SyntheticNaming.isSynthetic(reference, null, naming.HORIZONTAL_INIT_TYPE_ARGUMENT_2)
-        || SyntheticNaming.isSynthetic(reference, null, naming.HORIZONTAL_INIT_TYPE_ARGUMENT_3);
-  }
-
   public static boolean isWrapper(ClassReference reference) {
     return SyntheticNaming.isSynthetic(reference, null, naming.WRAPPER)
         || SyntheticNaming.isSynthetic(reference, null, naming.VIVIFIED_WRAPPER);
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesDefaultMethodInSubInterfaceTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesDefaultMethodInSubInterfaceTest.java
new file mode 100644
index 0000000..95ef480
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesDefaultMethodInSubInterfaceTest.java
@@ -0,0 +1,320 @@
+// Copyright (c) 2024, 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.tracereferences;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.DiagnosticsHandler;
+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.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.google.common.collect.ImmutableSet;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import org.junit.BeforeClass;
+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;
+
+// From b/319190998.
+@RunWith(Parameterized.class)
+public class TraceReferencesDefaultMethodInSubInterfaceTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withAllRuntimesAndApiLevels()
+        .withAllApiLevelsAlsoForCf()
+        .withNoneRuntime()
+        .build();
+  }
+
+  static Path targetJar;
+  static Path sourceJar;
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world!");
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    Path dir = getStaticTemp().newFolder().toPath();
+    targetJar =
+        ZipBuilder.builder(dir.resolve("target.jar"))
+            .addFilesRelative(
+                ToolHelper.getClassPathForTests(),
+                ToolHelper.getClassFileForTestClass(I.class),
+                ToolHelper.getClassFileForTestClass(J.class))
+            .build();
+    sourceJar =
+        ZipBuilder.builder(dir.resolve("source.jar"))
+            .addFilesRelative(
+                ToolHelper.getClassPathForTests(),
+                ToolHelper.getClassFileForTestClass(JImpl.class),
+                ToolHelper.getClassFileForTestClass(Main.class))
+            .build();
+  }
+
+  static class SeenReferencesConsumer implements TraceReferencesConsumer {
+
+    private final Set<MethodReference> seenMethods = new HashSet<>();
+
+    @Override
+    public void acceptType(TracedClass tracedClass, DiagnosticsHandler handler) {}
+
+    @Override
+    public void acceptField(TracedField tracedField, DiagnosticsHandler handler) {}
+
+    @Override
+    public void acceptMethod(TracedMethod tracedMethod, DiagnosticsHandler handler) {
+      seenMethods.add(tracedMethod.getReference());
+    }
+  }
+
+  @Test
+  public void testTracedReferences() throws Exception {
+    assumeTrue(parameters.isNoneRuntime());
+    SeenReferencesConsumer consumer = new SeenReferencesConsumer();
+    TraceReferences.run(
+        TraceReferencesCommand.builder()
+            .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+            .addSourceFiles(sourceJar)
+            .addTargetFiles(targetJar)
+            .setConsumer(consumer)
+            .build());
+
+    // TODO(b/319190998): Just tracing I.m is not enough.
+    ImmutableSet<MethodReference> expectedSet =
+        ImmutableSet.of(
+            Reference.method(
+                Reference.classFromClass(I.class),
+                "m",
+                Collections.emptyList(),
+                Reference.classFromClass(Object.class)));
+    assertEquals(expectedSet, consumer.seenMethods);
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    parameters.assumeJvmTestParameters();
+    testForJvm(parameters)
+        .addProgramFiles(sourceJar)
+        .addProgramFiles(targetJar)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    parameters.assumeDexRuntime();
+    Path targetDex =
+        testForD8().setMinApi(parameters).addProgramFiles(targetJar).compile().writeToZip();
+
+    testForD8()
+        .setMinApi(parameters)
+        .addClasspathFiles(targetJar)
+        .addProgramFiles(sourceJar)
+        .addRunClasspathFiles(targetDex)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testGeneratedKeepRulesFollowedByR8() throws Exception {
+    parameters.assumeR8TestParameters();
+
+    Path generatedKeepRules = temp.newFile("keep.rules").toPath();
+    TraceReferencesKeepRules keepRulesConsumer =
+        TraceReferencesKeepRules.builder()
+            // The use of keeper in b/319190998 disables obfuscation of generated keep rules.
+            .setAllowObfuscation(false)
+            .setOutputPath(generatedKeepRules)
+            .build();
+    TraceReferences.run(
+        TraceReferencesCommand.builder()
+            .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+            .addSourceFiles(sourceJar)
+            .addTargetFiles(targetJar)
+            .setConsumer(keepRulesConsumer)
+            .build());
+
+    Path r8CompiledTarget =
+        testForR8(parameters.getBackend())
+            .setMinApi(parameters)
+            .addProgramFiles(targetJar)
+            .addKeepRuleFiles(generatedKeepRules)
+            .compile()
+            .writeToZip();
+
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters)
+        .addClasspathFiles(targetJar)
+        .addProgramFiles(sourceJar)
+        .addRunClasspathFiles(r8CompiledTarget)
+        .addKeepMainRule(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        // TODO(b/319190998): This should not fail.
+        .applyIf(
+            hasDefaultInterfaceMethodsSupport(parameters),
+            r -> r.assertFailureWithErrorThatThrows(AbstractMethodError.class),
+            r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+  }
+
+  @Test
+  public void testGeneratedKeepRulesWithMissingRuleFollowedByR8() throws Exception {
+    parameters.assumeR8TestParameters();
+
+    Path generatedKeepRules = temp.newFile("keep.rules").toPath();
+    TraceReferencesKeepRules keepRulesConsumer =
+        TraceReferencesKeepRules.builder()
+            .setAllowObfuscation(true)
+            .setOutputPath(generatedKeepRules)
+            .build();
+    TraceReferences.run(
+        TraceReferencesCommand.builder()
+            .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+            .addSourceFiles(sourceJar)
+            .addTargetFiles(targetJar)
+            .setConsumer(keepRulesConsumer)
+            .build());
+
+    Path proguardMap = temp.newFolder().toPath().resolve("mapping.txt");
+    Path r8CompiledTarget =
+        testForR8(parameters.getBackend())
+            .setMinApi(parameters)
+            .addProgramFiles(targetJar)
+            .addKeepRuleFiles(generatedKeepRules)
+            .addKeepRules("-keep class " + J.class.getTypeName() + " { m(); }")
+            .compile()
+            .apply(r -> r.writeProguardMap(proguardMap))
+            .writeToZip();
+
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters)
+        .addClasspathFiles(targetJar)
+        .addProgramFiles(sourceJar)
+        .addApplyMapping(proguardMap)
+        .addKeepMainRule(Main.class)
+        .addRunClasspathFiles(r8CompiledTarget)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testGeneratedKeepRulesWithMissingRuleFollowedByD8() throws Exception {
+    parameters.assumeDexRuntime();
+
+    Path generatedKeepRules = temp.newFile("keep.rules").toPath();
+    TraceReferencesKeepRules keepRulesConsumer =
+        TraceReferencesKeepRules.builder()
+            // Don't obfuscate as D8 does not support apply mapping.
+            .setAllowObfuscation(false)
+            .setOutputPath(generatedKeepRules)
+            .build();
+    TraceReferences.run(
+        TraceReferencesCommand.builder()
+            .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+            .addSourceFiles(sourceJar)
+            .addTargetFiles(targetJar)
+            .setConsumer(keepRulesConsumer)
+            .build());
+
+    Path r8CompiledTarget =
+        testForR8(Backend.DEX)
+            .setMinApi(parameters)
+            .addProgramFiles(targetJar)
+            .addKeepRuleFiles(generatedKeepRules)
+            .addKeepRules("-keep class " + J.class.getTypeName() + " { m(); }")
+            .compile()
+            .writeToZip();
+
+    testForD8(Backend.DEX)
+        .setMinApi(parameters)
+        .addClasspathFiles(targetJar)
+        .addProgramFiles(sourceJar)
+        .addRunClasspathFiles(r8CompiledTarget)
+        .run(parameters.getRuntime(), Main.class)
+        .applyIf(
+            hasDefaultInterfaceMethodsSupport(parameters),
+            r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
+            // TODO(b/319190998): This should not fail.
+            r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+  }
+
+  @Test
+  public void testGeneratedKeepRulesWithMissingRuleAndDontObfuscateFollowedByD8() throws Exception {
+    parameters.assumeDexRuntime();
+
+    Path generatedKeepRules = temp.newFile("keep.rules").toPath();
+    TraceReferencesKeepRules keepRulesConsumer =
+        TraceReferencesKeepRules.builder()
+            // Don't obfuscate as D8 does not support apply mapping.
+            .setAllowObfuscation(false)
+            .setOutputPath(generatedKeepRules)
+            .build();
+    TraceReferences.run(
+        TraceReferencesCommand.builder()
+            .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+            .addSourceFiles(sourceJar)
+            .addTargetFiles(targetJar)
+            .setConsumer(keepRulesConsumer)
+            .build());
+
+    Path r8CompiledTarget =
+        testForR8(Backend.DEX)
+            .setMinApi(parameters)
+            .addProgramFiles(targetJar)
+            .addKeepRuleFiles(generatedKeepRules)
+            .addKeepRules("-keep class " + J.class.getTypeName() + " { m(); }")
+            // TODO(b/319190998): Adding dont obfuscate should not be needed as trace references is
+            //  already asked to not allow obfuscation. Hwing this will cause the CC class to not
+            //  get renamed.
+            .addDontObfuscate()
+            .compile()
+            .writeToZip();
+
+    testForD8(Backend.DEX)
+        .setMinApi(parameters)
+        .addClasspathFiles(targetJar)
+        .addProgramFiles(sourceJar)
+        .addRunClasspathFiles(r8CompiledTarget)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  // Interfaces I and J are in the target set for trace references.
+  interface I {
+    Object m();
+  }
+
+  interface J extends I {
+    default Object m() {
+      return "Hello, world!";
+    }
+  }
+
+  // Interfaces JImpl and Main are in the source set for trace references.
+  public static class JImpl implements J {}
+
+  public static class Main {
+
+    public static void m(I i) {
+      System.out.println(i.m());
+    }
+
+    public static void main(String[] args) {
+      m(new JImpl());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
index d711ac1..8c5bcb6 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
@@ -151,6 +151,29 @@
     };
   }
 
+  public static Matcher<MethodSubject> containsConstString(String string) {
+    return new TypeSafeMatcher<MethodSubject>() {
+      @Override
+      protected boolean matchesSafely(MethodSubject subject) {
+        return subject.isPresent()
+            && subject.getMethod().hasCode()
+            && subject
+                .streamInstructions()
+                .anyMatch(instructionSubject -> instructionSubject.isConstString(string));
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("contains const-string");
+      }
+
+      @Override
+      public void describeMismatchSafely(MethodSubject subject, Description description) {
+        description.appendText("method did not");
+      }
+    };
+  }
+
   public static Matcher<MethodSubject> instantiatesClass(Class<?> clazz) {
     return instantiatesClass(clazz.getTypeName());
   }
diff --git a/third_party/proguard/README.google b/third_party/proguard/README.google
index 0e6bb2e..4449832 100644
--- a/third_party/proguard/README.google
+++ b/third_party/proguard/README.google
@@ -2,9 +2,10 @@
 URL: https://sourceforge.net/projects/proguard/files/proguard/6.0/
 URL: https://github.com/Guardsquare/proguard/releases/download/v7.0.0/proguard-7.0.0.tar.gz
 URL: https://github.com/Guardsquare/proguard/releases/download/v7.3.2/proguard-7.3.2.tar.gz
-Version: 5.2.1, 6.0.1, 7.0.0, 7.3.2
+URL: https://github.com/Guardsquare/proguard/releases/download/v7.4.1/proguard-7.4.1.tar.gz
+Version: 5.2.1, 6.0.1, 7.0.0, 7.3.2, 7.4.1
 License: GPL
-License File: proguard5.2.1/docs/license.html, proguard6.0.1/docs/license.html, proguard-7.0.0/docs/license.md
+License File: proguard5.2.1/docs/license.html, proguard6.0.1/docs/license.html, proguard-7.0.0/docs/license.md, proguard-7.3.2/LICENSE, proguard-7.4.1/LICENSE
 
 Description:
 ProGuard Java Optimizer and Obfuscator
diff --git a/third_party/proguard/proguard-7.4.1.tar.gz.sha1 b/third_party/proguard/proguard-7.4.1.tar.gz.sha1
new file mode 100644
index 0000000..4c33fe2
--- /dev/null
+++ b/third_party/proguard/proguard-7.4.1.tar.gz.sha1
@@ -0,0 +1 @@
+95f547ec8e6d15338fb6c7c32cc7cf0b27e049d6
\ No newline at end of file
diff --git a/third_party/r8-releases/8.0.46.tar.gz.sha1 b/third_party/r8-releases/8.0.46.tar.gz.sha1
new file mode 100644
index 0000000..300d3fd
--- /dev/null
+++ b/third_party/r8-releases/8.0.46.tar.gz.sha1
@@ -0,0 +1 @@
+037a4cc645b882ecd6202f84220fd9d81c0a379e
\ No newline at end of file